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

# Body and parts

> What a recording is of, and the arms, hands, legs, head and torso of that body.

`recording.platform` describes the body the recording comes from: its class (`kind`), its model
(`embodiment`), its size, and in spec 1.1 its **parts**. Every embodiment uses the same small
vocabulary:

| Body | Parts |
| - | - |
| Franka Panda | one `arm` |
| Bimanual robot | two `arm`s, optionally two `hand`s |
| Excavator | one `arm` (the boom) with its own joint names |
| A person, egocentric footage | two `hand`s with keypoints |
| Humanoid | `arm`s, `hand`s, `leg`s, a `torso` and a `head` |
| Motion-capture subject | one `body` part with a whole-body skeleton |

## `platform`

```json theme={null}
"platform": {
  "kind": "humanoid",
  "embodiment": "my_humanoid_v2",
  "body_dimensions": {"length": 0.35, "width": 0.55, "height": 1.65},
  "parts": ["…"]
}
```

| Field | Type | Version | Meaning |
| - | - | - | - |
| `kind` | string | 1.1 | The body class. Omit it for a sensor-only recording. |
| `embodiment` | string | 1.0, 1.1 | The model of the body within its kind, e.g. `franka_panda`. Free text. |
| `body_dimensions` | object | 1.1 | The body's footprint: `{length, width, height}` in metres. |
| `ego_platform` | `"sedan"` \| `"truck"` | 1.0, 1.1 | The road-vehicle class. `vehicle` only. |
| `parts` | array | 1.1 | The body's parts. See [Parts](#parts). |
| `mount` | string | 1.0 | The 1.0 name for the body. See [Spec 1.0: `mount`](#spec-1-0-mount). |
| `vehicle_dimensions` | object | 1.0 | The 1.0 name for `body_dimensions`. |

### `kind`

| `kind` | Body | Moving base | Examples |
| - | - | - | - |
| `vehicle` | road vehicle | yes | AV, ADAS fleet, dashcam car |
| `heavy_vehicle` | construction or agricultural machine | yes | excavator, wheel loader, tractor |
| `manipulator` | fixed-base robot with one or more arms | no | Franka Panda, a bimanual arm station |
| `mobile_robot` | wheeled or legged robot, with or without arms | yes | AMR, mobile manipulator, quadruped |
| `humanoid` | whole-body humanoid robot | yes | a bipedal or wheeled humanoid |
| `human` | a person | yes | egocentric and head-worn footage |
| *(omitted)* | no body: a **sensor-only** recording | no | roadside or fixed site sensors |

`kind` is the body class only. What the body has (arms, hands, a gripper) comes from its parts, and
cameras that are not on the body (e.g. a third-person view, see
[Cameras](/mcap-spec/cameras#roles)) never change it.

Rules:

* `embodiment` MAY be declared on any kind, MUST be a non-empty string, and MUST be omitted on a
  sensor-only recording.
* `body_dimensions` MAY be declared on any kind with a moving base (every kind except
  `manipulator`). It takes exactly `length` (along x), `width` (along y) and `height` (along z), each
  greater than 0 and less than 1000 metres.
* `ego_platform` MAY be declared on a `vehicle` only.
* `parts` MUST NOT be declared on a sensor-only recording.

### Recognised embodiments

`embodiment` is free text, so any model name is accepted. These values have a defined joint
convention; when you declare one, publish its data in that convention:

| `embodiment` | Body | Publish |
| - | - | - |
| `franka_panda` | Franka Panda | one `arm` with 7 joint angles in Franka order (`j1` … `j7`) and the end-effector pose |
| `abc130k_bimanual` | two YAM arms | two `arm`s, 6 joint angles per side |
| `sharpa_wave_bimanual` | two Sharpa Wave hands (22 DoF) on two 7-DoF arms | parts `left_arm` / `right_arm` with an `end_pose` (the hand flange), and `left_hand` / `right_hand` with 22 `joints` named as in the Sharpa Wave URDF (`left_thumb_CMC_FE`, …); optionally a fingertip `force` per hand |

To have a new embodiment recognised, contact us with its URDF and joint order.

## Parts

Each part is one entry in `platform.parts`:

```json theme={null}
{"name": "left_arm", "type": "arm", "side": "left",
 "joints":   {"signal": "left_arm_joints", "names": ["j1", "j2", "j3", "j4", "j5", "j6", "j7"]},
 "end_pose": {"channel": "/left_arm/ee_pose"},
 "grip":     {"signal": "left_gripper_opening"}}
```

| Field | Type | Required | Rule |
| - | - | - | - |
| `name` | string | yes | Unique in the recording; lower\_snake\_case starting with a letter (`^[a-z][a-z0-9_]*$`). |
| `type` | string | yes | `arm`, `hand`, `leg`, `head`, `torso` or `body` (a whole-body skeleton). |
| `side` | `"left"` \| `"right"` | no | Omit for a part with no side (`head`, `torso`, a single arm). |
| `joints`, `end_pose`, `keypoints`, `grip`, `force` | object | at least one | The part's data. See [Slots](#slots). |

Name sided parts `<side>_<type>`, e.g. `left_hand`, `right_leg`. A hand's side is read from its
name, and a hand is paired with the arm of the same side (`left_hand` with `left_arm`).

### Slots

A part carries any of five data slots. Each slot has **exactly one form**, so there is never a
convention to negotiate.

| Slot | What it is | Points at | Form |
| - | - | - | - |
| `joints` | measured joint state | `{"signal", "names"}` | a declared `vector` signal in `rad` (or `m` when the joints are prismatic). `names` lists the joints in vector order and MUST have exactly `dim` unique, non-empty entries. |
| `end_pose` | pose of the part's tip: end effector, wrist, foot, head | `{"channel"}` | a `foxglove.PoseInFrame` channel: position in metres, orientation a unit quaternion. |
| `keypoints` | 3D points of the part, e.g. from hand or body tracking | `{"channel", "names", "parents"}` | a `nomadic.Skeleton` channel. See [Keypoints](#keypoints). |
| `grip` | gripper opening | `{"signal"}` | a declared `float` signal with unit `"1"`: 0 = closed … 1 = open. |
| `force` | wrench or contact forces | `{"signal"}` | a declared `vector` signal in one of three forms. See [Force](#force). |

Rules that apply to every slot:

* A slot's `signal` MUST name a signal declared in [`signals`](/mcap-spec/signals) by its `name`,
  with the type and unit the slot requires.
* One signal feeds **one** slot. Two parts MUST NOT share a signal.
* A slot's `channel` MUST exist in the file, carry the slot's message schema (protobuf), and not
  be declared anywhere else in the overlay (as a view, signal, point cloud or another part's slot).
* A part SHOULD have at least one of `joints`, `end_pose` or `keypoints`. These put the part on a
  timeline; a part with only `grip` or `force` is accepted with a warning.
* A part's `end_pose` is the part's pose, never the [body pose](/mcap-spec/frames-and-units#body-pose).

### Force

`force` points at a declared `vector` signal in one of three forms:

| Form | `dim` | `components` | `unit` |
| - | - | - | - |
| **One wrench** | 6 | exactly `["fx", "fy", "fz", "tx", "ty", "tz"]` | forces in N, torques in N·m |
| **One wrench per contact** | 6 × contacts | groups of six, `<contact>_fx`, `<contact>_fy`, `<contact>_fz`, `<contact>_tx`, `<contact>_ty`, `<contact>_tz`, each contact named once in lower\_snake\_case | forces in N, torques in N·m |
| **Contact forces** | one per contact | one name per contact, each different (omitted: contacts are numbered) | `"N"` |

For example, five fingertip wrenches are `dim: 30` with components `thumb_fx … thumb_tz, index_fx
… little_tz`; five fingertip normal forces are `dim: 5`, `unit: "N"`, components
`["thumb", "index", "middle", "ring", "little"]`.

## Keypoints

Publish 3D keypoints (hand or body tracking) as **one `nomadic.Skeleton` channel per part**, one
message per frame:

```proto theme={null}
message Skeleton {
  google.protobuf.Timestamp timestamp = 1;
  string frame_id = 2;                 // the frame the positions are in
  repeated Point3 positions = 3;       // one per joint, in the order `names` lists, metres
  repeated float confidence = 4;       // optional: empty, or one value in [0, 1] per joint
}
message Point3 { double x = 1; double y = 2; double z = 3; }
```

The topology is declared once, in the part's `keypoints` slot, not in every message:

```json theme={null}
{"name": "left_hand", "type": "hand", "side": "left",
 "keypoints": {"channel": "/hands/left/keypoints",
               "names":   ["wrist", "thumb_1", "thumb_2", "thumb_3", "thumb_tip", "index_1", "…"],
               "parents": [-1, 0, 1, 2, 3, 0, "…"]},
 "end_pose":  {"channel": "/hands/left/wrist"}}
```

* `names` lists the joints in message order: non-empty, unique strings.
* `parents[i]` is the index of joint `i`'s parent, or `-1` for a root. `parents` MUST have one entry
  per name and form a forest: every entry an integer in `-1 … len(names) − 1`, no joint its own
  parent, at least one root, no cycles. Several roots are allowed (e.g. detached fingertips).
* Every message MUST carry exactly one position per declared joint. Messages with the wrong count
  are skipped.
* `confidence` is either empty in every message or has one value per joint in `[0, 1]`.
* Positions are **metres**, **right-handed, z up**, in the frame named by `frame_id`. See
  [Frames for poses and keypoints](/mcap-spec/frames-and-units#frames-for-poses-and-keypoints).
* A hand SHOULD have at least 5 keypoints (a wrist and one joint per finger).
* The part's `end_pose`, when present, is the wrist pose with its orientation. Without one, the
  root joint is the wrist position.
* Publish a message **only when the part is tracked**. Do not publish placeholder zeros for a hand
  out of view; omit the message, and publish `confidence` if your tracker has it.

## Spec 1.0: `mount`

Spec 1.0 names the body with `mount`, which is required in 1.0, and has no `kind`, `parts` or
`body_dimensions`:

| 1.0 `mount` | 1.1 equivalent |
| - | - |
| `vehicle` | `kind: "vehicle"` |
| `heavy_equipment` | `kind: "heavy_vehicle"` |
| `robot_arm` | `kind: "manipulator"` |
| `roadside` | no `kind` (sensor-only) |

In 1.0, `embodiment` is allowed only on `robot_arm` and `heavy_equipment`, and `vehicle_dimensions`
only on `vehicle`. In a 1.1 overlay, `mount` is still accepted, with a warning, when it agrees with
`kind`; a `mount` that contradicts `kind` is rejected. A 1.0 overlay that uses a 1.1 field is
rejected with a rule naming the version it needs (e.g. `platform.parts.requires_spec_1_1`).


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