> ## Documentation Index
> Fetch the complete documentation index at: https://docs.nomadicml.com/llms.txt
> Use this file to discover all available pages before exploring further.

# File structure

> One recording per MCAP file, its channels, and the nomadic_spec overlay record.

## One file, one recording

One MCAP file is **one recording**: one continuous capture from one rig, robot or person. A
recording SHOULD cover one logical scene or episode, because it is the unit that is uploaded,
retried and displayed. Split a long drive into scenes, or a teleoperation session into episodes,
in your converter.

## Channels

Every channel MUST use `message_encoding: "protobuf"`, with its schema registered as a protobuf
`FileDescriptorSet` (`schema.encoding: "protobuf"`) named by the message's full name, e.g.
`foxglove.CompressedVideo`. The [`mcap-protobuf-support`](https://pypi.org/project/mcap-protobuf-support/)
library does this for you; see [Writing a converter](/mcap-spec/examples/writing-a-converter).

| Modality | Message schema | Declared in the overlay | Specified in |
| - | - | - | - |
| Camera views | `foxglove.CompressedVideo`, `foxglove.CompressedImage` or `foxglove.RawImage` | yes, in `views` | [Cameras](/mcap-spec/cameras) |
| Point clouds (LiDAR, radar) | `foxglove.PointCloud` | yes, in `point_clouds` | [Point clouds](/mcap-spec/point-clouds) |
| Signals (any time series) | `nomadic.Signal` | yes, in `signals` | [Signals](/mcap-spec/signals) |
| A part's pose (gripper, wrist, foot) | `foxglove.PoseInFrame` | yes, in a part's `end_pose` | [Body and parts](/mcap-spec/body-and-parts) |
| A part's keypoints (hand, body) | `nomadic.Skeleton` | yes, in a part's `keypoints` | [Body and parts](/mcap-spec/body-and-parts#keypoints) |
| Sensor extrinsics | `foxglove.FrameTransform` | no, found by schema | [Frames, poses and units](/mcap-spec/frames-and-units#transforms) |
| Camera intrinsics | `foxglove.CameraCalibration` | no, found by schema | [Cameras](/mcap-spec/cameras#intrinsics) |
| Body pose (ego pose) | `foxglove.PoseInFrame` | no, found by schema | [Frames, poses and units](/mcap-spec/frames-and-units#body-pose) |
| GPS | `foxglove.LocationFix` | no, found by schema | [GPS](/mcap-spec/gps) |

Channel rules:

* A channel topic MUST start with `/` and contain only printable ASCII with no whitespace
  (`^/[!-~]*$`), e.g. `/cam/front`, `/sig/speed`.
* Every channel the overlay names MUST exist in the file and carry the message schema its
  declaration requires.
* One channel carries **exactly one thing**. A channel MUST NOT be declared twice, whether as two
  views, a view and a signal, or a signal and a part's pose.
* Only declared views, point clouds and signals are part of the recording. A camera channel the
  overlay does not list in `views` is not ingested as a camera.

### A recording must have a video

Every recording MUST resolve to at least one video view: either a declared camera, or a LiDAR whose
points carry a `ring` field (it is rendered as a range-image video, see
[Point clouds](/mcap-spec/point-clouds#lidar-only-recordings)). A file with signals only, or a
LiDAR without `ring` and no camera, is rejected.

## The `nomadic_spec` overlay

Every file MUST contain an MCAP **metadata record** named `nomadic_spec`, with one key, `spec`,
whose value is the overlay serialized as a JSON string.

It MUST be a metadata record, not an attachment or a message. Metadata records are indexed in the
MCAP summary section, so the overlay can be read without scanning the file.

```python theme={null}
writer.add_metadata("nomadic_spec", {"spec": json.dumps(overlay)})
```

The overlay carries only what the Foxglove schemas cannot express: each view's role, each point
cloud's kind, each signal's name, type and unit, and the recording and body metadata. Pixels,
points, transforms and intrinsics are in the messages, never in the overlay.

### Top-level fields

| Field | Type | Required | Meaning |
| - | - | - | - |
| `spec_version` | `"1.0"` \| `"1.1"` | yes | The version of this specification the overlay conforms to. |
| `recording` | object | yes | Identity, clock, task and body. See [Recording and time](/mcap-spec/recording). |
| `views` | array | one of these four | Camera views. See [Cameras](/mcap-spec/cameras). |
| `point_clouds` | array | one of these four | LiDAR and radar. See [Point clouds](/mcap-spec/point-clouds). |
| `signals` | array | one of these four | Time series. See [Signals](/mcap-spec/signals). |
| `recording.platform.parts` | array | one of these four (1.1) | Body parts with pose or keypoint channels. See [Body and parts](/mcap-spec/body-and-parts). |
| `primary_view` | string | SHOULD, when there are views | The channel of the recording's default view. When present it MUST name a declared view. |
| `profile` | string | no | A named set of signal conventions. See [Signals](/mcap-spec/signals#profiles). |
| `source` | object | no | Free-form provenance (dataset name, converter version, how a value was derived). Kept, never interpreted. |

An overlay MUST declare at least one view, point cloud, signal or part. Unknown keys MUST NOT
appear anywhere in the overlay; the JSON Schema forbids them. A typo such as `point_cloud` is
rejected rather than silently dropping a modality.

### A minimal overlay

```json theme={null}
{
  "spec_version": "1.1",
  "recording": {
    "id": "drive-2026-03-14-0007",
    "clock": "log_time",
    "t0_ns": 1710403200000000000,
    "platform": {"kind": "vehicle"}
  },
  "primary_view": "/cam/front",
  "views": [{"channel": "/cam/front", "role": "front", "label": "Front wide"}],
  "signals": [{"channel": "/sig/speed", "name": "speed", "type": "float", "unit": "m/s", "rate_hz": 50}]
}
```


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.