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

# OrbbecCamera

> An Orbbec depth camera providing RGB, depth, and stereo infrared images

```python theme={null}
from grid_robot_api.sensor.camera import OrbbecCamera
```

An Orbbec depth camera providing RGB, depth, and stereo infrared images.

Framesets are captured continuously in a background thread, so the image
getters return the most recently captured frames without blocking on the
device.

Suits the Gemini stereo-vision family (e.g. Gemini 305 / 330 series), whose
depth is produced from a left + right infrared (IR) sensor pair alongside a
separate RGB color sensor. The left and right IR images form the stereo pair
(`getStereoImagePair`). By default depth is returned in its native geometry,
which matches the left IR image; pass `align_depth_to_color=True` to instead
align depth onto the color frame's pixel grid for RGB-D fusion.

Not every Orbbec model exposes both IR sensors. When the connected device
lacks the left/right IR streams they are skipped at startup, and requesting
them later raises `RuntimeError`.

When the config declares an `identity` block with a `serial_number`,
that serial *selects* the camera in place of `device_index`: bring-up
opens exactly that unit and fails loudly when it is not connected, rather
than streaming from a different one. USB enumeration order (and hence
`device_index`) is not stable across reboots and replugs, so a rig with
several Orbbec cameras should pin each config node's serial.

## Constructor

```python Signature theme={null}
OrbbecCamera(
    resolution: Optional[Tuple[int, int]] = None,
    fps: Optional[int] = None,
    *,
    enable_depth: bool = True,
    enable_ir: bool = True,
    align_depth_to_color: bool = False,
    device_index: int = 0,
)
```

## Properties

<ResponseField name="arms" type="Dict[str, 'Component']">
  Child components that are arms (derived, read-only).

  Filtered from `subcomponents` by `Arm` role; the returned dict
  is a fresh snapshot.
</ResponseField>

<ResponseField name="end_effector" type="Optional['Component']">
  The attached end effector, or `None` (derived, read-only).

  Returns the child named `"end_effector"` when it is an
  `EndEffector`, otherwise the first `EndEffector` child in
  insertion order. Attach one via
  `addSubcomponent("end_effector", ...)`.
</ResponseField>

<ResponseField name="expected_identity" type="ComponentIdentity">
  Hardware identity this component's config declared it should have.

  Set from the `identity` block of the config envelope by
  `from_config`, and empty for a component built any other
  way. Recording it acquires no hardware, and what it *means*
  splits by transport. For network-addressed hardware (an arm at a
  configured IP) it is a declaration to verify against: whether a
  mismatch warns or fails belongs to the caller that owns that
  policy, not to the driver. For bus-enumerated hardware (USB
  cameras), whose enumeration order is not a stable address, the
  declared `serial_number` is instead a *selector*: bring-up
  opens exactly that unit and fails loudly when it is absent,
  never falling back to a different device.

  Compare it against `getIdentity`, which is what the
  hardware reports.
</ResponseField>

<ResponseField name="sensors" type="Dict[str, 'Component']">
  Child components that are sensors (derived, read-only).

  Filtered from `subcomponents` by `Sensor` role; attach sensors
  via `addSensor`/`addSubcomponent` rather than writing to the
  returned dict, which is a fresh snapshot.
</ResponseField>

<ResponseField name="subcomponents" type="Dict[str, 'Component']">
  Mapping of child-component name to child component.

  The returned dict is the live backing store: mutating it mutates
  the component tree. Prefer `addSubcomponent` for inserts.
</ResponseField>

## Methods

### `addSubcomponent()`

```python Signature theme={null}
addSubcomponent(name: str, component: 'Component') -> None
```

Attach a child component under a name.

An existing child with the same name is replaced, matching dict
assignment semantics (`addSensor` has always overwritten).

<ParamField body="name" required>
  Non-empty name for the child, unique within this component's `subcomponents`. Names are addressable as `getState` path chunks, so the path syntax characters `/ * ? [ ]` are not allowed.
</ParamField>

<ParamField body="component" required>
  The child component to attach.
</ParamField>

**Raises:**

ValueError: If `name` is not a non-empty string, or contains
a path syntax character.

### `builtin_subcomponents()`

```python Signature theme={null}
builtin_subcomponents() -> Dict[str, Dict[str, Any]]
```

Declare the children this class always ships with (customization point).

Hardware that is part of the component itself — a humanoid's head
camera, an arm's wrist camera, a base built into a mobile
manipulator — is declared here rather than constructed in
`__init__`, so one description drives both the runtime tree and
the static config tooling. The declaration is **pure data** in the
same shape an authored `subcomponents` entry uses:
`&#123;"&lt;child name>": &#123;"type": "&lt;registry name>", "args": &#123;...&#125;,
"subcomponents": &#123;...&#125;, "enabled": True&#125;&#125;`, where `type` names a
robot- or sensor-registry key, `args` are that class's config
args, `subcomponents` declares grandchildren by the same rules,
and `enabled` (default `True`) can declare a child off by
default.

An override must be callable on the bare class, without
instantiation and without importing a driver or touching hardware:
the setup-time stream resolver reads it off the registry class to
resolve a robot's camera streams on a machine that has none of the
hardware attached.

The base class materializes the declaration on every construction
path — a direct constructor call attaches the children once the
constructor returns, and `from_config` attaches them merged
with the authored `subcomponents` block (authored keys win, so an
entry with the same name retunes `args` or drops the child with
`enabled: false` — and may omit `type`, inheriting the one
declared here — while an entry naming a different `type` is an
error). Attached builtin children are ordinary subcomponents:
they appear in `serialize`, in `getState`, and in the
lifecycle walks, and a builtin `Camera` child is skipped when the
tree is built with `enable_cameras=False`.

**Returns:**

Mapping of child name to its config declaration. The default is
empty — a class with no builtin children.

### `capture_specs()`

```python Signature theme={null}
capture_specs() -> Tuple[StreamSpec, ...]
```

Declare the V4L2 capture of a configured Orbbec camera.

**Returns:**

One V4L2 stream on the color node. The config supplies the
`device` path edge streaming opens, or a declared
`identity.serial_number` resolves it through target-local
discovery — and only to a node discovery confirms is the color
endpoint, since an Orbbec's depth and infrared nodes are capture
endpoints of their own.

### `cleanup()`

```python Signature theme={null}
cleanup() -> None
```

Stop the capture thread and pipeline, and release the camera.

### `configHash()`

```python Signature theme={null}
configHash(
    *,
    include_types: Optional[Tuple[type, ...]] = None,
    include_identity: bool = False,
    exclude_fields: AbstractSet[str] = frozenset(),
    type_prefix: bool = False,
) -> str
```

Hash this component's serialized config subtree.

A SHA-256 hex digest over the `serialize` output — the
config topology and args, not object identity and not
`getState` telemetry. Deterministic: the same tree with
the same options always produces the same digest, regardless of
dict insertion order. Touches no hardware; with
`include_identity` it hashes whatever identity is already
recorded (declared or previously read), triggering no new read.

Two hashes are comparable only when computed with the same
options; equality across differently-parameterized calls is
meaningless. Note that with `include_identity` the digest
changes as identity fields become known — a component reports
fields monotonically (some only once started), so hash at a
consistent point in the lifecycle when comparing across runs.

<ParamField body="include_types">
  When given, only tree nodes whose component is an instance of one of these classes are hashed; a non-matching node is pruned together with its entire subtree. `None` keeps every node.
</ParamField>

<ParamField body="include_identity" default="False">
  When True, each kept node's `identity` block (as `serialize` records it — hardware-read overlaid on declared) is included in the hash. When False the hash covers configuration only.
</ParamField>

<ParamField body="exclude_fields" default="frozenset()">
  Config `args` keys dropped from every kept node before hashing (e.g. an IP address that changes across networks).
</ParamField>

<ParamField body="type_prefix" default="False">
  When True, prepend this component's registry type and an underscore to the digest (e.g. `"go2_&lt;digest>"`, `"rig_&lt;digest>"` for a composite root) for a human-readable, greppable label. The type is already part of the hashed payload, so this only labels the same digest — the suffix after `_` is identical to the unprefixed call with the same options.
</ParamField>

**Returns:**

A 64-character lowercase SHA-256 hex digest, or
`"&lt;type>_&lt;digest>"` when `type_prefix` is True.

**Raises:**

RuntimeError: If this component was not constructed from a
config, so it cannot be serialized.
ValueError: If this component itself matches none of
`include_types` — the hash would cover an empty tree
and compare equal for unrelated components.

### `config_schema()`

```python Signature theme={null}
config_schema() -> dict
```

### `from_config()`

```python Signature theme={null}
from_config(config: Dict[str, Any], *, enable_cameras: bool = True) -> 'Component'
```

Construct this component and its config-declared subtree.

Recursive walk owned by the base class, the inverse of
`serialize`: the config envelope
(`&#123;"type": ..., "args": &#123;...&#125;, "subcomponents": &#123;...&#125;&#125;`) is
validated, this component is constructed from `args` via
`_from_config_args`, and each declared child is resolved
through the robot/sensor registries, constructed by its own
class's `from_config`, and attached. `type` may be omitted
when calling on a concrete class — it is then taken from the
class's registry entry.

The declared children are this class's
`builtin_subcomponents` overlaid by the config's
`subcomponents` block: builtin children first in declaration
order, then authored-only children. An authored entry whose name
matches a builtin one overlays it — its `args` merge over the
builtin `args` and `enabled: false` drops the child — rather
than colliding with it, and it may omit `type` entirely
(`&#123;"chassis_rear": &#123;"enabled": false&#125;&#125;` is a complete entry),
inheriting the builtin's; an entry naming a *different* `type`
than the builtin declaration is an error, as is an entry that
omits `type` while overlaying no builtin child. Any entry with
`enabled: false` is skipped, builtin or authored.

