This is the directory format written by rebake-cli export or by ParquetVideoExporterConfig in a pipeline. One directory corresponds to one ROS bag, with one Parquet table per topic and video files for camera and depth streams.
You can read this format without rebake; see reading without rebake. To feed it back into rebake and rebuild a dataset, start the pipeline with ParquetVideoIngestorConfig. For the export command, see CLI.
<output>/
βββ <uuid>/ # UUID from meta.json
βββ parquet/
β βββ <topic>.parquet # one per topic
β βββ _metadata.parquet # recording metadata, one row
β βββ _topic_type_map.parquet # topic -> ROS message type
β βββ _video_registry.parquet # topic -> video; absent when there are no videos
βββ videos/
βββ <topic>.mp4 # camera and lossy depth
βββ <topic>.mkv # lossless FFV1 depth
The file name is the topic name with the leading / removed and the remaining / replaced with __. For example, /camera/rgb/image_raw becomes camera__rgb__image_raw.parquet.
Columns preserve the ROS message structure. Nested messages become struct columns, arrays become list columns, and nested paths such as header.stamp remain nested.
Columns present in every table:
timestamp_ns(u64): recording timestamp in nanoseconds. For MCAP this is log time. Rows keep the order recorded in the ROS bag.publish_timestamp_ns(nullable u64): publish timestamp.
Output from rebake-cli export also always has a rosbag_uuid string column. When writing with ParquetVideoExporterConfig inside a pipeline, that column appears if the pipeline passed through UuidEnricherConfig. If you export after synchronization or enrichment, the columns present at that point, such as synched_timestamp_ns and is_fresh, are written as well.
One type caveat: ROS uint16 fields are widened to u32 columns.
For the following topics, the byte data field is replaced by a u32 index column.
| Topic | Detection | Where the bytes go |
|---|---|---|
| Compressed camera image | Topic name ends with /compressed |
RGB video under videos/. Frame number = index |
| Compressed depth | Topic name ends with /compressedDepth |
Depth video under videos/. Frame number = index |
| Raw image | Type is Image |
JPEG frames encoded into RGB video |
| Point cloud | Type is PointCloud2 |
Not saved in the current version; see known limits |
This keeps Parquet files from being dominated by image bytes; the table keeps only the reference (index).
Depth videos are written by rebake-cli export and by ParquetVideoExporterConfig when depth_config is set. If a pipeline exporter omits depth_config, depth payloads are not saved.
_metadata.parquet is the recording's meta.json as a one-row table. Field meanings are defined by the metadata page.
_topic_type_map.parquet:
| Column | Meaning |
|---|---|
rosbag_uuid |
Recording UUID |
topic_name |
Topic name, starting with / |
message_type |
ROS message type, for example sensor_msgs/msg/Image |
_video_registry.parquet is the ledger for topics that became videos:
| Column | Meaning |
|---|---|
rosbag_uuid |
Recording UUID |
topic_name |
Original topic name |
video_path |
Path relative to the directory, for example videos/camera__rgb__image_raw.mp4 |
media_type |
rgb or depth |
codec_family / encoder_name / pix_fmt |
Codec identity, for example av1 / libsvtav1 / yuv420p |
width / height / fps |
Video dimensions and frame rate (u32) |
encoding_config_json |
Full encoding config as JSON. For depth, this includes depth_max_mm |
video_path is relative so the whole directory can move or be stored in object storage without breaking the registry.
Camera (RGB) videos are mp4. Lossy depth is also mp4. Lossless FFV1 depth is mkv because FFV1 does not have an mp4 container path.
Lossy depth stores 10-bit distance values. The original 16-bit millimeter values are quantized to [1, 1023] and stored in the Y plane of P010LE as q10 << 6; chroma is neutral. Zero means invalid pixel. For choosing the quantization range, see encoding.
Lossless FFV1 uses gray16le, with millimeter values unchanged.
The tables are ordinary Parquet.
duckdb -c "SELECT timestamp_ns, position FROM '<uuid>/parquet/joint_states.parquet' LIMIT 5"import pandas as pd
df = pd.read_parquet("<uuid>/parquet/joint_states.parquet") # polars / pyarrow work tooCamera videos are ordinary mp4 files, readable by FFmpeg or OpenCV. The table's index column is the frame number.
To restore lossy depth to millimeters, decode each 16-bit pixel value y:
q10 = y >> 6
mm = 0 # when q10 = 0 (invalid pixel)
mm = (q10 * depth_max_mm + 511) / 1023 # otherwise, integer division
depth_max_mm is in encoding_config_json in _video_registry.parquet; the default is 4092. FFV1 depth needs no conversion, because the pixel value is already millimeters.
Feed the directory to a pipeline whose first stage is ParquetVideoIngestorConfig. rebake restores the topic tables, metadata, topic type map, and video registry. You no longer need a meta.json file because _metadata.parquet replaces it.
Videos are not decoded into in-memory frames on ingest. LeRobot transformation reads frames directly from the video files. The output dataset's videos are re-encoded with the transformer's video_config.
- PointCloud2 payloads are not saved. The table keeps only an
indexcolumn, and point cloud data is still unavailable after re-ingest. - ROS
uint16columns are widened to u32. - rebake is pre-1.0, and this format may change in later versions. Re-ingest with the same rebake version that wrote the output when you need maximum certainty.