Machine Learning Messages

HORUS ships one ML-adjacent message type: SegmentationMask, a 64-byte Pod header available via use horus::prelude::*. There are no message types for model metadata, inference metrics, training progress, or LLM conversations — model I/O is carried by the zero-copy TensorPool/TensorHandle API, and inference results are published as ordinary detection messages.

For zero-copy Pod detection types, see Vision Messages (Detection, Detection3D, BoundingBox2D, BoundingBox3D).

Tensor Data

There is no message struct for model inputs and outputs. Tensors travel through the zero-copy pool API instead: TensorHandle is a refcounted handle over memory allocated from a TensorPool, and only the 168-byte Tensor descriptor crosses a topic.

use horus::prelude::*;
use horus::memory::TensorHandle;

// Allocate a 3x3 f32 tensor from the global pool
let tensor = TensorHandle::from_shape(&[3, 3], TensorDtype::F32)?;

println!("Shape: {:?}", tensor.shape());
println!("Elements: {}", tensor.numel()); // 9
println!("Bytes: {}", tensor.nbytes());   // 36

TensorHandle is not in the prelude — import it from horus::memory. See TensorPool API for allocation and device placement, and Supported Data Types for the full TensorDtype table.

SegmentationMask

Pod segmentation mask header (64 bytes). The actual pixel data follows the header in shared memory — each pixel is a u8 class/instance ID. Panoptic masks carrying more than 256 instances store u16 pixels instead; size that buffer with data_size_u16() rather than data_size().

use horus::prelude::*;

// Create semantic segmentation mask header
let mask = SegmentationMask::semantic(640, 480, 21)
    .with_frame_id("camera_front")
    .with_timestamp(1234567890);

println!("Mask: {}x{}, {} classes", mask.width, mask.height, mask.num_classes);
println!("Data size: {} bytes", mask.data_size());

// Create instance segmentation mask
let instance_mask = SegmentationMask::instance(640, 480);

// Create panoptic segmentation mask
let panoptic_mask = SegmentationMask::panoptic(640, 480, 80);

Fields (64 bytes, #[repr(C)]):

FieldTypeDescription
widthu32Mask width in pixels
heightu32Mask height in pixels
num_classesu32Number of semantic classes
mask_typeu320=semantic, 1=instance, 2=panoptic
timestamp_nsu64Nanoseconds since epoch
sequ64Sequence number
frame_id[u8; 32]Camera frame identifier

Methods:

MethodReturnsDescription
semantic(w, h, num_classes)SegmentationMaskCreate semantic mask header
instance(w, h)SegmentationMaskCreate instance mask header
panoptic(w, h, num_classes)SegmentationMaskCreate panoptic mask header
with_frame_id(id)SelfSet frame ID (builder)
with_timestamp(ts)SelfSet timestamp (builder)
frame_id()&strGet frame ID as string
data_size()usizeMask data size in bytes for u8 masks (w * h)
data_size_u16()usizeMask data size in bytes for u16 masks (w * h * 2)
is_semantic()boolTrue if mask_type == 0
is_instance()boolTrue if mask_type == 1
is_panoptic()boolTrue if mask_type == 2

ML Inference Node Example

use horus::prelude::*;

struct ObjectDetectionNode {
    image_sub: Topic<Image>,
    detection_pub: Topic<Detection>,
    model_name: String,
}

impl Node for ObjectDetectionNode {
    fn name(&self) -> &str { "ObjectDetection" }

    fn tick(&mut self) {
        if let Some(image) = self.image_sub.recv() {
            let start = std::time::Instant::now();

            // Run inference (placeholder)
            let detection = self.run_inference(&image);

            let elapsed = start.elapsed().as_secs_f32() * 1000.0;

            // Publish detection
            self.detection_pub.send(detection);

            hlog!(debug, "{}: inference took {:.1}ms", self.model_name, elapsed);
        }
    }
}

impl ObjectDetectionNode {
    fn run_inference(&self, _image: &Image) -> Detection {
        // Model inference implementation
        Detection::new("unknown", 0.0, 0.0, 0.0, 0.0, 0.0)
    }
}

See Also