A node's optional `identity` block is recorded on the built
component as `expected_identity` — the hardware this entry
is *declared* to be. It is never checked against the device
here: construction acquires no hardware, and deciding what a
mismatch means belongs to the caller.

Component configs nested inside `args` (an arm's
`end_effector`, for example) are the constructor's business and
are built in `_from_config_args`; the recursion here covers
the `subcomponents` block only. The returned tree is
constructed but **not** started — use `make_robot`/
`make_sensor`, a `with` block, or an explicit
`start` call to bring it online.

<ParamField body="config" required>
  Config envelope for this component. Extra keys are rejected; `args` and `subcomponents` may be omitted.
</ParamField>

<ParamField body="enable_cameras" default="True">
  When True, camera components declared in the config are constructed and attached for local `getImage` use. When False, config-declared cameras are skipped at every depth because deploy streaming owns those devices; the flag is also passed to `_from_config_args` so a driver can skip cameras it builds itself.
</ParamField>

**Returns:**

The constructed component with its declared subcomponents
attached and its config recorded — including, per builtin
child, whether the authored config moved it off its
declaration's `enabled` state — so
`type(c).from_config(c.serialize())` rebuilds an equivalent
tree.

**Raises:**

ValueError: If `config` is not a mapping, a declared
subcomponent type is in neither registry, or an authored
entry overlays a builtin child with a different `type`.
ConfigValidationError: If the config tree does not match the
envelope shape; the message carries full config-tree
paths.

### `getAllImages()`

```python Signature theme={null}
getAllImages() -> Tuple[Image, Image]
```

Return the RGB and depth images from a single frameset.

When the camera was constructed with `align_depth_to_color=True` the
depth image shares the color frame's pixel grid; otherwise depth is in its
native geometry.

**Returns:**

A tuple `(rgb_image, depth_image)` where `rgb_image` is HxWx3 uint8
and `depth_image` is HxW uint16 in raw device units.

**Raises:**

RuntimeError: If the depth stream is disabled, or either frame is
missing from the frameset.

### `getCameraExtrinsics()`

```python Signature theme={null}
getCameraExtrinsics() -> Optional[np.ndarray]
```

Get the extrinsic parameters for the camera.

Returns the camera's extrinsic transformation matrix — the camera pose
relative to a reference frame. The recorded calibration takes
precedence: when a calibration run has stored a transform on this
camera's config, that 4x4 is returned (read off the config, not
hand-pasted by a consumer), so a consumer can ask a freshly connected
robot where its camera is. It overrides any transform set via
`setCameraExtrinsics`, and a warning is logged when both are
present. Otherwise the value set via `setCameraExtrinsics` is
returned, or `None` when neither is available.

Calibration is fixed-camera only, so a recorded transform is always
camera-with-respect-to-base — use it as `camera_wrt_base` directly.
Reads no hardware, so it is callable before `start()`.

**Returns:**

The (4, 4) homogeneous transform as a float array, or `None` when
the camera has neither a recorded calibration nor a set extrinsic.

### `getCameraIntrinsics()`

```python Signature theme={null}
getCameraIntrinsics() -> Optional[Tuple[float, float, float, float]]
```

Get the intrinsic parameters for the camera.

Retrieves the camera's intrinsic calibration parameters used for
image projection and rectification.

**Returns:**

Optional\[tuple\[float, float, float, float]]: Camera intrinsics as
(fx, fy, cx, cy) or None if not configured

### `getCameraMatrix()`

```python Signature theme={null}
getCameraMatrix(image_type: Optional[str] = None) -> np.ndarray
```

Return the 3x3 camera intrinsic matrix K for a stream.

K has the form `[[fx, 0, cx], [0, fy, cy], [0, 0, 1]]`.

<ParamField body="image_type">
  `"color"` / `"rgb"` / None for the color camera (default), or `"depth"` for the depth camera.
</ParamField>

**Returns:**

The 3x3 intrinsic matrix.

**Raises:**

ValueError: If `image_type` is not a recognized value.
RuntimeError: If intrinsics could not be read from the device.

### `getCameraSettings()`

```python Signature theme={null}
getCameraSettings() -> CameraSettings
```

Get the camera settings.

**Returns:**

CameraSettings: Current camera configuration and parameters

### `getData()`

```python Signature theme={null}
getData() -> Image
```

Get the image from the camera.

Implements the Sensor interface by delegating to getImage().

**Returns:**

Image: The captured image from the camera

### `getDistortionCoefficients()`

```python Signature theme={null}
getDistortionCoefficients(image_type: Optional[str] = None) -> np.ndarray
```

Return the radial/tangential distortion coefficients for a stream.

<ParamField body="image_type">
  `"color"` / `"rgb"` / None for the color camera (default), or `"depth"` for the depth camera.
</ParamField>

**Returns:**

The distortion coefficients as `[k1, k2, p1, p2, k3, k4, k5, k6]`.

**Raises:**

ValueError: If `image_type` is not a recognized value.
RuntimeError: If distortion data could not be read from the device.

### `getExtrinsics()`

```python Signature theme={null}
getExtrinsics(source_image_frame: str, target_image_frame: str) -> np.ndarray
```

Return the transform of the target frame with respect to the source frame.

The returned 4x4 homogeneous matrix transforms a 3D point (in meters) from
the source sensor frame into the target sensor frame.

<ParamField body="source_image_frame" required>
  Source sensor frame. One of `"color"` / `"rgb"` or `"depth"`.
</ParamField>

<ParamField body="target_image_frame" required>
  Target sensor frame. One of `"color"` / `"rgb"` or `"depth"`.
</ParamField>

**Returns:**

The 4x4 homogeneous transformation matrix.

**Raises:**

ValueError: If either frame name is not a recognized value.
RuntimeError: If extrinsics could not be read from the device.

### `getFrameCount()`

```python Signature theme={null}
getFrameCount() -> int
```

Return the total number of framesets successfully captured so far.

The counter is incremented by the background capture thread on every
successful capture and is safe to call from any thread.

**Returns:**

Total framesets captured since the camera was opened.

### `getIdentity()`

```python Signature theme={null}
getIdentity() -> ComponentIdentity
```

Get this component's own hardware identity (serial, model, version, MAC).

Local, not recursive: it answers for this component alone. The
tree-wide view is `serialize`, which carries each node's
identity alongside its topology; identity is deliberately absent
from `getState`, which is live telemetry rather than
constants burned into a device.

**Never raises and never blocks the caller's real work.** A
driver read that fails — controller unreachable, dashboard
refused, SDK error — is logged and degrades to whatever fields
are already known, down to an empty mapping. Absence means
*unknown*, never *different*: see `ComponentIdentity` for what
consumers may conclude from a missing field.

**Callable in every lifecycle state** — before `start()`,
while started, after `stop()`, and after `shutdown()`. Where
the transport allows it (the UR dashboard server needs only the
controller IP, ZED serials are enumerable from sysfs before the
device is opened) a constructed-but-unstarted component already
answers, which is what lets a caller de-duplicate a robot before
bringing it up. Drivers that can only read identity from a live
device handle return nothing until then, so a call after
`start()` may carry *more* fields than one before it.

**Monotonic per component.** Every field ever read successfully
is cached, so repeated calls only ever gain fields — a later
failure never drops one that was already known. A field whose
value is re-read is updated to the fresh value.

**The field set is closed.** Only the fields `ComponentIdentity`
declares are accepted; anything else a driver reports is logged
and dropped, exactly like a non-string value, so what a component
reports always round-trips through the config envelope.

**Returns:**

A fresh `ComponentIdentity` mapping holding only the fields
known for this component; `&#123;&#125;` when nothing is known.
Mutating the result does not affect the component.

### `getImage()`

```python Signature theme={null}
getImage(
    *,
    image_type: Optional[str] = None,
    compressed: bool = False,
    resolution: Optional[Tuple[int, int]] = None,
    keep_aspect_ratio: bool = False,
) -> Image
```

Return a single image of the requested type from the latest frameset.

<ParamField body="image_type">
  One of `"rgb"` (default), `"depth"`, `"depth_meters"`, `"left"` / `"infrared"` / `"ir"`, or `"right"`. `"rgb"` returns an HxWx3 uint8 RGB image. `"depth"` returns an HxW uint16 image in raw device units. `"depth_meters"` returns an HxW float32 image in meters. `"left"`, `"infrared"` and `"ir"` return the left IR image; `"right"` returns the right IR image — each a single-channel HxW image (uint16 for a Y16 stream, else uint8).
</ParamField>

<ParamField body="compressed" default="False">
  When True, JPEG-encode the RGB frame and return it as an `Image` with `encoding_format="jpeg"` (\~15-25x smaller on the wire); call `Image.decode()` on the result to get an RGB ndarray. Compression is supported only for `image_type="rgb"`.
</ParamField>

<ParamField body="resolution">
  Target `(width, height)` in pixels to resize the image to before any JPEG encoding — bilinear for RGB, nearest-neighbor for depth/IR so no-data zeros are never blended into fabricated values. None (default) keeps the native resolution. Requires OpenCV. Resized pixels no longer correspond to `getCameraMatrix()`, which keeps returning native-resolution intrinsics — scale fx/fy/cx/cy by the resize factors before deprojecting.
</ParamField>

<ParamField body="keep_aspect_ratio" default="False">
  Used only when `resolution` is given. When True, preserve the aspect ratio and center the scaled image on a zero background (black for RGB, no-data for depth/IR); when False (default), stretch to exactly `resolution`.
</ParamField>

**Returns:**

The requested image, or JPEG bytes tagged `encoding_format="jpeg"`
when `compressed` is True for RGB.

**Raises:**

ValueError: If `image_type` is not a recognized value.
RuntimeError: If depth/IR is requested but the corresponding stream is
disabled or unavailable, the frame is missing from the frameset,
OpenCV is unavailable for JPEG encoding, JPEG encoding fails, or
the camera stopped delivering frames.

### `getRobotId()`

```python Signature theme={null}
getRobotId() -> str
```

Stable, readable identifier for a robot or rig, for a database key.

A fixed-options digest over `serialize`, so a caller storing
robots by ID does not have to choose them: `Robot`/`Rig` nodes
only, `args` dropped entirely, each node's
`serial_number`/`mac_address` folded in, and the root's registry
type as a prefix. The result is `"&lt;type>_&lt;digest>"` (e.g.
`"go2_1a2b3c…"`, `"rig_…"` on a composite root): the type prefix
makes it greppable, and folding in hardware identity distinguishes
two units built from the same config — so two physically different
robots get different IDs, which a config-only hash would not give.
Distinct from `getIdentity`, which reports the physical unit's
manufacturer identity and hashes nothing.

Four properties to hold when using it as a key:

* **Robots and rigs only.** Only `Robot` and `Rig` nodes are
  hashed; sensors (and any other non-robot subcomponents, e.g.
  teleop devices) are pruned, so swapping or adding a camera does
  not change a robot's ID. Called on a component that is neither a
  `Robot` nor a `Rig` — e.g. a bare sensor, an end effector, a
  teleop device — it raises, because such a component has no robot
  identity to key on.
* **Structure and identity, never parameters.** `args` are
  dropped at every node, so editing a parameter — an IP address, a
  force limit, a speed scale — leaves the ID unchanged: it
  identifies the unit, not its tuning. What survives is the tree
  shape (registry types and subcomponent names among the kept
  nodes) plus the identity fields. Anything configured as an
  `arg` rather than a subcomponent — an end effector, for
  instance — is invisible to the ID; use `configHash` when
  the configuration itself is what needs fingerprinting.
* **Distinctness needs identity.** Uniqueness per physical unit
  comes from the `serial_number`/`mac_address` a component
  reports; components that report no identity fall back to
  structure-only distinctness, so two units of the same type with
  no serial share an ID. It is derived, not registered — a hash,
  not a UUID. That fallback is not only a property of serial-less
  *models*: a unit whose identity read failed this run (an
  unreachable UR dashboard) reports nothing, so it silently takes
  the structure-only ID and does not match the ID it produces on a
  run where the read succeeded. Where the key must be unique, check
  `getIdentity` for a `serial_number`/`mac_address` first.
  A rig has no identity of its own, so a rig's ID is only as
  distinct as the robots inside it — a rig of nothing but sensors
  hashes to one shared constant.
* **Only identifying identity fields count.** `serial_number` and
  `mac_address` say *which* unit this is; `model` and
  `controller_version` describe it, and are left out so a firmware
  upgrade does not rekey the arm. An ID still moves when a component
  starts reporting a field it did not report before, so IDs
  persisted across a driver gaining a new identity source do not
  match the ones computed after it.

Touches no hardware and triggers no identity read; it hashes
whatever identity is already recorded, so call it at a consistent
lifecycle point when comparing across runs.

**Returns:**

`"&lt;type>_&lt;64-char lowercase SHA-256 hex digest>"`.

**Raises:**

RuntimeError: If this component was not constructed from a
config, so it cannot be serialized.
ValueError: If this component is neither a `Robot` nor a
`Rig`, so it has no robot identity to key on.

### `getState()`

```python Signature theme={null}
getState(keys: Optional[Union[str, List[str]]] = None) -> Dict[str, Any]
```

Get a live state snapshot of this component's tree.

Recursive walk owned by the base class, following the lifecycle
template: each class contributes only the protected local hook
`_local_state`, and the walk composes the per-component
results into one nested mapping. Subclasses must not replace
this method — per-component state belongs in `_local_state`.

With no `keys`, returns this component's local state merged
with one nested node per subcomponent name — the tree structure
is the nesting itself, and the names mirror `subcomponents`
(and therefore `serialize()` and edge introspection): a
two-arm rig returns `&#123;"left_arm": &#123;"joint_positions": ...&#125;,
"right_arm": &#123;...&#125;&#125;`, and a leaf component returns its local
state alone. A component whose state could not be read (dead
telemetry, not yet started, already shut down) carries an
`"error"` entry — `&#123;"error": "&lt;ExceptionType>: &lt;message>"&#125;`,
with its children still nested alongside — instead of its state
keys. Errors are contained per node: one failing component never
loses the rest of the snapshot, and the walk still descends into
the failing component's children. `"error"` is reserved for
that purpose, and local state keys must not collide with
subcomponent names; a hook violating either is reported as that
node's error.

With `keys`, returns a flat mapping with one entry per match.
Each key is a `/`-separated path through the component tree:
every segment names a subcomponent, and the final segment may
instead name an entry in that component's local state. Because
structure is the nesting, a literal path is the same as plain
indexing — `getState(["left_arm/joint_positions"])` returns
the value of `getState()["left_arm"]["joint_positions"]` — and
a path ending on a subcomponent yields that component's full
nested node. Segments may use shell-style wildcards over
subcomponent names — `*` matches within one chunk and `**`
matches any chain of chunks — so `"*_arm/joint_positions"`
fans out to one entry per arm, keyed by the concrete matched
path. Wildcards never match state entries; only a literal final
segment does. A key that matches nothing raises, so a typo is
loud rather than silently absent. A matched component whose
state read failed yields `&#123;"error": "..."&#125;` at its path.

Keyed reads are lazy: a hook runs only where a key lands — a
literal path executes the final component's hook alone (not the
nodes traversed on the way), a glob executes only the matched
nodes, and each component's hook runs at most once per call
however many keys reach it. Components with a selective-read
hook (`_local_state_entries` — every `Robot` with
registered state getters) go further and execute only the getters a key names, so
a `**` glob probes their registries without touching
hardware. The caller pays only for the state actually
requested; `**` and the no-`keys` form visit the whole tree
because that is the request.

State is cheap by convention: local state carries kinematics,
status, and health — never bulk sensor payloads (camera frames,
point clouds, scans), which stay in their dedicated methods
(`getImage`, `getPointCloud`, ...). Callable on a stopped
tree — reading state after a soft e-stop is when it matters most
— and on a partially started one, where unstarted components
report a per-node error instead of failing the call.

<ParamField body="keys">
  State paths to read, or None for the full nested snapshot. A single string is shorthand for a one-element list.
</ParamField>

**Returns:**

The nested state snapshot when `keys` is None, otherwise a
flat mapping of matched path to state value or nested node.

**Raises:**

TypeError: If `keys` is neither None, a string, nor a
list/tuple of strings.
ValueError: If a key is empty, has an empty path segment, or
matches no subcomponent path or state entry.

### `getStereoImagePair()`

```python Signature theme={null}
getStereoImagePair() -> Tuple[Image, Image]
```

Return the left and right infrared images from a single frameset.

The two images come from the camera's stereo IR sensor pair and are
captured at the same instant, suitable for stereo correspondence.

**Returns:**

A tuple `(left_ir_image, right_ir_image)`, each a single-channel HxW
image (uint16 for a Y16 stream, else uint8).

**Raises:**

RuntimeError: If either infrared stream is unavailable or disabled, or
its frame is missing from the frameset.

### `recordCalibration()`

```python Signature theme={null}
recordCalibration(calibration: dict) -> None
```

Install a recorded extrinsic calibration on this camera in place.

Swaps the recorded calibration block on the live camera object, so the
current session serves the new extrinsic without a rebuild. A falsy
value clears the recorded calibration. A non-empty block is validated
against the calibration schema before being installed and stored in the
same normalized form `from_config` records, so a malformed remote call
is rejected at the boundary rather than stored raw.

<ParamField body="calibration" required>
  The recorded calibration block (transform plus provenance), as a calibration run produces it. A falsy value clears any recorded calibration.
</ParamField>

**Raises:**

ConfigValidationError: If a non-empty block does not satisfy the
calibration schema — e.g. a `status="calibrated"` block
missing its transform, a non-4x4 transform, or an unknown field.

### `reloadCalibration()`

```python Signature theme={null}
reloadCalibration() -> dict
```

Re-read this camera's calibration from the robot's config and install it.

Lets a running session pick up a recalibration in place, without
reconnecting. Available on a camera built from a config-file source
(see `grid_robot_api.config.record_calibration_sources`); a
co-located caller can pass the block to `recordCalibration`
directly instead.

**Returns:**

The calibration block that was installed, or `&#123;&#125;` when the config
names none for this camera.

**Raises:**

RuntimeError: If this camera was not built from a config-file source.

### `serialize()`

```python Signature theme={null}
serialize() -> Dict[str, Any]
```

Serialize this component tree back to its config envelope.

The exact inverse of `from_config`, producing the recursive
config schema it consumes:
`&#123;"type": ..., "args": &#123;...&#125;, "subcomponents": &#123;name: &lt;node>&#125;&#125;`,
minimized via `model_dump(exclude_defaults=True)` so empty
`args`/`subcomponents` are omitted. Only config-declared
children (those constructed through `from_config`) are
emitted; parts a driver constructs internally are implied by the
parent's `args` and reappear on reconstruction.

A child a class ships with (`builtin_subcomponents`) is
normally implied by the parent's `type` and needs no
`enabled` key. It gets an explicit one exactly when its
attached state diverges from what the bare class declaration
would produce, because reconstruction would otherwise flip it:

* declared enabled but switched off by the config
  (`enabled: false`) — emitted as a bare
  `&#123;"enabled": false&#125;` stub under `subcomponents`, the
  type-omitted overlay form that inherits the declaration's
  `type`. The stub exists only in this output; it is never a
  phantom entry in `subcomponents`, `getState`, or
  introspection.
