Skip to content

Latest commit

Β 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

English | ζ—₯本θͺž

rebake β€” turn your ROS bags into a queryable dataset, then into LeRobot training data

Decode a ROS bag once. Query it like a database β€” and bake it into LeRobot training data, as many times as you like.

License: Apache-2.0 Rust 1.88+ Python 3.9+

rebake converts ROS bags (.bag / .mcap) into a queryable Parquet + video dataset, and from that into LeRobot v2.1 training data β€” as a CLI, a Python package, and a Rust library.


Why rebake

A ROS bag is built for recording, not for using. It's large, slow to load, and serialized message by message β€” so every analysis or training run begins by re-parsing the ROS bag, rebuilding the transform tree, and re-aligning clocks that never matched. Training formats like LeRobot fix the loading problem but are deliberately lossy: they freeze in one set of choices β€” which topics, which rate, which features β€” so changing your mind means starting over from the ROS bag.

rebake decodes a ROS bag once into a queryable Parquet + video intermediate format, and runs everything else β€” synchronization, transform-tree math, the LeRobot export β€” on that:

  • ⚑ Query without deserialization. Each topic becomes a columnar Parquet table, so you read just the fields you need β€” never the whole message β€” straight from DuckDB, Polars, or pandas.
  • πŸ“¦ Smaller, and archival. Per-column compression, plus video for camera streams, makes the intermediate typically 7–10Γ— smaller than the ROS bag β€” while keeping state data losslessly. A durable archive, not just a temp file.
  • 🧱 Typed and structured. Nested ROS messages stay nested, with their types intact β€” so the fragile, rewrite-it-every-time parsing code disappears.
  • 🌍 Part of the data ecosystem. Parquet and video are first-class everywhere β€” DuckDB, Polars, pandas, Arrow, Spark, FFmpeg β€” so your robot data drops into the tools your team already runs, with nothing rebake-specific to install.

Because the heavy work lives in the intermediate format, re-curating with a different topic set or sample rate never touches the ROS bag again β€” and LeRobot v2.1 is simply the first export target the pipeline knows how to write.

Quickstart

Note

Each ROS bag needs a small meta.json sidecar (its dataset id, plus segment labels for the full pipeline); the shipped configs already expect it. See docs/metadata.md.

Build it in the dev container:

git clone --recursive https://github.com/airoa-org/rebake.git
cd rebake
docker compose -f docker/docker-compose.yml up -d --build
docker compose -f docker/docker-compose.yml exec rebake-dev bash

# inside the container
just build      # β†’ ./target/release/rebake-cli

Decode your ROS bags into a queryable intermediate format. Point at a single .bag/.mcap or a whole directory of them; -j converts the ROS bags in parallel, each in its own process:

rebake-cli export ./yubi_recordings -o ./out -j 8

Your opaque ROS bags are now plain Parquet and video β€” explore them with anything, no rebake required:

import pandas as pd
pd.read_parquet("out/<id>/parquet/joint_states.parquet")   # also: polars, pyarrow
duckdb -c "SELECT * FROM 'out/*/parquet/joint_states.parquet' LIMIT 5"

Bake LeRobot v2.1 datasets when you're ready to train β€” one declarative pipeline, no per-robot code, the same parallel batch over a directory:

rebake-cli run ./yubi_recordings -c config/pipeline/yubi.yaml -j 8
lerobot_dataset/
β”œβ”€β”€ meta/      info.json, episodes.jsonl, tasks.jsonl, episodes_stats.jsonl
β”œβ”€β”€ data/      one Parquet file per episode
└── videos/    one video per camera, per episode

It's a standard LeRobot v2.1 dataset β€” load it with the lerobot library and start training. To re-curate it β€” a different topic set, sample rate, or feature mapping β€” re-run pointed at the intermediate format: rebake re-ingests that instead of re-parsing the original ROS bags.

How it works

A pipeline is a declarative list of stages that share a context β€” reorder, add, or drop stages in YAML, no code changes.

