Session recording
Robot recordings use an episode and review workflow: engage teleoperation with a to begin a take, then pause or park to seal it. Only TELEOP events are saved. Startup, idle, pause, fault, and shutdown samples are discarded. Each episode has separate event, camera, and tactile files. Review changes metadata and retains the local samples. Camera-only recordings use the same startup confirmation and take workflow.
#Recording types
--robot selects the control rig; --record TYPE independently selects the
saved signals and cameras. UR5e and UR5e + Sharpa use these default BC profiles:
| Type | Signals | RGB views |
|---|---|---|
bc-ur5e |
Arm commands, targets, measured joints, wrist/EE poses, control events | ZED left, D405 color, wrist |
bc-ur5e-sharpa |
Arm + hand joints, glove, landmarks, physical five-fingertip tactile, control events | ZED left, wrist |
hand-eye-calib |
Arm commands, targets, measured joints, wrist/EE poses, control events | Two ZED devices × left/right = four views |
zed-only |
Camera frames and calibration metadata; requires --robot none |
Two ZED devices × left/right = four views |
uv run dmani --robot ur5e --mode hw --record bc-ur5e
uv run dmani --robot ur5e-sharpa --mode hw --record bc-ur5e-sharpa
uv run dmani --robot ur5e --mode hw --record hand-eye-calib
uv run dmani --robot none --record zed-only
# Choose the archive directory separately:
uv run dmani --robot ur5e --mode hw --record hand-eye-calib --record-output out/hand-eye-takeProfiles live in configs/recording/. Selecting a type leaves the robot input,
retargeting, IK, state machine, and actuator sinks unchanged. hand-eye-calib
requires the arm-only --robot ur5e; hand rigs are rejected before launch.
No hand driver, glove, or hand retargeting nodes are started. Arm feedback,
command safety, and y startup confirmation stay active.
An explicitly named type enables its cameras in sim, dummy, or hardware mode.
Bare --record uses the default profile and retains mock-input camera opt-out.
Legacy directory paths such as --record out/take still work.
--robot none defaults to zed-only and uses the shared state manager and keyboard.
Press y to confirm startup, wait for IDLE, then a to start recording.
a pauses/resumes, b returns to idle, and c shuts down.
Only TELEOP takes are saved; y/x keeps/excludes a paused or idle take.
No robot, hand, glove, Vive, IK, or simulator is started. Automatic/headless
launches are rejected. Sessions open in dmani-view. Only complete stereo pairs are saved;
unpaired_camera_frames counts any unmatched frames omitted at shutdown.
hand-eye-calib and zed-only store zed_1_left, zed_1_right, zed_2_left, and
zed_2_right as 1280 × 720 RGB images from 60 FPS stereo capture.
hand-eye-calib saves each view at 30 FPS; zed-only saves each view at 60 FPS.
Each ZED is opened once; its two eye streams share capture timestamps and frame
indices. Capture timing between the two devices is independent. Their USB serials
match, so the profile pins distinct /dev/v4l/by-path/ ports. Update these
sources if moving USB ports. Missing or duplicate devices stop capture startup;
--no-cameras is incompatible with these stereo profiles. Depth is not saved.
Calibration metadata in session.json includes each eye's pixel-space K,
fx/fy/cx/cy, distortion model and coefficients, native resolution, and
unrectified image status. camera_calibrations preserves the complete factory
files and SHA-256 hashes. The current files belong to serials 36155166 and
31793552; startup reads each ZED identity and rejects a mismatch before any
nodes start. Values use the raw factory calibration.
The native calibration views cannot use --image-size. --print-dataflow
shows configured intrinsics without querying devices; synthetic sources mark
intrinsics unavailable.
#Calibrate the two left eyes
See 2. Camera extrinsic calibration for board setup, offline fitting, validation, and the camera-to-base preview.
#Automatic hardware recording
Hardware launches through dmani, dmani-ur5e, tianji, dmani-hand,
dmani-replay, or dmani-policy deploy record automatically by default.
This includes hardware replay.
The launcher prints a new out/recordings/<local timestamp>/ directory; no
--record flag is needed. The run archive's run.json also names that directory
as recording_dir.
# Active hardware control with automatic telemetry capture:
uv run dmani --robot ur5e --mode hw
# Choose a new telemetry directory:
uv run dmani --robot ur5e --mode hw --record-output out/my-hardware-run --no-camerasDuring TELEOP, the archive retains each received arm/hand joint command, state-manager target, and measured joint position, plus available wrist/glove inputs and control/safety events. No rows or image/tactile payloads are written outside TELEOP. These are the joint-position streams published by the current drivers; velocity, torque, and motor current are not added by this change.
UR5e + Sharpa hardware automatically records all five tactile fingertips and
adds the wrist and ZED cameras from its default bc-ur5e-sharpa profile. No --record flag is needed:
uv run dmani --robot ur5e-sharpa --mode hwThe session manifest is created when the dataflow starts; data saving begins
only at the TELEOP state edge after a and ends when TELEOP exits. The
launcher checks camera device paths, Sharpa SDK files/network
routing, and local UDP joint-feedback port 50000 before starting any nodes.
An occupied port is reported with its socket owner; close that application
normally before restarting. A missing configured camera blocks startup;
it is never silently omitted. Enabled recording cameras must be connected.
Live operation also requires actual SDK binaries in hand_hw.sdk_root and
keyboard y confirmation after measured-pose enable.
Use --no-record on the teleop, hand-only, or replay launchers to disable saving:
uv run dmani --robot ur5e-sharpa --mode hw --no-record
uv run dmani-hand --mode hw --no-recordNo out/recordings session is created. Recording-camera nodes and Sharpa tactile
acquisition are skipped, including tactile startup and missing/expired-frame
checks. Run diagnostics still go to out/runs; measured-pose startup,
keyboard y, and all joint-command safety checks remain active.
--record and explicit --camera options conflict with --no-record.
Omitting --no-record restores the default capture behavior.
| Option on UR5e + Sharpa hardware | Session saved | Recording cameras | Tactile acquisition/checks |
|---|---|---|---|
| Default | Telemetry, images, tactile | Enabled | Enabled, all five fingertips |
--no-cameras |
Telemetry and tactile | Disabled | Enabled, all five fingertips |
--no-record |
None | Disabled | Disabled |
Other hardware rigs use --record for the live-input camera presets below;
--no-cameras explicitly suppresses cameras. Cameras already used by a policy
are included in its hardware recording. Simulation and dummy CLI runs require
--record or dmani-record run.
The direct uv run dora run dataflows/teleop_hw.yml graph also records, including
when its driver dry-run environment flags are set. Its recorder logs the chosen
path and reads an optional DMANI_RECORD_OUTPUT override. It saves telemetry
without cameras or interactive take-review keys; completed takes can be reviewed
offline. Recorder failures follow the existing safety-stop path.
#Start a recording
UR5e with the hand selected in configs/ur5e_wuji.yml:
# Device-free MuJoCo check; exits automatically after eight seconds of TELEOP:
uv run dmani-record run --headless --duration 8
# Live Vive + Wuji glove, controlling MuJoCo:
uv run dmani-record run --input vive --operator weison --task "pick and place"
# Live physical UR5e + a supported Wuji hand:
uv run dmani-record run --input vive --mode hwdmani-record run defaults to simulation and mock inputs. The dmani teleop
launcher defaults to Vive for UR5e, plus the Wuji glove for hand rigs.
Live hardware still requires
y during BRINGUP, after enable holds the measured pose. The Tianji + Sharpa
hardware block, selected-arm isolation, and all command gates remain in effect.
Tianji uses the same recorder:
uv run tianji --arm-mode bimanual --record
uv run tianji --arm-mode right --input vive --record
uv run tianji --arm-mode right --mode dummy --headless --duration 8 --recordOmit --output on dmani-record run, or supply bare --record on
tianji, to automatically name the directory from the local session start time:
out/recordings/YYYY-MM-DD_HH-MM-SS.ffffff±HHMM/, for example
out/recordings/2026-09-16_19-08-05.123456-0700/. Microseconds distinguish quick
consecutive runs; the suffix records the UTC offset. Both launchers print the
chosen path. An explicit directory overrides automatic naming and must be new. --operator and --task are optional
labels. --print-dataflow shows the wiring without launching devices or
creating a recording. The direct hardware graph also enables recording as described above.
#RGB camera recording
The bc-ur5e-sharpa profile selects wrist + ZED. bc-ur5e, Wuji, and Tianji
defaults also include the D405 for explicit live-input recordings. The table
shows the current UR5e + Sharpa preset; other rigs keep their own saved sizes.
The same camera nodes serve MuJoCo, dummy, and supported hardware sinks.
| Recording stream | Stored RGB size | Capture / saved FPS | USB selection |
|---|---|---|---|
zed_2i |
1280 × 720, native left unrectified view | 60 / 30 | ZED 2i UVC interface |
wrist |
640 × 480, full view | 90 / 30 | HD USB Camera, video-index0 |
realsense_d405 |
256 × 256, color view | 30 / 30 | D405 serial 318523070403, video-index4 |
Only RGB images are acquired and saved from these cameras. Depth, infrared,
point clouds, IMU data, and camera audio are not captured. The ZED's UVC frames
contain both eyes side by side; zed2i:PATH selects the first half as the
left view before encoding. The ZED requests 2560 × 720 YUYV capture at 60 FPS
with four driver buffers. This is the documented ZED 2i HD720 mode.
The full left 1280 × 720 view is saved at its original resolution without
resizing or cropping. The node reads at 60 FPS and selects frames for saving
at 30 FPS with their source timestamps. Startup rejects a negotiated mode
below 60 FPS. A six-second passive check on 2026-09-23
opened the real ZED in this mode, extracted the left eye, and encoded and
wrote 350 native 1280 × 720 JPEG files using the former 60 FPS save preset.
The rate between first and last host read timestamps was 60.02 FPS (median
interval 16.67 ms). This camera-only check does not include the current 30 FPS
save setting, Dora session recorder, or full teleop load.
The wrist uses MJPG capture and four driver buffers. Its native 640 × 480 view
is preserved without cropping. capture_fps: 90 paces reads, while fps: 30
selects frames for encoding and saving. A device may negotiate a faster native
mode; the node still reads at the configured capture rate. A camera-only check
of the former 60 FPS save setting measured 89.99 FPS capture and 60.06 FPS saved
through the current USB hub.
UR5e per-camera settings live under recording.cameras in the profile YAML;
Wuji and Tianji retain inline rig settings. Legacy
source strings use recording.image_size and recording.camera_fps defaults.
--image-size explicitly overrides all stored views to a square, and
--camera-fps overrides both capture and saved rates for every selected camera.
Replacing a named source, such as --camera wrist=/dev/video0, retains that
camera's preset unless those overrides are supplied.
# Live teleop recording with all RGB cameras (MuJoCo by default):
uv run dmani --robot ur5e --record
# Same cameras with UR5e + Wuji hand:
uv run dmani --robot ur5e-wuji --record
# Same cameras with Tianji + Sharpa (left, right, or bimanual):
uv run tianji --arm-mode right --input vive --record
# Inspect the graph without opening devices or creating an archive:
uv run dmani-record run --robot ur5e --input vive --print-dataflow
# Explicit camera options replace the selected camera list:
uv run dmani-record run --headless --camera zed_2i=synthetic --camera realsense_d405=synthetic --camera wrist=synthetic
# Record robot telemetry without opening cameras:
uv run dmani --robot ur5e --record --no-camerasBC default sources use /dev/v4l/by-id/ paths; changing /dev/videoN numbers
after reconnecting cannot silently select a different sensor or depth stream.
Update the selected profile's recording.cameras if a camera is replaced. Images are stored under
images/zed_2i/, images/realsense_d405/, and images/wrist/, with timestamps and RGB/view metadata
in the session archive. Camera failures trigger the existing recording fault
path. Bare --record with mock input or replay does not activate these defaults;
select a named type or supply --camera to add views there. USB power/link faults must be resolved
before collecting physical-camera data.
#Sharpa tactile recording
UR5e + Sharpa hardware recording enables the SDK tactile stream automatically,
including hardware replay and policy recording. --no-cameras keeps tactile
capture enabled. An explicit --no-record run skips tactile acquisition and
its health checks along with session saving. Each fresh per-channel frame in a
recording saves its available RAW,
RAW_JPEG, DEFORM, F6, and CONTACT_POINT blocks. Image bytes and float32
sensor values are preserved in NPZ files; no image resizing, JPEG re-encoding,
or tactile calibration is applied.
The hand_tactile_frame Parquet events index those files with the hand side,
finger name, original SDK channel, frame ID when supplied, block shapes/dtypes,
and unmodified SDK timestamp. sample_monotonic marks host callback receipt;
it is not a hardware exposure timestamp. Left thumb-to-pinky channels are
9, 8, 7, 6, 5; right channels are 4, 3, 2, 1, 0.
Acquisition copies every fresh SDK callback frame into a bounded queue, then publishes all queued frames on the recorder's tactile tick and flushes the tail at shutdown. The recorder saves frames only during TELEOP; a frame captured before engagement is excluded even if it arrives after the episode opens. Out-of-episode completion frames are drained but not saved. The recorder waits for the completion marker before closing. Repeated SDK stamps are not saved. Queue overflow raises a recording fault rather than silently dropping frames. Every channel must produce data within five seconds of stream startup and continue advancing within two seconds; shutdown also requires at least one frame from every finger. Missing or frozen tactile feedback raises a fault. Simulation contact projections remain a separate stream/schema, and dummy mode does not synthesize hardware tactile readings.
Load a frame using the relative NPZ path in its event's text column:
with np.load(session_path / row["text"], allow_pickle=False) as frame:
forces = frame["F6"]
raw_image = frame["RAW"] # When supplied by the SDK.#Record only the Vive trajectory
For a wrist trajectory take, vive-record captures the filtered wrist source without
starting Dora, a simulator, or any actuator driver:
uv run vive-recordPress Space to start and again to stop. The UI shows tracking health, the trail, recording state, frame count, and destination. Closing an active take also saves it when at least two valid poses were captured. Recenter and trail clearing change only the display; the saved poses remain in the same Z-up, metre, xyzw frame used by live teleoperation.
The new directory contains trajectory.npz, config.yml, and
recording.json. Replay remains source-level: the first pose is clutched to
the arm, then the normal mapper, IK, state manager, and command gate run.
The launcher automatically selects the take's config.yml unless --config
explicitly overrides it. Preview it in MuJoCo and the non-connecting dummy
backend before hardware:
uv run dmani --robot ur5e --replay out/vive-recordings/<take>
uv run dmani --robot ur5e --mode dummy --replay out/vive-recordings/<take>
uv run dmani --robot ur5e --mode hw --replay out/vive-recordings/<take>The hardware command is interactive: measured-pose hold and keyboard y
confirmation are mandatory, followed by a after IDLE. It never sends
recorded joint commands directly.
#Operator workflow
| Key | Action |
|---|---|
y during BRINGUP |
Confirm live startup |
a in IDLE or PAUSE |
Engage and open a new episode |
a in TELEOP |
Pause, hold measured joints, and seal the episode |
y in IDLE or PAUSE |
Keep the most recently sealed, unreviewed episode |
x in IDLE or PAUSE |
Exclude that episode; retain its local data |
b |
Park and end the current episode |
c |
Shut down through the existing state machine |
Repeated state messages do not create extra episodes. A new engagement clears the live review prompt; an unanswered take remains pending and can be reviewed offline. Episode numbers are never reused. Review keys do nothing during active motion. A fault or interrupted recording marks the active take incomplete, which prevents replay export and keep approval.
#What is saved
Each launched session contains:
session.json rig, joint order, episode paths, counters
config.yml rig snapshot with absolute asset paths and selected arm mode
episodes/episode_000001/events/00000000.parquet atomically published TELEOP events
episodes/episode_000001/images/wrist/000000000123.jpg optional camera frame
episodes/episode_000001/tactile/left/channel_9/000000000124.npz SDK blocks
episodes/episode_000002/events/00000000.parquet next TELEOP take
reviews/episode_000001.json explicit keep/exclude decision and operatorAvailable subscribed streams are:
- Wrist poses and mapped end-effector targets.
- Raw normalized glove data and tracked hand landmarks when the graph supplies them.
- Arm and hand joint commands, the state manager's actuator targets, and measured joint states.
- Sharpa hardware fingertip tactile frames, including available optical images, forces, and contact points.
- The TELEOP entry state and control/safety events received while engaged. Startup confirmation, idle trajectories, and pause-time movement are not saved. Keep/exclude decisions remain in separate review metadata.
Tianji's current live/mock input graph supplies wrists and mock hand commands.
Its hand commands and feedback are recorded; raw glove/landmark streams require
an input graph that publishes them. UR5e live input supplies both glove and wrist
streams. Camera selection and rates follow the rig presets above.
Add --camera front=/dev/video0 (repeat for more views) to choose other RGB
sources. Generic cameras without rig defaults use 256 square pixels at 30 Hz.
--image-size and --camera-fps override the selected presets. Frames are saved as
JPEG files before their timestamped Parquet entries. The camera timestamps
identify host read completion rather than hardware exposure time.
See Policy Learning to export kept episodes into a training
dataset and run a learned policy through the same robot sinks. Policy rollout
archives are labelled source_kind: policy; training export excludes them
unless explicitly requested.
The archive uses timestamped Parquet events with the project's existing PyArrow dependency. Continuous numeric streams save up to one real sample per topic on each 30 Hz episode-relative slot. State and safety events remain event driven; every physical SDK tactile frame is retained. The wrist camera selects frames at 30 FPS, and dataset export aligns all streams on a 30 Hz grid by default. It is not a URDS-compatible dataset.
Numeric payloads retain the bus's float32 values. Poses are metres and xyzw
quaternions; joints are radians. Every event has separate receipt-monotonic,
wall-clock, and transport timestamps. A producer's sample_monotonic is
preserved when supplied, along with all event metadata. If it is absent, the
source timestamp is null; receipt time is never labelled as capture time.
Nonfinite payloads and invalid source timestamps remain available for diagnosis.
The recorder orders inputs within a 50 ms transport window. Events outside TELEOP are discarded; a delayed frame with a valid pre-engagement capture time is also discarded. Ordering errors on saved events are counted. A late state or review event makes episode boundaries ambiguous and blocks approval/export. This cannot detect or recover samples lost before receipt, including upstream/Dora queue eviction.
#System data viewer
Open the local Viser viewer for saved sessions or a specific episode:
uv run dmani-view
uv run dmani-view out/recordings/2026-09-21_15-14-17.258762-0700
uv run dmani-view out/recordings/<session>/episodes/episode_000001
# Include captures saved outside the standard recordings directory:
uv run dmani-view --root out
# Open the page automatically if desired (default: print the URL only):
uv run dmani-view --browser --port 8768Viser shows the robot and background without task objects, even when the saved simulation scene includes them. It displays an interactive 3D model with a docked sensor panel and one shared playback cursor. Choose a session and episode, play at 0.25–4×, scrub, or step by 100 ms. Orbit and zoom the robot with the mouse. Use Refresh archive to pick up published chunks from an unfinalized session. The viewer does not change review decisions or recording files.
- RGB + RAW: the two configured RGB cameras and all five fingertip RAW images appear together. Each tile shows the age of the latest sample at the cursor; a missing frame stays visibly empty.
- 3D robot: measured joints move the robot mesh. Select recorded IK/retargeted commands, state-manager targets, sent commands, or measured-only display. A translucent cyan mesh shows whichever command layer is available. Use Hide command robot and Hide measured robot independently to view either pose alone; each button changes to Show when its robot is hidden. Recorded hand landmarks appear as a separate 3D skeleton when present. No physics stepping, new IK, or hardware connection occurs; joints without feedback use the saved idle pose.
- Tactile: all five RAW and DEFORM images are visible together. The tab also lists F6 values, contact arrays, SDK channel/frame/timestamp, available blocks, and receipt ages. Select a finger to plot its six F6 channels. DEFORM colors are a display map; the original NPZ payload remains untouched.
- Joints and signals: choose an arm or hand joint to see separate command, target, sent, and measured histories in degrees. Select any numeric topic and component to plot it and inspect all current values and metadata. Missing post-CommandGate sent angles are labeled as unavailable; upstream targets are never substituted.
- Events and timing: jump to recorded events or the largest command changes. Inspect per-stream receipt rates, p99 intervals, source ages, ordering flags, and which expected streams were not recorded.
All synchronization uses recorder receipt time. Every stream holds its latest preceding sample inside its episode, with no future samples borrowed and no interpolation. Playback is empty between takes; plots break at episode boundaries. Source-to-receipt ages appear only when a valid source timestamp exists and are not driver send timing metrics. Incomplete takes and published chunks from unfinalized recordings remain viewable for diagnosis.
The Viser server binds only to 127.0.0.1 (default port 8768). --port 0
selects a free port. It reads existing session manifests, Parquet events, JPEGs,
and NPZ payloads. Robot geometry uses the saved configuration and local model
assets; if geometry is unavailable, sensor data remains usable. Ctrl+C stops it.
#Review and save a LeRobot v3 dataset
uv run lerobot out/recordings/2026-09-25_02-06-12.021251-0700
# Open the browser automatically if desired (default: print the local URL):
uv run lerobot out/recordings/<session> --browser --port 8769The local dark page shows the recorded RGB cameras and five fingertip
tactile streams: RAW and DEFORM frames plus F6 force readings. The robot view
is hidden by default. Click Show 3D replay to display the recorded
robot with a solid measured pose, a cyan IK command overlay, and the saved Vive
tracker pose. The tracker is shown in room coordinates relative to its first
sample in each episode and placed beside the robot for comparison. The view uses
the same Viser robot scene as replay and follows the camera/tactile playback
cursor when playing, scrubbing, or selecting another episode. Click again to
hide it.
Select each sealed episode, play or scrub its
media, then click Keep this episode or Mark useless. An optional note is saved in
reviews/episode_*.json with the decision; the recorded events and payloads are
left intact. The review moves to the next pending take. Incomplete takes remain
viewable but cannot be kept. The page requires a decision for every sealed take
before enabling Save as LeRobot v3 dataset.
If the review buttons are dimmed, read the reason beneath them. An interrupted
session may still say recording after its writer has stopped. A nonzero
boundary_errors count means a state or review event arrived out of order, so
the saved episode tags need an audit before review. The media remains viewable.
Click Recover and audit saved takes on the review page, or Audit saved
takes if the session was already recovered. Recovery checks that
no recorder holds the session lock and rebuilds counts from published chunks;
the audit checks every saved event against its episode's sequence and receipt-time
range, including any incomplete trailing take, and checks camera files. If it
passes, review the sealed takes and mark each one Keep or Mark useless.
Incomplete takes remain excluded from the dataset, and the original
boundary_errors value stays in the archive and export provenance.
For a session already marked closed or recovered, the equivalent audited
override is also available when starting the reviewer:
uv run lerobot out/recordings/<session> --trust-recorded-boundaries --task "Toss the object"Dataset conversion still checks required streams, timing, and freshness; the
output provenance records that the saved boundaries were explicitly trusted.
The audit cannot recover the original timing of the late control event, so
inspect the first and last frames of each take before keeping it.
--task fills the shared task instruction field; you can edit it on the page.
If no task is specified or recorded, the field defaults to Tossing putaozhi.
The output directory must be
new; the page suggests out/datasets/<session>-lerobot-v3. Saving runs in the
background with a progress bar for frame conversion, video encoding, and
finalization. It exports only explicitly kept, sealed episodes and uses the
existing 30 Hz causal alignment and freshness checks, with measured arm/hand
joint positions as observation.state, state-manager joint targets as action,
and each recorded RGB camera as an MP4 observation.images.* stream. The
result includes LeRobot v3 metadata, task labels, alignment timestamps, and
dynamic-mani provenance. Tactile frames and raw glove payloads remain in the source
session for inspection. No data is uploaded. Export errors appear in the page;
an unsuccessful export does not publish a partial output directory.
#Standalone joint angle HTML
Generate one self-contained HTML page with every recorded arm and hand joint:
uv run dmani-joints out/recordings/<session>/episodes/episode_000001
uv run dmani-joints out/recordings/<session> --episode 1 --mark-time 4 --output out/reports/joint_angles.htmlThe command prints the output path and does not open a browser. Open that file
manually to compare IK/retargeted commands, state-manager targets, sent commands
when recorded, and measured angles in degrees. The page has per-joint plots,
joint-name filtering, time-range controls, and recorded arm limits. --mark-time
uses seconds from the start of the selected episode and adds a marker plus
before/after/close-up buttons. An episode path selects that take directly; use
--episode with a session path containing multiple takes. The default output is
out/reports/recording-<session>/episode_000001_joint_angles.html for episode 1.
This reads only saved data and never connects to a robot.
#Inspect, review, and replay
To visualize saved robot feedback and commands together in MuJoCo:
uv run dmani-record view out/recordings/<session> --episode 1
# Slow playback to half speed:
uv run dmani-record view out/recordings/<session> --episode 1 --speed 0.5The solid robot follows recorded measured joints; the cyan overlay follows
recorded arm_joint_cmd and hand_joint_cmd. This is offline kinematic playback
using the saved rig configuration and recorder receipt timestamps. It opens no
hardware connections and does not rerun IK or simulate a new robot response.
Incomplete takes can be viewed without changing their review/export eligibility.
Omit --episode to view all recorded takes. New archives contain no startup,
idle, pause, fault, or shutdown samples; older full-session archives remain readable.
An episode selection uses the stored tags even if boundary errors were reported.
In the viewer, Space pauses/resumes, R restarts, and Q/Esc closes.
Playback holds the final frame until restarted or closed.
uv run dmani-record inspect out/recordings/2026-09-16_19-08-05.123456-0700
uv run dmani-record review out/recordings/2026-09-16_19-08-05.123456-0700 --episode 1 --decision keep --operator weison
uv run dmani-record review out/recordings/2026-09-16_19-08-05.123456-0700 --episode 2 --decision exclude
uv run dmani-record export out/recordings/2026-09-16_19-08-05.123456-0700 --episode 1 --output out/replay/task_001_episode_1
uv run dmani-replay out/replay/task_001_episode_1Replay uses the current rig selected by dmani.env, including its tuned initial
pose and active arms. Add --recorded-config to use the exported config.yml
instead, or --config PATH to choose another rig YAML. The recording itself is
unchanged. The selected config path is printed before launch.
At the end, replay holds the final arm and hand commands and keeps the program open. Press b to return to the configured default pose and rewind. Once parked, the robot waits in the same IDLE state as startup; a starts another pass and c shuts down. Automatic replay keeps these controls when a viewer is present. Headless replay holds until the process is stopped.
Episode numbers start at 1. Offline review requires that the writer has stopped; live review uses the keyboard. Only an explicit keep marks a sealed take eligible. There is no uploader, automatic deletion, or implicit keep on exit.
Export creates trajectory.npz, config.yml, and export.json in a new
directory. It is an explicit replay view, so a sealed pending/excluded take can
also be exported for investigation. Raw archive files remain unchanged.
Alignment uses each wrist timestamp and the latest preceding hand sample within
--max-age (default 0.25 seconds). Both inputs use source timestamps if all are
present; otherwise both use receipt timestamps, recorded in export.json.
Initial hand warm-up is trimmed; internal or trailing stale gaps cause an error.
Landmarks are preferred to raw glove input. Available telemetry is aligned with
validity masks and original sample timestamps; it does not drive replay.
Exports load through the existing replay source, mapper, IK, GHR retargeter,
orchestrator, and safety gates. Deliberate playback holds remain fresh during
startup ramps and pauses; repeated hand frames reuse their solved pose without
advancing GHR filter history. A stalled new solve still expires normally.
Tianji wrist-only exports hold the hands idle during replay. Mock UR5e captures contain hand commands but no glove/landmark source, so they support inspection, not finger-source replay. Real UR5e captures provide the required source input. Source replay re-anchors each take at the configured idle pose and recomputes control; it does not promise identical measured motion.
Older wrist-only and IK-demo NPZ captures still work with dmani-replay.
New tianji --record sessions use this archive format and need the explicit
episode export above before replay.
#Durability and limits
Live buffering is bounded: 4,096 events in the ordering window, 4,096 in the writer queue, and at most 512 rows in a chunk. The writer also flushes every 0.5 seconds. Completed chunks are independently readable before the session ends. Queue overflow, disk errors, or disk space below the 256 MiB reserve raise an explicit recording failure and send SAFETY_STOP through the state manager.
After a crashed recorder, recover only the published prefix:
uv run dmani-record recover out/recordings/interrupted_sessionRecovery refuses a running writer, checks published per-episode chunks against the manifest, and marks an unfinished take incomplete. Older full-session archives still reconstruct boundaries from their state events. Pending chunks and an unflushed tail are not assumed to have survived. This is process-crash recovery, not a power-loss guarantee.
The config snapshot and hashes identify configured calibration/assets. Those external files must remain available; the archive does not bundle GHR, meshes, or all implicitly resolved GHR defaults. Matching calibration and retargeter settings remain necessary for replay.