ddea6b0e3d
Drop the offline ZED helper implementations that were moved into zed-offline-tools.\n\nThis removes the standalone conversion binaries, batch/index/inspection scripts, related configs and tests, and the tool-specific support code that no longer belongs in cvmmap-streamer.\n\nThe build files and docs are updated to point at the standalone repo while keeping the streamer runtime surface intact.
141 lines
7.1 KiB
Markdown
141 lines
7.1 KiB
Markdown
# MCAP Layout
|
||
|
||
`cvmmap-streamer` currently deals with three active MCAP layout shapes:
|
||
|
||
- 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 current bundled and copy contracts.
|
||
|
||
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
|
||
|
||
Bundled export namespaces each camera stream and adds one bundle manifest topic:
|
||
|
||
| Topic | Schema / Encoding | Notes |
|
||
|------|------|------|
|
||
| `/bundle` | `cvmmap_streamer.BundleManifest` | One message per emitted bundle |
|
||
| `/zedN/video` | `foxglove.CompressedVideo` | Per-camera encoded video |
|
||
| `/zedN/depth` | `cvmmap_streamer.DepthMap` | Per-camera depth |
|
||
| `/zedN/calibration` | `foxglove.CameraCalibration` | Per-camera video intrinsics |
|
||
| `/zedN/depth_calibration` | `foxglove.CameraCalibration` | Written only when exported depth resolution differs from video |
|
||
| `/zedN/pose` | `foxglove.PoseInFrame` | Optional per-camera pose |
|
||
| `/zedN/body` | raw `cvmmap.body_tracking.v1` | Optional raw body packets; see [mcap_body_tracking.md](./mcap_body_tracking.md) |
|
||
|
||
`nearest` is the default bundled policy. It emits one bundle on a common timeline, but it keeps the original per-camera timestamps on `/zedN/video`, `/zedN/depth`, and optional `/zedN/pose`.
|
||
|
||
`strict` is still available. In strict mode, bundle membership is thresholded by timestamp skew and `--sync-tolerance-ms` applies. In `nearest` mode, `--sync-tolerance-ms` is not used.
|
||
|
||
Because `nearest` emits on the slowest common timeline, faster cameras can legitimately end up with the same message count as slower cameras in bundled output.
|
||
|
||
## Multi-Camera Copy Layout
|
||
|
||
`copy` export also namespaces each camera stream under `/zedN/...`, but it does not emit `/bundle`.
|
||
|
||
| Topic | Schema / Encoding | Notes |
|
||
|------|------|------|
|
||
| `/zedN/video` | `foxglove.CompressedVideo` | Per-camera encoded video |
|
||
| `/zedN/depth` | `cvmmap_streamer.DepthMap` | Per-camera depth |
|
||
| `/zedN/calibration` | `foxglove.CameraCalibration` | Per-camera video intrinsics |
|
||
| `/zedN/depth_calibration` | `foxglove.CameraCalibration` | Written only when exported depth resolution differs from video |
|
||
| `/zedN/pose` | `foxglove.PoseInFrame` | Optional per-camera pose |
|
||
| `/zedN/body` | raw `cvmmap.body_tracking.v1` | Optional raw body packets; see [mcap_body_tracking.md](./mcap_body_tracking.md) |
|
||
|
||
`copy` preserves each camera topic exactly as an independent stream inside one MCAP:
|
||
|
||
- no `/bundle` topic
|
||
- no shared bundle index
|
||
- no resampling to a common timeline
|
||
- original per-camera timestamps and cadence are preserved
|
||
|
||
`--copy-range common` trims each camera to the common overlap window without resampling. `--copy-range full` preserves each camera’s full readable timestamp range from the grouped segment.
|
||
|
||
Single-source `zed_svo_to_mcap` now writes this same wire shape with one derived label, for example:
|
||
|
||
- `/zed4/video`, `/zed4/depth`, `/zed4/calibration` for `*_zed4.svo2`
|
||
- `/cam1/video`, `/cam1/depth`, `/cam1/calibration` when the filename has no `zedN` suffix
|
||
|
||
## Bundle Manifest Contract
|
||
|
||
`/bundle` is the authoritative grouping contract for bundled MCAP files. Consumers should not infer grouping from identical MCAP `logTime` values or from matching per-camera timestamps.
|
||
|
||
`cvmmap_streamer.BundleManifest` contains:
|
||
|
||
- `timestamp`: the nominal bundle timestamp on the common timeline
|
||
- `bundle_index`: zero-based emitted bundle index
|
||
- `policy`: `BUNDLE_POLICY_NEAREST` or `BUNDLE_POLICY_STRICT`
|
||
- `members`: one entry per camera label in the bundle
|
||
|
||
Each bundle member contains:
|
||
|
||
- `camera_label`
|
||
- `timestamp`: the original camera sample timestamp when a payload is present
|
||
- `delta_ns`: `member.timestamp - bundle.timestamp` when a payload is present
|
||
- `status`
|
||
- `corrupted_frames_skipped`
|
||
|
||
Current member statuses:
|
||
|
||
- `BUNDLE_MEMBER_STATUS_PRESENT`
|
||
- `BUNDLE_MEMBER_STATUS_CORRUPTED_GAP`
|
||
|
||
`CORRUPTED_GAP` means the exporter skipped one or more unreadable ZED frames and intentionally emitted no video, depth, or pose payload for that camera for this bundle. `corrupted_frames_skipped` reports the size of the skipped run that led to the next recovered readable frame.
|
||
|
||
## Timestamp Semantics
|
||
|
||
The bundled MCAP contract intentionally separates bundle time from per-camera sample time:
|
||
|
||
- `/bundle.logTime` and `/bundle.publishTime` use the nominal bundle timestamp
|
||
- `/zedN/video`, `/zedN/depth`, and `/zedN/pose` use the original camera sample timestamp
|
||
- calibration and depth-calibration messages use the timestamp of the first emitted sample on that camera
|
||
|
||
This means a single bundle can legitimately contain different per-camera timestamps, especially in `nearest` mode.
|
||
|
||
`copy` has no separate bundle time. Its `/zedN/video`, `/zedN/depth`, and `/zedN/pose` messages all use the original per-camera sample timestamp directly.
|
||
|
||
## Corruption And Partial Bundles
|
||
|
||
Bundled `nearest` export is resilient to ZED `CORRUPTED FRAME` runs:
|
||
|
||
- unreadable tail frames are treated as end-of-stream
|
||
- mid-stream corruption is skipped until a readable frame is found
|
||
- bundles inside the unreadable gap still exist
|
||
- the affected camera is marked `CORRUPTED_GAP` in `/bundle`
|
||
- no `/zedN/video`, `/zedN/depth`, or `/zedN/pose` message is written for that camera for those bundles
|
||
|
||
Bundled `strict` export stays strict:
|
||
|
||
- corruption is skipped internally until recovery
|
||
- only fully present, threshold-qualified groups are emitted
|
||
|
||
`copy` is also resilient to ZED `CORRUPTED FRAME` runs:
|
||
|
||
- unreadable tail frames are treated as end-of-stream
|
||
- mid-stream corruption is skipped until a readable frame is found
|
||
- there is no placeholder or manifest entry because `copy` has no grouping contract
|
||
- the affected camera topic simply resumes at the recovered readable frame
|
||
|
||
## Validation Expectations
|
||
|
||
For bundled MCAP files, the current validation contract is:
|
||
|
||
- `/bundle` must exist
|
||
- every bundle must contain one member per camera label
|
||
- for each camera, the count of `BUNDLE_MEMBER_STATUS_PRESENT` members must match the number of `/zedN/video` messages
|
||
- for each camera, the count of `BUNDLE_MEMBER_STATUS_PRESENT` members must match the number of `/zedN/depth` messages
|
||
|
||
That is why a partially written MCAP with topic presence but mismatched counts is treated as invalid.
|
||
|
||
For multi-camera `copy` MCAP files, the current validation contract is:
|
||
|
||
- `/bundle` must not exist
|
||
- 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 standalone helper [zed-offline-tools/scripts/mcap_bundle_validator.py](../../zed-offline-tools/scripts/mcap_bundle_validator.py) understands bundled, copy, and legacy `/camera/*` layouts and reports which one it found before applying the corresponding validation rules.
|