HandUMI Setup and Calibration#

Complete this page before recording. No robot arm is required: these steps configure HandUMI, its tracking device, cameras, grippers, and workspace. Some calibrations are permanent for one physical assembly; the table/session alignment must be checked each session.

Calibration

Repeat when

Servo homing and opening width

Servo, linkage, or gripper geometry changes

Camera intrinsics

Camera, resolution, or focus changes

Controller-to-camera mount

A controller or wrist camera mount moves

Controller-to-TCP

The controller/gripper mount or physical tool changes

Table/session frame

Each session, relocalization, or tracking reset

1. Map HandUMI Hardware#

install.sh creates the ignored machine-local configs/rig.yaml. Inspect the connected cameras and Feetech adapters:

handumi setup ports

Reconnect one physical device at a time and assign its port under cameras or feetech in configs/rig.yaml. Robot-arm buses do not belong in this recording setup; configure them only for real-robot teleoperation.

Set new Feetech IDs only when required:

handumi servo set-id --port /dev/ttyACM0 --new-id 0
handumi servo set-id --port /dev/ttyACM1 --new-id 1
Hardware mapping details

Two grippers may share one serial port only when they use different servo_id values. With separate USB adapters, each side normally has its own port.

A USB camera commonly exposes two /dev/video* nodes. Start with the first node reported for each physical camera and confirm the stream. Camera keys are logical dataset names; map each physical camera explicitly in configs/rig.yaml.

Keep these machine-local paths in configs/rig.yaml; do not commit them as portable project configuration.

Camera types and resolutions#

Declare capture settings for the three logical roles: left, right, and workspace. Their keys may be any names. The aliases left/left_wrist, right/right_wrist, and top/workspace are recognized automatically; otherwise add role: left, role: right, or role: workspace. Each view can use the normal opencv backend or zedmini, which expects the ZED Mini side-by-side UVC mode and exposes only its left image:

cameras:
  workspace:
    type: zedmini
    index_or_path: 4
    width: 1344
    height: 376
    fps: 30

For zedmini, width and height describe the captured stereo frame. HandUMI keeps frame[:, :672], so previews and datasets contain one 672×376 RGB image. The supported rates for this mode are 15, 30, 60, and 100 FPS. index_or_path accepts either an OpenCV integer such as 4 or an explicit Linux path such as /dev/video4. Per-camera values in cameras: take precedence over the global camera fallbacks used by older rig files.

For USB cameras whose high-resolution/high-rate modes are available only as Motion-JPEG, add fourcc: MJPG to that camera entry. For example, a camera that exposes 1280x960 @ 30 FPS as MJPG but only 5 FPS as YUYV should use:

cameras:
  left_wrist:
    type: opencv
    index_or_path: /dev/video0
    width: 1280
    height: 960
    fps: 30
    fourcc: MJPG

2. Calibrate the Grippers#

First confirm that both encoders change smoothly while opening and closing:

handumi calibrate grippers monitor

Run the complete guided calibration:

handumi calibrate grippers
handumi calibrate grippers --side right  # one side only

For each side, enter the maximum physical opening in millimeters. The command then asks you to hold the gripper at mid-travel and press Enter, which centers the encoder range to avoid the 0/4095 wrap point. Finally, place the gripper fully open and press Enter, then fully close it and press Enter. The result is stored in ~/.cache/handumi/calibration.yaml. Open and close each gripper again with monitor and confirm that width increases toward fully open without flipping or saturating.

3. Connect Tracking#

Meta Quest#

Enable Developer Mode, connect the headset over USB, authorize adb, and install HandUMI Quest App:

wget https://github.com/murobotics-ai/handumi-quest-app/releases/download/v0.2.1/handumi-quest-app-v0.2.1.apk
adb install -r handumi-quest-app-v0.2.1.apk
adb shell ip route  # find the address after "src"

Set that address as meta_quest.connection.quest_ip in configs/rig.yaml. Launch the app from Library → Unknown Sources and keep it in the foreground.

python -m handumi.tracking.meta_quest --config configs/rig.yaml

A healthy stream reports steady FPS and both controllers tracked.

PICO#

install.sh already installed the XRoboToolkit PC service unless it was run with --skip-xrt; install it from the XRoboToolkit PC Service releases only if /opt/apps/roboticsservice/runService.sh is missing. Follow the current XR Robotics headset instructions for the headset side. Start the PC service in its own terminal and leave it running – xrt.init() aborts with a core dump without it:

bash /opt/apps/roboticsservice/runService.sh

Use 127.0.0.1:63901 for USB or the workstation IP with --pico-wifi.

Smoke-test a short capture before calibration:

handumi record --output-dir outputs/pico-smoke \
  --device pico --skip-feetech --no-voice-control \
  --task "pico smoke" --episodes 1 --episode-time-s 10

Healthy output reports xrobotoolkit_sdk initialised without repeated still waiting for PICO data messages.

