Files
cvmmap-streamer/docs/mcap_recipes.md
T
crosstyan d0f3dc5cf1 docs(mcap): split legacy single-camera layout reference
Move the legacy /camera/* contract into its own reference page so the main MCAP layout doc can stay focused on current bundled and copy-layout behavior.

Document the compatibility model explicitly: legacy single-camera is operationally equivalent to one-camera copy when the effective camera label is treated as the literal `camera`.

Update the mcap_rgbd_example helper and recipe docs to accept legacy /camera/* inputs under that compatibility rule instead of rejecting them.
2026-03-24 18:56:52 +08:00

5.6 KiB

MCAP Recipes

This guide is the simple, non-GUI path for inspecting RGB+depth MCAP files.

Use it when you want to:

  • confirm whether an MCAP is bundled, copy, or legacy /camera/*
  • inspect camera labels, message counts, and timestamp ranges
  • export one RGB frame and one decoded depth sample as a concrete example
  • understand how /bundle changes the meaning of timestamps and sample grouping

For the current bundled/copy layout contract, see mcap_layout.md. The older /camera/* wire shape is documented separately in mcap_legacy_single_camera_layout.md.

Quick Summary

The repository includes a small example helper:

uv run python scripts/mcap_rgbd_example.py --help

It has two commands:

  • summary: print layout, per-camera counts, and timestamp ranges
  • export-sample: write one RGB image plus one depth array/preview

summary works with the base Python dependencies:

uv sync

export-sample also needs:

  • ffmpeg on PATH
  • the optional depth decoder binding:
uv sync --extra viewer

The Practical Cases

For this helper, there are really two operational cases:

  • bundled: multiple namespaced camera topics plus /bundle
  • single-camera stream with no /bundle

That second case can appear in two wire shapes:

  • copy: namespaced topics such as /zed4/video
  • legacy single-camera: /camera/video

Current single-source zed_svo_to_mcap output uses the one-camera copy shape by default, so even a one-camera file usually looks like namespaced /{label}/* topics with no /bundle.

The helper treats legacy /camera/* as compatible with copy by using the implicit camera label camera.

Recipe: Summarize One MCAP

uv run python scripts/mcap_rgbd_example.py summary <MCAP_PATH>

What the summary prints:

  • layout and validation status
  • camera labels
  • per-camera video, depth, pose, calibration, depth_calibration, and body counts
  • per-camera video/depth timestamp ranges
  • for bundled files only:
    • bundle count
    • bundle timestamp range
    • bundle policy counts
    • per-camera present/corrupted-gap/unknown bundle-member counts

This is the fastest way to answer:

  • “is this file bundled, copy, or legacy single-camera?”
  • “which camera labels are inside?”
  • “do video and depth counts match?”
  • “what timestamp range does each camera cover?”

Recipe: Export One RGB + Depth Sample

uv run python scripts/mcap_rgbd_example.py export-sample \
    <MCAP_PATH> \
    --output-dir /tmp/mcap_sample

For multi-camera or namespaced one-camera files, choose the camera explicitly when needed:

uv run python scripts/mcap_rgbd_example.py export-sample \
    <MCAP_PATH> \
    --camera-label zed2 \
    --sample-index 25 \
    --output-dir /tmp/mcap_sample_zed2

Outputs:

  • rgb.png
  • depth.npy
  • depth_preview.png
  • sample_metadata.json

sample_index is always zero-based per-camera RGB+depth sample order.

That means:

  • legacy /camera/*: sample N is /camera/video[N] + /camera/depth[N]
  • copy: sample N is /{label}/video[N] + /{label}/depth[N]
  • bundled: sample N is the Nth present sample for that camera, not bundle index N

In bundled files, sample_metadata.json also records the matched /bundle member metadata for the selected camera sample.

Recipe: Understand Bundled vs Non-Bundled Timing

Bundled files intentionally separate bundle time from camera sample time:

  • /bundle.timestamp is the nominal common-timeline bundle timestamp
  • /zedN/video and /zedN/depth keep the original per-camera sample timestamps

Copy and legacy single-camera files do not have bundle time at all:

  • there is no /bundle
  • each camera topic keeps its own original cadence and timestamps

If you care about grouping, use /bundle in bundled files. For copy and legacy single-camera files, treat each camera stream independently.

Recipe: Inspect /bundle In Python

The helper script is intentionally small, but sometimes it is easier to inspect /bundle directly. This snippet shows how to print bundle membership for one camera:

from pathlib import Path

import zed_batch_svo_to_mcap as batch


path = Path("<MCAP_PATH>").expanduser().resolve()
camera_label = "zed1"
reader_module = batch.load_mcap_reader()

with path.open("rb") as stream:
    reader = reader_module.make_reader(stream)
    for schema, channel, message in reader.iter_messages():
        if channel.topic != "/bundle":
            continue
        if schema is None or schema.name != "cvmmap_streamer.BundleManifest":
            continue

        bundle_class, present_value = batch.load_bundle_manifest_type(schema.data)
        bundle = bundle_class()
        bundle.ParseFromString(message.data)

        for member in bundle.members:
            if str(member.camera_label) != camera_label:
                continue
            status_value = int(getattr(member, "status", 0))
            status_field = member.DESCRIPTOR.fields_by_name.get("status")
            status_enum = status_field.enum_type if status_field is not None else None
            status_name = (
                status_enum.values_by_number.get(status_value).name
                if status_enum is not None and status_enum.values_by_number.get(status_value) is not None
                else str(status_value)
            )
            print(bundle.bundle_index, status_name)
            break

This is the important mental model:

  • bundled: follow /bundle for grouping
  • copy: treat each namespaced camera as an independent stream
  • legacy /camera/*: same model as one-camera copy, with the implicit label camera