Skip to content

Latest commit

 

History

1 Commit

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

BOP-ASK: Object Interaction Reasoning for Vision Language Models (CVPR 2026).

This repository converts BOP-format 6D pose estimation datasets into the BOP-ASK visual question answering format for VLM fine-tuning. The pipeline generates 10+ question types covering camera parameters, object poses, grasps, spatial relationships, trajectories, depth, and object rearrangement.

Supported datasets: hope, handal, ycbv, hot3d


Repository Structure

bopask-github/
├── config/
│   └── dataset_config.json                # Per-dataset depth scales, resolutions, methods
│
├── generate_surface_masks.py              # Step 1:  Molmo + SAM2 surface segmentation
├── render_objects_and_descriptions.py     # Step 2:  PyVista renders + GPT descriptions
├── export_bounding_boxes.py               # Step 3:  3D bbox export per scene
├── run_all_extrinsic_estimations.py       # Step 4:  Camera extrinsic estimation (wrapper)
├── bop2m2t2.py                            # Step 5:  BOP -> M2T2 format conversion
├── generate_all_trajectories_parallel.py  # Step 6:  RRT pick-and-place trajectories (parallel)
├── run_all_grasp_generation.py            # Step 7:  M2T2 6-DoF grasp generation
├── filter_grasp_collisions.py             # Step 8:  3D grasp collision filtering
├── generate_question_templates.py         # Step 9:  Pre-generate question variations
├── generate_batch_qa_dataset.py           # Step 10: Final JSONL + image generation (emits debug visualizations)
│
├── utils/                                 # Shared helpers imported by the pipeline
│   ├── bop_utils.py                       # OpenAI client, text drawing
│   ├── estimate_cam2world.py              # Core extrinsic computation (invoked by step 4)
│   ├── generate_all_trajectories.py       # Sequential trajectory generation + helpers (imported by step 6)
│   ├── plan_motion_rrt.py                 # Core RRT planner (imported by step 6)
│   ├── grasp_vis_utils.py                 # 2D grasp point extraction (used by steps 8 and 10)
│   └── visualize_3d_boxes.py              # BOP bbox visualizer class (used by steps 3 and 10)
│
├── M2T2/                                  # Vendored: M2T2 grasp prediction model
├── question_templates/                    # Cached question templates (per dataset/split, git-ignored)
├── requirements.txt                       # pip dependencies (see Environment Setup)
└── download_bop_datasets.sh               # Convenience script to fetch BOP datasets

Environment Setup

The pipeline depends on PyTorch 2.6 built for CUDA 12.4 plus a matching CUDA 12.4 toolkit (needed to compile the M2T2/pointnet2_ops C++/CUDA extension). The steps below install both inside a dedicated conda env and are known to work end-to-end from a clean machine.

Prerequisites: a recent conda/mamba, an NVIDIA GPU with a driver supporting CUDA ≥ 12.4, and gcc 11–13.

1. Create the conda env

conda create -n bopask python=3.10 -y
conda activate bopask
conda install -c nvidia/label/cuda-12.4.1 -y \
    cuda-nvcc=12.4 cuda-cudart=12.4 cuda-cudart-dev=12.4 \
    cuda-libraries=12.4 cuda-libraries-dev=12.4 \
    cuda-nvrtc=12.4 cuda-nvrtc-dev=12.4 \
    cuda-nvtx=12.4 cuda-command-line-tools=12.4 \
    cuda-version=12.4

Make these persistent for the env (optional but highly recommended):

mkdir -p $CONDA_PREFIX/etc/conda/activate.d
cat > $CONDA_PREFIX/etc/conda/activate.d/cuda.sh <<'EOF'
export CUDA_HOME=$CONDA_PREFIX
export CUDA_PATH=$CONDA_PREFIX
export PATH=$CONDA_PREFIX/bin:$PATH
export LD_LIBRARY_PATH=$CONDA_PREFIX/lib:${LD_LIBRARY_PATH:-}
EOF

Rest of the installation:

pip install torch==2.6.0+cu124 torchvision==0.21.0+cu124 \
    --index-url https://download.pytorch.org/whl/cu124
pip install -r requirements.txt
# Molmo's remote-code modeling file breaks on transformers >= 4.50, so pin 4.46.
pip install 'transformers==4.46.3' 'tokenizers<0.21' einops accelerate
pip install --no-build-isolation M2T2/pointnet2_ops/
pip install --no-build-isolation ./M2T2/