4. Calibrate Cameras and Workspace#

Fix the 5 × 7 ChArUco board flat at its marked table position, with IDs 15 and 16 nearest the operator. Its center defines the table origin: +X right, +Y away, and +Z up.

Camera Intrinsics#

handumi calibrate spatial intrinsics --camera left
handumi calibrate spatial intrinsics --camera right
handumi calibrate spatial intrinsics --camera top

Move the board throughout each image and vary distance and inclination. The tool automatically accepts a distinct valid view every two seconds. Repeat after changing camera, resolution, or focus.

Controller-to-Camera Mounts#

Keep the board fixed. Move the complete HandUMI through varied roll, pitch, and yaw poses, pausing briefly for each automatic capture. Keep the controller tracking ring visible to the headset.

Choose the tracking device explicitly. Global options such as --device, --pico-wifi, and --quest-ip come before the subcommand.

Meta Quest:

handumi calibrate spatial --device meta mount --side left
handumi calibrate spatial --device meta mount --side right

PICO:

handumi calibrate spatial --device pico --pico-mode controllers mount --side left
handumi calibrate spatial --device pico --pico-mode controllers mount --side right

PICO calibration relies on live XRoboToolkit snapshots, so hold the HandUMI steady while each view is accepted. Use --pico-wifi for a wireless PICO setup.

Repeat only if a controller or wrist-camera mount moves.

Session/Table Frame#

With the board still at its marked position and the headset fixed as it will be during recording, solve the table frame for the same tracking device.

handumi calibrate spatial --device meta session --side left
handumi calibrate spatial --device meta verify --side left
handumi calibrate spatial --device meta visualize

For PICO:

handumi calibrate spatial --device pico --pico-mode controllers session --side left
handumi calibrate spatial --device pico --pico-mode controllers verify --side left
handumi calibrate spatial --device pico --pico-mode controllers visualize

session reads wrist-camera intrinsics and the controller-to-camera mount from outputs/calibration/spatial.yaml by default. If calibration was saved under a different name, select that same file explicitly for both commands, for example --spatial outputs/calibration/spatial_60mm.yaml. The command stops before hardware capture when the selected spatial file and configs/rig.yaml describe different ChArUco dimensions, and warns when their camera mappings differ.

verify captures fresh views without changing either calibration and rejects a table-frame error above its acceptance limits. Run it while the board is still fixed; recapture the session if it fails.

Inspect the wrist cameras and both TCP trails in Rerun. The table surface must align with z=0.

The fixed workspace camera is not part of the controller-to-table calibration chain. Recording its video does not require a spatial pose. Only calibrate its pose if a downstream workflow needs geometric projection or inpainting:

handumi calibrate spatial workspace

This optional command requires workspace-camera intrinsics. Its absence does not make session, verify, recording, or replay invalid.

Remove the board without moving the table, cameras, or headset. Repeat the session calibration after relocalization or a tracking reset. The saved outputs/calibration/session.yaml records tracking_device and table_from_device; use it only with the same --device.

When continuing a dataset with handumi record --resume, the recorder reads the current session file again and archives its calibration for the new episode range. You may reuse the same session while the VR origin is unchanged; after relocalization or a new day’s tracking setup, regenerate and verify it first. Use --session-calibration PATH if its location changed. See Resume a Recording for selection and history details.

5. Calibrate the HandUMI Tool Tip#

Controller-to-TCP reconstructs the physical tool-tip pose from each tracked controller. It belongs to the tool assembly – the gripper tip screwed onto the HandUMI shells, plus the controller mount – and not to the robot the data is later retargeted to. A different tip needs its own calibration even on the same robot, and one tip serves every robot it is used with.

Each side is captured the same way: wedge the tip into a firm indentation so it cannot slide, then rotate the rest of the assembly around it for 25 seconds, through as many different orientations as the mount allows. The tip staying put is what makes the fit correct; the variety of orientations is what makes it well-conditioned.

Step 1. Capture and fit the left side#

handumi calibrate tcp pivot --side left --time-s 25

This command opens only the VR tracking backend: it does not initialize cameras, Feetech grippers, or robot motors. After the left controller is tracked, fix the tip in the indentation and press ENTER. The raw controller poses are saved to outputs/tcp_pivot_left/poses.csv, and the fit is written to outputs/calibration/controller_tcp_candidate.yaml. The tracking device comes from recording.device in configs/rig.yaml; pass --device to override it.

--output-dir can override the capture directory. The older --dataset, --parquet, and --csv fitting inputs remain supported.

Step 2. Capture and fit the right side#

The same two commands with right in place of left:

handumi calibrate tcp pivot --side right --time-s 25

Both sides write into the same candidate file, including their fit metrics; nothing is applied to the project yet.

Step 3. Check the fit#

handumi calibrate tcp inspect

Metric

Accept

If it fails

RMS

below 0.50 cm

The tip slipped. Find a deeper indentation and recapture.

Maximum error

