TensorPool, Image & PointCloud API
HORUS provides zero-copy shared memory pools for large data: camera frames, lidar scans, and neural network tensors. Data lives in a pre-allocated SHM region and is passed between nodes by reference -- no serialization, no copies.
#include <horus/pool.hpp>
Architecture
The pool system has four classes arranged in a hierarchy:
| Class | Purpose |
|---|---|
TensorPool | Manages a shared memory region with fixed-size slots |
Tensor | Raw N-dimensional array allocated from a pool |
Image | Camera frame with width, height, and pixel encoding |
PointCloud | Lidar scan with per-point fields (XYZ, intensity, color) |
Image and PointCloud are convenience wrappers over pool-backed memory. When you publish any pool-backed type on a topic, subscribers get a pointer into the same SHM region -- zero copies.
TensorPool
A fixed-size shared memory region divided into allocation slots.
Constructor
horus::TensorPool pool(
uint32_t pool_id, // unique pool identifier
size_t pool_size_bytes, // total SHM region size
size_t max_slots // maximum concurrent allocations
);
Parameters
| Name | Type | Description |
|---|---|---|
pool_id | uint32_t | Unique identifier for this pool. Must not collide with other pools in the same system. |
pool_size_bytes | size_t | Total size of the SHM-backed memory region in bytes. |
max_slots | size_t | Maximum number of tensors/images/point clouds that can be allocated simultaneously. |
Example
// 16 MB pool with up to 64 concurrent allocations
auto pool = horus::TensorPool(1, 16 * 1024 * 1024, 64);
stats()
Returns current pool utilization.
TensorPool::Stats stats() const;
Returns a Stats struct:
| Field | Type | Description |
|---|---|---|
allocated | size_t | Number of active allocations |
used_bytes | size_t | Bytes currently in use |
free_bytes | size_t | Bytes available for allocation |
auto s = pool.stats();
horus::log::info("pool", ("used: " + std::to_string(s.used_bytes) +
" free: " + std::to_string(s.free_bytes)).c_str());
Ownership and Validity
TensorPool is move-only -- copy construction and copy assignment are deleted. Use explicit operator bool() to check if the pool handle is valid:
auto pool2 = std::move(pool); // OK -- transfers ownership
if (!pool2) {
horus::log::error("init", "Failed to create tensor pool");
}
Tensor
A raw N-dimensional array allocated from a TensorPool.
Constructor
horus::Tensor tensor(
const TensorPool& pool, // pool to allocate from
const uint64_t* shape, // dimension sizes array
size_t ndim, // number of dimensions
Dtype dtype // element data type
);
Parameters
| Name | Type | Description |
|---|---|---|
pool | const TensorPool& | Pool to allocate from. Must remain valid for the tensor's lifetime. |
shape | const uint64_t* | Array of dimension sizes. Length must equal ndim. |
ndim | size_t | Number of dimensions (rank of the tensor). |
dtype | Dtype | Element data type. See Dtype enum below. |
Methods
| Method | Return | Description |
|---|---|---|
data() | uint8_t* | Raw pointer to tensor memory. Cast based on dtype. |
nbytes() | uint64_t | Total size in bytes (product of dimensions * element size). |
release() | void | Returns the slot to the pool early. Called automatically on destruction. |
Dtype Enum
Element data types for tensors.
enum class Dtype : uint8_t {
F32, // 32-bit float
F64, // 64-bit float
U8, // unsigned 8-bit integer
I32, // signed 32-bit integer
};
| Variant | Size | Use Case |
|---|---|---|
Dtype::F32 | 4 bytes | Neural network weights, sensor readings |
Dtype::F64 | 8 bytes | High-precision computation |
Dtype::U8 | 1 byte | Image pixels, raw byte buffers |
Dtype::I32 | 4 bytes | Integer indices, counters |
Image
A camera frame with width, height, and pixel encoding, backed by pool memory.
Constructor
horus::Image img(
const TensorPool& pool,
uint32_t width,
uint32_t height,
Encoding enc = Encoding::Rgb8 // optional, defaults to RGB8
);
Parameters
| Name | Type | Description |
|---|---|---|
pool | const TensorPool& | Pool to allocate from. |
width | uint32_t | Image width in pixels. |
height | uint32_t | Image height in pixels. |
enc | Encoding | Pixel encoding format. Defaults to Encoding::Rgb8. |
Methods
| Method | Return Type | Description |
|---|---|---|
width() | uint32_t | Image width in pixels |
height() | uint32_t | Image height in pixels |
data_size() | size_t | Total image data size in bytes (width * height * bytes_per_pixel) |
Encoding Enum
Pixel encoding formats for images.
enum class Encoding : uint8_t {
Rgb8, // 3 bytes per pixel: red, green, blue
Rgba8, // 4 bytes per pixel: red, green, blue, alpha
Gray8, // 1 byte per pixel: grayscale
Bgr8, // 3 bytes per pixel: blue, green, red (OpenCV default)
};
| Variant | Bytes/Pixel | Use Case |
|---|---|---|
Encoding::Rgb8 | 3 | Standard color images |
Encoding::Rgba8 | 4 | Images with transparency |
Encoding::Gray8 | 1 | Depth maps, IR images, edge detection output |
Encoding::Bgr8 | 3 | OpenCV-native format, avoids channel swap |
PointCloud
A lidar scan or 3D point set, backed by pool memory.
Constructor
horus::PointCloud pc(
const TensorPool& pool,
uint32_t num_points,
uint32_t fields_per_point
);
Parameters
| Name | Type | Description |
|---|---|---|
pool | const TensorPool& | Pool to allocate from. |
num_points | uint32_t | Number of points in the cloud. |
fields_per_point | uint32_t | Floats per point: 3 = XYZ, 4 = XYZI, 6 = XYZRGB. |
Methods
| Method | Return Type | Description |
|---|---|---|
num_points() | uint64_t | Number of points in the cloud |
fields_per_point() | uint32_t | Number of float fields per point |
Common Field Layouts
| Fields | Layout | Use Case |
|---|---|---|
| 3 | X, Y, Z | Basic geometry |
| 4 | X, Y, Z, Intensity | Lidar with reflectance |
| 6 | X, Y, Z, R, G, B | Colored point cloud |
Example: Camera Pipeline
A camera node that captures frames and publishes them over a zero-copy topic.
#include <horus/node.hpp>
#include <horus/pool.hpp>
auto pool = horus::TensorPool(1, 32 * 1024 * 1024, 32);
auto cam_topic = horus::Topic<horus::Image>("camera.rgb");
// Inside tick():
auto frame = horus::Image(pool, 1280, 720, horus::Encoding::Rgb8);
if (frame) {
// Fill frame data from hardware driver...
// data_size() = 1280 * 720 * 3 = 2,764,800 bytes
cam_topic.send(std::move(frame));
// Subscriber receives a pointer into the same SHM -- zero copies
}
Example: Neural Network Tensor
#include <horus/pool.hpp>
auto pool = horus::TensorPool(2, 64 * 1024 * 1024, 16);
// Allocate a [1, 3, 224, 224] input tensor for a vision model
uint64_t shape[] = {1, 3, 224, 224};
auto input = horus::Tensor(pool, shape, 4, horus::Dtype::F32);
if (input) {
float* data = reinterpret_cast<float*>(input.data());
// Fill with preprocessed image data...
// nbytes() = 1 * 3 * 224 * 224 * 4 = 602,112 bytes
}
Pool Sizing Guidelines
| Workload | Pool Size | Max Slots | Rationale |
|---|---|---|---|
| Single 720p camera | 16 MB | 8 | ~2.7 MB per frame, triple-buffered with headroom |
| Stereo 1080p cameras | 64 MB | 16 | ~6.2 MB per frame, two cameras, pipeline depth |
| 16-beam lidar | 8 MB | 16 | ~120 KB per scan at 30K points |
| NN inference batch | 64 MB | 16 | Depends on model input/output sizes |
Overallocating pool size is safe -- unused SHM is not committed to physical memory on Linux. Underallocating max_slots causes allocation failures when the pipeline is full.