OpenSpatial Quick Start

September 1, 2026 · View on GitHub

1. Environment Setup

1.1 Create Conda Environment

conda create -n openspatial python=3.10 -y
conda activate openspatial

1.2 Install Dependencies

cd OpenSpatial

# Core dependencies
pip install -r requirements.txt

# PyTorch (CUDA 12.6)
pip install torch==2.6.0 torchvision==0.21.0 torchaudio==2.6.0 \
    --index-url https://download.pytorch.org/whl/cu126

# spaCy model
python -m spacy download en_core_web_sm

# Foundation models
pip install 'git+https://github.com/facebookresearch/sam2.git' --quiet
pip install -q -U flash-attn --no-build-isolation

### 1.3 Verify Installation

```bash
python -c "import torch; print(f'PyTorch {torch.__version__}, CUDA: {torch.cuda.is_available()}')"
python -m pytest tests/ -x -q

2. Data Preparation

2.1 Coordinate System & 3D Box Convention

World coordinate system: The scene mesh must be gravity-aligned — the ground plane is parallel to the XY plane, and the Z-axis points upward.

        Z (up)
        |
        |
        |_______ Y
       /
      /
     X

  Ground plane = XY

Camera coordinate system (OpenCV convention): X-axis points right, Y-axis points down, Z-axis points forward (into the scene along the optical axis). The depth value of a pixel equals its Z coordinate in camera frame.

              Z (forward / optical axis)
             /
            /
           /_______ X (right)
           |
           |
           Y (down)

  Image plane is perpendicular to Z.
  Pixel (u, v) back-projects to:
    X_cam = (u - cx) * depth / fx
    Y_cam = (v - cy) * depth / fy
    Z_cam = depth

The pose field stores the 4x4 camera-to-world extrinsic matrix. To convert from world to camera frame: P_cam = inv(pose) @ P_world.

3D Oriented Bounding Box (OBB): Each object's 3D bounding box is represented as a 9-element vector in world coordinates:

[cx, cy, cz, xl, yl, zl, roll, pitch, yaw]
ElementDescription
cx, cy, czCenter position in world frame
xl, yl, zlExtents (full lengths) along the box's local X / Y / Z axes. zl corresponds to the object's height
roll, pitch, yawEuler angles in zxy intrinsic order (radians), encoding the rotation from the world frame to the box's local frame

The pipeline converts world-frame OBBs to camera frame via inv(pose) @ T_world. The resulting camera-frame OBB uses the same 9-element representation.

2.2 Data Format

OpenSpatial uses Parquet files as the unified data format. Each row represents a scene sample.

FieldSingleviewMultiviewSourceDescription
imagestrlist[str]InputRGB image path
depth_mapstrlist[str]InputDepth map path
posestrlist[str]InputCamera extrinsic (4x4 txt)
intrinsicstrlist[str]InputCamera intrinsic (4x4 txt)
obj_tagslist[str]list[list[str]]InputObject tags (per-view in multiview)
bboxes_3d_world_coordslist[list[float]]list[list[list[float]]]Input3D OBB [cx,cy,cz,xl,yl,zl,roll,pitch,yaw]
depth_scaleintintInputDepth scale factor (e.g. 1000)
is_metric_depthboolboolInputWhether depth is metric
maskslist[str]list[list[str]]PipelineMask PNG paths per object
bboxes_2dlist[list[int]]list[list[list[int]]]Pipeline2D bbox [x1,y1,x2,y2] per object
pointcloudslist[str]list[list[str]]PipelinePoint cloud .pcd paths per object

Note: Not all fields are required at the outset. Input fields come from data preprocessing (Section 2.3); Pipeline fields (masks, bboxes_2d, pointclouds) are intermediate results generated by pipeline stages such as Localizer, Sam2Refiner, and DepthBackProjecter. Different annotation tasks depend on different subsets of these fields.

2.3 Data Preprocessing

OpenSpatial supports two types of input data:

Public 3D-Annotated Datasets

For public datasets with existing 3D annotations (e.g. ScanNet++, Hypersim, EmbodiedScan), we provide preprocessing scripts to convert them into the standard Parquet format.

ScanNet++:

python data_preprocessing/scannetpp/prepare_scannetpp.py \
    --input_root /path/to/scannetpp/data \
    --output_dir /path/to/output/parquet \
    --selected_tags_file data_preprocessing/scannetpp/scannet-labels.combined.tsv \
    --chunk_size 100 \
    --max_workers 32

Hypersim:

python data_preprocessing/hypersim/prepare_hypersim.py \
    --input_root /path/to/Hypersim \
    --output_dir /path/to/output/parquet \
    --camera_params_csv data_preprocessing/hypersim/metadata_camera_parameters.csv \
    --labels_tsv data_preprocessing/scannetpp/scannet-labels.combined.tsv \
    --chunk_size 1000 \
    --max_workers 32

EmbodiedScan (ScanNet, 3RScan, Matterport3D, ARKitScenes):

# Install the preprocessing package first
cd data_preprocessing/embodiedscan && pip install -e . && cd ../..

# Extract per-image data
python -m embodiedscan_data extract \
    --dataset all \
    --data-root /path/to/EmbodiedScan/data \
    --output /path/to/output \
    --workers 24

# Merge into per-scene records
python -m embodiedscan_data merge --input /path/to/output

# Export to Parquet
python -m embodiedscan_data export --input /path/to/output --format both

See data_preprocessing/embodiedscan/README.md for detailed data directory structure and per-dataset notes.

3D Lifting for Unannotated Web Data

See the 3D Lifting Module section for installation, model downloads, input format, and the standalone command.


3. Running the Pipeline

3.1 Basic Usage

python run.py --config <config.yaml> --output_dir <output_directory>

3.2 Config File Structure

Config files are in config/ and define the dataset source, pipeline executor, and processing stages. Below is a full end-to-end pipeline config with annotations:

# ── Dataset ──────────────────────────────────────────────────
dataset:
  modality: image                          # input modality: "image"
  dataset_name: image_base                 # dataset loader class (dataset/image_base.py)
  data_dir: /path/to/input.parquet         # path to Parquet file(s), str or list[str]

# ── Pipeline ─────────────────────────────────────────────────
pipeline:
  file_name: base_pipeline                 # pipeline module (pipeline/base_pipeline.py)
  class_name: BasePipeline                 # pipeline class

  stages:
    # ── Stage 1: Filter ────────────────────────────────────
    # Validate 3D bounding boxes via 2D projection & point cloud
    filter_stage:
      -
        file_name: 3dbox_filter
        method: ThreeDBoxFilter
        filter_tags: ["ceiling", "floor", "wall", "object"]
        output_dir:

    # ── Stage 2: Localization ──────────────────────────────
    # Refine masks with SAM2 box prompts
    localization_stage:
      -
        file_name: sam2_refiner
        method: Sam2Refiner
        update_keys: ["obj_tags", "bboxes_3d_world_coords"]
        depends_on: filter_stage/3dbox_filter
        output_dir:

    # ── Stage 3: Scene Fusion ──────────────────────────────
    # Back-project depth to per-object 3D point clouds
    scene_fusion:
      -
        file_name: depth_back_projection
        method: DepthBackProjecter
        depends_on: localization_stage/sam2_refiner
        output_dir:

    # ── Stage 4: Annotation ────────────────────────────────
    # Generate spatial QA pairs
    annotation_stage:
      -
        file_name: distance                # task module name under task/<stage>/
        method: AnnotationGenerator        # task class name
        depends_on: scene_fusion/depth_back_projection

        # Common parameters (BaseTask)
        use_multi_processing: false        # enable multi-process execution
        num_workers: 8                     # number of parallel workers

        # Annotation parameters (BaseAnnotationTask)
        scaling_factor: 1                  # coordinate scaling factor
        filter_tags: ["ceiling", "floor"]  # tags to exclude from QA generation
        sub_tasks:                         # sub-task names → count per sample
          absolute_distance: 1
          relative_distance: 1

        # Multiview parameters (BaseMultiviewTask, multiview tasks only)
        # max_num_views: 400               # max views to consider per scene
        # min_rot_angle: 15.0              # min rotation angle (deg) between views
        # min_translation: 0.0             # min camera center distance between views

        # Task-specific parameters vary by task (see demo configs for full list)

        output_dir:

Each stage is a list of tasks (the - items). A stage can contain multiple tasks that run sequentially. Stages execute in dependency order defined by depends_on. For annotation-only runs, a single annotation_stage is sufficient (see demo configs).

3.3 Available Tasks

Note: This list is continuously updated. Check the config/ directory for the latest available tasks.

Annotation (singleview):

ConfigTaskDescription
config/annotation/demo_distance.yamlDistanceAbsolute & relative distance QA
config/annotation/demo_depth.yamlDepthDepth ordering & comparison QA
config/annotation/demo_size.yamlSizeAbsolute & relative size QA
config/annotation/demo_position.yamlPositionHeight comparison & proximity QA
config/annotation/demo_counting.yamlCountingObject counting QA
config/annotation/demo_3d_grounding.yaml3D Grounding3D bounding box grounding QA

Annotation (multiview):

ConfigTaskDescription
config/annotation/demo_multiview_distance.yamlMultiview DistanceCross-view distance QA
config/annotation/demo_multiview_size.yamlMultiview SizeCross-view size QA
config/annotation/demo_multiview_correspondence.yamlCorrespondenceCross-view object matching QA
config/annotation/demo_multiview_distance_obj_cam.yamlObject-Camera DistanceObject to camera distance QA
config/annotation/demo_multiview_object_position.yamlObject PositionCross-view position QA

Processing stages (used in full pipeline):

StageTaskDescription
filter_stageThreeDBoxFilterValidate 3D boxes via 2D projection & point cloud
localization_stageSam2RefinerRefine masks with SAM2 box prompts
scene_fusionDepthBackProjecterBack-project depth to per-object point clouds

3.4 Annotation Pipeline by Data Mode

Input data falls into two modes depending on the Parquet structure. The preprocessing pipeline differs slightly between them:

Singleview input (per-image Parquet, e.g. Hypersim, EmbodiedScan per_image):

Each row is one image. The pipeline runs filter → localize → scene_fusion directly, then groups into multiview if needed.

filter_stage ──> localization_stage ──> scene_fusion_stage ──> group_stage
(3dbox_filter)   (sam2_refiner)         (depth_back_projection) (SampleGrouper)
python run.py --config config/preprocessing/demo_preprocessing_hypersim.yaml --output_dir output/

Multiview input (per-scene Parquet, e.g. ScanNet++, EmbodiedScan per_scene):

Each row is a scene with list-valued fields. The pipeline first flattens to per-image, then follows the same stages. Datasets like ScanNet++ fall into this mode because their 3D box annotations are provided at the scene level (shared across all views), rather than per-image.

flatten_stage ──> filter_stage ──> localization_stage ──> scene_fusion_stage ──> group_stage
(SampleFlattener) (3dbox_filter)   (sam2_refiner)         (depth_back_projection) (SampleGrouper)
python run.py --config config/preprocessing/demo_preprocessing_scannetpp.yaml --output_dir output/

Note: The output of scene_fusion_stage is per-image Parquet, used as input for singleview annotation tasks. The output of group_stage aggregates per-image records back into per-scene Parquet, used as input for multiview annotation tasks.

# Singleview annotations
for cfg in demo_distance demo_depth demo_size demo_position demo_counting demo_3d_grounding; do
    python run.py --config config/annotation/${cfg}.yaml --output_dir output/demo
done

# Multiview annotations
for cfg in demo_multiview_distance demo_multiview_size demo_multiview_correspondence \
           demo_multiview_distance_obj_cam demo_multiview_object_position; do
    python run.py --config config/annotation/${cfg}.yaml --output_dir output/demo
done

4. Visualization

Launch the built-in visualization server to browse annotation results:

python visualize_server.py --data_dir output/demo --port 8888

Then open http://<host>:8888 in a browser. Features:

  • Dropdown to switch between task outputs
  • QA pairs with rendered images
  • Lightbox for full-size image viewing
  • Keyboard navigation (left/right arrows)

5. Development Guide

For extending OpenSpatial with new annotation tasks, pipeline stages, prompt templates, or dataset preprocessors, see the Development Guide.

It covers:

  • Class hierarchy (BaseTaskBaseAnnotationTaskBaseMultiviewAnnotationTask)
  • Step-by-step: adding singleview / multiview annotation tasks
  • Prompt template system and placeholder conventions
  • SceneGraph data model
  • Adding new pipeline stages and dataset preprocessors
  • Testing and common patterns