1use crate::{arg_max, BBoxTypeTrait, BoundingBox, DetectBox};
5use ndarray::{
6 parallel::prelude::{IntoParallelIterator, ParallelIterator as _},
7 Array1, ArrayView2, Zip,
8};
9use num_traits::{AsPrimitive, Float};
10use rayon::slice::ParallelSliceMut;
11
12pub fn postprocess_boxes_float<
16 B: BBoxTypeTrait,
17 BOX: Float + AsPrimitive<f32> + Send + Sync,
18 SCORE: Float + AsPrimitive<f32> + Send + Sync,
19>(
20 threshold: SCORE,
21 boxes: ArrayView2<BOX>,
22 scores: ArrayView2<SCORE>,
23) -> Vec<DetectBox> {
24 assert_eq!(scores.dim().0, boxes.dim().0);
25 assert_eq!(boxes.dim().1, 4);
26 Zip::from(scores.rows())
27 .and(boxes.rows())
28 .into_par_iter()
29 .filter_map(|(score, bbox)| {
30 let (score_, label) = arg_max(score);
31 if score_ < threshold {
32 return None;
33 }
34
35 let bbox = B::ndarray_to_xyxy_float(bbox);
36 Some(DetectBox {
37 label,
38 score: score_.as_(),
39 bbox: bbox.into(),
40 })
41 })
42 .collect()
43}
44
45pub fn postprocess_boxes_index_float<
52 B: BBoxTypeTrait,
53 BOX: Float + AsPrimitive<f32> + Send + Sync,
54 SCORE: Float + AsPrimitive<f32> + Send + Sync,
55>(
56 threshold: SCORE,
57 boxes: ArrayView2<BOX>,
58 scores: ArrayView2<SCORE>,
59) -> Vec<(DetectBox, usize)> {
60 assert_eq!(scores.dim().0, boxes.dim().0);
61 assert_eq!(boxes.dim().1, 4);
62 let indices: Array1<usize> = (0..boxes.dim().0).collect();
63 Zip::from(scores.rows())
64 .and(boxes.rows())
65 .and(&indices)
66 .into_par_iter()
67 .filter_map(|(score, bbox, i)| {
68 let (score_, label) = arg_max(score);
69 if score_ < threshold {
70 return None;
71 }
72
73 let bbox = B::ndarray_to_xyxy_float(bbox);
74 Some((
75 DetectBox {
76 label,
77 score: score_.as_(),
78 bbox: bbox.into(),
79 },
80 *i,
81 ))
82 })
83 .collect()
84}
85
86pub fn postprocess_boxes_multilabel_index_float<
102 B: BBoxTypeTrait,
103 BOX: Float + AsPrimitive<f32> + Send + Sync,
104 SCORE: Float + AsPrimitive<f32> + Send + Sync,
105>(
106 threshold: SCORE,
107 boxes: ArrayView2<BOX>,
108 scores: ArrayView2<SCORE>,
109) -> Vec<(DetectBox, usize)> {
110 assert_eq!(scores.dim().0, boxes.dim().0);
111 assert_eq!(boxes.dim().1, 4);
112 let n = boxes.dim().0;
113 let indices: Array1<usize> = (0..n).collect();
114 Zip::from(scores.rows())
115 .and(boxes.rows())
116 .and(&indices)
117 .into_par_iter()
118 .flat_map(|(score_row, bbox_row, &anchor_idx)| {
119 let bbox = B::ndarray_to_xyxy_float(bbox_row);
121 let bbox: crate::BoundingBox = bbox.into();
122 score_row
123 .iter()
124 .enumerate()
125 .filter(|(_, &s)| s >= threshold)
126 .map(move |(c, &s)| {
127 (
128 DetectBox {
129 label: c,
130 score: s.as_(),
131 bbox,
132 },
133 anchor_idx,
134 )
135 })
136 .collect::<Vec<_>>()
137 })
138 .collect()
139}
140
141#[must_use]
149pub fn nms_float(iou: f32, max_det: Option<usize>, mut boxes: Vec<DetectBox>) -> Vec<DetectBox> {
150 boxes.par_sort_by(|a, b| b.score.total_cmp(&a.score));
153
154 if iou >= 1.0 {
157 return match max_det {
158 Some(n) => {
159 boxes.truncate(n);
160 boxes
161 }
162 None => boxes,
163 };
164 }
165
166 let cap = max_det.unwrap_or(usize::MAX);
167 let mut survivors: usize = 0;
168
169 for i in 0..boxes.len() {
171 if boxes[i].score < 0.0 {
172 continue;
174 }
175 for j in (i + 1)..boxes.len() {
176 if boxes[j].score < 0.0 {
179 continue;
181 }
182 if jaccard(&boxes[j].bbox, &boxes[i].bbox, iou) {
183 boxes[j].score = -1.0;
185 }
186 }
187
188 survivors += 1;
193 if survivors >= cap {
194 break;
195 }
196 }
197 boxes
200 .into_iter()
201 .filter(|b| b.score >= 0.0)
202 .take(cap)
203 .collect()
204}
205
206#[must_use]
212pub fn nms_extra_float<E: Send + Sync>(
213 iou: f32,
214 max_det: Option<usize>,
215 mut boxes: Vec<(DetectBox, E)>,
216) -> Vec<(DetectBox, E)> {
217 boxes.par_sort_by(|a, b| b.0.score.total_cmp(&a.0.score));
220
221 if iou >= 1.0 {
224 return match max_det {
225 Some(n) => {
226 boxes.truncate(n);
227 boxes
228 }
229 None => boxes,
230 };
231 }
232
233 let cap = max_det.unwrap_or(usize::MAX);
234 let mut survivors: usize = 0;
235
236 for i in 0..boxes.len() {
238 if boxes[i].0.score < 0.0 {
239 continue;
241 }
242 for j in (i + 1)..boxes.len() {
243 if boxes[j].0.score < 0.0 {
246 continue;
248 }
249 if jaccard(&boxes[j].0.bbox, &boxes[i].0.bbox, iou) {
250 boxes[j].0.score = -1.0;
252 }
253 }
254 survivors += 1;
255 if survivors >= cap {
256 break;
257 }
258 }
259
260 boxes
263 .into_iter()
264 .filter(|b| b.0.score >= 0.0)
265 .take(cap)
266 .collect()
267}
268
269#[must_use]
295pub fn nms_class_aware_float(
296 iou: f32,
297 max_det: Option<usize>,
298 mut boxes: Vec<DetectBox>,
299) -> Vec<DetectBox> {
300 boxes.par_sort_by(|a, b| b.score.total_cmp(&a.score));
301
302 if iou >= 1.0 {
303 return match max_det {
304 Some(n) => {
305 boxes.truncate(n);
306 boxes
307 }
308 None => boxes,
309 };
310 }
311
312 let cap = max_det.unwrap_or(usize::MAX);
313 let mut survivors: usize = 0;
314
315 for i in 0..boxes.len() {
316 if boxes[i].score < 0.0 {
317 continue;
318 }
319 for j in (i + 1)..boxes.len() {
320 if boxes[j].score < 0.0 {
321 continue;
322 }
323 if boxes[j].label == boxes[i].label && jaccard(&boxes[j].bbox, &boxes[i].bbox, iou) {
325 boxes[j].score = -1.0;
326 }
327 }
328 survivors += 1;
329 if survivors >= cap {
330 break;
331 }
332 }
333 boxes
334 .into_iter()
335 .filter(|b| b.score >= 0.0)
336 .take(cap)
337 .collect()
338}
339
340#[must_use]
345pub fn nms_extra_class_aware_float<E: Send + Sync>(
346 iou: f32,
347 max_det: Option<usize>,
348 mut boxes: Vec<(DetectBox, E)>,
349) -> Vec<(DetectBox, E)> {
350 boxes.par_sort_by(|a, b| b.0.score.total_cmp(&a.0.score));
351
352 if iou >= 1.0 {
355 return match max_det {
356 Some(n) => {
357 boxes.truncate(n);
358 boxes
359 }
360 None => boxes,
361 };
362 }
363
364 let cap = max_det.unwrap_or(usize::MAX);
365 let mut survivors: usize = 0;
366
367 for i in 0..boxes.len() {
368 if boxes[i].0.score < 0.0 {
369 continue;
370 }
371 for j in (i + 1)..boxes.len() {
372 if boxes[j].0.score < 0.0 {
373 continue;
374 }
375 if boxes[j].0.label == boxes[i].0.label
377 && jaccard(&boxes[j].0.bbox, &boxes[i].0.bbox, iou)
378 {
379 boxes[j].0.score = -1.0;
380 }
381 }
382 survivors += 1;
383 if survivors >= cap {
384 break;
385 }
386 }
387 boxes
388 .into_iter()
389 .filter(|b| b.0.score >= 0.0)
390 .take(cap)
391 .collect()
392}
393
394pub fn jaccard(a: &BoundingBox, b: &BoundingBox, iou: f32) -> bool {
407 let left = a.xmin.max(b.xmin);
408 let top = a.ymin.max(b.ymin);
409 let right = a.xmax.min(b.xmax);
410 let bottom = a.ymax.min(b.ymax);
411
412 let intersection = (right - left).max(0.0) * (bottom - top).max(0.0);
413 let area_a = (a.xmax - a.xmin) * (a.ymax - a.ymin);
414 let area_b = (b.xmax - b.xmin) * (b.ymax - b.ymin);
415
416 let union = area_a + area_b - intersection;
418
419 intersection > iou * union
420}
421
422#[inline]
431pub fn jaccard_batch4(a: &BoundingBox, boxes: &[BoundingBox; 4], iou: f32) -> [bool; 4] {
432 #[cfg(target_arch = "aarch64")]
433 {
434 unsafe { jaccard_batch4_neon(a, boxes, iou) }
436 }
437 #[cfg(not(target_arch = "aarch64"))]
438 {
439 [
440 jaccard(a, &boxes[0], iou),
441 jaccard(a, &boxes[1], iou),
442 jaccard(a, &boxes[2], iou),
443 jaccard(a, &boxes[3], iou),
444 ]
445 }
446}
447
448#[cfg(target_arch = "aarch64")]
454#[target_feature(enable = "neon")]
455unsafe fn jaccard_batch4_neon(a: &BoundingBox, boxes: &[BoundingBox; 4], iou: f32) -> [bool; 4] {
456 use std::arch::aarch64::*;
457
458 let zero = vdupq_n_f32(0.0);
459 let iou_v = vdupq_n_f32(iou);
460
461 let a_xmin = vdupq_n_f32(a.xmin);
463 let a_ymin = vdupq_n_f32(a.ymin);
464 let a_xmax = vdupq_n_f32(a.xmax);
465 let a_ymax = vdupq_n_f32(a.ymax);
466 let area_a = vmulq_f32(vsubq_f32(a_xmax, a_xmin), vsubq_f32(a_ymax, a_ymin));
467
468 let b0 = vld1q_f32(&boxes[0].xmin as *const f32);
470 let b1 = vld1q_f32(&boxes[1].xmin as *const f32);
471 let b2 = vld1q_f32(&boxes[2].xmin as *const f32);
472 let b3 = vld1q_f32(&boxes[3].xmin as *const f32);
473
474 let t01_lo = vtrn1q_f32(b0, b1); let t01_hi = vtrn2q_f32(b0, b1); let t23_lo = vtrn1q_f32(b2, b3);
478 let t23_hi = vtrn2q_f32(b2, b3);
479
480 let b_xmin = vreinterpretq_f32_f64(vtrn1q_f64(
481 vreinterpretq_f64_f32(t01_lo),
482 vreinterpretq_f64_f32(t23_lo),
483 ));
484 let b_ymin = vreinterpretq_f32_f64(vtrn1q_f64(
485 vreinterpretq_f64_f32(t01_hi),
486 vreinterpretq_f64_f32(t23_hi),
487 ));
488 let b_xmax = vreinterpretq_f32_f64(vtrn2q_f64(
489 vreinterpretq_f64_f32(t01_lo),
490 vreinterpretq_f64_f32(t23_lo),
491 ));
492 let b_ymax = vreinterpretq_f32_f64(vtrn2q_f64(
493 vreinterpretq_f64_f32(t01_hi),
494 vreinterpretq_f64_f32(t23_hi),
495 ));
496
497 let left = vmaxq_f32(a_xmin, b_xmin);
499 let top = vmaxq_f32(a_ymin, b_ymin);
500 let right = vminq_f32(a_xmax, b_xmax);
501 let bottom = vminq_f32(a_ymax, b_ymax);
502 let w = vmaxq_f32(vsubq_f32(right, left), zero);
503 let h = vmaxq_f32(vsubq_f32(bottom, top), zero);
504 let intersection = vmulq_f32(w, h);
505
506 let area_b = vmulq_f32(vsubq_f32(b_xmax, b_xmin), vsubq_f32(b_ymax, b_ymin));
508
509 let union = vsubq_f32(vaddq_f32(area_a, area_b), intersection);
511
512 let iou_union = vmulq_f32(iou_v, union);
514 let mask = vcgtq_f32(intersection, iou_union);
515
516 [
518 vgetq_lane_u32(mask, 0) != 0,
519 vgetq_lane_u32(mask, 1) != 0,
520 vgetq_lane_u32(mask, 2) != 0,
521 vgetq_lane_u32(mask, 3) != 0,
522 ]
523}
524
525#[cfg(test)]
526mod tests {
527 use super::*;
528 use crate::BoundingBox;
529
530 fn make_nms_boxes_float(n: usize) -> Vec<DetectBox> {
532 (0..n)
533 .map(|i| DetectBox {
534 bbox: BoundingBox {
535 xmin: i as f32 * 100.0,
536 ymin: 0.0,
537 xmax: i as f32 * 100.0 + 10.0,
538 ymax: 10.0,
539 },
540 label: 0,
541 score: 1.0 - i as f32 * 0.01,
542 })
543 .collect()
544 }
545
546 #[test]
547 fn nms_float_max_det_matches_full_truncated() {
548 let boxes = make_nms_boxes_float(20);
549 let n = 5;
550 let full = nms_float(0.5, None, boxes.clone());
551 let capped = nms_float(0.5, Some(n), boxes);
552 assert_eq!(capped.len(), n);
553 for (f, c) in full[..n].iter().zip(capped.iter()) {
554 assert_eq!(f.bbox, c.bbox);
555 assert_eq!(f.score, c.score);
556 }
557 }
558
559 #[test]
560 fn nms_float_max_det_zero_returns_empty() {
561 let boxes = make_nms_boxes_float(10);
562 let result = nms_float(0.5, Some(0), boxes);
563 assert!(result.is_empty());
564 }
565
566 #[test]
567 fn nms_float_max_det_iou_ge_1_returns_sorted_truncated() {
568 let boxes = make_nms_boxes_float(10);
569 let result = nms_float(1.0, Some(3), boxes);
570 assert_eq!(result.len(), 3);
571 assert!(result[0].score >= result[1].score);
572 assert!(result[1].score >= result[2].score);
573 }
574
575 #[test]
576 fn nms_float_max_det_larger_than_input() {
577 let boxes = make_nms_boxes_float(5);
578 let full = nms_float(0.5, None, boxes.clone());
579 let capped = nms_float(0.5, Some(100), boxes);
580 assert_eq!(full.len(), capped.len());
581 }
582
583 #[test]
584 fn jaccard_batch4_matches_scalar() {
585 let a = BoundingBox::new(0.0, 0.0, 10.0, 10.0);
586 let boxes = [
587 BoundingBox::new(5.0, 5.0, 15.0, 15.0), BoundingBox::new(20.0, 20.0, 30.0, 30.0), BoundingBox::new(0.0, 0.0, 10.0, 10.0), BoundingBox::new(8.0, 8.0, 18.0, 18.0), ];
592 let iou_threshold = 0.1;
593 let batch = jaccard_batch4(&a, &boxes, iou_threshold);
594 for (i, b) in boxes.iter().enumerate() {
595 let scalar = jaccard(&a, b, iou_threshold);
596 assert_eq!(
597 batch[i], scalar,
598 "batch4 mismatch at {i}: batch={} scalar={}",
599 batch[i], scalar
600 );
601 }
602 }
603}