Download the M2T2 checkpoint (for steps 7-8)

cd M2T2/
wget https://huggingface.co/wentao-yuan/m2t2/resolve/main/m2t2.pth
cd ..

Install Meta's SAM 2 and download weights (for step 1)

Step 1 (surface-mask generation) uses Meta's official SAM 2, not the similarly-named PyPI package. Clone it as a sibling directory, install it, and fetch the hiera_large checkpoint:

# Clone and install SAM 2 from Meta's repo
git clone https://github.com/facebookresearch/sam2.git
cd sam2
pip install -e .

# Download the hiera_large checkpoint (~900 MB)
cd checkpoints && bash download_ckpts.sh && cd ..

cd ..

8. (Optional) OpenAI API key

Needed only for step 2 (object descriptions) and step 9 (question templates). Everything else runs offline.

export OPENAI_API_KEY="sk-..."

9. Download BOP datasets

# All four supported datasets
bash download_bop_datasets.sh

# Or just a subset
bash download_bop_datasets.sh --datasets hope --splits val --output_dir ./bop

Troubleshooting

Symptom Cause Fix
ModuleNotFoundError: No module named 'torch' during pip install M2T2/pointnet2_ops/ pip built in an isolated env without torch Add --no-build-isolation (step 6)
RuntimeError: The detected CUDA version (X.Y) mismatches the version that was used to compile PyTorch (12.4) System nvcc is on PATH ahead of the conda one Re-run step 3; verify with which nvcc
error: could not create 'build/...': Permission denied Stale root-owned build/ or *.egg-info/ from an earlier sudo install sudo rm -rf M2T2/pointnet2_ops/build M2T2/pointnet2_ops/pointnet2_ops.egg-info M2T2/build M2T2/m2t2.egg-info then retry step 6
ls $CONDA_PREFIX/bin/nvcc says "No such file or directory" cuda-toolkit metapackage didn't pull nvcc on your platform Install the explicit component list in step 2
torch.cuda.is_available() returns False GPU driver too old, or torch install mismatched CUDA Check nvidia-smi shows a driver supporting ≥ 12.4, then reinstall the exact +cu124 wheel in step 4

Input: BOP Dataset Format

Your data should follow standard BOP layout:

bop/
└── hope/
    ├── camera.json                   # Global camera intrinsics
    ├── models/
    │   ├── obj_XXXXXX.ply            # 3D meshes
    │   ├── obj_XXXXXX.png            # Textures (optional)
    │   └── models_info.json          # Per-model bounding box metadata
    ├── models_eval/                  # Simplified models for evaluation
    └── val/                          # Split directory
        └── 000001/                   # Scene directory
            ├── rgb/                  # RGB images: 000000.png, 000001.png, ...
            ├── depth/                # 16-bit PNG depth maps (values in mm)
            ├── mask/                 # Per-object binary masks: {frame}_{idx}.png
            ├── mask_visib/           # Visible-part masks
            ├── scene_camera.json     # Per-frame camera intrinsics
            ├── scene_gt.json         # Ground-truth 6D poses
            └── scene_gt_info.json    # Additional GT metadata

Pipeline Steps

Step 1 — Surface Mask Generation

Generates binary masks of the flat surface (table/shelf) on which objects rest. Uses Molmo (a pointing VLM) to identify the surface, then SAM 2 to segment it.

huggingface-cli download allenai/Molmo-72B-D-0924 \
    --local-dir models/molmo-72b-d-0924
python generate_surface_masks.py \
    --dataset_path bop/hope --split val \
    --molmo_model models/molmo-72b-d-0924 \
    --sam2_checkpoint sam2/checkpoints/sam2.1_hiera_large.pt \
    --resume

Output:

bop/hope/val/surface_masks/
    ├── 1_0.png       # scene 1, frame 0 — binary mask (0/255), same resolution as RGB
    ├── 1_1.png
    └── ...

Step 2 — Object Renders & Descriptions

Renders 4-view composites of each 3D model and optionally generates natural-language descriptions via OpenAI.

python render_objects_and_descriptions.py \
    --dataset_path bop/hope \
    --output_dir bop/hope/renders_pyvista_hope
cp bop/hope/renders_pyvista_hope/object_descriptions.json bop/hope/

Pass --skip_description to skip the OpenAI call. On headless servers: xvfb-run -a python ...

Output:

