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/ttyUSB0 --new-id 0
handumi servo set-id --port /dev/ttyUSB0 --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. Map left_wrist, right_wrist, and workspace 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 views: left_wrist, right_wrist, and 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.

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_wrist
handumi calibrate spatial intrinsics --camera right_wrist
handumi calibrate spatial intrinsics --camera workspace

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 mandos mount --side left
handumi calibrate spatial --device pico --pico-mode mandos 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 mandos session --side left
handumi calibrate spatial --device pico --pico-mode mandos verify --side left
handumi calibrate spatial --device pico --pico-mode mandos 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 all cameras and both TCP trails in Rerun. The table surface must align with z=0. If only the workspace-camera stage fails, retry it with:

handumi calibrate spatial workspace

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.

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#

LEFT=outputs/tcp_pivot_left
handumi record --output-dir $LEFT --skip-feetech --no-voice-control \
  --cameras left_wrist --task "tcp pivot left" \
  --episodes 1 --episode-time-s 25 --tracking-loss-timeout-s 3 --no-sounds

handumi calibrate tcp pivot --side left --dataset $LEFT

--dataset resolves the recording’s parquet and episode, 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.

Your hands are busy holding the tool during a pivot capture, so --no-voice-control keeps the episode on the plain ENTER-then-timer flow instead of waiting to be spoken to.

Step 2. Capture and fit the right side#

The same two commands with right in place of left:

RIGHT=outputs/tcp_pivot_right
handumi record --output-dir $RIGHT --skip-feetech --no-voice-control \
  --cameras right_wrist --task "tcp pivot right" \
  --episodes 1 --episode-time-s 25 --tracking-loss-timeout-s 3 --no-sounds

handumi calibrate tcp pivot --side right --dataset $RIGHT

Both sides write into the same candidate file; 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.

Then compare the two sides. The mounts are mirror twins, so two of the position components should agree between them and only one flips sign. A mismatch of several millimeters on the components that should agree means one of the captures drifted, not that the tool is asymmetric.

Which component flips sign depends on the tracking device’s own controller frame convention, not on the physical mount: for meta it is y (x/z agree); for pico it is x (y/z agree). Identify it from your own two captures before symmetrizing – don’t assume y just because the formula below is written that way.

Step 4. Promote it into the project#

Pivot fitting solves translation only, so keep the official quaternions and symmetrize just the measured positions. Below, y names whichever component you identified as the sign-flipping one in Step 3 (x for pico, y for meta), and x/z name the two that agree:

x = (left.x + right.x) / 2
y = (left.y - right.y) / 2
z = (left.z + right.z) / 2
left.position  = [x,  y, z]
right.position = [x, -y, z]

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_tool:
  gripper: ARX5_beta          # the tip physically screwed onto HandUMI
  controller_mount: handumi_v1
controller_tcp_calibrations:
  meta: configs/calibration/controller_tcp/meta_ARX5_beta.yaml

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.