Teleopit is a lightweight, extensible, self-contained humanoid robot whole-body teleoperation framework. It integrates GMR (General Motion Retargeting) and supports train_mimic-exported ONNX RL policy inference.
Language: Python 3.10+
Package: teleopit (installed via pip install -e .)
Config: Hydra/OmegaConf YAML files in teleopit/configs/
InputProvider (BVH file / Pico4 VR) → Retargeter (GMR) → ObservationBuilder (167D) → Controller (dual-input TemporalCNN ONNX) → Robot (MuJoCo + PD / Unitree SDK)
Host policy service → onboard policy client/scheduler → 36D reference → same ObservationBuilder/Controller → Unitree SDK
Offline core modules communicate through InProcessBus (zero-copy). Sim2real
workers use localhost ZMQ plus shared-memory video rings, while the external
host-policy boundary uses strict msgpack/ZeroMQ without pickle. Core interfaces
are defined as typing.Protocol in teleopit/interfaces.py.
- Training task:
General-Tracking-G1 - Inference observation:
velcmd_history(167D, dual-input ONNX withobs+obs_history) - TemporalCNN actor/critic with scaled dims (2048,1024,512,256,128)
- Realtime inference uses a retargeted-reference timeline before observation build;
reference_steps=[0]is the default production path - Host high-level-policy deployment uses an independent script/environment boundary; its network structure is defined by the current client/server code and protocol tests, and LeRobot is not a Teleopit dependency
teleopit/ # Core inference package
├── interfaces.py # Protocol definitions: Robot, Controller, InputProvider, Retargeter, etc.
├── pipeline.py # TeleopPipeline — thin sim runtime facade
├── runtime/ # Shared runtime assembly: config/path resolution, factories, CLI helpers
├── bus/ # InProcessBus message pub/sub
├── configs/ # Hydra YAML configs
│ ├── default.yaml # Offline sim2sim
│ ├── sim2real.yaml # sim2real
│ ├── robot/g1.yaml # G1 robot: XML path, PD gains, default angles, action dims
│ ├── controller/rl_policy.yaml
│ ├── input/bvh.yaml # Offline BVH file input
│ └── input/pico4.yaml # Pico4 realtime input
├── controllers/
│ ├── rl_policy.py # RLPolicyController — single-input or dual-input ONNX inference with fail-fast dim checks
│ └── observation.py # VelCmdObservationBuilder
├── inputs/
│ ├── bvh_provider.py # BVHInputProvider — offline BVH file
│ ├── pico4_provider.py # Pico4InputProvider — pico_bridge receiver input
│ ├── pico_video.py # Optional camera preview pushed back to Pico through pico-bridge
│ ├── rot_utils.py # Quaternion helpers for input-space transforms
│ └── udp_bvh_provider.py # UDPBVHInputProvider — realtime BVH packet input
├── retargeting/
│ ├── core.py # RetargetingModule + extract_mimic_obs()
│ └── gmr/ # Self-contained GMR code; heavyweight assets are downloaded into an ignored path
├── robots/
│ └── mujoco_robot.py # MuJoCoRobot — MuJoCo sim wrapper
├── sim/
│ └── loop.py # SimulationLoop — PD control at 200Hz, policy at 50Hz
├── sim2real/
│ ├── mp/ # Process-isolated sim2real runtime and IPC
│ ├── hands/ # Optional LinkerHand driver/mapper plugins
│ └── neck/ # Optional OpenNeck active-vision gimbal control
├── high_level_policy/ # Host-policy protocol, strict client, frame transform, and action scheduler
└── recording/ # Pico motion NPZ recording helpers
scripts/
├── run/run_sim.py # Offline sim2sim pipeline
├── run/run_sim2real.py # G1 sim2real control; supports offline BVH playback and Pico4
├── run/run_high_level_policy_sim2real.py # Independent host-policy deployment runtime
├── run/record_pico_motion.py # Interactive Pico recording → G1 motion NPZ clips
├── render/render_sim.py # Render single BVH → 3 MuJoCo videos (mocap input, retarget, sim2sim)
├── view/view_recording.py # Read-only synchronized sim2real recording reviewer
└── dev/compute_ik_offsets.py # Compute IK quaternion offsets for new BVH formats
train_mimic/ # Training package
├── app.py # Shared app helpers for train/play/benchmark
├── tasks/tracking/config/
│ ├── constants.py # Public task constants
│ ├── registry.py # Registers General-Tracking-G1 task
│ ├── env.py # General-Tracking-G1 env builder
│ └── rl.py # TemporalCNN PPO cfg
├── tasks/tracking/rl/
│ ├── runner.py # Training runner and policy ONNX export wrapper
│ ├── conv1d_encoder.py # 1-D CNN encoder for temporal history groups
│ └── temporal_cnn_model.py # TemporalCNN actor/critic model
└── scripts/
├── train.py # Training entry point
├── play.py # Checkpoint playback
├── benchmark.py # OmniXtreme-style policy benchmark
└── save_onnx.py # Export TemporalCNN ONNX
- Policy runs at 50Hz, PD control at 200Hz (
decimation=4,sim_dt=0.005) - Action flow:
compute_action()returns raw action →get_target_dof_pos()applies clip[-10, 10], scale, anddefault_dof_pos assets/robots/unitree_g1/g1_29dof.xmlis the default G1 XML, not a model allowlist; training can select another task-compatible model with--robot_xml, and each workflow should keep its robot joint/body definitions consistent
SimulationLoop supports multiple simultaneous viewer windows controlled by the viewers config:
python scripts/run/run_sim.py controller.policy_path=policy.onnx viewers=sim2sim
python scripts/run/run_sim.py controller.policy_path=policy.onnx 'viewers=[mocap,retarget,sim2sim]'
python scripts/run/run_sim.py controller.policy_path=policy.onnx viewers=all
python scripts/run/run_sim.py controller.policy_path=policy.onnx 'viewers=[retarget,sim2sim]'
python scripts/run/run_sim.py controller.policy_path=policy.onnx 'viewers=[sim2sim,camera]'
python scripts/run/run_sim.py controller.policy_path=policy.onnx viewers=nonesim2sim: MuJoCo physics resultretarget: kinematic retarget resultmocap: retargeting input skeleton rendered by MuJoCo custom geomscamera: G1d435i_rgbfixed RGB camera viewbvhviewer naming is removed; usemocapviewers=allopensmocap,retarget, andsim2sim; addcameraexplicitly when needed- All viewers run in separate subprocesses because GLFW/GLX only supports one window per process
- Simulation exits when all active viewer windows are closed
- sim2real defaults to
viewers=none; it supports only optionalviewers=retarget viewersis the only supported viewer key; legacyvieweralias is removed
RL policy outputs action offsets relative to the default standing pose:
target_dof_pos = clip(action, -10, 10) × action_scale + default_dof_pos
default_dof_pos comes from robot/g1.yaml default_angles. TeleopPipeline automatically propagates robot_cfg.default_angles into controller_cfg.default_dof_pos. If this propagation is missing, knees and elbows lose their standing offset and the robot cannot balance.
- Offline sim2sim and default sim2real both read
input.bvh_filedirectly; no UDP relay path remains - Offline sim2sim playback can be keyboard-controlled:
Space/Ppause/resume,Rreplay from frame 0,Qstop - Offline pause holds the commanded pose; resume resets policy/reference state and reanchors yaw/XY without qpos interpolation or retargeter IK reset
- sim2sim keyboard playback is optional via
playback.keyboard.enabled=true - sim2real reuses the Unitree remote:
Start→STANDING,Y→ playback,X→ back toSTANDING,L1+R1→DAMPING playback.pause_on_end=truekeeps the final pose and waits for manual replay
Pico4InputProviderreads realtime body tracking from the in-processpico_bridge.PicoBridge- The pico-bridge receiver runs on the Teleopit host, which can be a workstation PC or robot onboard computer; do not maintain a separate onboard Pico input mode
- pico-bridge 0.2.1 is the supported runtime; camera preview uses
PicoBridge(video="frames").push_video_frame(rgb_uint8) - Pico video preview is optional and disabled by default; sim2sim uses the MuJoCo
d435i_rgbcamera and sim2real uses RealSense wheninput.video.enabled=true - RealSense frame timeouts and disconnects are non-critical in sim2real: the video producer rebuilds the capture pipeline in the background, and video start/tick/stop failures must never exit
pico_inputor stop G1 control - The supervisor treats
pico_inputas non-critical; if that process exits,robot_controlremains active, stale mocap references hold the last command, and the Unitree remote remains available forSTANDINGor manualDAMPING - Bone naming follows
pico_bridge_to_g1.json - The provider applies an input-space transform to match the current retarget config
- Do not hardcode that transform as a public coordinate-system contract; validate against actual retarget/sim2sim behavior when SDK or firmware changes
- Pico4 realtime control uses the same retargeted-reference timeline path as the shared realtime input stack
- Pico sim2sim supports a keyboard-driven top-level mode state machine:
STANDING → MOCAP ↔ ARMS,Xreturns toSTANDING - Default Pico sim2sim keyboard mappings are
Y→MOCAP,A→ pause/resume mocap,B→ toggleMOCAP/ARMS,X→ back toSTANDING,Q→ quit - Pico4 sim2real pause/resume is handled as a mocap-session control event (
toggle_pause), not as a mode switch toSTANDING - Default Pico/controller pause button is
A; Unitree remoteBalso pauses/resumes Pico sim2real. Resume resets policy/reference state and yaw/XY root-offset alignment while the process-isolated realtime reference worker continues its live input timeline - Pico4 sim2real arms the process-isolated reference worker only when entering
MOCAP;STANDINGandDAMPINGdisarm it so cold startup frames do not warm-start GMR before mocap entry - Pico4 sim2sim/sim2real support
ARMSmode toggled fromMOCAPwith Pico/controllerB; retargeting continues, while the control loop sends the motion tracker a composed reference with stand-pose body/legs/waist and live retargeted arms ARMSentering/exiting/resume resets policy/reference alignment and uses Kp ramp; offline BVH sim2real does not useARMS, and Unitree remoteBremains BVH replay- Realtime Pico pause/resume and
MOCAP ↔ ARMSswitches use a retargeter-preserving soft reset: policy/reference state, smoothers, and reference alignment are reset, while the GMR IK warm-start is retained - Optional LinkerHand control uses
hands.enabled=true,hands.driver=linkerhand_l6|linkerhand_o6, andhands.mode=gripper|vr_hand_pose; default is disabled - Optional Pico sim2real HDF5 recording uses
--config-name sim2real_recordorrecording.enabled=true; it requiresinput.provider=pico4,input.video.enabled=true,input.video.source=realsense, an interactive terminal, and therecordingextra - Recording is manual only: terminal
Rstarts an episode,Ssaves,Ddiscards the active episode, andQshuts down;STANDING,MOCAP,ARMS, and paused mocap are recordable - Recording requires a fresh RealSense frame before
Rcan start an episode; an active episode is discarded after one second without a fresh camera frame while Pico input and G1 control continue, and recording does not restart automatically when video recovers - Recording captures
observation.images.d435i_rgbRealSense RGB video at 30Hz plusobservation.state(68), scalarobservation.mode, andaction(36)as the root-plus-joint reference consumed by the motion tracker; when LinkerHand control is enabled,observation.state.hand(12)stores the left/right hardware joint readback andaction.hand(12)stores the target; when OpenNeck control is enabled,observation.state.neck(2)stores the servo[yaw_deg, pitch_deg]readback andaction.neck(2)stores the mechanically clamped target - Sim2real recording uses an editable source layout:
schema.json,episodes.jsonl, per-episode HDF5 files underrecording.output_dir/data/, and compressed MP4 files underrecording.output_dir/videos/d435i_rgb/; task prompts live only inepisodes.jsonl, and HDF5 files contain only frame arrays with no metadata attributes or raw RGB datasets - Recording
schema.jsonstoresrobot_type=unitree_g1_29dof,hand_type=none|linkerhand_l6|linkerhand_o6,neck_type=none|openneck, FPS, and feature definitions; optional hand/neck state and action fields are controlled directly byhands.enabledandneck.enabled; the recording worker rejects an existing mismatched schema without writing episodes, but remains non-critical and must not stop the G1 control runtime; the previous attribute-based HDF5 layout is unsupported - Episodes interrupted before their
episodes.jsonlentry is committed are discarded on the next recording-worker startup and do not consume an episode index - Review saved sim2real recordings with
scripts/view/view_recording.py; it validates manifest/HDF5/MP4 alignment and synchronizes camera video, an observed-vs-reference MuJoCo overlay, joint/mode plots, and optional hand/neck signals; because measured root XYZ is not recorded, the observed robot is anchored to the reference root position grippermode reusesPico4InputProvider.get_controller_snapshot()and supports LinkerHand L6 and O6; the side grip trigger is a deadman enable, the index trigger controls closure while it is held, and releasing the side grip opens that handvr_hand_posemode reusesPico4InputProvider.get_hand_snapshot()and somehand 0.3.0 publicsomehand.apifor continuous Pico hand-pose retargeting; do not start a secondPicoBridgefor hand control- Teleopit owns Pico 26-joint hand-state to 21-landmark conversion; do not import
somehand.pico_input - LinkerHand O6 supports
hands.mode=gripper|vr_hand_pose; its defaultclose_poseis[86, 73, 118, 111, 110, 111] - L6
grippermode uses the configuredhands.linkerhand_l6.speed(default[50]*6); O6grippermode useshands.linkerhand_o6.speed(default[255]*6);vr_hand_posealways sets LinkerHand L6/O6 speed to[255]*6 vr_hand_posedefaults to a low-latency somehand path:hands.somehand.rate_hz=60,max_iterations=12,temporal_filter_alpha=1.0, andoutput_alpha=1.0; this prioritizes response speed over smoothing- LinkerHand control is active in all sim2real modes when
hands.enabled=true; shutdown and hand-runtime failure must send the configured open pose - In
vr_hand_posemode, missing/inactive hand pose holds the last commanded pose for that side instead of opening the hand - Optional OpenNeck active-vision gimbal control uses
neck.enabled=trueandneck.driver=openneck; it requiresinput.provider=pico4, reuses the existing Pico receiver, and must not start a secondPicoBridge - OpenNeck is integrated as a non-critical sim2real
neck_worker; failures should not stop the G1 control loop, and no OpenNeck state is added to the 167D policy observation
scripts/run/run_high_level_policy_sim2real.pyis independent from the Picorun_sim2real.pyruntime; it must not start PicoBridge, GMR, or the realtime retarget reference worker- The host LeRobot/ReplayPolicy service runs in the separate
lerobot-teleopitrepository and environment; Teleopit must not depend on LeRobot, Transformers, or host policy classes - The current client/server code and protocol tests define the ZeroMQ request/response structure. During active development, Teleopit and
lerobot-teleopitmust update that structure together; no legacy network envelope is supported - The only shared data file is
hand_calibration.json, which contains the LinkerHand O6 open/close calibration and must stay identical in both repositories - The host boundary uses ZeroMQ REQ/REP with msgpack and non-pickle float32 arrays. Deployment is asynchronous and receding-horizon: the isolated client worker keeps at most one request in flight, submits the latest eligible observation every
high_level_policy.replan_steps30 Hz source frames, and leaves the current plan executing while inference runs. The onboard scheduler uses the echoed monotonic observation timestamp to skip elapsed source frames and replace the active plan when a newer response arrives; process isolation keeps the 50 Hz robot loop running - Policy
get_actioninput is RGB JPEG plus G1 joint positionsfloat32[29], raw measured left/right LinkerHand O6 readbackfloat32[12], measured OpenNeck yaw/pitch degreesfloat32[2], and the observation-time active reference root posefloat32[7](xyz + quaternion wxyz) in the session-local frame. The first three arrays form the 43D model state; the source reference pose is used only to reconstruct source-relative root output and is not a model input - The scheduler keeps a short history of the rate-limited active session-local reference and queries/interpolates it at each camera timestamp. Session reset seeds this history from the initial active reference; the source anchor must never be reconstructed from the robot's measured root pose
- Canonical action is
float32[T,50]with protocol horizonTin[1,50]: local rootxyz(3)+ local root quaternionwxyz(4)+ G1 joint reference29+ left/right O6 closure12+ OpenNeck yaw/pitch degrees2 - The body reference
[0:36]is yaw/XY-delocalized once and sent through the existing motion tracker. It is never sent directly as a G1 motor command and must not pass through the mocap alignment a second time - High-level-policy formal robot modes are
IDLE,STANDING,POLICY, andDAMPING. After Unitree remoteY, Teleopit creates exactly one host session and remains inSTANDINGonly while waiting for its first valid chunk; that chunk entersPOLICYdirectly. There is no candidate-reference alignment, entry Kp ramp, second session/reset, orPOLICY_STARTINGmode. The 50 Hz output limiter starts from the measured robot reference captured when the session begins, and an entry failure remains inSTANDING - Unitree remote controls are
Start -> STANDING,Y -> request POLICY,B -> pause/resume,X -> STANDING/cancel pending, andL1+R1 -> DAMPING - Policy pause freezes the scheduler/body reference and holds the latest hand/neck command. Leaving
POLICYopens LinkerHand and centers OpenNeck - The onboard scheduler clips G1 joint references to
real_robot.joint_pos_lower/upperwhen the required correction is at mosthigh_level_policy.safety.max_joint_projection_rad(default0.1rad), and clips OpenNeck yaw/pitch commands to their configured degree ranges. It rejects whole chunks on excessive joint correction, shape/finiteness, session/sequence, quaternion, absolute root height, hand closure, or staleness failures. It accepts temporal root, yaw, and joint-reference discontinuities at entry, inside chunks, and across chunks because recorded pause/resume transitions may be discontinuous; accepted root translation, yaw, and joint output is rate-limited at 50 Hz. Never pad or trim malformed host output, and do not clip other out-of-range fields into validity - A newer timestamp-aligned chunk normally replaces the active plan before its horizon ends. If inference runs longer, the scheduler holds the plan's final reference for
high_level_policy.hold_s; exhausting that grace period triggers the action watchdog. A request timeout, host/network failure, watchdog expiry, or loss of a required camera/client worker putsPOLICYinto the same resumable pause state used by remoteB; an invalid result is rejected while the last valid plan remains available. The runtime never entersSTANDINGautomatically;Bresumes after a fresh valid chunk is available, whileXremains the manual transition toSTANDING - Initial production hardware support requires two LinkerHand O6 hands and OpenNeck because all 50 canonical action dimensions are active
- OpenNeck 0.2.0 is the supported runtime; Teleopit sends physical degrees through
move_deg(), and the direct-drive OpenNeck package converts degrees to servo steps and clips them to its calibrated mechanical limits; the removed normalized API and config fields are unsupported - OpenNeck maps the independent HMD
PicoFrame.head.rotationrelative to the same-frame full-bodyBody.Spine3orientation; it must never use the full-bodyBody.Headskeleton joint for neck control, and HMD pose updates must remain independent of duplicate-body-frame filtering - OpenNeck uses a fixed identity neutral pose and no neck-side EMA; it must not capture the first live frame as a runtime zero pose, so tracking can start while the operator's head is turned
- After the raw relative-angle dead zone,
neck.pitch_gain(default1.4) scales pitch beforemove_deg()while yaw remains one-to-one; OpenNeck remains responsible for final mechanical clipping; positive yaw turns left and positive pitch looks up
realtime=trueenforces wall-clock pacing even without a viewernum_steps=0means infinite loop (max_steps = 2**63)KeyboardInterruptis handled for clean shutdown- BVH frame alignment is time-based:
bvh_idx = int(policy_time × input_fps) - Realtime reference buffering is controlled by
retarget_buffer_enabled,retarget_buffer_window_s,retarget_buffer_delay_s,reference_steps, andrealtime_buffer_warmup_steps - Realtime inferred
motion_joint_vel, anchor linear velocity, and anchor angular velocity can be EMA-smoothed viareference_velocity_smoothing_alphaandreference_anchor_velocity_smoothing_alpha - Sim2real Pico pause/resume uses mocap-session states
ACTIVE ↔ PAUSED; resume clears policy/reference state, rebuilds yaw/XY root alignment, and does not interpolate retarget qpos from the paused pose - Realtime sim2sim with Pico control events uses the same mocap-session pause/resume semantics and rebuilds the realtime reference path on resume, including the configured warmup
- Realtime Pico sim2sim
STANDING -> MOCAPresets GMR, seeds its floating root from the current live pelvis target, and rebuilds the realtime reference path before accepting references; Pico sim2real performs the same GMR cold start through its rearmed process-isolated reference worker - Realtime Pico sim2sim can start directly in
STANDINGwith keyboard mode control enabled via top-levelkeyboard.enabled
Observation format: velcmd_history (167D, dual-input ONNX)
ref_joint_pos(29)
+ ref_joint_vel(29)
+ ref_anchor_ori_b(6)
+ robot_base_ang_vel_b(3)
+ robot_joint_pos_rel(29)
+ robot_joint_vel(29)
+ prev_action(29)
+ robot_projected_gravity_b(3)
+ ref_anchor_lin_vel_b(3)
+ ref_anchor_ang_vel_b(3)
+ ref_projected_gravity_b(3)
+ ref_anchor_height(1)
Runtime constraints:
- Public builder is
VelCmdObservationBuilder RLPolicyControlleraccepts dual-inputobs+obs_historyONNX- Startup validates the observation definition against the ONNX signature and raises immediately on mismatch
The single supported training task is General-Tracking-G1 (experiment name: g1_general_tracking).
- Uses TemporalCNN actor/critic with scaled dims (2048,1024,512,256,128)
- 167D
velcmd_historyobservation, dual-input ONNX export - Training env uses
sampling_mode="rewind" - Tracking rewards include root position/orientation/linear velocity/angular velocity, body pose/velocity, joint position/velocity, survival, action-rate, joint-limit, self-collision, and ankle acceleration terms
- Supported motion sampling modes are
uniform,start, andrewind;rewindrestarts failed environments from the same clip after stepping backrewind_min_steps..rewind_max_stepswith probabilityrewind_prob, otherwise it falls back to uniform sampling - Playback and benchmark use
play=True, which switches motion sampling tostart; benchmark pins exact clip ids/start times, disables clip-end resampling, and reportsMPJPE(m),root_pos_error(m),root_rot_error(rad),root_vel_error(m/s), andsuccess_rate(%) window_steps=[0]save_onnx.pyexports dual-input TemporalCNN ONNX
- Dataset build spec supports a
preprocesssection for root-xy normalization, ground alignment, and basic clip filtering - Final distributed dataset build outputs are minimal HDF5 shards directly under
data/datasets/<dataset>/(recursive shard discovery is supported; no train/val split and no manifest file) train_mimic/scripts/data/precompute_dataset.pyconverts a minimal dataset into a separate precomputed training dataset directory;build_dataset.pymust not run precompute- Each shard stores only
root_pos,root_quat_w,joint_pos,body_names, and clip-aware window metadata (clip_starts,clip_lengths,clip_fps); long clips are split into overlapping bounded windows - Training
motion_filemust point to a precomputed training dataset, not the minimal distributed dataset; training reads joint velocities and body FK/velocities from those precomputed shards and must not run MuJoCo FK while loading motion clips MotionLibloads all discovered precomputed HDF5 motion windows into CPU/GPU memory at startupMotionLibsamples only valid center frames for the configuredwindow_steps; default iswindow_steps=[0]- Training supports
uniformandrewindsampling over the fully loaded precomputed dataset scripts/run/record_pico_motion.pyrecords Pico live body tracking as retargeted G1 motion NPZ clips indata/pico_motion/clips/; it opens a liveRetargetviewer, uses terminal keysR/S/D/N/Q, stores semantic labels in filenames, and intentionally does not write per-clip JSON- Build Pico-recorded clips into shards with
python train_mimic/scripts/data/build_dataset.py --spec data/pico_motion/pico_recorded.yaml --force
Quick reference:
python train_mimic/scripts/data/build_dataset.py --spec train_mimic/configs/datasets/twist2.yaml
python scripts/run/record_pico_motion.py
python train_mimic/scripts/data/build_dataset.py --spec data/pico_motion/pico_recorded.yaml --force
python train_mimic/scripts/data/precompute_dataset.py data/datasets --outdir data/datasets_precomputed --jobs 8
python train_mimic/scripts/train.py --motion_file data/datasets_precomputed
python train_mimic/scripts/data/precompute_dataset.py data/datasets/twist2 --outdir data/datasets/twist2_precomputed --jobs 8 --force
python train_mimic/scripts/save_onnx.py --checkpoint logs/rsl_rl/g1_general_tracking/<run>/model_30000.pt --output policy.onnx --history_length 10- Self-contained in
teleopit/retargeting/gmr/; assets needscripts/setup/download_assets.py --only robots gmr - Supports
lafan1BVH (22 joints, 30fps, centimeters) - Supports
hc_mocapBVH (50 joints, 60fps downsampled to 30fps, meters) lafan1-resolvedstill needs an adapter layer and remains unsupported
- Do not commit robot meshes, datasets, checkpoints, or demo media to Git; use
scripts/setup/download_assets.py - G1 XML variants and their meshes are downloaded under
assets/robots/unitree_g1/by therobotsasset group and are not tracked in Git;g1_29dof.xmlis the default - The neck-and-O6 runtime variant is
assets/robots/unitree_g1/g1_29dof_neck_o6.xml - Released tracking assets download under
ckpt/as the matchingtrack_g1.{pt,onnx}andtrack_g1_neck_o6.{pt,onnx}pairs teleopit/retargeting/gmr/assets/is gitignored; downloaded at runtimetrain_mimic/assets/is no longer tracked; FK tooling uses the robot assets underassets/robots/, withassets/robots/unitree_g1/g1_29dof.xmlas the default G1 modelthird_party/linkerhand-python-sdkandthird_party/somehandsupport optional LinkerHand sim2real control- Run
python scripts/dev/check_large_tracked_files.pybefore pushing
Assets are split across two ModelScope repos by type:
| Repo | Type | Contents |
|---|---|---|
BingqianWu/Teleopit-models |
model | checkpoints, GMR retargeting assets, sample BVH |
BingqianWu/Teleopit-datasets |
dataset | training/validation data shards |
Asset group → repo mapping is defined in teleopit/runtime/external_assets.py (MODEL_REPO_ID / DATASET_REPO_ID).
Uploading a new release:
# 1. Prepare upload directory
python scripts/setup/prepare_modelscope_assets.py --only ckpt robots gmr bvh --clean
python scripts/setup/prepare_modelscope_assets.py --only data
# 2. Upload to each repo
modelscope upload --repo-type model BingqianWu/Teleopit-models \
data/modelscope_upload/checkpoints checkpoints --sync
modelscope upload --repo-type model BingqianWu/Teleopit-models \
data/modelscope_upload/archives archives
modelscope upload --repo-type dataset BingqianWu/Teleopit-datasets \
data/modelscope_upload/data data
# 3. Tag the release on the model repo (match the Git tag; dataset repo does not support tags)
python -c "from modelscope.hub.api import HubApi; api=HubApi(); print(api.create_model_tag('BingqianWu/Teleopit-models', 'vX.Y.Z'))"The old BingqianWu/Teleopit-assets repo is deprecated; do not upload to it.
For each (robot_body, human_bone) pair, IK config stores a quaternion offset R_offset (w,x,y,z, scalar-first):
R_result = R_human * R_offset
R_offset = R_human_tpose^{-1} * R_robot_tpose
Critical note: align robot root orientation to the BVH human forward direction before computing R_robot_tpose. For hc_mocap, G1 default faces +X while the BVH human faces -Y (Z-up), so the robot root must receive a -90° Z rotation first.
scripts/dev/compute_ik_offsets.py can print or write calibrated offsets.
- Prefer the smallest implementation that satisfies the current requirement
- Do not add speculative configuration switches, abstraction layers, compatibility paths, or extensibility without a concrete use case
- Reuse existing enable flags and data flows when they already express the required behavior
- Fail fast for logical mismatches such as observation definition vs. ONNX signature mismatch
- Do not silently pad, trim, clip, or replace invalid data/config to "make it run"
- Error messages should identify the mismatched components and the direct fix path
- Do not auto-commit changes
- Use the default git user as commit author
- After major feature changes, update
AGENTS.mdandREADME.mdtogether with the code - English docs (
docs/docs/), Chinese docs (docs/i18n/zh-Hans/), and code implementation must stay in sync. Chinese docs are translations of the English originals — never generate Chinese content independently; always translate from the corresponding English page - Documentation updates must be written for users and developers as stable product/development guidance, not as explanations of the current code patch or implementation diff
pip install -e .
pytest tests/ -vlafan1-resolvedretargeting is still broken because it uses a different BVH skeleton layout.- Legacy downloaded GMR XMLs under
teleopit/retargeting/gmr/assets/unitree_g1/are separate retargeting assets, not replacements for the runtime robot bundle underassets/robots/unitree_g1/;g1_29dof.xmlis the default runtime G1 model.