renders_pyvista_hope/
    ├── renders/
    │   └── obj_000001.png             # 2x2 grid of 4 views per object
    └── object_descriptions.json       # {obj_key: {render_path, description, model_info}}

bop/hope/object_descriptions.json      # Copy for downstream scripts

You typically run this once per dataset (not per split) since object models are shared.


Step 3 — 3D Bounding Box Export

Projects 3D model bounding boxes into each frame. Also assigns unique IDs when multiple instances of the same object appear (e.g., 2a, 2b).

python export_bounding_boxes.py bop/hope --split val

Output (per scene directory):

bop/hope/val/000001/
    ├── bboxes_000001.json          # Per-frame bounding box data
    ├── scene_gt.json               # Modified with unique object IDs
    └── scene_gt_original.json      # Backup of original scene_gt

Data format (bboxes_{scene}.json):

{
  "0": [
    {
      "obj_id": "2a",
      "corners_2d": [[x,y], ...],           // 8 projected 2D corners
      "rotation_matrix": [r00, r01, ...],    // 3x3 row-major, 9 floats
      "position": [tx, ty, tz],              // mm, in camera frame
      "scale": [sx, sy, sz],                 // mm, model bbox dimensions
      "euler_xyz": [ex, ey, ez],             // radians
      "quaternion_xyzw": [qx, qy, qz, qw],
      "original_obj_id": 2
    }
  ]
}

Step 4 — Camera Extrinsic Estimation

Estimates a camera-to-world transform for each frame using object pose up-vectors and RANSAC plane fitting on (surface-masked) depth data.

python run_all_extrinsic_estimations.py --dataset_path bop/hope --split val

Output (per scene directory):

bop/hope/val/000001/
    ├── T_wc_0.txt                   # 4x4 float matrix (or "FAILED" on first line)
    ├── T_wc_1.txt
    └── extrinsic_visualizations/    # Debug overlays
        ├── object_up_vectors_0.png
        └── fitted_ransac_plane_0.png

Data format (T_wc_{frame_id}.txt): 4x4 matrix, space-separated floats. First line is FAILED if estimation failed (these frames are skipped in step 5).

The depth scale and normal estimation method are auto-selected per dataset/split (see config/dataset_config.json).


Step 5 — BOP to M2T2 Format Conversion

Converts each valid frame (with successful extrinsics) into the per-folder format expected by M2T2 grasp generation.

python bop2m2t2.py --bop_root bop/hope/val

Output:

bop/hope/val/converted-bop/
    └── scene_000001_frame_000000/
        ├── rgb.png                   # Resized to 1280x720
        ├── depth.npy                 # float32, metres, shape (720, 1280)
        ├── seg.png                   # uint16 semantic mask (0=bg, obj_label=fg)
        └── meta_data.pkl            # dict: camera_pose (4x4), intrinsics (3x3),
                                     #       label_map ({"obj_2a": 297, ...}),
                                     #       scene_bounds (None), ee_pose (None)

Important: Frames where T_wc_{frame_id}.txt contains FAILED are skipped entirely.


Step 6 — Trajectory Generation

Generates collision-free pick-and-place trajectories for all object pairs using an RRT planner with cartesian smoothing. Runs in parallel.

python generate_all_trajectories_parallel.py \
    --dataset_path bop/hope --split val --max_workers 8

Output:

bop/hope/trajectories_val.json

Data format: JSON with keys image_paths and per-trajectory entries containing 3D waypoints, 2D pixel projections, pick/place object IDs, and motion descriptions.


Step 7 — Grasp Generation

Runs M2T2 inference on each converted frame to produce 6-DoF grasp candidates for every object.

python run_all_grasp_generation.py \
    --converted_bop_dir bop/hope/val/converted-bop \
    --checkpoint_path M2T2/m2t2.pth

Output (per converted frame):

bop/hope/val/converted-bop/scene_000001_frame_000000/
    └── scene_state_complete.json     # Per-object: grasp poses, point cloud, confidence

Step 8 — Grasp Collision Filtering

Removes grasps that collide with neighboring objects in 3D (within 1.5cm). Objects where all grasps collide are flagged for rearrangement questions.

python filter_grasp_collisions.py \
    --converted_bop_dir bop/hope/val/converted-bop

Output: Updates scene_state_complete.json in-place. Adds filtered_grasps, needs_rearrangement, and blocking_objects fields.


Step 9 — Question Template Generation

