> ## 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.

# Point clouds

> LiDAR and radar as foxglove.PointCloud: declaration, frames and fields.

## Declaration

Each LiDAR or radar is one channel of **`foxglove.PointCloud`** messages, declared in the overlay's
`point_clouds` array with its `kind`, which a `PointCloud` message cannot carry:

```json theme={null}
"point_clouds": [
  {"channel": "/lidar/top",   "name": "lidar_top",   "kind": "lidar"},
  {"channel": "/radar/front", "name": "radar_front", "kind": "radar"}
]
```

| Field | Type | Required | Rule |
| - | - | - | - |
| `channel` | string | yes | The sensor's channel. |
| `name` | string | yes | Non-empty, unique among point clouds. |
| `kind` | `"lidar"` \| `"radar"` | yes | The sensor type. |

## Fields

| Field | Requirement |
| - | - |
| `x`, `y`, `z` | MUST be present, metres. |
| `intensity` | SHOULD be included when the sensor provides it. |
| `ring` (beam index) | SHOULD be included when the sensor provides it. Required for a LiDAR-only recording; see below. |
| `red`, `green`, `blue`, or one packed `rgb` / `rgba` | MAY be included for per-point colour. |
| anything else (`rcs`, `vx`, `vy`, …) | MAY be included; kept as published. |

Include `intensity` and `ring` whenever you have them. They cost only storage, and they cannot be
added later without re-uploading.

## Frames

Every LiDAR cloud is placed in the [`vehicle` frame](/mcap-spec/frames-and-units#the-vehicle-frame)
(x forward, y left, z up, z = 0 at the ground). Choose one of two shapes per cloud, by the
message's `frame_id`:

| `frame_id` | Meaning | Requirement |
| - | - | - |
| `"vehicle"` | Points are already in the vehicle frame. | Used as is. |
| `"<sensor>"` | Points are in the sensor's own frame. | A `foxglove.FrameTransform` with `parent_frame_id: "vehicle"` and `child_frame_id: "<sensor>"` MUST be present. Positions are transformed once: `p_vehicle = R · p_sensor + t`. |

A sensor-frame cloud **without** a transform is rejected, never used as is: a transform applied
twice, or not at all, produces a plausible-looking and entirely wrong cloud.

Positions, vectors and scalars transform differently, and mixing them up is silent:

* **positions** (`x`, `y`, `z`) rotate, then translate;
* **velocities and directions** (e.g. radar `vx`, `vy`) **rotate only**. Only positions are
  transformed at ingest, so publish per-point vectors **already in the `vehicle` frame**;
* **scalars** (`intensity`, `ring`, `rcs`) are frame-invariant and pass through.

A sensor with no elevation channel (most automotive radar) shows a constant z equal to its mounting
height after transformation. That is correct.

## Several LiDARs

Publish either **one** already-merged LiDAR in the `vehicle` frame, **or** one channel per sensor
in its own frame with its transform. Several `lidar` channels are merged **by frame index**: frame
*i* of every sensor forms one cloud. Per-sensor channels MUST therefore be index-aligned: the same
number of frames, frame *i* of each covering the same sweep. Unequal counts are merged up to the
shortest channel, with a warning.

## LiDAR-only recordings

A recording with no camera is valid when its **first declared `lidar`** carries `x`, `y`, `z` **and
`ring`**: the LiDAR is rendered as a range-image video (one row per beam), which becomes the
recording's video. A recording with no camera and a LiDAR without `ring` is rejected, with an error
naming the missing field. See [A recording must have a video](/mcap-spec/file-structure#a-recording-must-have-a-video).


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