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 shared by reference between the processes that open the same pool -- no serialization, no copies.

#include <horus/pool.hpp>

Architecture

The pool system has four classes arranged in a hierarchy:

ClassPurpose
TensorPoolManages a shared memory region divided into a fixed number of allocation slots
TensorRaw N-dimensional array allocated from a pool
ImageCamera frame with width, height, and pixel encoding
PointCloudLidar scan with per-point fields (XYZ, intensity, color)

Image and PointCloud are convenience wrappers over pool-backed memory: the pixel and point data live in the pool's SHM region rather than inside the object. All three release their slot automatically when destroyed; Tensor additionally exposes release() to return the slot early -- see release() below.


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

NameTypeDescription
pool_iduint32_tIdentifier for this pool. Constructing a pool with an id that already exists opens that SHM region instead of creating a new one -- that is how two processes share a pool -- so distinct pools must use distinct ids.
pool_size_bytessize_tTotal size of the SHM-backed memory region in bytes.
max_slotssize_tMaximum 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:

FieldTypeDescription
allocatedsize_tNumber of active allocations
used_bytessize_tBytes currently in use
free_bytessize_tBytes 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

NameTypeDescription
poolconst TensorPool&Pool to allocate from. Must remain valid for the tensor's lifetime.
shapeconst uint64_t*Array of dimension sizes. Length must equal ndim.
ndimsize_tNumber of dimensions (rank of the tensor).
dtypeDtypeElement data type. See Dtype enum below.

Methods

MethodReturnDescription
data()uint8_t*Raw pointer to tensor memory. Cast based on dtype.
nbytes()uint64_tTotal size in bytes (product of dimensions * element size).
release()voidReturns the slot to the pool early. Optional -- ~Tensor() already releases the slot before destroying the handle. After release() the Tensor is empty and data() returns nullptr, and the destructor will not release the same slot twice.

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
};
VariantSizeUse Case
Dtype::F324 bytesNeural network weights, sensor readings
Dtype::F648 bytesHigh-precision computation
Dtype::U81 byteImage pixels, raw byte buffers
Dtype::I324 bytesInteger 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

NameTypeDescription
poolconst TensorPool&Pool to allocate from.
widthuint32_tImage width in pixels.
heightuint32_tImage height in pixels.
encEncodingPixel encoding format. Defaults to Encoding::Rgb8.

Methods

MethodReturn TypeDescription
width()uint32_tImage width in pixels
height()uint32_tImage height in pixels
data_size()size_tTotal 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)
};
VariantBytes/PixelUse Case
Encoding::Rgb83Standard color images
Encoding::Rgba84Images with transparency
Encoding::Gray81Depth maps, IR images, edge detection output
Encoding::Bgr83OpenCV-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

NameTypeDescription
poolconst TensorPool&Pool to allocate from.
num_pointsuint32_tNumber of points in the cloud.
fields_per_pointuint32_tFloats per point: 3 = XYZ, 4 = XYZI, 6 = XYZRGB.

Methods

MethodReturn TypeDescription
num_points()uint64_tNumber of points in the cloud
fields_per_point()uint32_tNumber of float fields per point

Common Field Layouts

FieldsLayoutUse Case
3X, Y, ZBasic geometry
4X, Y, Z, IntensityLidar with reflectance
6X, Y, Z, R, G, BColored point cloud

Example: Camera Frame Allocation

A camera node that reserves frame buffers in pool-backed shared memory.

#include <horus/node.hpp>
#include <horus/pool.hpp>

auto pool = horus::TensorPool(1, 32 * 1024 * 1024, 32);

// Inside tick():
auto frame = horus::Image(pool, 1280, 720, horus::Encoding::Rgb8);
if (frame) {
    // Frame memory is now reserved in the pool's SHM region.
    // data_size() = 1280 * 720 * 3 = 2,764,800 bytes
}

There is no publish step here: Publisher<T> / Subscriber<T> are specialized only for the fixed-size horus::msg:: POD types, so sched.advertise<horus::Image>(...) does not compile. From C++, pool-backed types are allocated inside the processes that open the pool, and only Tensor exposes a data pointer (data()) -- Image and PointCloud expose metadata only. See Topic for the message types that can be published.


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
    input.release();  // optional -- returns the slot early; ~Tensor() also releases it
}

Pool Sizing Guidelines

WorkloadPool SizeMax SlotsRationale
Single 720p camera16 MB8~2.7 MB per frame, triple-buffered with headroom
Stereo 1080p cameras64 MB16~6.2 MB per frame, two cameras, pipeline depth
16-beam lidar8 MB16~360 KB per XYZ scan at 30K points (num_points * fields * 4 bytes, f32)
NN inference batch64 MB16Depends on model input/output sizes

Overallocating pool size is not free. At construction HORUS pre-faults the pool's data region with madvise(MADV_POPULATE_WRITE), up to HORUS_SHM_PREFAULT_MAX_BYTES (64 MiB by default), which commits those pages to physical memory. A pool of 64 MB or less -- every size in the table above -- is therefore fully resident the moment it is created; only the part of a larger pool beyond the cap stays uncommitted. Set HORUS_SHM_PREFAULT=0 to disable pre-faulting, or lower HORUS_SHM_PREFAULT_MAX_BYTES, if you would rather pay the page faults in the control loop. Underallocating max_slots causes allocation failures when the pipeline is full.