TraceForge is a unified dataset pipeline that converts cross-embodiment videos into consistent 3D traces via camera motion compensation and speed retargeting. For model training on the processed datasets, please refer to TraceGen.
Project Website: tracegen.github.io
arXiv: 2511.21690
conda create -n traceforge python=3.11
conda activate traceforgeInstalls PyTorch 2.8.0 (CUDA 12.8) and all required packages.
bash setup_env.shDownload the TAPIP3D model checkpoint:
mkdir -p checkpoints
wget -O checkpoints/tapip3d_final.pth https://huggingface.co/zbww/tapip3d/resolve/main/tapip3d_final.pthRecord from an Intel RealSense D435i
pyrealsense2is included insetup_env.sh. If you did not use that script, install it first:pip install pyrealsense2
- Check the connected camera before recording:
python tools/record_realsense.py --list-devices
- Print the full CLI help:
python tools/record_realsense.py --help
- By default, the recorder saves image frames. If you do not pass output paths, it writes to
data/<timestamp>/. - To save frames under
data/with an automatically generated timestamp subdirectory:python tools/record_realsense.py \ --frames-dir data \ --preview - This creates a folder like
data/20260402_153000/and writesYYYYMMDD_HHMMSS_mmm.pngfiles there. Use the printedSaved frames:path asinfer.py --video_path. - If
--previewis enabled, pressqin the preview window to stop. Otherwise, stop withCtrl-C. - Record an
mp4that can be passed directly toinfer.py:python tools/record_realsense.py \ --mode video \ --output data/realsense/demo.mp4 \ --preview \ --show-depth - The frame folder contains
YYYYMMDD_HHMMSS_mmm.pngfiles and can be used directly:python infer.py --video_path data/demo --out_dir outputs/demo
- To turn an existing frame folder into an
mp4later:python tools/frames_to_video.py \ --frames-dir data/realsense/demo \ --output data/realsense/demo.mp4 - If
data/realsense/demo/recording_metadata.jsonexists, the tool reuses its recordedfpsautomatically unless you pass--fps. - To save aligned depth for
infer.py --depth_pathat the same time:python tools/record_realsense.py \ --frames-dir data/realsense/demo \ --save-depth \ --preview \ --show-depth - Then run inference with both RGB and depth:
python infer.py \ --video_path data/realsense/demo \ --depth_path data/realsense/demo/depth \ --out_dir outputs/demo - Record for a fixed amount of time instead of stopping manually:
python tools/record_realsense.py \ --frames-dir data/realsense/demo \ --duration 10 \ --preview - Select one camera explicitly when multiple RealSense devices are connected:
python tools/record_realsense.py \ --serial <device_serial> \ --frames-dir data/realsense/demo \ --preview - Use
--mode bothto save both themp4and the frame folder in one run. - Saved depth frames are aligned to RGB and encoded as 16-bit PNG with the same
/10000convention used by TraceForge visualization outputs. - The recorder also writes metadata with device info and intrinsics. When saving video, it is stored as a sidecar
.json; when saving only frames, it is written torecording_metadata.jsoninside the frame folder.
Common recorder arguments
| Argument | Description | Default |
|---|---|---|
--output |
Base output path. Used to derive video path and/or frame directory when explicit paths are not provided. | data/<timestamp> |
--mode |
Save video, frames, or both. |
frames |
--frames-dir |
Directory for RGB frames named like YYYYMMDD_HHMMSS_mmm.png. Passing data creates data/<timestamp>/. |
Derived from --output |
--save-depth |
Save aligned depth PNGs to a depth/ subdirectory under the resolved RGB frame directory, for example data/<timestamp>/depth. |
False |
--duration |
Recording duration in seconds. Use 0 to record until stopped manually. |
0 |
--width / --height / --fps |
Requested stream resolution and FPS. | 1280 / 720 / 30 |
--serial |
Target device serial number when multiple cameras are connected. | None |
--codec |
Optional FOURCC override for video output, for example mp4v or XVID. |
Auto by output suffix |
--warmup-frames |
Frames discarded before recording to let auto exposure settle. | 30 |
--preview |
Show an OpenCV preview window during recording. | False |
--show-depth |
Show aligned depth beside RGB in preview. Requires --preview. |
False |
Output notes
- Frame folders produced by
tools/record_realsense.pycan be passed directly toinfer.py --video_path. --frames-dir datais treated as a base directory, and the recorder createsdata/<timestamp>/automatically.- If
--mode videois used and--outputhas no suffix, the recorder writes an.mp4file automatically. - If
--save-depthis enabled, the recorder creates adepth/subdirectory under the resolved RGB frame directory. - With
--frames-dir data, depth is written todata/<timestamp>/depth/. - If OpenCV preview is unavailable in the current environment, recording continues without the preview window.
Case A: videos directly in the input folder
<input_video_directory>/
├── 1.webm
├── 2.webm
└── ...
- Use
--scan_depth 0because the videos are already in the root folder.
Case B: one subfolder per video containing extracted frames
<input_video_directory>/
├── <video_name_1>/
│ ├── 000000.png
│ ├── 000001.png
│ └── ...
├── <video_name_2>/
│ ├── 000000.png
│ └── ...
└── ...
- Use
--scan_depth 1so TraceForge scans one level down to reach each video’s frames.
Case C: two-level layout (per-video folder with an images/ subfolder)
<input_video_directory>/
├── <video_name_1>/
│ └── images/
│ ├── 000000.png
│ ├── 000001.png
│ └── ...
├── <video_name_2>/
│ └── images/
│ ├── 000000.png
│ └── ...
└── ...
- Use
--scan_depth 2to search two levels down for the image frames.
Quick test dataset
- Download a small sample dataset and unpack it under
data/test_dataset:pip install gdown # if not installed mkdir -p data gdown --fuzzy https://drive.google.com/file/d/1Vn1FNbthz-K8o2ijq9V7jYv10rElWuUd/view?usp=sharing -O data/test_dataset.tar tar -xf data/test_dataset.tar -C data
- The downloaded data follows the Case B layout above; run inference with
python infer.py \ --video_path data/test_dataset \ --out_dir <output_directory> \ --batch_process \ --use_all_trajectories \ --skip_existing \ --frame_drop_rate 5 \ --scan_depth 1
python infer.py \
--video_path <input_video_directory> \
--out_dir <output_directory> \
--batch_process \
--use_all_trajectories \
--skip_existing \
--frame_drop_rate 5 \
--scan_depth 2| Argument | Description | Default |
|---|---|---|
--video_path |
Path to video directory | Required |
--depth_path |
Path to aligned depth directory | None |
--depth_png_scale |
Decode depth_path PNG depth via value / scale |
10000 |
--out_dir |
Output directory | outputs |
--batch_process |
Process all video folders in the directory | False |
--skip_existing |
Skip if output already exists | False |
--frame_drop_rate |
Query points every N frames | 1 |
--scan_depth |
Directory levels to scan for subfolders | 2 |
--fps |
Frame sampling stride (0 for auto) | 1 |
--max_frames_per_video |
Target max frames per episode | 50 |
--future_len |
Tracking window length per query frame | 128 |
<output_dir>/
└── <video_name>/
├── images/
│ ├── <video_name>_0.png
│ ├── <video_name>_5.png
│ └── ...
├── depth/
│ ├── <video_name>_0.png
│ ├── <video_name>_0_raw.npz
│ └── ...
├── samples/
│ ├── <video_name>_0.npz
│ ├── <video_name>_5.npz
│ └── ...
└── <video_name>.npz # Full video visualization data
Visualize 3D traces on single images using viser:
python visualize_single_image.py \
--npz_path <output_dir>/<video_name>/samples/<video_name>_0.npz \
--image_path <output_dir>/<video_name>/images/<video_name>_0.png \
--depth_path <output_dir>/<video_name>/depth/<video_name>_0.png \
--port 8080Check saved NPZ files:
# 3D trajectory checker
python checker/batch_process_result_checker_3d.py <output_dir> --max-videos 1 --max-samples 3
# 2D trajectory checker
python checker/batch_process_result_checker.py <output_dir> --max-videos 1 --max-samples 3Generate task descriptions using VLM (Vision-Language Model).
Create a .env file in the project root:
# For OpenAI (default)
OPENAI_API_KEY=your_openai_api_key
# For Google Gemini
GOOGLE_API_KEY=your_gemini_api_keycd text_generation/
python generate_description.py --episode_dir <dataset_directory>
# Skip episodes that already have descriptions
python generate_description.py --episode_dir <dataset_directory> --skip_existing- Reading 3D data: See
ThreedReaderinvisualize_single_image.py - Point and camera transformations: See
utils/threed_utils.py
If you find this work useful, please consider citing our paper:
@article{lee2025tracegen,
title={TraceGen: World Modeling in 3D Trace Space Enables Learning from Cross-Embodiment Videos},
author={Lee, Seungjae and Jung, Yoonkyo and Chun, Inkook and Lee, Yao-Chih and Cai, Zikui and Huang, Hongjia and Talreja, Aayush and Dao, Tan Dat and Liang, Yongyuan and Huang, Jia-Bin and Huang, Furong},
journal={arXiv preprint arXiv:2511.21690},
year={2025}
}
