Tensor
A lightweight tensor descriptor for zero-copy ML data sharing across nodes and processes.
// simplified
use horus::prelude::*;
use horus_core::types::Tensor;
Tensor is imported from horus_core::types — it is not re-exported by horus::prelude (the prelude only provides TensorDtype and Device).
Most users do not need Tensor directly. For camera images use Image, for 3D points use PointCloud, for depth data use DepthImage. Tensor is for advanced ML pipelines where you need direct control over shape, dtype, and layout — for example, feeding preprocessed batches into a model or reading raw model outputs.
Overview
Tensor is a lightweight descriptor that references data in shared memory. Only the descriptor is transmitted through topics — the actual tensor data stays in-place, enabling zero-copy transport for large ML payloads.
Creating a Tensor
The Tensor descriptor above is what you receive — it points at data already in a shared-memory pool. To create a tensor to fill in and publish, use TensorHandle, the owned RAII handle that allocates from a pool and manages its refcount. It is imported from horus::memory (not the prelude):
// simplified
use horus::memory::TensorHandle;
use horus_core::types::TensorDtype;
// Allocate a [1000, 1000] f32 costmap from the global pool (CPU by default):
let mut costmap = TensorHandle::from_shape(&[1000, 1000], TensorDtype::F32)?;
// Fill it, then publish the descriptor (zero-copy — only the descriptor is sent):
costmap.data_slice_mut()?.fill(0);
let topic: Topic<Tensor> = Topic::new("nav.costmap")?;
topic.send_handle(&costmap);
TensorHandle::from_shape(shape, dtype) mirrors the Python horus.Tensor([shape], dtype=...) constructor and the new/new_on convenience on Image, PointCloud, and DepthImage. It uses the process-wide global pool; for an explicit pool or device (e.g. a CUDA GPU), use TensorHandle::alloc(pool, shape, dtype, device). Returns an error if the shape is empty or any dimension is zero.
Methods
| Method | Return Type | Description |
|---|---|---|
shape() | &[u64] | Tensor dimensions (e.g., [1080, 1920, 3]) |
strides() | &[u64] | Byte strides per dimension |
numel() | u64 | Total number of elements |
nbytes() | u64 | Total size in bytes (numel * dtype.element_size()) |
dtype() | TensorDtype | Element data type |
is_contiguous() | bool | True if memory layout is C-contiguous |
view(new_shape) | Option | Reshape without copying (fails if not contiguous or element count changes) |
slice_first_dim(start, end) | Option | Slice along the first dimension, adjusting strides |
Reshape and Slice
// simplified
let topic: Topic<Tensor> = Topic::new("model.input")?;
if let Some(tensor) = topic.recv() {
// Reshape a flat 1D tensor into a batch of images
if let Some(reshaped) = tensor.view(&[4, 3, 224, 224]) {
println!("Batch shape: {:?}", reshaped.shape()); // [4, 3, 224, 224]
}
// Take the first 2 items from a batch
if let Some(sliced) = tensor.slice_first_dim(0, 2) {
println!("Sliced shape: {:?}", sliced.shape()); // [2, 3, 224, 224]
}
}
TensorDtype
Supported element types with sizes and common use cases:
| Dtype | Size | Use Case |
|---|---|---|
F32 | 4 bytes | ML training and inference |
F64 | 8 bytes | High-precision computation |
F16 | 2 bytes | Memory-efficient inference |
BF16 | 2 bytes | Training on modern GPUs |
I8 | 1 byte | Quantized inference |
I16 | 2 bytes | Audio, sensor data |
I32 | 4 bytes | General integer |
I64 | 8 bytes | Large signed values |
U8 | 1 byte | Images |
U16 | 2 bytes | Depth sensors (mm) |
U32 | 4 bytes | Large indices |
U64 | 8 bytes | Counters, timestamps |
Bool | 1 byte | Masks |
TensorDtype Methods
// simplified
let dtype = TensorDtype::F32;
// Size in bytes
assert_eq!(dtype.element_size(), 4);
// Display (lowercase string representation)
println!("{}", dtype); // "float32"
// Parse from string — accepts common aliases
let parsed = TensorDtype::parse("float32").unwrap(); // F32
let parsed = TensorDtype::parse("f16").unwrap(); // F16
let parsed = TensorDtype::parse("uint8").unwrap(); // U8
let parsed = TensorDtype::parse("bool").unwrap(); // Bool
ML Pipeline Example
A camera node captures frames using Image, while a preprocessing node converts them into batched Tensor data for model inference:
// simplified
use horus::prelude::*;
// Producer: camera capture node — uses Image, not raw Tensor
node! {
CameraNode {
pub { frames: Image -> "camera.rgb" }
data { frame_count: u64 = 0 }
tick {
let image = Image::new(640, 480, ImageEncoding::Rgb8);
// ... fill pixel data from camera driver ...
self.frames.send(&image);
self.frame_count += 1;
}
}
}
// Consumer: inference node — works with raw Tensor input/output
node! {
InferenceNode {
sub { input: Tensor -> "model.input" }
pub { detections: GenericMessage -> "model.detections" }
tick {
if let Some(tensor) = self.input.recv() {
hlog!(debug, "Input: {:?}, {} bytes", tensor.shape(), tensor.nbytes());
// Run inference on the batch tensor, publish results ...
}
}
}
}
Python Usage
In Python, use Image, PointCloud, or DepthImage for zero-copy tensor data — they wrap the pool-backed tensor system automatically and provide .to_numpy() / .from_numpy() conversions. Bridge into PyTorch or JAX through the NumPy view — torch.from_numpy(...) shares memory (zero-copy for CPU tensors):
import horus
import numpy as np
import torch
import jax.numpy as jnp
# Image → NumPy (zero-copy)
img = horus.Image(480, 640, "rgb8")
arr = img.to_numpy() # shape: (480, 640, 3), dtype: uint8
# PointCloud → PyTorch (via NumPy, zero-copy for CPU tensors)
cloud = horus.PointCloud.from_numpy(np.random.randn(1000, 3).astype(np.float32))
tensor = torch.from_numpy(cloud.to_numpy())
# DepthImage → JAX (via NumPy)
depth = horus.DepthImage(480, 640, "float32")
jax_arr = jnp.asarray(depth.to_numpy())
See Python Image, Python PointCloud, and Python DepthImage for the full APIs.
TensorDtype
Enumerates all supported tensor element types. Matches common ML framework dtypes for seamless interop with PyTorch, NumPy, and JAX.
| Variant | Value | Size | NumPy | Use Case |
|---|---|---|---|---|
F32 | 0 | 4 bytes | <f4 | Default for most ML models |
F64 | 1 | 8 bytes | <f8 | High-precision computation |
F16 | 2 | 2 bytes | <f2 | GPU inference, mixed precision |
BF16 | 3 | 2 bytes | <V2 | Training, transformer models |
I8 | 4 | 1 byte | |i1 | Quantized models |
I16 | 5 | 2 bytes | <i2 | Audio, depth sensors |
I32 | 6 | 4 bytes | <i4 | Labels, indices |
I64 | 7 | 8 bytes | <i8 | Timestamps, large indices |
U8 | 8 | 1 byte | |u1 | Images (RGB pixels) |
U16 | 9 | 2 bytes | <u2 | Depth images (mm) |
U32 | 10 | 4 bytes | <u4 | Point cloud indices |
U64 | 11 | 8 bytes | <u8 | Large counters |
Bool | 12 | 1 byte | |b1 | Masks, flags |
Methods
// simplified
use horus::prelude::*;
let dtype = TensorDtype::F32;
// Element size in bytes
assert_eq!(dtype.element_size(), 4);
// NumPy type string (for __array_interface__)
assert_eq!(dtype.numpy_typestr(), "<f4");
// Parse from string
let parsed = TensorDtype::parse("f16");
assert_eq!(parsed, Some(TensorDtype::F16));
// Safe construction from raw u8 (for SHM/network data)
let safe = TensorDtype::from_raw(255); // invalid → defaults to F32
assert_eq!(safe, TensorDtype::F32);
See Also
- Data Types & Encoding — Image, PointCloud, DepthImage for common robotics data
- Message Types — All HORUS message types
- Python Image, Python PointCloud, Python DepthImage