Pre-generates question text variations offline using OpenAI, keyed by (question_type, object_description, spatial_phrase). Reduces API costs during QA generation.

python generate_question_templates.py \
    --dataset_path bop/hope --split val \
    --desc_json bop/hope/object_descriptions.json \
    --incremental \
    --output_file question_templates/question_templates_hope_val.json

Use --incremental to accumulate templates across runs (e.g., run for val then train).

Output:

question_templates/question_templates_hope_val.json

Step 10 — QA Dataset Generation

Produces the final VQA dataset in LLaMA-Factory format.

python generate_batch_qa_dataset.py \
    --dataset_path bop/hope --split val \
    --desc_json bop/hope/object_descriptions.json \
    --use_templates \
    --template_file question_templates/question_templates_hope_val.json \
    --use_depth_questions

Output:

hope-val-hf/
    ├── val.jsonl                     # ShareGPT-format conversations
    ├── images/                       # One RGB image per scene-frame
    │   └── scene_000001_frame_000000.png
    └── debug-images/                 # QA overlays for verification

JSONL format (one line per conversation):

{
  "conversations": [
    {"from": "human", "value": "<image>\nQuestion text..."},
    {"from": "gpt", "value": "Answer text..."}
  ],
  "images": ["images/scene_000001_frame_000000.png"]
}

Complete Directory After Pipeline

bop/
└── hope/
    ├── camera.json
    ├── object_descriptions.json           # From step 2
    ├── trajectories_val.json              # From step 6
    ├── models/
    ├── models_eval/
    └── val/
        ├── 000001/                        # Scene directory
        │   ├── rgb/
        │   ├── depth/
        │   ├── mask/
        │   ├── mask_visib/
        │   ├── scene_camera.json
        │   ├── scene_gt.json              # Modified with unique IDs (step 3)
        │   ├── scene_gt_original.json     # Backup (step 3)
        │   ├── bboxes_000001.json         # 3D bbox data (step 3)
        │   ├── T_wc_0.txt                 # Extrinsics (step 4)
        │   ├── T_wc_1.txt
        │   └── extrinsic_visualizations/  # Debug images (step 4)
        ├── surface_masks/                 # Surface masks (step 1)
        │   ├── 1_0.png
        │   └── ...
        └── converted-bop/                 # M2T2 format (step 5)
            └── scene_000001_frame_000000/
                ├── rgb.png
                ├── depth.npy
                ├── seg.png
                ├── meta_data.pkl
                └── scene_state_complete.json  # Grasps (steps 7-8)

hope-val-hf/                               # Final output (step 10)
    ├── val.jsonl
    ├── images/
    └── debug-images/

renders_pyvista_hope/                      # Object renders (step 2)
    ├── renders/
    └── object_descriptions.json

question_templates/                        # Cached templates (step 9)
    └── question_templates_hope_val.json

Post-Pipeline Utilities

Debug visualizations (one per question type) are emitted automatically under ${DATASET}-${SPLIT}-hf/debug_samples/ by step 10 — use those to eyeball correctness before running the utilities below.


Question Types Generated

Type Subtype Description
camera extrinsics 4x4 camera-to-world matrix
pose 2dbbox 8 projected 2D bounding box corners
pose 3dbbox 8 corners with depth values
pose matrix Position + scale + 3x3 rotation
pose euler Position + scale + Euler angles
grasp 6dof 6-DoF grasp pose (position + rotation)
grasp 2dplane 5 points defining the grasp in 2D
grasp 3dplane 5 grasp points with depth
trajectory 2d 2D pixel path from pick to place
trajectory 3d 3D path with depth
spatial relative_position Spatial relationship between objects
depth absolute Depth/distance measurements
object_rearrangement point_wise Which objects to move before grasping

Citation

If you use this code or dataset, please cite the BOP-ASK paper (arXiv:2511.16857):

@article{bhat2025bop,
  title={BOP-ASK: Object-Interaction Reasoning for Vision-Language Models},
  author={Bhat, Vineet and Kim, Sungsu and Blukis, Valts and Heinrich, Greg and Krishnamurthy, Prashanth and Karri, Ramesh and Birchfield, Stan and Khorrami, Farshad and Tremblay, Jonathan},
  journal={arXiv preprint arXiv:2511.16857},
  year={2025}
}

Roadmap / TODO

  • Release data generation framework (this repository)
  • Release evaluation scripts
  • Release model checkpoints

About

No description, website, or topics provided.

Resources

Stars

4 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages