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

MethodReturn TypeDescription
shape()&[u64]Tensor dimensions (e.g., [1080, 1920, 3])
strides()&[u64]Byte strides per dimension
numel()u64Total number of elements
nbytes()u64Total size in bytes (numel * dtype.element_size())
dtype()TensorDtypeElement data type
is_contiguous()boolTrue if memory layout is C-contiguous
view(new_shape)OptionReshape without copying (fails if not contiguous or element count changes)
slice_first_dim(start, end)OptionSlice 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:

DtypeSizeUse Case
F324 bytesML training and inference
F648 bytesHigh-precision computation
F162 bytesMemory-efficient inference
BF162 bytesTraining on modern GPUs
I81 byteQuantized inference
I162 bytesAudio, sensor data
I324 bytesGeneral integer
I648 bytesLarge signed values
U81 byteImages
U162 bytesDepth sensors (mm)
U324 bytesLarge indices
U648 bytesCounters, timestamps
Bool1 byteMasks

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.

VariantValueSizeNumPyUse Case
F3204 bytes<f4Default for most ML models
F6418 bytes<f8High-precision computation
F1622 bytes<f2GPU inference, mixed precision
BF1632 bytes<V2Training, transformer models
I841 byte|i1Quantized models
I1652 bytes<i2Audio, depth sensors
I3264 bytes<i4Labels, indices
I6478 bytes<i8Timestamps, large indices
U881 byte|u1Images (RGB pixels)
U1692 bytes<u2Depth images (mm)
U32104 bytes<u4Point cloud indices
U64118 bytes<u8Large counters
Bool121 byte|b1Masks, 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

Spotted an error on this page?