# abridged from config/pipeline/yubi.yaml
stage_configs:
  - Rosbag2IngestorConfig: {}             # read .bag / .mcap
  - TfBufferEnricherConfig: {}            # build the transform tree
  - TfChainEnricherConfig:                # compute poses between frame pairs
      frame_pairs:
        - source: quest_origin
          target: quest_hmd
  - ZeroOrderHoldTimeSynchronizerConfig:  # resample to one timeline
      fps: 30
  - LeRobotV21TransformerConfig:          # write the LeRobot dataset
      robot_model: ./config/robot_model/yubi.yaml
  • Synchronize β€” resample topics recorded at different rates onto one timeline (zero-order-hold, nearest-neighbor, or timestamp-merge), marking each row is_fresh so a held value is never mistaken for a fresh measurement.
  • Enrich β€” build the transform tree from /tf and /tf_static, then compute end-effector and camera poses between any two frames with forward kinematics (SE(3) composition, lowest-common-ancestor lookup), and derive action labels β€” the things a policy needs but the ROS bag never stored directly.
  • Encode β€” write camera streams to AV1/H.264/H.265 video, and keep depth metric β€” lossless FFV1 or a compact 10-bit form β€” instead of crushing 16-bit millimeters into 8-bit RGB.
The full stage list

Ingest (ROS 1/2, or re-ingest a rebake intermediate) Β· Synchronize (ZOH / nearest-neighbor / timestamp-merge) Β· Enrich (TF buffer & chain, joint/transform deltas, action shift, uuid) Β· Encode (RGB & depth video, software or VA-API/NVENC) Β· Export (Parquet + video intermediate) Β· Transform (LeRobot v2.1) Β· Merge (combine datasets without re-encoding). Full reference: docs/configuration.md.

Bring your own robot

A new robot is one YAML file that maps its ROS topics and field paths to LeRobot features:

# robot_model.yaml
- type: Parquet
  topic: /joint_states
  field: /position
  feature: observation.state
- type: Video
  topic: /camera/color/image_raw/compressed
  feature: observation.image.head
- type: Parquet
  topic: /right_hand/command
  field: /position
  feature: action.right_hand
- type: Parquet
  topic: /left_hand/command
  field: /position
  feature: action.left_hand

See config/robot_model/ for complete examples.

Python

The same stages from Python β€” built from source with maturin, with zero-copy Arrow/PyArrow exchange. Every stage is Config().build().run(context), all the way to a LeRobot dataset:

from rebake.core import Context
from rebake.encode import VideoEncoderConfig
from rebake.enrich import FramePair, TfBufferEnricherConfig, TfChainEnricherConfig
from rebake.ingest import Rosbag2IngestorConfig
from rebake.synchronize import ZeroOrderHoldTimeSynchronizerConfig
from rebake.transform import LeRobotV21TransformerConfig

context = Context()
context.set_rosbag_path("recording.mcap")
context = Rosbag2IngestorConfig().build().run(context)
context = TfBufferEnricherConfig().build().run(context)
context = TfChainEnricherConfig(
    frame_pairs=[FramePair(source="quest_origin", target="quest_hmd")],
).build().run(context)
context = ZeroOrderHoldTimeSynchronizerConfig(fps=10).build().run(context)
context = LeRobotV21TransformerConfig(
    outdir="./lerobot_dataset",
    robot_model="config/robot_model/yubi.yaml",
    video_config=VideoEncoderConfig(fps=10),
).build().run(context)

See python/ for the full API and examples.

Learn more

Contributing

Issues and pull requests are welcome β€” see CONTRIBUTING.md and come say hello in Discussions.

License

Licensed under the Apache License, Version 2.0 β€” see the LICENSE file for details.

Copyright Β© 2026 AI Robot Association.

About

Decode a ROS bag once. Query it like a database β€” and bake it into LeRobot training data, as many times as you like. 🍞

Resources

Code of conduct

Contributing

Stars

12 stars

Watchers

2 watching

Forks

Releases

Packages

Contributors

Languages