* declared `enabled: false` but switched on by the config
  (`enabled: true`) — its normal serialized entry additionally
  carries `"enabled": true`, which an omitted key would not
  preserve (an authored entry that omits `enabled` keeps the
  declaration's value).

A child whose state matches its declaration is emitted exactly as
before, with no `enabled` key and no stub.

The asymmetry to
know: children skipped because the tree was built with
`enable_cameras=False` get **no** stub. That flag is
factory-only camera-ownership semantics — deploy streaming owns
those devices for this process, the config never said to drop
them — so the serialized config keeps describing the robot's
cameras, and passing the flag again is what skips them again.

A node also carries an `identity` block —
`&#123;"serial_number": ..., "model": ..., "controller_version":
..., "mac_address": ...&#125;`, each key present only when known —
whenever this component has a hardware identity to record, so the serialized
tree says which physical units it was built from and not just
which types. It is what `getIdentity` reports overlaid on
the identity the config declared (`expected_identity`), so
hardware that has identified itself wins over the declaration
while an unread declaration survives the round-trip. Omitted
entirely when nothing is known — never an empty block.

**Returns:**

The minimal config mapping such that
`make_robot(component.serialize())` reconstructs an
equivalent component tree.

**Raises:**

RuntimeError: If this component was not constructed from a
config, so no config type is recorded.
ConfigValidationError: If the recorded config does not fit
the envelope (e.g. a recorded arg key is not a string).

### `setCameraExtrinsics()`

```python Signature theme={null}
setCameraExtrinsics(transform: np.ndarray) -> None
```

Set the extrinsic parameters for the camera.

Configures the camera's extrinsic transformation matrix representing
the camera pose relative to a reference frame.

<ParamField body="transform" required>
  4x4 homogeneous transformation matrix
</ParamField>

**Raises:**

ValueError: If transform is not a 4x4 matrix

### `setCameraIntrinsics()`

```python Signature theme={null}
setCameraIntrinsics(fx: float, fy: float, cx: float, cy: float) -> None
```

Set the intrinsic parameters for the camera.

Configures the camera's intrinsic calibration parameters for
accurate image projection and rectification.

<ParamField body="fx" required>
  Focal length in x direction (pixels)
</ParamField>

<ParamField body="fy" required>
  Focal length in y direction (pixels)
</ParamField>

<ParamField body="cx" required>
  Principal point x coordinate (pixels)
</ParamField>

<ParamField body="cy" required>
  Principal point y coordinate (pixels)
</ParamField>

### `setCameraSettings()`

```python Signature theme={null}
setCameraSettings(settings: CameraSettings) -> None
```

Set the camera settings.

<ParamField body="settings" type="CameraSettings" required>
  New camera configuration to apply
</ParamField>

### `setup_shutdown_handlers()`

```python Signature theme={null}
setup_shutdown_handlers() -> None
```

Register the process-wide atexit and signal handlers for safe teardown.

Installs an `atexit` hook and SIGINT/SIGTERM (and SIGHUP where
available) handlers that shut down every enrolled bring-up entry
point, newest first, so hardware is released on normal
termination and on Ctrl+C. Registration normally happens
automatically the first time a component is enrolled from the
main thread; call this explicitly from the main thread when
components are only ever brought up from background threads,
where Python forbids installing signal handlers — enrollment
there defers the atexit hook together with the signal handlers.

### `shutdown()`

```python Signature theme={null}
shutdown() -> None
```

Shut down this component's tree, halting motion and releasing resources.

Two phases, both owned by the base class. First a tree-wide
`stop`: motion is halted everywhere before anything is torn
down, so no part of the robot is still commandable while another
part is being released. Then the teardown walk — every
subcomponent is shut down first (children before their parent,
leaf-to-root; siblings in `subcomponents` insertion order),
then this component's own `_shutdown_self` hook runs.
Children-first ordering is a contract for shared resources: a
parent that owns a resource its children borrow (e.g. one ROS
node shared by both arms) releases it in its own hook, after all
children have shut down — a child must never release a resource
it does not own.

Idempotent: the first call latches, and every later call is a
no-op; the tree-wide stop also runs once, so a nested
`shutdown()` reached by the walk does not re-stop its subtree.
Best-effort: the stop phase and every hook failure are logged and
the walk continues, so this method never raises — it can run
safely from `__exit__`, signal handlers, and interpreter-exit
hooks. Blocks until every hook has returned; the component is
unusable afterwards.

Shutting down also drops the component from process-wide exit
cleanup (the enrollment made by the public `start`), so
the atexit/signal handlers only ever touch components that are
still live.

Subclasses must not replace this method — per-component cleanup
belongs in `_shutdown_self`. An override may only *extend*
the walk and must delegate to `super().shutdown()`; it must
never re-implement the recursion.

### `start()`

```python Signature theme={null}
start() -> None
```

Bring this component's tree online (connect, enable, arm).

Recursive walk owned by the base class and the mirror of
`shutdown`. Per node the walk runs `_start_self`,
then `_provision_children`, then descends into every
subcomponent (parents before their children, root-to-leaf;
siblings in `subcomponents` insertion order). That ordering is
the resource-handoff contract: a parent creates the runtime
resources its children borrow (an RTDE connection, a ROS node) in
its own bring-up hook and hands them to its children in
`_provision_children`, before any child hook runs.

**Warning: bring-up hooks move hardware.** `start()` is not a
passive connect. Driver hooks power on, enable, home, calibrate,
or stand a robot up — a Robotiq gripper strokes its jaws to
auto-calibrate, a WidowX homes, a Flexiv enables and homes its
gripper, a Go2 stands up. Restarting a tree re-runs those hooks,
so `stop()` then `start()` repeats that motion. Clear the
workspace before starting or restarting a physical robot; each
driver's `_start_self` documents the motion it commands.

Idempotent: the walk always descends the whole tree, but a
component whose bring-up hook already ran — and whose
`_still_live` health hook still reports it live — is
skipped, so calling `start()` twice re-runs no bring-up and
calling it after attaching a new subcomponent brings up only the
newcomer. A started component whose health hook reports its
resources dead (a power-cycled controller, a dropped connection)
gets its bring-up re-run, so `start()` alone is the recovery
call after such a fault.
`_provision_children` runs on every descent through a node
regardless of that mark, so a newcomer is provisioned by its
parent before its own hook runs. `stop` re-arms every
component it visits, so `stop()` then `start()` re-runs the
bring-up hooks (reconnect-after-fault) — while leaving the stopped
tree callable throughout. Blocks until every hook has returned.

Fail-fast: the first failing hook aborts the walk and
`ComponentStartError` is raised — a half-started component is
never handed back to the caller. Cleanup is scoped to this call:
the components this walk brought up for the first time are shut
down (best-effort) and the failing subtree is released, while
components that were already live before the call keep running
and the tree is not latched. On an initial bring-up nothing was
live, so that scope is the whole tree. Components this walk was
*restarting* (started before, re-armed by `stop`) are only
stopped, never shut down — a failed reconnect (e.g. a robot still
in an emergency stop) stays retryable with another `start()`.
A subtree that was already shut down is skipped with a warning
rather than resurrected.

Calling this method also enrolls the component in process-wide
exit cleanup: the atexit/signal handlers shut down every
component whose public `start()` was called (a tree root, or a
subtree driven directly) and that has not been shut down yet, so
hardware is released on normal termination and Ctrl+C once the
handlers are installed — automatic on the first main-thread
enrollment; a process that only brings components up from
background threads must call `setup_shutdown_handlers`
from its main thread. `shutdown` unenrolls.

Subclasses must not replace this method — per-component bring-up
belongs in `_start_self`. An override may only *extend* the
walk by delegating to `super().start()`; it must never
re-implement the recursion.

**Raises:**

RuntimeError: If this component has already been shut down;
`shutdown()` is final, so a new object is required.
ComponentStartError: If a bring-up hook raised. Whatever this
call brought up has been released by the time it
propagates, and the original failure is the exception's
`__cause__`.

### `stop()`

```python Signature theme={null}
stop() -> None
```

Halt all motion across this component's tree (soft e-stop).

Recursive walk owned by the base class: every subcomponent is
stopped first (children before their parent, leaf-to-root;
siblings in `subcomponents` insertion order), then this
component's own `_stop_self` hook runs. The walk is
best-effort — a failing component never prevents the rest of the
tree from being stopped; failures are collected and raised
together after the walk completes. Components (and their
subtrees) that have already been shut down are skipped. Safe to
call repeatedly: stopping an already-stopped tree re-runs the
hooks, which must tolerate that. Blocks until every hook has
returned.

Stops motion only; resources stay live and the component remains
usable — telemetry and every other public method keep working on a
stopped tree. Use `shutdown` to release resources. Stopping
re-arms each visited component, so a later `start` re-runs
the bring-up hooks — that is the stop-then-start restart path —
but it does not revoke callability: only `shutdown` does
that.

Subclasses must not replace this method — per-component stop
behavior belongs in `_stop_self`. An override may only
*extend* the walk (adding a driver-specific mode, as `UR5e` does
with its `immediate` escape hatch) and must delegate to
`super().stop()` for the normal path; it must never
re-implement the recursion.

**Raises:**

ComponentStopError: If one or more stop hooks raised. The
walk still visited every component; the exception carries
every `(component_path, exception)` pair.


## Related topics

- [Camera Calibration](/deployment/camera-calibration.md)
- [Robot](/python-api/robot-interface/robot.md)
- [Wheeled](/python-api/robot-interface/wheeled.md)
- [IsaacMobileBimanual](/python-api/isaac-robots/isaacmobilebimanual.md)
- [AerialDrone](/python-api/robot-interface/aerialdrone.md)
