Running the pipeline end to end#
The complete procedure across all five stages, from demonstrations to an evaluation report. Each step links to the page documenting it in detail. For a minimal install check, see First steps.
Before you start#
Hardware, software versions and the calibration references are in Requirements. Two things matter for the run itself: the simulator stages have to run one at a time, because concurrent Isaac Sim processes exhaust GPU memory and an aborted one can hold its allocation until it is killed, and a full recording grid is hours of manual work.
1. Environment#
cd so101-camera-multiview-bench
source .venv/bin/activate
unset PYTHONPATH
export PYTHONNOUSERSITE=1
export OMNI_KIT_ACCEPT_EULA=YES
Those three lines belong in every shell that runs a simulator command. They
keep the machine’s globally installed Python packages out of this environment.
PYTHONPATH is usually set by a sourced ROS setup and would pull ROS packages
in, PYTHONNOUSERSITE stops packages under ~/.local from shadowing the ones
you installed here, and OMNI_KIT_ACCEPT_EULA answers the licence prompt that
would otherwise stall a headless run.
Installation explains each one, and covers installing Isaac Sim and
PyTorch cu128.
Verify the environment with:
python -m so101_mvbench.tools.list_envs # prints the LiftCube-* task IDs
2. Get demonstrations#
Record new demonstrations (the path that produces new data):
python -m so101_mvbench.recording.teleop_recorder \
--task LiftCube-Sim --action_source gamepad \
--num_episodes 27 \
--spawn_bins --spawn_bin_col 1 --spawn_bin_row 1 \
--repo_id local/bin_c1_r1 --repo_root datasets/01_raw_gamepad \
--task_name 'lift the cube' --seed 42
Guide the arm to the cube, lift it, and press Y to return to the home
pose. Repeat per bin (the recorder appends bin_c<col>_r<row> to the root
itself). Controls and constraints: Stage 1 — Recording.
Or use an existing dataset. Any LeRobot v3 dataset with the camera
streams and meta/*_scene_state.json files works from stage 3 onward. The
reference corpus is on the Hugging Face Hub as
ill337/so101-bin-lift-6cam-cylroom;
how to download a pinned revision is in Data layout.
Check a recorded bin before spending GPU time on it:
# adjust: BIN_ID counts row-major from 1, so column 1 / row 1 on a 5-column
# grid is 1*5 + 1 + 1 = 7. The tool prints which bin it resolved to.
BIN_ID=7
python -m so101_mvbench.tools.validate_bin \
--bin_id "$BIN_ID" --dataset_root datasets/01_raw_gamepad/
3. Render the camera views and fuse#
# Extract per-episode trajectories (CPU-only)
for bin in datasets/01_raw_gamepad/bin_c*_r*; do
python -m so101_mvbench.recording.trajectory_extractor \
--dataset_root "$bin" --output_dir "$bin/trajectories"
done
# Render all cameras of each bin in one replay pass
for bin in datasets/01_raw_gamepad/bin_c*_r*; do
python -m so101_mvbench.recording.multicam_replay \
--dataset_root "$bin" --episodes all \
--scene_config src/so101_mvbench/tasks/scene_configs/lift_cube_6cam.json \
--output_base datasets --mode create --headless
done
# Concatenate the per-bin datasets into one training dataset
export TMPDIR="datasets/04_fused/.tmp"
mkdir -p "$TMPDIR" # nothing creates it, and tempfile does not either
python -m so101_mvbench.assembly.bin_fusion \
--datasets_root datasets/lift_cube_6cam_cylroom \
--target datasets/04_fused/my_corpus \
--bins bin_c1_r1 bin_c2_r1
The camera set is defined in the scene-config JSON — changing it is an edit there, not a code change. Append mode, the TMPDIR pitfall, and a known encoder deadlock: Stage 2 — Assembly.
4. Train a policy#
python -m so101_mvbench.training.train_act_camera_perm \
--cameras wrist front \
--dataset my_corpus \
--dataset_root datasets/04_fused/my_corpus \
--steps 65000 \
--output_dir outputs/2cam_wrist_front
--cameras selects this policy’s subset from the streams the dataset
provides — the variable a viewpoint comparison manipulates. Holdout bins
are excluded via --episodes. Details: Stage 3 — Training.
5. Evaluate#
One policy, one bin, in-distribution:
python -m so101_mvbench.evaluation.async_eval \
--mode async --num_envs 10 --num_episodes 27 \
--policy_path outputs/2cam_wrist_front/checkpoints/065000/pretrained_model \
--scene_config src/so101_mvbench/tasks/scene_configs/lift_cube_6cam.json \
--dataset_root datasets/01_raw_gamepad/bin_c1_r1 \
--output_dir outputs/2cam_wrist_front/eval_cylroom_async_bin_c1_r1/run01 \
--seed 42 --settle_steps 130 --full_report --headless
For a comparison between camera configurations, use the campaign orchestrator instead of repeating the command by hand — it applies one protocol to every policy and is resume-safe:
POLICY_ROOT=outputs DATASETS_ROOT=datasets/01_raw_gamepad OUT_ROOT=outputs/campaign \
POLICIES="2cam_wrist_front 2cam_wrist_left" \
bash scripts/run_campaign.sh
DRY_RUN=1 prints the exact per-policy commands without booting Isaac.
Protocol rationale, OOD variants, and what to check after a killed run:
Stage 4 — Evaluation.
Warning
Redirect Isaac Sim output to a file — never pipe it through tail or
grep. SIGPIPE kills the simulator instantly. tail -F on the log file
afterwards is fine.
6. Report#
python -m so101_mvbench.evaluation.report \
--dir outputs/2cam_wrist_front/eval_cylroom_async_bin_c1_r1/run01
python -m so101_mvbench.evaluation.aggregate_report \
--run_dirs outputs/2cam_wrist_front/eval_cylroom_async_bin_c1_r1/run* \
--output_dir outputs/2cam_wrist_front/eval_cylroom_async_bin_c1_r1/aggregate
CPU-only and repeatable. The aggregate gives mean ± std across the repeats. That spread measures floating-point non-determinism rather than seed variation, so report both together. Available plots: Stage 5 — Reporting.
Expected results#
Stage |
Artefacts |
Runtime |
Completed correctly when |
|---|---|---|---|
1 Recording |
|
manual, 1–2 min per episode |
|
2 Replay + fusion |
|
about 20 min per bin, fusion minutes on CPU |
The episode count matches the source and the pixel-diff evidence in |
3 Training |
|
under 2 h for 65 k steps, two cameras |
Loss drops steeply and then flattens, and checkpoints appear at the save frequency |
4 Evaluation |
|
minutes per run, hours for a campaign |
Episodes reach |
5 Reporting |
|
seconds |
The reported success rate matches what the videos show, and the cross-run spread stays within a few percentage points |
Two frequent failure modes:
Every episode ends
0_miss, the gripper closes on empty space. The scene state was not restored — check that--dataset_rootpoints at the recording bin (the directory withbinmap.jsonandmeta/).Training loss decreases but success stays near zero. Usually a camera mismatch: the policy was trained on streams the evaluation scene config does not provide. Compare
--camerasagainst the scene-config camera names.
Success rates are interpretable only with their conditions. Report episode count, evaluated bins, checkpoint step, and number of repeats alongside every number.