below 1.00 cm

As above; check for a moment of lost tracking.

Condition

below 500

The capture lacked rotational variety. Recapture covering more orientations.

Recapture that side until it passes. Do not promote a fit that does not.

inspect reports bilateral symmetry as an advisory diagnostic. The nominal mirror axis is x for PICO and y for Meta. This is an assembly assumption, not a measured constraint: seating and mounting rotation can affect every component, including the mirror axis. Differences produce warnings, never automatic averaging or rejection solely for asymmetry. handumi calibrate verify uses the same advisory policy.

Validate each side independently. Repeat a pivot capture with varied rotations and compare its offset with the previous capture of that same side. A low fit residual alone does not prove the correct physical point was held fixed. Use the intended grasp TCP consistently; after promotion, keep that point in a fixed indentation and check that it stays fixed while rotating the tool. Check known-height contacts in the verified table frame as a separate test.

Step 4. Promote it into the project#

Pivot fitting solves translation only, so keep the official quaternions and copy just the two measured position vectors, one per side, into the calibration file for this tool assembly. Record the pivot statistics and the date under metadata so the next reader knows what the numbers are worth.

Update only those two position values in the calibration file for this tool assembly. Those files live in configs/calibration/controller_tcp/ as {device}_{tool}.yaml, and each robot points at its own under controller_tcp_calibrations in configs/robots/<robot>.yaml:

handumi calibrate tcp promote --target pico_piper_beta.yaml

Only the filename is accepted; it is always resolved below configs/calibration/controller_tcp/. Promotion repeats the individual-fit and bilateral-symmetry diagnostics, and copies each side’s measured position without averaging. Both individual fits must pass; positions must be finite and both fits must identify the same tracking device. If the target exists, confirm the Override it? [Y/n] prompt. Existing quaternions and historical metadata are preserved. The top-level tcp_promotion record is authoritative for the current translations: it records promotion time, candidate fingerprint, per-side metrics and positions, and the orientation source. Older metadata may describe older measurements. --force is deprecated and cannot bypass individual fit checks.

For a genuinely new tool file there are no official quaternions to preserve. Name an existing calibration made with the same tracking device and controller mount as the orientation source:

handumi calibrate tcp promote --target pico_new_tip.yaml \
  --quaternion-source pico_piper_beta.yaml
handumi_tool:
  gripper: piper_beta         # the tip physically screwed onto HandUMI
  controller_mount: handumi_v1
controller_tcp_calibrations:
  pico: configs/calibration/controller_tcp/pico_piper_beta.yaml
  meta: configs/calibration/controller_tcp/meta_ARX5_beta.yaml  # Meta rig, ARX5 tip

Fitting a new tip means writing a new file and pointing the robot at it, rather than overwriting the previous tip’s calibration. Existing datasets keep their own recorded assembly identity, so old recordings stay reproducible.

Step 5. Verify#

Continue with the complete verification below. It replaces the removed tests/tracking/test_transforms.py and tests/scripts/test_replay_in_sim.py checks with a supported operator command.

6. Verify All Calibrations#

Run the final preflight after gripper, spatial/session, TCP, and robot/table calibration. --robot and --device may be omitted when they are set under recording in configs/rig.yaml:

handumi calibrate verify --robot piper --device pico

Physical robot/table placement is laboratory-local. Copy configs/calibration/table/local/example.yaml to the ignored conventional per-robot path, then measure it:

cp configs/calibration/table/local/example.yaml \
  configs/calibration/table/local/piper.yaml

Identify the laboratory in configs/rig.yaml:

deployment:
  lab: my_research_lab

Use the same stable lab identifier inside the copied calibration file. This identifier is saved with replay and conversion provenance; a mismatch is rejected instead of silently applying another laboratory’s transform. deployment.table_calibrations.<robot> remains available as an optional path override for laboratories that keep private calibration outside the repository.

The verifier requires scope: physical; it never treats the portable files in configs/calibration/table/sim/ as measurements of the current lab.

The command first checks:

  • both cached gripper calibrations and their encoder spans;

  • all three camera intrinsics, both controller-camera mounts, and their error limits;

  • session identity, spatial hash, tracking device, and table-frame residual;

  • the controller-to-TCP file actually selected for this robot/device, its device-specific mirror invariant, quaternion normalization, and tip-to-controller distance;

  • the robot-to-table file and whether it is marked physically verified.

It then guides the physical check: touch a marked point first with the left tip and then with the right tip, followed by touching the table with both tips. It fails if the calibrated point separation exceeds 15 mm or either table contact is farther than 15 mm from z=0. Use --static-only to inspect files without connecting the tracking device.

The default tip-distance ceiling is 0.35 m. That limit describes the physical tips currently in use, not a universal HandUMI constant. A genuinely longer new tip may use --max-tip-distance-m <meters> after its pivot fit has passed; do not widen the limit to hide a wrong robot/tool selection.

Next: Record Demonstrations.