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.
This commit is contained in:
+6
-28
@@ -1,30 +1,15 @@
|
||||
# MCAP Layout
|
||||
|
||||
`cvmmap-streamer` currently deals with three active MCAP layouts plus one legacy single-camera layout:
|
||||
`cvmmap-streamer` currently deals with three active MCAP layout shapes:
|
||||
|
||||
- legacy single-camera `/camera/*` MCAP export
|
||||
- bundled multi-camera timeline export
|
||||
- multi-camera copy export
|
||||
- single-source `zed_svo_to_mcap` output, which now uses the one-camera `copy` shape
|
||||
|
||||
This document covers the topic layout, schema types, and timestamp semantics for both.
|
||||
This document covers the current bundled and copy contracts.
|
||||
|
||||
## Legacy Single-Camera Layout
|
||||
|
||||
Default topics:
|
||||
|
||||
| Topic | Schema / Encoding | Notes |
|
||||
|------|------|------|
|
||||
| `/camera/video` | `foxglove.CompressedVideo` | H.264 or H.265 Annex B access units |
|
||||
| `/camera/depth` | `cvmmap_streamer.DepthMap` | RVL-compressed depth payload |
|
||||
| `/camera/calibration` | `foxglove.CameraCalibration` | Video intrinsics |
|
||||
| `/camera/depth_calibration` | `foxglove.CameraCalibration` | Written only when depth resolution differs from video |
|
||||
| `/camera/pose` | `foxglove.PoseInFrame` | Optional; only when pose export is enabled and tracking is valid |
|
||||
| `/camera/body` | raw `cvmmap.body_tracking.v1` | Optional raw body-tracking packets; see [mcap_body_tracking.md](./mcap_body_tracking.md) |
|
||||
|
||||
Single-camera export preserves the original per-frame source timestamp on video, depth, and pose messages.
|
||||
|
||||
This layout is still used by the generic sink/testers in the repository, but it is no longer the default shape written by `zed_svo_to_mcap` for a single `.svo` or `.svo2` input.
|
||||
Legacy `/camera/*` is documented separately in [mcap_legacy_single_camera_layout.md](./mcap_legacy_single_camera_layout.md).
|
||||
Conceptually, it is compatible with one-camera `copy` if you treat the camera label as the literal `camera` instead of `zedN` or another derived label.
|
||||
|
||||
## Bundled Multi-Camera Layout
|
||||
|
||||
@@ -135,15 +120,6 @@ Bundled `strict` export stays strict:
|
||||
|
||||
## Validation Expectations
|
||||
|
||||
For single-camera MCAP files, the current validation contract is:
|
||||
|
||||
- `/camera/video` must exist and contain at least one message
|
||||
- `/camera/depth` must exist and contain at least one message
|
||||
- `/camera/calibration` must exist exactly once
|
||||
- `/camera/video` and `/camera/depth` message counts must match
|
||||
- `/camera/depth_calibration` may appear zero or one time
|
||||
- `/camera/pose` is optional, but it may not outnumber `/camera/video`
|
||||
|
||||
For bundled MCAP files, the current validation contract is:
|
||||
|
||||
- `/bundle` must exist
|
||||
@@ -159,4 +135,6 @@ For multi-camera `copy` MCAP files, the current validation contract is:
|
||||
- each camera must have `/zedN/video`, `/zedN/depth`, and `/zedN/calibration`
|
||||
- for each camera, `/zedN/video` and `/zedN/depth` message counts must match
|
||||
|
||||
Legacy `/camera/*` validation expectations are documented in [mcap_legacy_single_camera_layout.md](./mcap_legacy_single_camera_layout.md).
|
||||
|
||||
The repository-level Python helper [scripts/mcap_bundle_validator.py](../scripts/mcap_bundle_validator.py) understands bundled, copy, and legacy `/camera/*` layouts and reports which one it found before applying the corresponding validation rules.
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
# Legacy Single-Camera MCAP Layout
|
||||
|
||||
This page is the reference for the older `/camera/*` MCAP wire shape.
|
||||
|
||||
It is still used by some generic sink/tester paths in the repository, but it is no longer the default shape written by `zed_svo_to_mcap` for a single `.svo` or `.svo2` input.
|
||||
|
||||
## Compatibility With Copy Layout
|
||||
|
||||
In practice, this layout is very close to the one-camera `copy` shape:
|
||||
|
||||
- legacy single-camera uses `/camera/video`, `/camera/depth`, and related `/camera/*` topics
|
||||
- one-camera `copy` uses `/{label}/video`, `/{label}/depth`, and related `/{label}/*` topics
|
||||
|
||||
So the compatibility mental model is:
|
||||
|
||||
- treat the camera label as the literal `camera`
|
||||
- replace `/{label}/...` with `/camera/...`
|
||||
- there is still no `/bundle`
|
||||
- timestamps remain per-camera sample timestamps
|
||||
|
||||
That is why legacy single-camera is conceptually compatible with `copy`, even though the topic paths are not namespaced the same way.
|
||||
|
||||
## Topics
|
||||
|
||||
| Topic | Schema / Encoding | Notes |
|
||||
|------|------|------|
|
||||
| `/camera/video` | `foxglove.CompressedVideo` | H.264 or H.265 Annex B access units |
|
||||
| `/camera/depth` | `cvmmap_streamer.DepthMap` | RVL-compressed depth payload |
|
||||
| `/camera/calibration` | `foxglove.CameraCalibration` | Video intrinsics |
|
||||
| `/camera/depth_calibration` | `foxglove.CameraCalibration` | Written only when depth resolution differs from video |
|
||||
| `/camera/pose` | `foxglove.PoseInFrame` | Optional; only when pose export is enabled and tracking is valid |
|
||||
| `/camera/body` | raw `cvmmap.body_tracking.v1` | Optional raw body-tracking packets; see [mcap_body_tracking.md](./mcap_body_tracking.md) |
|
||||
|
||||
## Timestamp Semantics
|
||||
|
||||
Legacy single-camera export preserves the original per-frame source timestamp on video, depth, and pose messages.
|
||||
|
||||
Like `copy`, there is no separate bundle time:
|
||||
|
||||
- `/camera/video`, `/camera/depth`, and `/camera/pose` use the original camera sample timestamp
|
||||
- calibration and depth-calibration messages use the timestamp of the first emitted sample
|
||||
|
||||
## Corruption Behavior
|
||||
|
||||
Like `copy`, this layout has no manifest or placeholder contract:
|
||||
|
||||
- unreadable tail frames are treated as end-of-stream
|
||||
- mid-stream corruption is skipped until a readable frame is found
|
||||
- the topic stream simply resumes at the recovered readable frame
|
||||
|
||||
## Validation Expectations
|
||||
|
||||
The current validation contract is:
|
||||
|
||||
- `/camera/video` must exist and contain at least one message
|
||||
- `/camera/depth` must exist and contain at least one message
|
||||
- `/camera/calibration` must exist exactly once
|
||||
- `/camera/video` and `/camera/depth` message counts must match
|
||||
- `/camera/depth_calibration` may appear zero or one time
|
||||
- `/camera/pose` is optional, but it may not outnumber `/camera/video`
|
||||
+20
-13
@@ -4,12 +4,12 @@ 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 or `copy`
|
||||
- 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 full layout contract, see [mcap_layout.md](./mcap_layout.md).
|
||||
For the current bundled/copy layout contract, see [mcap_layout.md](./mcap_layout.md). The older `/camera/*` wire shape is documented separately in [mcap_legacy_single_camera_layout.md](./mcap_legacy_single_camera_layout.md).
|
||||
|
||||
## Quick Summary
|
||||
|
||||
@@ -41,14 +41,19 @@ uv sync --extra viewer
|
||||
|
||||
## The Practical Cases
|
||||
|
||||
For this helper, there are really two supported layouts:
|
||||
For this helper, there are really two operational cases:
|
||||
|
||||
- `bundled`: multiple namespaced camera topics plus `/bundle`
|
||||
- `copy`: namespaced camera topics with no `/bundle`
|
||||
- single-camera stream with no `/bundle`
|
||||
|
||||
Current single-source `zed_svo_to_mcap` output uses the one-camera `copy` shape by default, so even a one-camera file still looks like namespaced `/{label}/*` topics with no `/bundle`.
|
||||
That second case can appear in two wire shapes:
|
||||
|
||||
Legacy `/camera/*` MCAP files are intentionally out of scope for this helper. Regenerate them into `copy` layout if you still have old files around.
|
||||
- `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
|
||||
|
||||
@@ -70,7 +75,7 @@ What the summary prints:
|
||||
|
||||
This is the fastest way to answer:
|
||||
|
||||
- “is this file bundled or copy?”
|
||||
- “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?”
|
||||
@@ -83,7 +88,7 @@ uv run python scripts/mcap_rgbd_example.py export-sample \
|
||||
--output-dir /tmp/mcap_sample
|
||||
```
|
||||
|
||||
For multi-camera or one-camera `copy` files, choose the camera explicitly when needed:
|
||||
For multi-camera or namespaced one-camera files, choose the camera explicitly when needed:
|
||||
|
||||
```bash
|
||||
uv run python scripts/mcap_rgbd_example.py export-sample \
|
||||
@@ -104,25 +109,26 @@ Outputs:
|
||||
|
||||
That means:
|
||||
|
||||
- `copy`: sample `N` is `/zedN/video[N]` + `/zedN/depth[N]`
|
||||
- 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 `N`th 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 Copy Timing
|
||||
## 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 files do not have bundle time at all:
|
||||
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`, treat each camera stream independently.
|
||||
For `copy` and legacy single-camera files, treat each camera stream independently.
|
||||
|
||||
## Recipe: Inspect `/bundle` In Python
|
||||
|
||||
@@ -169,4 +175,5 @@ with path.open("rb") as stream:
|
||||
This is the important mental model:
|
||||
|
||||
- `bundled`: follow `/bundle` for grouping
|
||||
- `copy`: treat each camera as an independent stream
|
||||
- `copy`: treat each namespaced camera as an independent stream
|
||||
- legacy `/camera/*`: same model as one-camera `copy`, with the implicit label `camera`
|
||||
|
||||
Reference in New Issue
Block a user