1use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Serialize, Deserialize)]
9pub struct Detection {
10 pub timestamp: u64,
12
13 pub class_id: u32,
15
16 pub class_name: String,
18
19 pub confidence: f32,
21
22 pub bbox: BoundingBox,
24
25 #[serde(skip_serializing_if = "Option::is_none")]
27 pub track_id: Option<u32>,
28
29 #[serde(skip_serializing_if = "Option::is_none")]
31 pub position_3d: Option<[f32; 3]>,
32}
33
34#[derive(Debug, Clone, Serialize, Deserialize)]
36pub struct BoundingBox {
37 pub x: f32,
39
40 pub y: f32,
42
43 pub width: f32,
45
46 pub height: f32,
48}
49
50impl BoundingBox {
51 pub fn new(x: f32, y: f32, width: f32, height: f32) -> Self {
53 Self { x, y, width, height }
54 }
55
56 pub fn center(&self) -> (f32, f32) {
58 (self.x + self.width / 2.0, self.y + self.height / 2.0)
59 }
60
61 pub fn area(&self) -> f32 {
63 self.width * self.height
64 }
65
66 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#[derive(Debug, Clone, Serialize, Deserialize)]
88pub struct Pose {
89 pub timestamp: u64,
91
92 pub position: [f32; 3],
94
95 pub orientation: [f32; 4],
97
98 #[serde(skip_serializing_if = "Option::is_none")]
100 pub frame_id: Option<String>,
101
102 #[serde(skip_serializing_if = "Option::is_none")]
104 pub covariance: Option<Vec<f32>>,
105}
106
107impl Detection {
109 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 pub fn is_confident(&self, threshold: f32) -> bool {
124 self.confidence >= threshold
125 }
126
127 pub fn center(&self) -> (f32, f32) {
129 self.bbox.center()
130 }
131
132 pub fn area(&self) -> f32 {
134 self.bbox.area()
135 }
136
137 pub fn has_3d_position(&self) -> bool {
139 self.position_3d.is_some()
140 }
141
142 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 pub fn age_micros(&self) -> u64 {
150 crate::prelude::now_micros().saturating_sub(self.timestamp)
151 }
152}
153
154impl Pose {
155 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 pub fn new_2d(x: f32, y: f32, yaw: f32) -> Self {
168 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 pub fn to_2d(&self) -> (f32, f32, f32) {
184 let x = self.position[0];
185 let y = self.position[1];
186
187 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 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 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 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
226impl TrackingResult {
228 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 pub fn is_active(&self) -> bool {
243 self.time_since_update == 0
244 }
245
246 pub fn is_stale(&self, max_age: u32) -> bool {
248 self.time_since_update > max_age
249 }
250
251 pub fn is_mature(&self, min_age: u32) -> bool {
253 self.age >= min_age
254 }
255
256 pub fn speed(&self) -> Option<f32> {
258 self.velocity_2d.map(|v| (v[0] * v[0] + v[1] * v[1]).sqrt())
259 }
260
261 pub fn is_confident(&self, threshold: f32) -> bool {
263 self.confidence >= threshold
264 }
265}
266
267#[derive(Debug, Clone, Serialize, Deserialize)]
269pub struct TrackingResult {
270 pub timestamp: u64,
272
273 pub track_id: u32,
275
276 pub detection: Detection,
278
279 #[serde(skip_serializing_if = "Option::is_none")]
281 pub velocity_2d: Option<[f32; 2]>,
282
283 pub age: u32,
285
286 pub time_since_update: u32,
288
289 pub confidence: f32,
291}