1pub(crate) const MAX_CANDIDATES: usize = 1024;
3
4#[derive(Debug, Clone, Copy, Default)]
6#[repr(C)]
7pub struct Point2f {
8 pub x: f32,
10 pub y: f32,
12}
13
14#[derive(Debug, Clone, Copy, Default)]
16#[repr(C, align(32))]
17pub struct Matrix3x3 {
18 pub data: [f32; 9],
20 pub padding: [f32; 7],
22}
23
24#[derive(Debug, Clone, Copy, Default)]
26#[repr(C, align(32))]
27pub struct Pose6D {
28 pub data: [f32; 7],
30 pub padding: f32,
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
36#[repr(u8)]
37pub enum CandidateState {
38 #[default]
40 Empty = 0,
41 Active = 1,
43 FailedDecode = 2,
45 Valid = 3,
47}
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
51#[repr(u8)]
52pub enum FunnelStatus {
53 #[default]
55 None = 0,
56 PassedContrast = 1,
58 RejectedContrast = 2,
60 RejectedSampling = 3,
62}
63
64#[repr(C, align(32))]
67pub struct DetectionBatch {
68 pub corners: [[Point2f; 4]; MAX_CANDIDATES],
70 pub homographies: [Matrix3x3; MAX_CANDIDATES],
72 pub ids: [u32; MAX_CANDIDATES],
74 pub payloads: [u64; MAX_CANDIDATES],
76 pub error_rates: [f32; MAX_CANDIDATES],
78 pub poses: [Pose6D; MAX_CANDIDATES],
80 pub status_mask: [CandidateState; MAX_CANDIDATES],
82 pub funnel_status: [FunnelStatus; MAX_CANDIDATES],
84 pub corner_covariances: [[f32; 16]; MAX_CANDIDATES],
87}
88
89impl DetectionBatch {
90 #[must_use]
92 #[allow(clippy::large_stack_arrays)]
93 pub fn new() -> Self {
94 *Box::new(Self {
95 corners: [[Point2f { x: 0.0, y: 0.0 }; 4]; MAX_CANDIDATES],
96 homographies: [Matrix3x3 {
97 data: [0.0; 9],
98 padding: [0.0; 7],
99 }; MAX_CANDIDATES],
100 ids: [0; MAX_CANDIDATES],
101 payloads: [0; MAX_CANDIDATES],
102 error_rates: [0.0; MAX_CANDIDATES],
103 poses: [Pose6D {
104 data: [0.0; 7],
105 padding: 0.0,
106 }; MAX_CANDIDATES],
107 status_mask: [CandidateState::Empty; MAX_CANDIDATES],
108 funnel_status: [FunnelStatus::None; MAX_CANDIDATES],
109 corner_covariances: [[0.0; 16]; MAX_CANDIDATES],
110 })
111 }
112 #[must_use]
114 #[allow(clippy::unused_self)]
115 pub fn capacity(&self) -> usize {
116 MAX_CANDIDATES
117 }
118
119 pub fn partition(&mut self, n: usize) -> usize {
122 let mut v = 0;
123 let n_clamped = n.min(MAX_CANDIDATES);
124
125 for i in 0..n_clamped {
129 if self.status_mask[i] == CandidateState::Valid {
130 if i != v {
131 self.corners.swap(i, v);
133 self.homographies.swap(i, v);
134 self.ids.swap(i, v);
135 self.payloads.swap(i, v);
136 self.error_rates.swap(i, v);
137 self.poses.swap(i, v);
138 self.status_mask.swap(i, v);
139 self.funnel_status.swap(i, v);
140 self.corner_covariances.swap(i, v);
141 }
142 v += 1;
143 }
144 }
145 v
146 }
147
148 #[must_use]
150 #[allow(clippy::cast_sign_loss)]
151 pub fn reassemble(&self, v: usize) -> Vec<crate::Detection> {
152 let mut detections = Vec::with_capacity(v);
153 for i in 0..v {
154 let corners = [
155 [
156 f64::from(self.corners[i][0].x),
157 f64::from(self.corners[i][0].y),
158 ],
159 [
160 f64::from(self.corners[i][1].x),
161 f64::from(self.corners[i][1].y),
162 ],
163 [
164 f64::from(self.corners[i][2].x),
165 f64::from(self.corners[i][2].y),
166 ],
167 [
168 f64::from(self.corners[i][3].x),
169 f64::from(self.corners[i][3].y),
170 ],
171 ];
172
173 let center = [
174 (corners[0][0] + corners[1][0] + corners[2][0] + corners[3][0]) / 4.0,
175 (corners[0][1] + corners[1][1] + corners[2][1] + corners[3][1]) / 4.0,
176 ];
177
178 let pose = if self.poses[i].data[2] > 0.0 {
180 let d = self.poses[i].data;
181 let t = nalgebra::Vector3::new(f64::from(d[0]), f64::from(d[1]), f64::from(d[2]));
183 let q = nalgebra::UnitQuaternion::from_quaternion(nalgebra::Quaternion::new(
184 f64::from(d[6]), f64::from(d[3]), f64::from(d[4]), f64::from(d[5]), ));
189 Some(crate::pose::Pose {
190 rotation: q.to_rotation_matrix().into_inner(),
191 translation: t,
192 })
193 } else {
194 None
195 };
196
197 detections.push(crate::Detection {
198 id: self.ids[i],
199 center,
200 corners,
201 hamming: self.error_rates[i] as u32,
202 rotation: 0,
203 decision_margin: 0.0,
204 bits: self.payloads[i],
205 pose,
206 pose_covariance: None,
207 });
208 }
209 detections
210 }
211}
212
213#[cfg(feature = "bench-internals")]
215pub fn partition_batch_soa(batch: &mut DetectionBatch, n: usize) -> usize {
216 batch.partition(n)
217}
218
219#[derive(Debug, Clone, Copy)]
221pub struct TelemetryPayload {
222 pub binarized_ptr: *const u8,
224 pub threshold_map_ptr: *const u8,
226 pub subpixel_jitter_ptr: *const f32,
229 pub num_jitter: usize,
231 pub reprojection_errors_ptr: *const f32,
234 pub num_reprojection: usize,
236 pub gwlf_fallback_count: usize,
238 pub gwlf_avg_delta: f32,
240 pub width: usize,
242 pub height: usize,
244 pub stride: usize,
246}
247
248#[allow(unsafe_code)]
252unsafe impl Send for TelemetryPayload {}
253
254#[allow(unsafe_code)]
257unsafe impl Sync for TelemetryPayload {}
258
259#[derive(Debug, Clone, Copy)]
264pub struct DetectionBatchView<'a> {
265 pub ids: &'a [u32],
267 pub corners: &'a [[Point2f; 4]],
269 pub homographies: &'a [Matrix3x3],
271 pub payloads: &'a [u64],
273 pub error_rates: &'a [f32],
275 pub poses: &'a [Pose6D],
277 pub corner_covariances: &'a [[f32; 16]],
279 pub telemetry: Option<TelemetryPayload>,
281 pub rejected_corners: &'a [[Point2f; 4]],
283 pub rejected_error_rates: &'a [f32],
285 pub rejected_funnel_status: &'a [FunnelStatus],
287}
288
289impl DetectionBatchView<'_> {
290 #[must_use]
292 pub fn len(&self) -> usize {
293 self.ids.len()
294 }
295
296 #[must_use]
298 pub fn is_empty(&self) -> bool {
299 self.ids.is_empty()
300 }
301
302 #[must_use]
308 #[allow(clippy::cast_sign_loss)]
309 pub fn reassemble_owned(&self) -> Vec<crate::Detection> {
310 let v = self.ids.len();
311 let mut detections = Vec::with_capacity(v);
312 for i in 0..v {
313 let corners = [
314 [
315 f64::from(self.corners[i][0].x),
316 f64::from(self.corners[i][0].y),
317 ],
318 [
319 f64::from(self.corners[i][1].x),
320 f64::from(self.corners[i][1].y),
321 ],
322 [
323 f64::from(self.corners[i][2].x),
324 f64::from(self.corners[i][2].y),
325 ],
326 [
327 f64::from(self.corners[i][3].x),
328 f64::from(self.corners[i][3].y),
329 ],
330 ];
331 let center = [
332 (corners[0][0] + corners[1][0] + corners[2][0] + corners[3][0]) / 4.0,
333 (corners[0][1] + corners[1][1] + corners[2][1] + corners[3][1]) / 4.0,
334 ];
335 let pose = if self.poses[i].data[2] > 0.0 {
336 let d = self.poses[i].data;
337 let t = nalgebra::Vector3::new(f64::from(d[0]), f64::from(d[1]), f64::from(d[2]));
338 let q = nalgebra::UnitQuaternion::from_quaternion(nalgebra::Quaternion::new(
339 f64::from(d[6]),
340 f64::from(d[3]),
341 f64::from(d[4]),
342 f64::from(d[5]),
343 ));
344 Some(crate::pose::Pose {
345 rotation: q.to_rotation_matrix().into_inner(),
346 translation: t,
347 })
348 } else {
349 None
350 };
351 detections.push(crate::Detection {
352 id: self.ids[i],
353 center,
354 corners,
355 hamming: self.error_rates[i] as u32,
356 rotation: 0,
357 decision_margin: 0.0,
358 bits: self.payloads[i],
359 pose,
360 pose_covariance: None,
361 });
362 }
363 detections
364 }
365}
366
367impl DetectionBatch {
368 #[must_use]
370 pub fn view(&self, v: usize) -> DetectionBatchView<'_> {
371 let n = v.min(MAX_CANDIDATES);
372 DetectionBatchView {
373 ids: &self.ids[..n],
374 corners: &self.corners[..n],
375 homographies: &self.homographies[..n],
376 payloads: &self.payloads[..n],
377 error_rates: &self.error_rates[..n],
378 poses: &self.poses[..n],
379 corner_covariances: &self.corner_covariances[..n],
380 telemetry: None,
381 rejected_corners: &[],
382 rejected_error_rates: &[],
383 rejected_funnel_status: &[],
384 }
385 }
386
387 #[must_use]
389 pub fn view_with_telemetry(
390 &self,
391 v: usize,
392 n: usize,
393 telemetry: Option<TelemetryPayload>,
394 ) -> DetectionBatchView<'_> {
395 let v_clamped = v.min(MAX_CANDIDATES);
396 let n_clamped = n.min(MAX_CANDIDATES);
397 DetectionBatchView {
398 ids: &self.ids[..v_clamped],
399 corners: &self.corners[..v_clamped],
400 homographies: &self.homographies[..v_clamped],
401 payloads: &self.payloads[..v_clamped],
402 error_rates: &self.error_rates[..v_clamped],
403 poses: &self.poses[..v_clamped],
404 corner_covariances: &self.corner_covariances[..v_clamped],
405 telemetry,
406 rejected_corners: &self.corners[v_clamped..n_clamped],
407 rejected_error_rates: &self.error_rates[v_clamped..n_clamped],
408 rejected_funnel_status: &self.funnel_status[v_clamped..n_clamped],
409 }
410 }
411}
412
413impl Default for DetectionBatch {
414 fn default() -> Self {
415 Self::new()
416 }
417}