Detection & Detection3D

Fixed-size object detection messages for zero-copy IPC. Detection holds a 2D bounding box result from models like YOLO or SSD. Detection3D holds a 3D oriented bounding box from point cloud detectors or depth-aware models. Both are fixed-size types — transferred via zero-copy shared memory (72 and 104 bytes respectively).

When to Use

Use Detection when your robot runs a 2D object detection model on camera images and needs to publish results to downstream nodes (tracking, planning, visualization). Use Detection3D when you have 3D detections from LiDAR-based or depth-aware models.

ROS2 Equivalent

  • Detection maps to vision_msgs/Detection2D
  • Detection3D maps to vision_msgs/Detection3D

Rust Example

use horus::prelude::*;

// 2D detection from YOLO
let det = Detection::new("person", 0.92, 100.0, 50.0, 200.0, 400.0);
//                       class     conf  x      y      width   height

// Check confidence threshold
if det.is_confident(0.5) {
    println!("Found {} at ({}, {})", det.class_name(), det.bbox.x, det.bbox.y);
}

// 3D detection from point cloud
let bbox3d = BoundingBox3D::new(5.0, 2.0, 0.5, 4.5, 2.0, 1.5, 0.1);
//                              cx   cy   cz   len  wid  hgt  yaw
let det3d = Detection3D::new("car", 0.88, bbox3d)
    .with_velocity(10.0, 5.0, 0.0);

// Publish detections
let topic: Topic<Detection> = Topic::new("detections.2d")?;
topic.send(&det);

Python Example

from horus import Detection, Topic

# Create a detection
det = Detection("person", 0.92, 100.0, 50.0, 200.0, 400.0)
#               class    conf  x      y      width   height

# Access properties
print(f"Class: {det.class_name}, Confidence: {det.confidence}")
print(f"BBox: ({det.bbox.x}, {det.bbox.y}, {det.bbox.width}, {det.bbox.height})")
print(f"Area: {det.bbox.area}")

# Confidence filtering
if det.is_confident(0.5):
    topic = Topic(Detection)
    topic.send(det)

Detection Fields

FieldTypeUnitSizeDescription
bboxBoundingBox2Dpx16 BBounding box (x, y, width, height)
confidencef320--14 BDetection confidence
class_idu32--4 BNumeric class identifier
class_name[u8; 32]--32 BUTF-8 class label, null-padded (max 31 chars)
instance_idu32--4 BInstance ID for instance segmentation

Total size: 72 bytes (fixed-size, zero-copy)

Detection Methods

MethodSignatureDescription
new(name, conf, x, y, w, h)(&str, f32, f32, f32, f32, f32) -> SelfCreate with class name and bounding box
with_class_id(id, conf, bbox)(u32, f32, BoundingBox2D) -> SelfCreate with numeric class ID
class_name()-> &strGet class name as string
set_class_name(name)(&str) -> ()Set class name (truncates to 31 chars)
is_confident(threshold)(f32) -> boolTrue if confidence >= threshold

BoundingBox2D Methods

MethodSignatureDescription
new(x, y, w, h)(f32, f32, f32, f32) -> SelfCreate from top-left corner
from_center(cx, cy, w, h)(f32, f32, f32, f32) -> SelfCreate from center (YOLO format)
center_x(), center_y()-> f32Center coordinates
area()-> f32Box area in pixels
iou(other)(&BoundingBox2D) -> f32Intersection over Union

Detection3D Fields

FieldTypeUnitSizeDescription
bboxBoundingBox3Dm, rad48 B3D box: center, dimensions, rotation (roll/pitch/yaw)
confidencef320--14 BDetection confidence
class_idu32--4 BNumeric class identifier
class_name[u8; 32]--32 BUTF-8 class label
velocity_x/y/zf32m/s12 BObject velocity (for tracking-enabled detectors)
instance_idu32--4 BTracking/instance ID

Total size: 104 bytes (fixed-size, zero-copy)

Common Patterns

Camera-to-tracking pipeline:

Camera --> Image --> YOLO model --> Detection --> tracker --> TrackedObject
                                              \-> filter by confidence
                                              \-> filter by class

Confidence filtering pattern:

use horus::prelude::*;

fn filter_detections(detections: &[Detection], min_conf: f32) -> Vec<&Detection> {
    detections.iter()
        .filter(|d| d.is_confident(min_conf))
        .collect()
}

NMS (Non-Maximum Suppression):

use horus::prelude::*;

fn nms(dets: &mut Vec<Detection>, iou_threshold: f32) {
    dets.sort_by(|a, b| b.confidence.partial_cmp(&a.confidence).unwrap());
    let mut keep = vec![true; dets.len()];
    for i in 0..dets.len() {
        if !keep[i] { continue; }
        for j in (i + 1)..dets.len() {
            if keep[j] && dets[i].bbox.iou(&dets[j].bbox) > iou_threshold {
                keep[j] = false;
            }
        }
    }
    let mut idx = 0;
    dets.retain(|_| { let k = keep[idx]; idx += 1; k });
}