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)]):
| Field | Type | Description |
|---|---|---|
width | u32 | Mask width in pixels |
height | u32 | Mask height in pixels |
num_classes | u32 | Number of semantic classes |
mask_type | u32 | 0=semantic, 1=instance, 2=panoptic |
timestamp_ns | u64 | Nanoseconds since epoch |
seq | u64 | Sequence number |
frame_id | [u8; 32] | Camera frame identifier |
Methods:
| Method | Returns | Description |
|---|---|---|
semantic(w, h, num_classes) | SegmentationMask | Create semantic mask header |
instance(w, h) | SegmentationMask | Create instance mask header |
panoptic(w, h, num_classes) | SegmentationMask | Create panoptic mask header |
with_frame_id(id) | Self | Set frame ID (builder) |
with_timestamp(ts) | Self | Set timestamp (builder) |
frame_id() | &str | Get frame ID as string |
data_size() | usize | Mask data size in bytes for u8 masks (w * h) |
data_size_u16() | usize | Mask data size in bytes for u16 masks (w * h * 2) |
is_semantic() | bool | True if mask_type == 0 |
is_instance() | bool | True if mask_type == 1 |
is_panoptic() | bool | True 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
- TensorPool API - Zero-copy tensor memory management
- Vision Messages - Image, camera, and detection messages
- Perception Messages - Point cloud and depth sensing