Skip to content

Repository files navigation

BitRobot HIW-500 → Rerun

This repository demonstrates how Rerun can be used to process and use the BitRobot/HIW-500 dataset (Unitree G1 bimanual teleoperation, "Humanoid In the Wild"). We show how Rerun helps preprocess and augment the raw ROS2 MCAP episodes into per-episode recordings, view them, query across episodes, and train on them.

HIW-500 in the Rerun viewer

Work in progress: this repository is evolving — some capabilities (e.g. training) are still being built out, and we'll add more over time.

Note: this demo uses Pixi — you can get it here.

Download

We use a subset of the dataset for this demo — three "Move the pillow…" takes, 29 short episodes (~5.6 GB). Download them into HIW-500/:

pixi run download_sample_episodes

The downloader deliberately resolves only these subtrees before downloading their files. Avoid using hf download --include for this dataset: the Hugging Face CLI enumerates the entire repository (over 90,000 entries) before applying the pattern, which can look like a hang.

Agent skills

This repository was built using Rerun's agent skills. You can get them via npx skills add rerun-io/rerun.

What the episodes contain

The dataset consists of episodes, each a ROS2 MCAP recording of a Unitree G1 with two Dex1 single-DoF grippers performing a manipulation task. The MCAPs carry custom ROS2 message types alongside the standard ones, and a sibling info.json sidecar holds the task label, subtask boundaries (ns timestamps), and scene id.

Topic Rate Meaning
/camera/head/image/compressed 30 Hz stereo head cam, 1280×480 (two 640×480 side-by-side)
/camera/{left,right}_wrist/image/compressed 30 Hz wrist cams, 640×480
/wbc_lerobot 50 Hz LeRobot payload (JSON): bimanual EE-space ee_state (obs), ee_action, gripper_controls, pivot
/stamped/lowstate 100 Hz 35-slot Unitree motor array (first 29 real): q/dq/ddq/tau_est/temp + IMU
/stamped/lowcmd 100 Hz motor commands (q/dq/tau/kp/kd)
/stamped/secondary_imu 100 Hz quaternion/gyro/accel/rpy
/stamped/dex1/{left,right}/{cmd,state} 100 Hz per-gripper joint
/lf/odommodestate 20 Hz base odometry: position, velocity, rpy, foot forces
/annotation static task label string

/wbc_lerobot.ee_state / ee_action are 12 floats = [left 6][right 6], each a 6-DoF position(xyz) + rotation-vector(3).

Processing → per-episode RRDs

Each episode is processed into several Rerun recordings (.rrd) that share a recording_id, so the viewer/catalog stacks them as layers of one logical recording: a faithful base layer plus derived layers (robot model, odometry, cameras, metadata properties). Layers let us flexibly augment the dataset — add, replace, or re-run a derived view without touching the others, and overlay them only when wanted.

Build every layer for every episode in one command:

pixi run process            # all episodes under HIW-500/
pixi run process <ep.mcap>  # a single episode

Each layer also has its own task (process_base, process_urdf, process_odom, process_cameras, process_properties) writing its own .rrd, so any layer can be run or re-run independently — fix the camera layer and rebuild just that, or add a layer to already-processed episodes. Each task takes the same optional single-episode argument. (After an entity-layout change, re-run register so the catalog picks it up.)

Base layer

hiw_500/preprocess.py — a faithful conversion of the raw streams, no kinematics. A single McapReader stream shaped by lenses: cameras decode to EncodedImage (the stereo head is split into /camera/head/{left,right}), the custom homies/* / unitree_go/* messages become per-joint and end-effector scalars/transforms (/state/…, /cmd/…, /lerobot/…), and info.json becomes the /episode + /task/subtask sidecar. Entities keep the MCAP-native timelines.

URDF layer

hiw_500/urdf_layer.py — the animated Unitree G1 mesh, driven by forward kinematics from the /stamped/lowstate joint positions (emitted to /robot/transforms). Its 29 revolute joints match the documented Unitree motor order. (The Dex1 finger joints stay at rest — no 1:1 mapping in the URDF.)

The URDF is not part of the HF dataset. We use g1_29dof_mode_15_with_dex1_1.urdf from Unitree's unitree_ros (the Dex1 variant), vendored with its meshes under urdf/g1/.

Odometry layer

hiw_500/odom_layer.py — connects the robot to the world. The URDF layer roots the G1 at pelvis, so on its own it animates in place; this layer adds the time-varying odom → pelvis transform from /lf/odommodestate so the whole robot moves through the scene.

Camera layer

hiw_500/camera_layer.py — places the head camera in 3D. The head D435 is a fixed joint in the URDF, so its mount is already known; this layer adds the optical-frame transform plus the Pinhole intrinsics (from a vendored D435 calibration at calibration/) so the head image projects onto its image plane (both stereo eyes). The wrist cameras stay 2D — the dataset provides no camera→robot (hand-eye) calibration for them.

TODO / caveat: the calibration in calibration/ was grabbed from a different episode's calibration/params/ on HuggingFace (our sample episodes don't ship one). It may not be the right calibration for these cameras — cleaning this up (using each episode's own calibration) is a known TODO.

Properties layer

hiw_500/properties_layer.py — per-episode metadata logged as recording properties, which the catalog surfaces as columns to filter, sort, and search on: task, task_group, duration_sec, num_subtasks, subtask_labels, robot. Values come from info.json, the dataset path, and a constant.

View & Serve

Each episode is one logical recording split across five .rrd layers (base, urdf, odom, cameras, properties) that share a recording_id.

Quick viewing

Open the recordings straight in the viewer — same recording_ids merge into one recording per episode, so all layers line up:

rerun rrds/*.rrd        # every episode
rerun rrds/<id>*.rrd    # one episode's layers

Good for a quick look. No server, no registration.

Blueprint

hiw_500/blueprint.py builds a default viewer layout (a 3D scene in the odom frame, the four camera streams, a task log, and joint / end-effector plots) and saves it to blueprints/hiw_500.rbl:

pixi run blueprint

register (below) installs it as the dataset's default blueprint, so every episode opens with this layout instead of an auto-generated blob.

Catalog server

The more interesting use of a dataset like this is a catalog: episodes as queryable segments with named layers, the way the cloud ingestion flow works. Layer names are assigned at registration time — so don't point rerun server -d rrds at the folder, it loads every file under the default layer base and fails with Layer 'base' already exists. Register instead:

pixi run serve      # start the in-memory Rerun catalog on :51234 (leave running)
pixi run register   # in another shell: register all episodes as the `hiw_500` dataset

register (hiw_500/catalog.py) creates the dataset, attaches each episode's RRDs as its base/urdf/odom/cameras/properties layers (paired by recording_id; the properties layer becomes per-segment columns), and installs blueprints/hiw_500.rbl as the default blueprint. Then connect the viewer to rerun+http://127.0.0.1:51234, where you can browse, sort, filter, and query across episodes rather than open them one at a time.

hiw-500-1080.mp4

References

About

Demo repository with some examples using the HIW 500 humanoid dataset

Resources

Stars

5 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages