Metadata-Version: 2.4
Name: hex_flow_node_camera
Version: 0.0.1
Summary: Hex Flow Node for Camera (USB, Dummy, RealSense, Berxel)
Author-email: Dong Zhaorui <847235539@qq.com>
License-Expression: Apache-2.0
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: hex_flow_core<0.1.0,>=0.0.0
Requires-Dist: hex_driver_camera<0.2.0,>=0.1.0
Requires-Dist: hex_util_msg<0.1.0,>=0.0.0
Requires-Dist: hex_util_runtime<0.1.0,>=0.0.0
Requires-Dist: hex_util_robot<0.1.0,>=0.0.0
Provides-Extra: berxel
Requires-Dist: berxel_py_wrapper>=2.0.182; extra == "berxel"
Provides-Extra: realsense
Requires-Dist: pyrealsense2>=2.56.5.9235; extra == "realsense"
Provides-Extra: all
Requires-Dist: berxel_py_wrapper>=2.0.182; extra == "all"
Requires-Dist: pyrealsense2>=2.56.5.9235; extra == "all"
Dynamic: license-file

<h1 align="center">HEX FLOW NODE CAMERA</h1>

<p align="center">
    <a href="https://github.com/hexfellow/hex_flow_node_camera/stargazers">
        <img src="https://img.shields.io/github/stars/hexfellow/hex_flow_node_camera?style=flat-square&logo=github" />
    </a>
    <a href="https://github.com/hexfellow/hex_flow_node_camera/forks">
        <img src="https://img.shields.io/github/forks/hexfellow/hex_flow_node_camera?style=flat-square&logo=github" />
    </a>
    &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
    <a href="https://github.com/hexfellow/hex_flow_node_camera/issues">
        <img src="https://img.shields.io/github/issues/hexfellow/hex_flow_node_camera?style=flat-square&logo=github" />
    </a>
</p>

---

# 📖 Overview

## What is `hex_flow_node_camera`

`hex_flow_node_camera` provides hex-flow nodes for reading camera streams from USB (V4L2), RealSense, Berxel, and dummy (simulated) cameras. Each node captures frames from the hardware via `hex_driver_camera`, serializes them into FlatBuffer messages using `hex_util_robot`, and publishes at the configured rate (default 500 Hz) over Zenoh via `hex_flow_core`.

## What problem it solves

- **Hardware abstraction**: Abstracts different camera backends (V4L2 USB, Intel RealSense, Berxel ToF) into structured, rate-controlled image topics over Zenoh.
- **Unified interface**: All camera types share the same topic and message schema for color images, making it easy to swap cameras without changing downstream consumer code.
- **Depth support**: RealSense and Berxel nodes additionally publish depth streams alongside color, with automatic colormap conversion in test/viewer nodes.
- **Simulation friendly**: The dummy camera node generates test pattern frames without any hardware, enabling offline development and integration testing.
- **Flexible deployment**: Supports device auto-detection (serial number), topic remapping, and environment variable configuration — no code changes needed for different hardware setups.

## Target users

- Engineers integrating USB cameras (V4L2), Intel RealSense, or Berxel depth cameras into HEXFELLOW robot systems.
- Researchers requiring synchronized color + depth streams for perception and mapping.
- Developers building on the hex-flow framework who need a working camera input layer.
- Teams wanting a simulated camera source for testing vision pipelines offline.

## Nodes

| Node                            | Description                                  | Publishes         | Subscribes        |
| ------------------------------- | -------------------------------------------- | ----------------- | ----------------- |
| `hex-flow-cam-usb`              | USB V4L2 camera color stream reader          | `color`           | -                 |
| `hex-flow-cam-dummy`            | Simulated camera (test pattern generator)    | `color`           | -                 |
| `hex-flow-cam-realsense`        | Intel RealSense color & depth stream reader  | `color`, `depth`  | -                 |
| `hex-flow-cam-berxel`           | Berxel ToF color & depth stream reader       | `color`, `depth`  | -                 |
| `hex-flow-cam-test-usb`         | OpenCV viewer for USB camera                 | -                 | `color`           |
| `hex-flow-cam-test-dummy`       | OpenCV viewer for dummy camera               | -                 | `color`           |
| `hex-flow-cam-test-realsense`   | OpenCV viewer for RealSense (color + depth)  | -                 | `color`, `depth`  |
| `hex-flow-cam-test-berxel`      | OpenCV viewer for Berxel (color + depth)     | -                 | `color`, `depth`  |

## Prerequisites

