Coordinate frames#
The scene is modelled as a pose graph: every component is positioned relative to
whichever frame is most natural for it, and scenes differ only in where the
graph’s root sits in the world. This page is the contract. The constants themselves live
in so101_mvbench.utils.so101_transforms.
Read it as two chains that meet at the cube {P}. The arm chain runs world
{0} -> arm base {B} -> end effector {E} -> wrist camera {C_w}, given by the
scene and the robot’s kinematics. The workspace chain runs {0} -> workspace
{W} -> external camera {C_E}, and the bin spawner places the cube relative to
{W}. Three edges carry the task: the two cameras observe the cube, and the
policy has to learn {E} -> {P}, the transform that closes the loop,
from those observations alone. {W} -> {C_E} is the experimental variable —
changing it is what a camera configuration does.
Moving the rig into another scene changes {0} -> {W} and {0} -> {B} by the
same rigid translation, so every edge inside the two chains is untouched. That
is why a recorded joint trajectory replays unchanged in a different scene.
The workspace frame {W}#
All task geometry is authored in one frame, the workspace frame {W}: the bin grid is centred on it, external cameras orbit it on a hemisphere (azimuth, elevation, radius), the cube spawns on it, and the arm base sits at a fixed offset from it. A scene places this frame in the world by declaring one value:
"workspace_origin_m": [0.0, 0.0, 0.8663]
in its scene config (SceneConfig.workspace_origin_m). That triple is the
{W} origin expressed in the scene’s world frame — nothing else about the task
geometry changes between scenes.
Fixed {W}-relative anchors (identical in every scene):
Anchor |
Position in {W} |
|---|---|
Bin grid centre |
|
Arm base |
|
Cube spawn height |
|
Camera hemisphere centre |
|
The two origins that matter#
Constant |
Value |
Meaning |
|---|---|---|
|
|
{W} origin of the retired floor-level table scene. Kept solely to interpret legacy datasets. |
|
|
{W} origin of the cylindrical room — the scene |
Their difference, (-0.20, 0.0, 0.7463), is the historical ARM_BASE_OFFSET:
the rigid translation that carried the whole floor-scene rig onto the pedestal
table.
Recorded data declares its frame#
Every recording writes its scene’s {W} origin into both sidecars:
binmap.json→ top-levelworkspace_origin_mmeta/episode_*_scene_state.json→workspace_origin_m
A dataset without the field predates the mechanism and by definition means
the floor-level table scene (HEMISPHERE_ORIGIN_W). That is why the released
corpus records the bin_c0_r0 cube at world x = 0.32 while a fresh cylroom
recording of the same bin records x = 0.12: both are 0.12 in {W}.
Two shifts, not one#
Replay and evaluation derive two distinct translations, and conflating them is the documented failure mode (it once put the arm on the floor):
RIG_SHIFT= target origin −BASE_SCENE_ORIGIN_W. Re-anchors the scene furniture (robot base, action pad, cube spawn, external camera). Zero for the shipped configs, because the base scene already is the cylindrical room.DATA_SHIFT= target origin − source origin. Transports recorded coordinates (binmap x/y, spawn height) from the scene the data was captured in. Zero when replaying a cylroom recording in the cylroom, and the full table-to-cylroom delta for a legacy dataset.
Because the whole rig moves rigidly, the arm-to-cube geometry is identical in
every scene and recorded joint trajectories replay unchanged — this is the
pose-graph property the entire cross-scene pipeline rests on. Verified
end-to-end: the cube lands at the predicted world position with
err_xy = 0.0 m for both legacy and native recordings
(multicam_replay --verify_placement).
Conventions worth pinning#
Robot forward = +X. The SO-ARM101 USD mesh defaults to −Y, and a +90° yaw in
init_state.rotturns it to +X.Quaternion convention is XYZW since Isaac Lab PR #4437. Helpers in
so101_white.pyandlift_cube_env_cfg.pyswizzle theeuler_angles_to_quat()output (which is WXYZ) accordingly.XFormPrim.set_local_poses()is WXYZ. Different convention thanOffsetCfg.rotandinit_state.rot— easy to mix up.