RoboSpatial

repository·main·Indexed 40 days ago

https://github.com/nvlabs/robospatial

A pipeline for generating rich spatial annotations—including object grounding, spatial context, configuration, and compatibility—from 3D scan datasets to teach spatial understanding to Vision-Language Models. It includes tools for preprocessing EmbodiedScan data, a BaseLoader interface for custom dataset integration, and visualization scripts for verifying input and output annotations.

Tokens
4.3K
Snippets
7
Records
18
Agent score
100%

What's inside RoboSpatial

  1. Project Structure

    main

    The repository is organized as follows:

    • configs/: YAML configuration files (e.g., example_config.yaml).
    • data_loader/: Modules for loading 3D datasets (e.g., embodiedscan_loader.py).
    • spatial_analysis/: Core spatial reasoning logic:
      • context/: Spatial context (points relative to an object).
      • configuration/: Spatial configuration (relative position between objects).
      • compatibility/: Spatial compatibility (fitting assessment).
      • grounding.py: 2D object grounding.
      • relationships.py: High-level wrappers for spatial analysis.
      • relationship_utils.py: Geometry and projection utilities.
      • topdown_map.py: 2D top-down occupancy grid functions.
      • obj_properties.py: Object property definitions.
    • annotation_generator.py: Orchestrates generation for a single scene.
    • run_generation.py: Main script to run generation across datasets/scenes.
  2. How Spatial Configuration works

    main

    Spatial Configuration (spatial_analysis/configuration/configuration.py) determines the pairwise spatial relationship between two objects (obj1 relative to obj2) across camera, world, and object frames (e.g., left/right, infront/behind, above/below, overlapping).

    Logic:

    • Camera/World-centric: Uses the spatial_configuration_strictness parameter:
      • 'strict': Uses absolute min/max bounds. Requires clear separation; considers objects overlapping if bounds intersect at all.
      • 'lenient': Uses projected centers, average visible depths, and average Z coordinates. More robust to partial occlusion.
    • Object-centric: Uses the Separating Axis Theorem (SAT) on 3D OBBs to check for overlap. If not overlapping, determines direction based on obj1's center projected onto obj2's local forward and right axes.

    Configuration (data_generation.generation_options):

    • spatial_configuration_strictness: Either 'strict' or 'lenient' (default).
    • pairwise_relationship_mode:
      • 'unique_categories_only': Only considers pairs where each object is the only instance of its category visible.
      • 'all_visible_objects': Considers all permutations of visible objects.
  3. How Spatial Context sampling works

    main

    Spatial Context (spatial_analysis/context/context.py) samples points in the empty space surrounding a reference object and categorizes them by their spatial relationship (infront, behind, left, right) across three frames: objectcentric, cameracentric, and worldcentric.

    Logic:

    1. Identifies empty space on the floor using a top-down 2D grid.
    2. Finds grid points within a context_threshold distance from the reference object's footprint.
    3. Projects 3D points onto the image, filtering out points that are off-view or behind the camera.
    4. Checks for occlusions using an environment occupancy map.
    5. Categorizes non-occluded points based on their position relative to the reference object.
    6. Randomly samples up to context_num_samples points per category.

    Configuration (data_generation.generation_options):

    • context_threshold: Max distance (in world units) from the reference object's footprint to consider sampling.
    • context_grid_resolution: Size of cells in the top-down 2D grid (smaller is more precise but slower).
    • context_num_samples: Max points to sample per valid category.
  4. How Spatial Compatibility works

    main

    Spatial Compatibility (spatial_analysis/compatibility/compatibility.py) assesses if an object (obj_a) can be placed in empty space relative to another object (obj_b) without collision (e.g., left, right, in front, behind, or on_top).

    Logic:

    1. Samples potential placement points around obj_b using the Spatial Context logic with a dynamic threshold based on object sizes.
    2. Simulates placing obj_a at each point.
    3. Checks for collisions against the static environment and the reference object obj_b (respecting compatibility_min_distance).
    4. A relationship is considered compatible if any sampled point allows the placement.
    5. can_fit_on_top is a separate check comparing horizontal dimensions, provided obj_a is placeable and obj_b has a flat surface.

    Configuration (data_generation.generation_options):

    • compatibility_grid_resolution: Resolution of the 2D grid for environment collision checking.
    • compatibility_num_samples: Number of potential placement points to sample.
    • compatibility_min_distance: Minimum required distance between obj_a and obj_b in world units.
    • compatibility_buffer_ratio: Safety margin applied to obj_a's dimensions (e.g., 0.1 for a 10% buffer).
    • context_threshold: The base threshold used for sampling (dynamically increased based on object sizes).
  5. How Object Grounding works

    main

    Object Grounding (spatial_analysis/grounding.py) generates a tight 2D axis-aligned bounding box (clipped_bbox) for an object.

    Logic: It uses a pre-calculated 2D boolean occupancy_map (representing the object's 3D model projected onto the image) to find the minimum and maximum x (column) and y (row) coordinates. These bounds define the [xmin, ymin, xmax, ymax] coordinates of the 2D bounding box.

    Note: Accuracy depends on the input 3D models and camera parameters used to generate the occupancy_map.

  6. Verify loader output with `visualize_input.py`

    main

    Use scripts/visualize_input.py to debug your custom loader's output before running the full pipeline. This script projects 3D bounding boxes onto a 2D image to verify camera parameters and object alignment.

    Requirements for the input JSON:

    • The JSON must contain an objects array.
    • Each object must have a bbox_3d field containing a list with 9 DoF parameters (center, size, rotation) as its first element.

    Customization: If your loader uses a different format for 3D bounding boxes, you must modify the visualize_single_image function in scripts/visualize_input.py (around line 195) to parse your specific data structure.

  7. Run RoboSpatial annotation generation

    main

    The core script for generating spatial annotations is robospatial/run_generation.py.

    Before processing large datasets, run the pipeline using the provided example scene in example_data/ to verify your setup:

    cd robospatial
    python run_generation.py --config configs/example_dataset.yaml

    Run on Full Datasets

    Once configured, run the script pointing to your specific configuration file:

    cd robospatial
    python run_generation.py --config configs/your_chosen_config.yaml
    python run_generation.py --config configs/example_dataset.yaml
  8. Download and Preprocess EmbodiedScan Raw Data

    main

    To use the EmbodiedScan dataset with RoboSpatial, you must first download and preprocess the raw data following the official EmbodiedScan instructions.

    Required Data Sources:

    1. ScanNet v2 data
    2. 3RScan data
    3. Matterport3D data
    4. ARKitScenes data
    5. EmbodiedScan annotations (.pkl files)

    Preprocessing Steps:

    • Extract images for ScanNet and 3RScan using the original EmbodiedScan scripts: generate_image_scannet.py and generate_image_3rscan.py.
    • Note: You do not need to perform the final step of extracting occupancy annotations required by the original EmbodiedScan repository.

    Refer to the official EmbodiedScan data preparation guide for detailed download instructions.

  9. Implement a custom data loader using BaseLoader

    main

    To integrate a custom dataset into the RoboSpatial annotation generation pipeline, you must create a class that inherits from robospatial.data_loader.base_loader.BaseLoader. Your loader is responsible for mapping your dataset's specific format to the interface expected by the pipeline, including scenes, images, and 3D object instances.

    Your implementation must provide the following methods:

    1. __init__(self, config): Initialize the loader using the data_loading dictionary from your YAML configuration.
    2. list_scenes(self, dataset_list): A generator yielding (dataset_name, scene_idx, scene_name) tuples.
    3. list_images(self, dataset_name, scene_name): Returns a dictionary of image annotations containing extrinsic (4x4 matrix), intrinsic (4x4 matrix), and img_path.
    4. list_objects(self, dataset_name, scene_name, image_ann): Processes visible objects, returning a 5-element tuple containing visible objects, category sets, floor bounds, and all scene objects.
    from robospatial.data_loader.base_loader import BaseLoader
    
    class YourCustomLoader(BaseLoader):
        def __init__(self, config):
            super().__init__()
            self.config = config
            # Load annotations using config keys
    
        def list_scenes(self, dataset_list):
            # yield (dataset_name, scene_idx, scene_name)
            pass
    
        def list_images(self, dataset_name, scene_name):
            # return {image_id: {'extrinsic': ..., 'intrinsic': ..., 'img_path': ...}}
            pass
    
        def list_objects(self, dataset_name, scene_name, image_ann):
            # return (vis_objs, unique_vis_categories, multi_vis_categories, floor_bound, all_objs)
            pass
  10. Install RoboSpatial and prepare the environment

    main

    To use RoboSpatial for generating spatial annotations, follow these setup steps:

    1. Python Environment: Use Python 3.8+ (via conda or venv).
    2. Dependencies: Install required packages using pip:
      pip install -r requirements.txt
    3. Datasets: Ensure you have access to your target 3D scan datasets. For instructions on the EmbodiedScan dataset, see data/README.md in the repository.
    4. Configuration: Update your YAML configuration file (e.g., robospatial/configs/embodiedscan.yaml) with the following keys:
      • data_loading.loader_class: The Python class for your data loader (e.g., data_loader.embodiedscan_loader.EmbodiedScanLoader).
      • Dataset paths: image_root and format-specific annotation files (e.g., embodiedscan_ann).
      • data_generation.output_dir: The directory where .annotations.json files will be saved.
    pip install -r requirements.txt
  11. Configure a custom data loader in YAML

    main

    To use your custom loader, update the data_loading section of your configuration file (e.g., configs/example_config.yaml). You must provide the fully qualified Python path to your class via loader_class and include any custom keys your __init__ method requires.

    Example configuration:

    data_loading:
      loader_class: path.to.your.module.YourCustomLoaderClassName
      your_custom_annotation_path:
        train: /path/to/your/train_annotations.pkl
        val:   /path/to/your/val_annotations.pkl
  12. Configure EmbodiedScan data paths in robospatial/configs/embodiedscan.yaml

    main

    After downloading and preprocessing the EmbodiedScan data, you must update the robospatial/configs/embodiedscan.yaml configuration file to point to your local data directories.

    Under the data_loading section, update the following keys:

    • image_root: The directory containing extracted images (e.g., scannet/posed_images or 3rscan/<scene_id>/sequence).
    • embodiedscan_ann.train: Path to the training .pkl annotation file.
    • embodiedscan_ann.val: Path to the validation .pkl annotation file.
    • embodiedscan_ann.test: Path to the test .pkl annotation file.
    data_loading:
      # ... other settings ...
      image_root: /path/to/your/processed/image/data # <- UPDATE THIS
      embodiedscan_ann:
        train: /path/to/your/EmbodiedScan/data/embodiedscan_infos_train.pkl # <- UPDATE THIS
        val:   /path/to/your/EmbodiedScan/data/embodiedscan_infos_val.pkl   # <- UPDATE THIS
        test:  /path/to/your/EmbodiedScan/data/embodiedscan_infos_test.pkl  # <- UPDATE THIS
      # ... other settings ...