mecha10_core/messages/
perception.rs

1// Perception Message Types
2
3use serde::{Deserialize, Serialize};
4
5/// Object detection result
6///
7/// Represents a detected object with bounding box and classification
8#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Detection {
10    /// Timestamp in microseconds since epoch
11    pub timestamp: u64,
12
13    /// Class ID
14    pub class_id: u32,
15
16    /// Class name (e.g., "person", "cup", "table")
17    pub class_name: String,
18
19    /// Confidence score (0.0 to 1.0)
20    pub confidence: f32,
21
22    /// Bounding box in image coordinates
23    pub bbox: BoundingBox,
24
25    /// Optional tracking ID (for multi-object tracking)
26    #[serde(skip_serializing_if = "Option::is_none")]
27    pub track_id: Option<u32>,
28
29    /// Optional 3D position (if depth available)
30    #[serde(skip_serializing_if = "Option::is_none")]
31    pub position_3d: Option<[f32; 3]>,
32}
33
34/// 2D Bounding box
35#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct BoundingBox {
37    /// X coordinate of top-left corner
38    pub x: f32,
39
40    /// Y coordinate of top-left corner
41    pub y: f32,
42
43    /// Width of the box
44    pub width: f32,
45
46    /// Height of the box
47    pub height: f32,
48}
49
50impl BoundingBox {
51    /// Create a new bounding box
52    pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
53        Self { x, y, width, height }
54    }
55
56    /// Get the center point of the box
57    pub fn center(&self) -> (f32, f32) {
58        (self.x + self.width / 2.0, self.y + self.height / 2.0)
59    }
60
61    /// Get the area of the box
62    pub fn area(&self) -> f32 {
63        self.width * self.height
64    }
65
66    /// Calculate Intersection over Union (IoU) with another box
67    pub fn iou(&self, other: &BoundingBox) -> f32 {
68        let x1 = self.x.max(other.x);
69        let y1 = self.y.max(other.y);
70        let x2 = (self.x + self.width).min(other.x + other.width);
71        let y2 = (self.y + self.height).min(other.y + other.height);
72
73        if x2 < x1 || y2 < y1 {
74            return 0.0;
75        }
76
77        let intersection = (x2 - x1) * (y2 - y1);
78        let union = self.area() + other.area() - intersection;
79
80        intersection / union
81    }
82}
83
84/// 3D Pose (position + orientation)
85///
86/// Represents a 6-DOF pose in 3D space
87#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct Pose {
89    /// Timestamp in microseconds since epoch
90    pub timestamp: u64,
91
92    /// Position (x, y, z) in meters
93    pub position: [f32; 3],
94
95    /// Orientation as quaternion (x, y, z, w)
96    pub orientation: [f32; 4],
97
98    /// Optional frame ID
99    #[serde(skip_serializing_if = "Option::is_none")]
100    pub frame_id: Option<String>,
101
102    /// Optional covariance matrix (6x6, row-major) - stored as Vec for serde compatibility
103    #[serde(skip_serializing_if = "Option::is_none")]
104    pub covariance: Option<Vec<f32>>,
105}
106
107// Helper methods for Detection
108impl Detection {
109    /// Creates a new detection
110    pub fn new(class_id: u32, class_name: String, confidence: f32, bbox: BoundingBox) -> Self {
111        Self {
112            timestamp: crate::prelude::now_micros(),
113            class_id,
114            class_name,
115            confidence,
116            bbox,
117            track_id: None,
118            position_3d: None,
119        }
120    }
121
122    /// Checks if confidence is above a threshold
123    pub fn is_confident(&self, threshold: f32) -> bool {
124        self.confidence >= threshold
125    }
126
127    /// Returns the center of the bounding box
128    pub fn center(&self) -> (f32, f32) {
129        self.bbox.center()
130    }
131
132    /// Returns the area of the bounding box
133    pub fn area(&self) -> f32 {
134        self.bbox.area()
135    }
136
137    /// Checks if this detection has 3D position information
138    pub fn has_3d_position(&self) -> bool {
139        self.position_3d.is_some()
140    }
141
142    /// Returns the 3D distance from origin (if position available)
143    pub fn distance_3d(&self) -> Option<f32> {
144        self.position_3d
145            .map(|pos| (pos[0] * pos[0] + pos[1] * pos[1] + pos[2] * pos[2]).sqrt())
146    }
147
148    /// Returns the age of this detection in microseconds
149    pub fn age_micros(&self) -> u64 {
150        crate::prelude::now_micros().saturating_sub(self.timestamp)
151    }
152}
153
154impl Pose {
155    /// Create a new pose at origin with identity orientation
156    pub fn identity() -> Self {
157        Self {
158            timestamp: crate::prelude::now_micros(),
159            position: [0.0, 0.0, 0.0],
160            orientation: [0.0, 0.0, 0.0, 1.0],
161            frame_id: None,
162            covariance: None,
163        }
164    }
165
166    /// Create a new 2D pose (x, y, yaw)
167    pub fn new_2d(x: f32, y: f32, yaw: f32) -> Self {
168        // Convert yaw to quaternion
169        let half_yaw = yaw / 2.0;
170        let qz = half_yaw.sin();
171        let qw = half_yaw.cos();
172
173        Self {
174            timestamp: crate::prelude::now_micros(),
175            position: [x, y, 0.0],
176            orientation: [0.0, 0.0, qz, qw],
177            frame_id: None,
178            covariance: None,
179        }
180    }
181
182    /// Extract 2D pose (x, y, yaw)
183    pub fn to_2d(&self) -> (f32, f32, f32) {
184        let x = self.position[0];
185        let y = self.position[1];
186
187        // Convert quaternion to yaw
188        let qz = self.orientation[2];
189        let qw = self.orientation[3];
190        let yaw = 2.0 * qz.atan2(qw);
191
192        (x, y, yaw)
193    }
194
195    /// Calculates distance to another pose in 3D space
196    pub fn distance_to(&self, other: &Pose) -> f32 {
197        let dx = self.position[0] - other.position[0];
198        let dy = self.position[1] - other.position[1];
199        let dz = self.position[2] - other.position[2];
200        (dx * dx + dy * dy + dz * dz).sqrt()
201    }
202
203    /// Calculates 2D distance to another pose
204    pub fn distance_2d_to(&self, other: &Pose) -> f32 {
205        let dx = self.position[0] - other.position[0];
206        let dy = self.position[1] - other.position[1];
207        (dx * dx + dy * dy).sqrt()
208    }
209
210    /// Normalizes the orientation quaternion
211    pub fn normalize_orientation(&mut self) {
212        let q = self.orientation;
213        let mag = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]).sqrt();
214        if mag > 0.0 {
215            self.orientation = [q[0] / mag, q[1] / mag, q[2] / mag, q[3] / mag];
216        }
217    }
218}
219
220impl Default for Pose {
221    fn default() -> Self {
222        Self::identity()
223    }
224}
225
226// Helper methods for TrackingResult
227impl TrackingResult {
228    /// Creates a new tracking result
229    pub fn new(track_id: u32, detection: Detection) -> Self {
230        Self {
231            timestamp: crate::prelude::now_micros(),
232            track_id,
233            detection,
234            velocity_2d: None,
235            age: 0,
236            time_since_update: 0,
237            confidence: 1.0,
238        }
239    }
240
241    /// Checks if the track is active (recently updated)
242    pub fn is_active(&self) -> bool {
243        self.time_since_update == 0
244    }
245
246    /// Checks if the track is stale (not updated for several frames)
247    pub fn is_stale(&self, max_age: u32) -> bool {
248        self.time_since_update > max_age
249    }
250
251    /// Checks if the track is mature (tracked for enough frames)
252    pub fn is_mature(&self, min_age: u32) -> bool {
253        self.age >= min_age
254    }
255
256    /// Returns the speed if velocity is available
257    pub fn speed(&self) -> Option<f32> {
258        self.velocity_2d.map(|v| (v[0] * v[0] + v[1] * v[1]).sqrt())
259    }
260
261    /// Checks if confidence is above a threshold
262    pub fn is_confident(&self, threshold: f32) -> bool {
263        self.confidence >= threshold
264    }
265}
266
267/// Tracking result for a single object
268#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct TrackingResult {
270    /// Timestamp in microseconds since epoch
271    pub timestamp: u64,
272
273    /// Unique tracking ID
274    pub track_id: u32,
275
276    /// Current detection
277    pub detection: Detection,
278
279    /// Estimated velocity (x, y) in pixels/second (optional)
280    #[serde(skip_serializing_if = "Option::is_none")]
281    pub velocity_2d: Option<[f32; 2]>,
282
283    /// Number of frames this object has been tracked
284    pub age: u32,
285
286    /// Number of consecutive frames without detection
287    pub time_since_update: u32,
288
289    /// Tracking confidence (0.0 to 1.0)
290    pub confidence: f32,
291}