USB camera nodes require read/write access to `/dev/video*`. Ensure your user is in the `video` group:

```bash
sudo usermod -aG video $USER
# Log out and back in for the change to take effect
```

For RealSense cameras, the `librealsense2` runtime must be installed. For Berxel cameras, the vendor SDK must be available (see `berxel_py_wrapper` documentation).

# 📦 Installation

## Requirements

- **Python** >= 3.10
- **OS**: Ubuntu (or other Linux)
- **Core dependencies**:
  - `hex_flow_core` >= 0.0.0, < 0.1.0
  - `hex_driver_camera` >= 0.1.0, < 0.2.0
  - `hex_util_msg` >= 0.0.0, < 0.1.0
  - `hex_util_runtime` >= 0.0.0, < 0.1.0
  - `hex_util_robot` >= 0.0.0, < 0.1.0

## Install `hex-flow-cli`

For Ubuntu or any Debian-based system, install Zenoh and hex-flow CLI:

```bash
sudo apt update
sudo apt install -y curl gpg

curl -L https://download.eclipse.org/zenoh/debian-repo/zenoh-public-key | sudo gpg --dearmor --yes --output /etc/apt/keyrings/zenoh-public-key.gpg
echo "deb [signed-by=/etc/apt/keyrings/zenoh-public-key.gpg] https://download.eclipse.org/zenoh/debian-repo/ /" | sudo tee -a /etc/apt/sources.list > /dev/null
sudo apt update
sudo apt install zenoh curl

curl -fsSL https://raw.githubusercontent.com/hexfellow/hex-flow/main/install.sh | sh
```

For other systems, please install `zenohd` yourself, then run the [install script](https://raw.githubusercontent.com/hexfellow/hex-flow/main/install.sh).

## Install `hex-flow-node-camera` from PyPI

```bash
# Base install (USB + Dummy cameras)
pip install hex_flow_node_camera

# With RealSense support
pip install hex_flow_node_camera[realsense]

# With Berxel support
pip install hex_flow_node_camera[berxel]

# With all camera backends
pip install hex_flow_node_camera[all]
```

## Install `hex-flow-node-camera` from source

We provide a [venv.sh](venv.sh) script to create a virtual environment with all dependencies installed. However, you need to install uv first. For uv installation, please refer to `uv` official [installation guide](https://docs.astral.sh/uv/getting-started/installation/).

```bash
curl -LsSf https://astral.sh/uv/install.sh | sh
```

Then you can use our [venv.sh](venv.sh) to create a virtual environment with all dependencies installed:

```bash
git clone https://github.com/hexfellow/hex_flow_node_camera.git
cd hex_flow_node_camera
./venv.sh
```

# 📑 Python Config API

The package provides helper functions that return `NodeConfig` objects for easy integration into your `LaunchConfig`.

```python
from hex_flow_core import LaunchConfig
from hex_flow_node_camera import (
    default_cam_usb_node,
    default_cam_usb_test_node,
    default_cam_realsense_node,
    default_cam_realsense_test_node,
)

config = LaunchConfig(
    local_only=True,
    enable_tui=True,
    log_to_file=True,
    save_path="/tmp/cam_test.yml",
)

nodes = {
    "cam_usb": default_cam_usb_node(
        name="cam_usb",
        cam_path="/dev/video2",
        width=640,
        height=480,
        frame_rate=30,
        rate_hz=500.0,
        required=True,
        remap_dict={"color": "cam_usb/color"},
    ),
    "test_cam_usb": default_cam_usb_test_node(
        name="test_cam_usb",
        rate_hz=30.0,
        required=False,
        remap_dict={"color": "cam_usb/color"},
    ),
}

config.set_nodes(nodes)
print(config.export())
```

## `default_cam_usb_node`

| Parameter        | Type    | Default          | Description                                        |
| ---------------- | ------- | ---------------- | -------------------------------------------------- |
| `name`           | `str`   | `"cam_usb"`      | Node name and remap prefix                         |
| `frame_rate`     | `int`   | `30`             | Camera sensor frame rate                           |
| `height`         | `int`   | `480`            | Image height in pixels                             |
| `width`          | `int`   | `640`            | Image width in pixels                              |
| `cam_buffer_size`| `int`   | `8`              | Internal camera driver buffer size                  |
| `sens_ts`        | `bool`  | `True`           | Use sensor timestamp when available                |
| `cam_path`       | `str`   | `"/dev/video0"`  | V4L2 device path                                   |
| `exposure`       | `int`   | `100`            | Exposure value                                     |
| `temperature`    | `int`   | `4000`           | White balance temperature                          |
| `color_encoding` | `str`   | `"bgr8"`         | Color image encoding (e.g. `"bgr8"`, `"rgb8"`)    |
| `rate_hz`        | `float` | `500.0`          | Publishing rate in Hz                              |
| `required`       | `bool`  | `True`           | Required for launch                                |
| `hidden`         | `bool`  | `False`          | Hidden from TUI                                    |
| `remap_dict`     | `dict`  | `None`           | Custom remap; defaults to `{name}/color`           |

## `default_cam_usb_test_node`

| Parameter    | Type    | Default             | Description                                               |
| ------------ | ------- | ------------------- | --------------------------------------------------------- |
| `name`       | `str`   | `"test_cam_usb"`    | Node name                                                 |
| `rate_hz`    | `float` | `30.0`              | Display refresh rate in Hz                                |
| `required`   | `bool`  | `False`             | Required for launch                                       |
| `hidden`     | `bool`  | `False`             | Hidden from TUI                                           |
| `remap_dict` | `dict`  | `None`              | Custom remap; defaults to `{cam_name}/color`              |
| `cam_name`   | `str`   | `"cam_usb"`         | Camera node to subscribe to                               |

## `default_cam_dummy_node`

| Parameter        | Type    | Default          | Description                                        |
| ---------------- | ------- | ---------------- | -------------------------------------------------- |
| `name`           | `str`   | `"cam_dummy"`    | Node name and remap prefix                         |
| `frame_rate`     | `int`   | `30`             | Camera sensor frame rate                           |
| `height`         | `int`   | `480`            | Image height in pixels                             |
| `width`          | `int`   | `640`            | Image width in pixels                              |
| `cam_buffer_size`| `int`   | `8`              | Internal camera driver buffer size                  |
| `sens_ts`        | `bool`  | `True`           | Use sensor timestamp when available                |
| `color_encoding` | `str`   | `"bgr8"`         | Color image encoding                               |
| `rate_hz`        | `float` | `500.0`          | Publishing rate in Hz                              |
| `required`       | `bool`  | `True`           | Required for launch                                |
| `hidden`         | `bool`  | `False`          | Hidden from TUI                                    |
| `remap_dict`     | `dict`  | `None`           | Custom remap; defaults to `{name}/color`           |

## `default_cam_dummy_test_node`

| Parameter       | Type    | Default              | Description                                               |
| --------------- | ------- | -------------------- | --------------------------------------------------------- |
| `name`          | `str`   | `"test_cam_dummy"`   | Node name                                                 |
| `rate_hz`       | `float` | `30.0`               | Display refresh rate in Hz                                |
| `show_color_ts` | `bool`  | `False`              | Overlay timestamp text on the displayed image             |
| `required`      | `bool`  | `False`              | Required for launch                                       |
| `hidden`        | `bool`  | `False`              | Hidden from TUI                                           |
| `remap_dict`    | `dict`  | `None`               | Custom remap; defaults to `{cam_name}/color`              |
| `cam_name`      | `str`   | `"cam_dummy"`        | Camera node to subscribe to                               |

## `default_cam_realsense_node`

| Parameter        | Type    | Default             | Description                                        |
| ---------------- | ------- | ------------------- | -------------------------------------------------- |
| `name`           | `str`   | `"cam_realsense"`   | Node name and remap prefix                         |
| `frame_rate`     | `int`   | `30`                | Camera sensor frame rate                           |
| `height`         | `int`   | `480`               | Image height in pixels                             |
| `width`          | `int`   | `640`               | Image width in pixels                              |
| `cam_buffer_size`| `int`   | `8`                 | Internal camera driver buffer size                  |
| `sens_ts`        | `bool`  | `True`              | Use sensor timestamp when available                |
| `serial_number`  | `str`   | `"b0"`              | RealSense device serial number                     |
| `color_encoding` | `str`   | `"bgr8"`            | Color image encoding                               |
| `depth_encoding` | `str`   | `"mono16"`          | Depth image encoding                               |
| `rate_hz`        | `float` | `500.0`             | Publishing rate in Hz                              |
| `required`       | `bool`  | `True`              | Required for launch                                |
| `hidden`         | `bool`  | `False`             | Hidden from TUI                                    |
| `remap_dict`     | `dict`  | `None`              | Custom remap; defaults to `{name}/color`, `{name}/depth` |

## `default_cam_realsense_test_node`

| Parameter    | Type    | Default                 | Description                                               |
| ------------ | ------- | ----------------------- | --------------------------------------------------------- |
| `name`       | `str`   | `"test_cam_realsense"`  | Node name                                                 |
| `rate_hz`    | `float` | `30.0`                  | Display refresh rate in Hz                                |
| `required`   | `bool`  | `False`                 | Required for launch                                       |
| `hidden`     | `bool`  | `False`                 | Hidden from TUI                                           |
| `remap_dict` | `dict`  | `None`                  | Custom remap; defaults to `{cam_name}/color`, `{cam_name}/depth` |
| `cam_name`   | `str`   | `"cam_realsense"`       | Camera node to subscribe to                               |

## `default_cam_berxel_node`

| Parameter        | Type    | Default          | Description                                        |
| ---------------- | ------- | ---------------- | -------------------------------------------------- |
| `name`           | `str`   | `"cam_berxel"`   | Node name and remap prefix                         |
| `frame_rate`     | `int`   | `30`             | Camera sensor frame rate                           |
| `height`         | `int`   | `400`            | Image height in pixels                             |
| `width`          | `int`   | `640`            | Image width in pixels                              |
| `cam_buffer_size`| `int`   | `8`              | Internal camera driver buffer size                  |
| `sens_ts`        | `bool`  | `True`           | Use sensor timestamp when available                |
| `serial_number`  | `str`   | `"b0"`           | Berxel device serial number                        |
| `exposure`       | `int`   | `10000`          | Exposure value                                     |
| `gain`           | `int`   | `100`            | Gain value                                         |
| `color_encoding` | `str`   | `"bgr8"`         | Color image encoding                               |
| `depth_encoding` | `str`   | `"mono16"`       | Depth image encoding                               |
| `rate_hz`        | `float` | `500.0`          | Publishing rate in Hz                              |
| `required`       | `bool`  | `True`           | Required for launch                                |
| `hidden`         | `bool`  | `False`          | Hidden from TUI                                    |
| `remap_dict`     | `dict`  | `None`           | Custom remap; defaults to `{name}/color`, `{name}/depth` |

## `default_cam_berxel_test_node`

| Parameter    | Type    | Default              | Description                                               |
| ------------ | ------- | -------------------- | --------------------------------------------------------- |
| `name`       | `str`   | `"test_cam_berxel"`  | Node name                                                 |
| `rate_hz`    | `float` | `30.0`               | Display refresh rate in Hz                                |
| `required`   | `bool`  | `False`              | Required for launch                                       |
| `hidden`     | `bool`  | `False`              | Hidden from TUI                                           |
| `remap_dict` | `dict`  | `None`               | Custom remap; defaults to `{cam_name}/color`, `{cam_name}/depth` |
| `cam_name`   | `str`   | `"cam_berxel"`       | Camera node to subscribe to                               |

# 💡 Examples

Ready-to-run config scripts are provided in the [`example/`](example/) directory. Each script prints a launch YAML to stdout, intended for use with `hexflow run`:

### USB Camera

```bash
# /dev/video2, 640x480, 30 FPS sensor, 500 Hz publish, 30 Hz test display
hexflow run example/usb_test.launch.py
```

### Dummy Camera (simulated)

```bash
# No hardware needed, generates test patterns
hexflow run example/dummy_test.launch.py
```

### RealSense

```bash
# Color + depth, 500 Hz publish, 30 Hz test display
hexflow run example/realsense_test.launch.py
```

### Berxel

```bash
# Color + depth, 500 Hz publish, 30 Hz test display
hexflow run example/berxel_test.launch.py
```

# YAML Examples

### USB Camera (500 Hz)

```yaml
nodes:
  - name: cam_usb
    build: pip install hex_flow_node_camera
    run: hex-flow-cam-usb
    required: true
    remap:
      color: cam_usb/color
    env:
      FRAME_RATE: "30"
      HEIGHT: "480"
      WIDTH: "640"
      CAM_BUFFER_SIZE: "8"
      SEN_TS: "True"
      CAM_PATH: "/dev/video2"
      EXPOSURE: "100"
      TEMPERATURE: "4000"
      COLOR_ENCODING: "bgr8"
      RATE_HZ: "500"

  - name: test_cam_usb
    build: pip install hex_flow_node_camera
    run: hex-flow-cam-test-usb
    required: false
    remap:
      color: cam_usb/color
    env:
      RATE_HZ: "30"
```

### Dummy Camera (500 Hz)

```yaml
nodes:
  - name: cam_dummy
    build: pip install hex_flow_node_camera
    run: hex-flow-cam-dummy
    required: true
    remap:
      color: cam_dummy/color
    env:
      FRAME_RATE: "30"
      HEIGHT: "480"
      WIDTH: "640"
      CAM_BUFFER_SIZE: "8"
      SEN_TS: "True"
      COLOR_ENCODING: "bgr8"
      RATE_HZ: "500"

  - name: test_cam_dummy
    build: pip install hex_flow_node_camera
    run: hex-flow-cam-test-dummy
    required: false
    remap:
      color: cam_dummy/color
    env:
      RATE_HZ: "30"
      SHOW_COLOR_TS: "False"
```

### RealSense (500 Hz)

```yaml
nodes:
  - name: cam_realsense
    build: pip install hex_flow_node_camera[realsense]
    run: hex-flow-cam-realsense
    required: true
    remap:
      color: cam_realsense/color
      depth: cam_realsense/depth
    env:
      FRAME_RATE: "30"
      HEIGHT: "480"
      WIDTH: "640"
      CAM_BUFFER_SIZE: "8"
      SEN_TS: "True"
      SERIAL_NUMBER: "0"
      COLOR_ENCODING: "bgr8"
      DEPTH_ENCODING: "mono16"
      RATE_HZ: "500"

  - name: test_cam_realsense
    build: pip install hex_flow_node_camera
    run: hex-flow-cam-test-realsense
    required: false
    remap:
      color: cam_realsense/color
      depth: cam_realsense/depth
    env:
      RATE_HZ: "30"
```

### Berxel (500 Hz)

```yaml
nodes:
  - name: cam_berxel
    build: pip install hex_flow_node_camera[berxel]
    run: hex-flow-cam-berxel
    required: true
    remap:
      color: cam_berxel/color
      depth: cam_berxel/depth
    env:
      FRAME_RATE: "30"
      HEIGHT: "400"
      WIDTH: "640"
      CAM_BUFFER_SIZE: "8"
      SEN_TS: "True"
      SERIAL_NUMBER: "b0"
      EXPOSURE: "10000"
      GAIN: "100"
      COLOR_ENCODING: "bgr8"
      DEPTH_ENCODING: "mono16"
      RATE_HZ: "500"

  - name: test_cam_berxel
    build: pip install hex_flow_node_camera
    run: hex-flow-cam-test-berxel
    required: false
    remap:
      color: cam_berxel/color
      depth: cam_berxel/depth
    env:
      RATE_HZ: "30"
```

# Message Types (FlatBuffer)

All topics use FlatBuffer messages from `hex_util_msg`.

## `color` — `HexCamColor`

| Field      | Type     | Description                                  |
| ---------- | -------- | -------------------------------------------- |
| `ts_ns`    | `int64`  | Timestamp in nanoseconds                     |
| `data`     | `[uint8]`| Raw image data (encoded per `encoding`)      |
| `height`   | `int32`  | Image height in pixels                       |
| `width`    | `int32`  | Image width in pixels                        |
| `encoding` | `string` | Image encoding string (e.g. `"bgr8"`)        |

Schema: [`msgs/msg_cam/color_image.fbs`](https://github.com/hexfellow/hex_util_msg/blob/main/msgs/msg_cam/color_image.fbs)

## `depth` — `HexCamDepth`

| Field      | Type      | Description                                  |
| ---------- | --------- | -------------------------------------------- |
| `ts_ns`    | `int64`   | Timestamp in nanoseconds                     |
| `data`     | `[uint8]` | Raw depth image data                         |
| `height`   | `int32`   | Depth image height in pixels                 |
| `width`    | `int32`   | Depth image width in pixels                  |
| `encoding` | `string`  | Depth encoding string (e.g. `"mono16"`)      |

Schema: [`msgs/msg_cam/depth_image.fbs`](https://github.com/hexfellow/hex_util_msg/blob/main/msgs/msg_cam/depth_image.fbs)

# Environment Variables

## All Nodes

| Variable             | Type  | Default         | Description                                                |
| -------------------- | ----- | --------------- | ---------------------------------------------------------- |
| `HEX_FLOW_NODE_NAME` | `str` | constructor arg | Overrides node name (handled by `hex_flow_core`)           |
| `HEX_FLOW_REMAP`     | `str` | `{}`            | JSON dict for topic remapping (handled by `hex_flow_core`) |
| `RUST_LOG`           | `str` | `info`          | Log level for `envlog`                                     |

## Camera Nodes (USB / Dummy / RealSense / Berxel)

| Variable         | Type    | Default               | Description                                    |
| ---------------- | ------- | --------------------- | ---------------------------------------------- |
| `FRAME_RATE`     | `int`   | `30`                  | Camera sensor frame rate                       |
| `HEIGHT`         | `int`   | `480` (Berxel: `400`)| Image height in pixels                         |
| `WIDTH`          | `int`   | `640`                 | Image width in pixels                          |
| `CAM_BUFFER_SIZE`| `int`   | `8`                   | Internal camera driver buffer size             |
| `SEN_TS`         | `bool`  | `True`                | Use sensor timestamp when available            |
| `COLOR_ENCODING` | `str`   | `"bgr8"`              | Color image encoding                           |
| `RATE_HZ`        | `float` | `500.0`               | Publishing rate in Hz                          |

### USB-specific

| Variable    | Type  | Default          | Description                      |
| ----------- | ----- | ---------------- | -------------------------------- |
| `CAM_PATH`  | `str` | `"/dev/video0"`  | V4L2 device path                 |
| `EXPOSURE`  | `int` | `100`            | Exposure value                   |
| `TEMPERATURE`| `int`| `4000`           | White balance temperature        |

### RealSense-specific

| Variable         | Type  | Default         | Description                      |
| ---------------- | ----- | --------------- | -------------------------------- |
| `SERIAL_NUMBER`  | `str` | `"0"`           | RealSense device serial number   |
| `DEPTH_ENCODING` | `str` | `"mono16"`      | Depth image encoding             |

### Berxel-specific

| Variable         | Type  | Default         | Description                      |
| ---------------- | ----- | --------------- | -------------------------------- |
| `SERIAL_NUMBER`  | `str` | `"b0"`          | Berxel device serial number      |
| `EXPOSURE`       | `int` | `10000`         | Exposure value                   |
| `GAIN`           | `int` | `100`           | Gain value                       |
| `DEPTH_ENCODING` | `str` | `"mono16"`      | Depth image encoding             |

## Test Nodes

| Variable        | Type    | Default | Description                            |
| --------------- | ------- | ------- | -------------------------------------- |
| `RATE_HZ`       | `float` | `30.0`  | Display refresh rate in Hz             |
| `SHOW_COLOR_TS` | `bool`  | `False` | Overlay timestamp on image (dummy only)|

# Architecture

All camera nodes follow the same pattern:

1. **Camera initialization** — creates a camera driver instance (`HexCamUsbCallback`, `HexCamDummyCallback`, `HexCamRealsenseCallback`, or `HexCamBerxelCallback`) from `hex_driver_camera`, configured with params such as resolution, frame rate, and device path/serial number.

2. **Callback registration** — user-provided callback functions are registered with the driver. Each callback receives a state dict `{"ts_ns": int, "data": numpy.ndarray}` when a new frame arrives from the camera hardware.

3. **FlatBuffer serialization** — within each callback, the frame data and timestamp are serialized into a FlatBuffer message (`HexCamColor` or `HexCamDepth`) using helpers from `hex_util_robot` (`build_color_msg()`, `build_depth_msg()`). The binary payload is then published via `hex_flow_core.Node.pub()`.

4. **Rate-controlled publish loop** — using `HexRate` from `hex_util_runtime`, the node publishes at the configured rate (default 500 Hz) while the camera driver's capture thread feeds frames asynchronously into the callbacks.

Test nodes subscribe via `hex_flow_core.Node.create_sub()` and poll with `Node.get(topic, latest=True)` at a lower rate (default 30 Hz), deserializing the FlatBuffer and displaying frames with OpenCV (`cv2.imshow()`). For depth-capable cameras (RealSense, Berxel), test nodes also display a color-mapped depth visualization using `depth_to_cmap()`.

This architecture decouples the variable-rate hardware frame capture from the fixed-rate publish output, ensuring consistent state updates regardless of actual camera frame rate.

# 📄 License

Apache License 2.0. See [LICENSE](LICENSE).

# 🌟 Star History

[![Star History Chart](https://api.star-history.com/svg?repos=hexfellow/hex_flow_node_camera&type=Date)](https://star-history.com/#hexfellow/hex_flow_node_camera&Date)

# 👥 Contributors

<a href="https://github.com/hexfellow/hex_flow_node_camera/graphs/contributors">
    <img src="https://contrib.rocks/image?repo=hexfellow/hex_flow_node_camera" />
</a>
