1#[cfg(target_arch = "aarch64")]
19use crate::arg_max_i8;
20use crate::{
21 arg_max, float::jaccard, BBoxTypeTrait, BoundingBox, DetectBoxQuantized, Quantization,
22};
23use ndarray::{
24 parallel::prelude::{IntoParallelIterator, ParallelIterator as _},
25 Array1, ArrayView1, ArrayView2, Zip,
26};
27use num_traits::{AsPrimitive, PrimInt};
28use rayon::slice::ParallelSliceMut;
29
30#[cfg(target_arch = "aarch64")]
41unsafe fn column_max_update_neon(
42 col_ptr: *const u8,
43 max_ptr: *mut u8,
44 class_ptr: *mut u8,
45 n: usize,
46 class_idx: u8,
47 signed: bool,
48) {
49 unsafe {
50 use std::arch::aarch64::*; let class_vec = vdupq_n_u8(class_idx);
53 let chunks = n / 16;
54 let remainder = n % 16;
55
56 if signed {
57 for chunk in 0..chunks {
59 let offset = chunk * 16;
60 let col = vld1q_s8(col_ptr.add(offset) as *const i8);
61 let cur_max = vld1q_s8(max_ptr.add(offset) as *const i8);
62 let mask = vcgeq_s8(col, cur_max);
64 let new_max = vmaxq_s8(col, cur_max);
66 vst1q_s8(max_ptr.add(offset) as *mut i8, new_max);
67 let cur_class = vld1q_u8(class_ptr.add(offset));
69 let new_class = vbslq_u8(mask, class_vec, cur_class);
70 vst1q_u8(class_ptr.add(offset), new_class);
71 }
72 for i in (chunks * 16)..n {
74 let val = *(col_ptr.add(i) as *const i8);
75 let cur = *(max_ptr.add(i) as *const i8);
76 if val >= cur {
77 *(max_ptr.add(i) as *mut i8) = val;
78 *class_ptr.add(i) = class_idx;
79 }
80 }
81 } else {
82 for chunk in 0..chunks {
84 let offset = chunk * 16;
85 let col = vld1q_u8(col_ptr.add(offset));
86 let cur_max = vld1q_u8(max_ptr.add(offset));
87 let mask = vcgeq_u8(col, cur_max);
88 let new_max = vmaxq_u8(col, cur_max);
89 vst1q_u8(max_ptr.add(offset), new_max);
90 let cur_class = vld1q_u8(class_ptr.add(offset));
91 let new_class = vbslq_u8(mask, class_vec, cur_class);
92 vst1q_u8(class_ptr.add(offset), new_class);
93 }
94 for i in (chunks * 16)..n {
96 let val = *col_ptr.add(i);
97 let cur = *max_ptr.add(i);
98 if val >= cur {
99 *max_ptr.add(i) = val;
100 *class_ptr.add(i) = class_idx;
101 }
102 }
103 }
104 let _ = remainder; }
106}
107
108#[cfg(target_arch = "aarch64")]
121#[allow(dead_code)] unsafe fn column_max_update_neon_prefetch(
123 col_ptr: *const u8,
124 max_ptr: *mut u8,
125 class_ptr: *mut u8,
126 n: usize,
127 class_idx: u8,
128 signed: bool,
129) {
130 unsafe {
131 use std::arch::aarch64::*; const PREFETCH_AHEAD: usize = 128; let class_vec = vdupq_n_u8(class_idx);
136 let chunks = n / 16;
137
138 if signed {
139 for chunk in 0..chunks {
140 let offset = chunk * 16;
141 if offset + PREFETCH_AHEAD < n {
143 core::arch::asm!(
144 "prfm pldl1strm, [{ptr}]",
145 ptr = in(reg) col_ptr.add(offset + PREFETCH_AHEAD),
146 options(nostack, preserves_flags),
147 );
148 }
149 let col = vld1q_s8(col_ptr.add(offset) as *const i8);
150 let cur_max = vld1q_s8(max_ptr.add(offset) as *const i8);
151 let mask = vcgeq_s8(col, cur_max);
152 let new_max = vmaxq_s8(col, cur_max);
153 vst1q_s8(max_ptr.add(offset) as *mut i8, new_max);
154 let cur_class = vld1q_u8(class_ptr.add(offset));
155 let new_class = vbslq_u8(mask, class_vec, cur_class);
156 vst1q_u8(class_ptr.add(offset), new_class);
157 }
158 for i in (chunks * 16)..n {
159 let val = *(col_ptr.add(i) as *const i8);
160 let cur = *(max_ptr.add(i) as *const i8);
161 if val >= cur {
162 *(max_ptr.add(i) as *mut i8) = val;
163 *class_ptr.add(i) = class_idx;
164 }
165 }
166 } else {
167 for chunk in 0..chunks {
168 let offset = chunk * 16;
169 if offset + PREFETCH_AHEAD < n {
170 core::arch::asm!(
171 "prfm pldl1strm, [{ptr}]",
172 ptr = in(reg) col_ptr.add(offset + PREFETCH_AHEAD),
173 options(nostack, preserves_flags),
174 );
175 }
176 let col = vld1q_u8(col_ptr.add(offset));
177 let cur_max = vld1q_u8(max_ptr.add(offset));
178 let mask = vcgeq_u8(col, cur_max);
179 let new_max = vmaxq_u8(col, cur_max);
180 vst1q_u8(max_ptr.add(offset), new_max);
181 let cur_class = vld1q_u8(class_ptr.add(offset));
182 let new_class = vbslq_u8(mask, class_vec, cur_class);
183 vst1q_u8(class_ptr.add(offset), new_class);
184 }
185 for i in (chunks * 16)..n {
186 let val = *col_ptr.add(i);
187 let cur = *max_ptr.add(i);
188 if val >= cur {
189 *max_ptr.add(i) = val;
190 *class_ptr.add(i) = class_idx;
191 }
192 }
193 }
194 }
195}
196
197#[inline(always)]
199fn fast_arg_max<T: PrimInt + Copy>(score: ArrayView1<T>) -> (T, usize) {
200 #[cfg(target_arch = "aarch64")]
201 {
202 if std::mem::size_of::<T>() == 1 && score.as_slice().is_some() {
204 let slice = score.as_slice().unwrap();
205 let ptr = slice.as_ptr() as *const i8;
209 let i8_slice = unsafe { std::slice::from_raw_parts(ptr, slice.len()) };
210 if T::min_value() < T::zero() {
213 let (max_val, idx) = arg_max_i8(i8_slice);
214 let result: T = unsafe { std::mem::transmute_copy(&max_val) };
217 return (result, idx);
218 }
219 }
220 }
221 arg_max(score)
222}
223
224#[doc(hidden)]
229pub fn postprocess_boxes_quant<
230 B: BBoxTypeTrait,
231 Boxes: PrimInt + AsPrimitive<f32> + Send + Sync,
232 Scores: PrimInt + AsPrimitive<f32> + Send + Sync,
233>(
234 threshold: Scores,
235 boxes: ArrayView2<Boxes>,
236 scores: ArrayView2<Scores>,
237 quant_boxes: Quantization,
238) -> Vec<DetectBoxQuantized<Scores>> {
239 assert_eq!(scores.dim().0, boxes.dim().0);
240 assert_eq!(boxes.dim().1, 4);
241
242 if scores.strides()[0] == 1 && scores.as_slice().is_none() {
244 return postprocess_boxes_quant_column_major::<B, _, _>(
245 threshold,
246 boxes,
247 scores,
248 quant_boxes,
249 );
250 }
251
252 Zip::from(scores.rows())
253 .and(boxes.rows())
254 .into_par_iter()
255 .filter_map(|(score, bbox)| {
256 let (score_, label) = fast_arg_max(score);
257 if score_ < threshold {
258 return None;
259 }
260
261 let bbox_quant = B::ndarray_to_xyxy_dequant(bbox.view(), quant_boxes);
262 Some(DetectBoxQuantized {
263 label,
264 score: score_,
265 bbox: BoundingBox::from(bbox_quant),
266 })
267 })
268 .collect()
269}
270
271fn postprocess_boxes_quant_column_major<
273 B: BBoxTypeTrait,
274 Boxes: PrimInt + AsPrimitive<f32> + Send + Sync,
275 Scores: PrimInt + AsPrimitive<f32> + Send + Sync,
276>(
277 threshold: Scores,
278 boxes: ArrayView2<Boxes>,
279 scores: ArrayView2<Scores>,
280 quant_boxes: Quantization,
281) -> Vec<DetectBoxQuantized<Scores>> {
282 let (n_candidates, n_classes) = scores.dim();
283
284 if n_classes > 255 {
286 return Zip::from(scores.rows())
287 .and(boxes.rows())
288 .into_par_iter()
289 .filter_map(|(score, bbox)| {
290 let (score_, label) = fast_arg_max(score);
291 if score_ < threshold {
292 return None;
293 }
294 let bbox_quant = B::ndarray_to_xyxy_dequant(bbox.view(), quant_boxes);
295 Some(DetectBoxQuantized {
296 label,
297 score: score_,
298 bbox: BoundingBox::from(bbox_quant),
299 })
300 })
301 .collect();
302 }
303 let mut max_scores = vec![Scores::min_value(); n_candidates];
304 let mut max_classes = vec![0u8; n_candidates];
305
306 for class_idx in 0..n_classes {
307 let col = scores.column(class_idx);
308 if let Some(slice) = col.as_slice() {
309 #[cfg(target_arch = "aarch64")]
310 {
311 if std::mem::size_of::<Scores>() == 1 {
312 unsafe {
313 column_max_update_neon(
316 slice.as_ptr() as *const u8,
317 max_scores.as_mut_ptr() as *mut u8,
318 max_classes.as_mut_ptr(),
319 n_candidates,
320 class_idx as u8,
321 Scores::min_value() < Scores::zero(),
322 );
323 }
324 continue;
325 }
326 }
327 for (i, &val) in slice.iter().enumerate() {
328 if val >= max_scores[i] {
329 max_scores[i] = val;
330 max_classes[i] = class_idx as u8;
331 }
332 }
333 } else {
334 for (i, &val) in col.iter().enumerate() {
335 if val >= max_scores[i] {
336 max_scores[i] = val;
337 max_classes[i] = class_idx as u8;
338 }
339 }
340 }
341 }
342
343 let boxes_buf: [Vec<Boxes>; 4] = if boxes.strides()[0] == 1 && boxes.as_slice().is_none() {
345 let mut cols: [Vec<Boxes>; 4] = [
346 vec![Boxes::zero(); n_candidates],
347 vec![Boxes::zero(); n_candidates],
348 vec![Boxes::zero(); n_candidates],
349 vec![Boxes::zero(); n_candidates],
350 ];
351 for (dim, col_buf) in cols.iter_mut().enumerate() {
352 let col = boxes.column(dim);
353 if let Some(slice) = col.as_slice() {
354 col_buf.copy_from_slice(slice);
355 } else {
356 for (i, &val) in col.iter().enumerate() {
357 col_buf[i] = val;
358 }
359 }
360 }
361 cols
362 } else {
363 [vec![], vec![], vec![], vec![]]
364 };
365 let boxes_copied = !boxes_buf[0].is_empty();
366
367 let mut result = Vec::new();
368 for i in 0..n_candidates {
369 if max_scores[i] >= threshold {
370 let bbox_quant = if boxes_copied {
371 let raw = [
372 boxes_buf[0][i],
373 boxes_buf[1][i],
374 boxes_buf[2][i],
375 boxes_buf[3][i],
376 ];
377 B::to_xyxy_dequant(&raw, quant_boxes)
378 } else {
379 B::ndarray_to_xyxy_dequant(boxes.row(i), quant_boxes)
380 };
381 result.push(DetectBoxQuantized {
382 label: max_classes[i] as usize,
383 score: max_scores[i],
384 bbox: BoundingBox::from(bbox_quant),
385 });
386 }
387 }
388
389 result
390}
391
392#[doc(hidden)]
404pub fn postprocess_boxes_index_quant<
405 B: BBoxTypeTrait,
406 Boxes: PrimInt + AsPrimitive<f32> + Send + Sync,
407 Scores: PrimInt + AsPrimitive<f32> + Send + Sync,
408>(
409 threshold: Scores,
410 boxes: ArrayView2<Boxes>,
411 scores: ArrayView2<Scores>,
412 quant_boxes: Quantization,
413) -> Vec<(DetectBoxQuantized<Scores>, usize)> {
414 assert_eq!(scores.dim().0, boxes.dim().0);
415 assert_eq!(boxes.dim().1, 4);
416
417 if scores.strides()[0] == 1 && scores.as_slice().is_none() {
421 return postprocess_boxes_index_quant_column_major::<B, _, _>(
422 threshold,
423 boxes,
424 scores,
425 quant_boxes,
426 );
427 }
428
429 let indices: Array1<usize> = (0..boxes.dim().0).collect();
430 Zip::from(scores.rows())
431 .and(boxes.rows())
432 .and(&indices)
433 .into_par_iter()
434 .filter_map(|(score, bbox, index)| {
435 let (score_, label) = fast_arg_max(score);
436 if score_ < threshold {
437 return None;
438 }
439
440 let bbox_quant = B::ndarray_to_xyxy_dequant(bbox.view(), quant_boxes);
441
442 Some((
443 DetectBoxQuantized {
444 label,
445 score: score_,
446 bbox: BoundingBox::from(bbox_quant),
447 },
448 *index,
449 ))
450 })
451 .collect()
452}
453
454fn postprocess_boxes_index_quant_column_major<
462 B: BBoxTypeTrait,
463 Boxes: PrimInt + AsPrimitive<f32> + Send + Sync,
464 Scores: PrimInt + AsPrimitive<f32> + Send + Sync,
465>(
466 threshold: Scores,
467 boxes: ArrayView2<Boxes>,
468 scores: ArrayView2<Scores>,
469 quant_boxes: Quantization,
470) -> Vec<(DetectBoxQuantized<Scores>, usize)> {
471 let (n_candidates, n_classes) = scores.dim();
472
473 if n_classes > 255 {
477 let indices: Array1<usize> = (0..n_candidates).collect();
478 return Zip::from(scores.rows())
479 .and(boxes.rows())
480 .and(&indices)
481 .into_par_iter()
482 .filter_map(|(score, bbox, index)| {
483 let (score_, label) = fast_arg_max(score);
484 if score_ < threshold {
485 return None;
486 }
487 let bbox_quant = B::ndarray_to_xyxy_dequant(bbox.view(), quant_boxes);
488 Some((
489 DetectBoxQuantized {
490 label,
491 score: score_,
492 bbox: BoundingBox::from(bbox_quant),
493 },
494 *index,
495 ))
496 })
497 .collect();
498 }
499 let mut max_scores = vec![Scores::min_value(); n_candidates];
500 let mut max_classes = vec![0u8; n_candidates];
501 for class_idx in 0..n_classes {
502 let col = scores.column(class_idx);
503 if let Some(slice) = col.as_slice() {
504 #[cfg(target_arch = "aarch64")]
506 {
507 if std::mem::size_of::<Scores>() == 1 {
508 unsafe {
511 column_max_update_neon(
512 slice.as_ptr() as *const u8,
513 max_scores.as_mut_ptr() as *mut u8,
514 max_classes.as_mut_ptr(),
515 n_candidates,
516 class_idx as u8,
517 Scores::min_value() < Scores::zero(), );
519 }
520 continue;
521 }
522 }
523 for (i, &val) in slice.iter().enumerate() {
524 if val >= max_scores[i] {
525 max_scores[i] = val;
526 max_classes[i] = class_idx as u8;
527 }
528 }
529 } else {
530 for (i, &val) in col.iter().enumerate() {
531 if val >= max_scores[i] {
532 max_scores[i] = val;
533 max_classes[i] = class_idx as u8;
534 }
535 }
536 }
537 }
538
539 let boxes_buf: [Vec<Boxes>; 4] = if boxes.strides()[0] == 1 && boxes.as_slice().is_none() {
543 let mut cols: [Vec<Boxes>; 4] = [
544 vec![Boxes::zero(); n_candidates],
545 vec![Boxes::zero(); n_candidates],
546 vec![Boxes::zero(); n_candidates],
547 vec![Boxes::zero(); n_candidates],
548 ];
549 for (dim, col_buf) in cols.iter_mut().enumerate() {
550 let col = boxes.column(dim);
551 if let Some(slice) = col.as_slice() {
552 col_buf.copy_from_slice(slice);
553 } else {
554 for (i, &val) in col.iter().enumerate() {
555 col_buf[i] = val;
556 }
557 }
558 }
559 cols
560 } else {
561 [vec![], vec![], vec![], vec![]]
563 };
564 let boxes_copied = !boxes_buf[0].is_empty();
565
566 let mut result = Vec::new();
568 for i in 0..n_candidates {
569 if max_scores[i] >= threshold {
570 let bbox_quant = if boxes_copied {
571 let raw = [
572 boxes_buf[0][i],
573 boxes_buf[1][i],
574 boxes_buf[2][i],
575 boxes_buf[3][i],
576 ];
577 B::to_xyxy_dequant(&raw, quant_boxes)
578 } else {
579 B::ndarray_to_xyxy_dequant(boxes.row(i), quant_boxes)
580 };
581 result.push((
582 DetectBoxQuantized {
583 label: max_classes[i] as usize,
584 score: max_scores[i],
585 bbox: BoundingBox::from(bbox_quant),
586 },
587 i,
588 ));
589 }
590 }
591
592 result
593}
594
595#[doc(hidden)]
598#[must_use]
599pub fn nms_int<SCORE: PrimInt + AsPrimitive<f32> + Send + Sync>(
600 iou: f32,
601 max_det: Option<usize>,
602 mut boxes: Vec<DetectBoxQuantized<SCORE>>,
603) -> Vec<DetectBoxQuantized<SCORE>> {
604 boxes.par_sort_by(|a, b| b.score.cmp(&a.score));
608
609 if iou >= 1.0 {
612 return match max_det {
613 Some(n) => {
614 boxes.truncate(n);
615 boxes
616 }
617 None => boxes,
618 };
619 }
620
621 let min_val = SCORE::min_value();
622 let cap = max_det.unwrap_or(usize::MAX);
623 let mut survivors: usize = 0;
624 for i in 0..boxes.len() {
626 if boxes[i].score <= min_val {
627 continue;
629 }
630 for j in (i + 1)..boxes.len() {
631 if boxes[j].score <= min_val {
634 continue;
636 }
637
638 if jaccard(&boxes[j].bbox, &boxes[i].bbox, iou) {
639 boxes[j].score = min_val;
641 }
642 }
643 survivors += 1;
644 if survivors >= cap {
645 break;
646 }
647 }
648 boxes
651 .into_iter()
652 .filter(|b| b.score > min_val)
653 .take(cap)
654 .collect()
655}
656
657#[doc(hidden)]
663#[must_use]
664pub fn nms_extra_int<SCORE: PrimInt + AsPrimitive<f32> + Send + Sync, E: Send + Sync>(
665 iou: f32,
666 max_det: Option<usize>,
667 mut boxes: Vec<(DetectBoxQuantized<SCORE>, E)>,
668) -> Vec<(DetectBoxQuantized<SCORE>, E)> {
669 boxes.par_sort_by(|a, b| b.0.score.cmp(&a.0.score));
672
673 if iou >= 1.0 {
676 return match max_det {
677 Some(n) => {
678 boxes.truncate(n);
679 boxes
680 }
681 None => boxes,
682 };
683 }
684
685 let min_val = SCORE::min_value();
686 let cap = max_det.unwrap_or(usize::MAX);
687 let mut survivors: usize = 0;
688 for i in 0..boxes.len() {
690 if boxes[i].0.score <= min_val {
691 continue;
693 }
694 for j in (i + 1)..boxes.len() {
695 if boxes[j].0.score <= min_val {
698 continue;
700 }
701 if jaccard(&boxes[j].0.bbox, &boxes[i].0.bbox, iou) {
702 boxes[j].0.score = min_val;
704 }
705 }
706 survivors += 1;
707 if survivors >= cap {
708 break;
709 }
710 }
711
712 boxes
714 .into_iter()
715 .filter(|b| b.0.score > min_val)
716 .take(cap)
717 .collect()
718}
719
720#[doc(hidden)]
727#[must_use]
728pub fn nms_class_aware_int<SCORE: PrimInt + AsPrimitive<f32> + Send + Sync>(
729 iou: f32,
730 max_det: Option<usize>,
731 mut boxes: Vec<DetectBoxQuantized<SCORE>>,
732) -> Vec<DetectBoxQuantized<SCORE>> {
733 boxes.par_sort_by(|a, b| b.score.cmp(&a.score));
734
735 if iou >= 1.0 {
738 return match max_det {
739 Some(n) => {
740 boxes.truncate(n);
741 boxes
742 }
743 None => boxes,
744 };
745 }
746
747 let min_val = SCORE::min_value();
748 let cap = max_det.unwrap_or(usize::MAX);
749 let mut survivors: usize = 0;
750 for i in 0..boxes.len() {
751 if boxes[i].score <= min_val {
752 continue;
753 }
754 for j in (i + 1)..boxes.len() {
755 if boxes[j].score <= min_val {
756 continue;
757 }
758 if boxes[j].label == boxes[i].label && jaccard(&boxes[j].bbox, &boxes[i].bbox, iou) {
760 boxes[j].score = min_val;
761 }
762 }
763 survivors += 1;
764 if survivors >= cap {
765 break;
766 }
767 }
768 boxes
769 .into_iter()
770 .filter(|b| b.score > min_val)
771 .take(cap)
772 .collect()
773}
774
775#[doc(hidden)]
781#[must_use]
782pub fn nms_extra_class_aware_int<
783 SCORE: PrimInt + AsPrimitive<f32> + Send + Sync,
784 E: Send + Sync,
785>(
786 iou: f32,
787 max_det: Option<usize>,
788 mut boxes: Vec<(DetectBoxQuantized<SCORE>, E)>,
789) -> Vec<(DetectBoxQuantized<SCORE>, E)> {
790 boxes.par_sort_by(|a, b| b.0.score.cmp(&a.0.score));
791
792 if iou >= 1.0 {
795 return match max_det {
796 Some(n) => {
797 boxes.truncate(n);
798 boxes
799 }
800 None => boxes,
801 };
802 }
803
804 let min_val = SCORE::min_value();
805 let cap = max_det.unwrap_or(usize::MAX);
806 let mut survivors: usize = 0;
807 for i in 0..boxes.len() {
808 if boxes[i].0.score <= min_val {
809 continue;
810 }
811 for j in (i + 1)..boxes.len() {
812 if boxes[j].0.score <= min_val {
813 continue;
814 }
815 if boxes[j].0.label == boxes[i].0.label
817 && jaccard(&boxes[j].0.bbox, &boxes[i].0.bbox, iou)
818 {
819 boxes[j].0.score = min_val;
820 }
821 }
822 survivors += 1;
823 if survivors >= cap {
824 break;
825 }
826 }
827 boxes
828 .into_iter()
829 .filter(|b| b.0.score > min_val)
830 .take(cap)
831 .collect()
832}
833
834#[doc(hidden)]
849pub fn quantize_score_threshold<T: PrimInt + AsPrimitive<f32>>(score: f32, quant: Quantization) -> T
850where
851 f32: AsPrimitive<T>,
852{
853 if quant.scale == 0.0 {
854 return T::max_value();
855 }
856 let v = (score / quant.scale + quant.zero_point as f32).ceil();
857 let v = v.clamp(T::min_value().as_(), T::max_value().as_());
858 v.as_()
859}
860
861#[cfg(test)]
862mod tests {
863 use super::*;
864 use crate::{XYWH, XYXY};
865 use ndarray::Array2;
866
867 #[test]
870 fn column_major_matches_row_major() {
871 let n_classes = 80usize;
873 let n_candidates = 100usize;
874 let mut scores_physical = Array2::<u8>::zeros((n_classes, n_candidates));
875 for c in 0..n_classes {
877 for i in 0..n_candidates {
878 scores_physical[[c, i]] = ((c * 3 + i * 7) % 256) as u8;
879 }
880 }
881
882 let mut boxes_physical = Array2::<i16>::zeros((4, n_candidates));
884 for i in 0..n_candidates {
885 boxes_physical[[0, i]] = (i * 10) as i16; boxes_physical[[1, i]] = (i * 20) as i16; boxes_physical[[2, i]] = (i * 10 + 50) as i16; boxes_physical[[3, i]] = (i * 20 + 100) as i16; }
890
891 let quant = Quantization {
892 scale: 0.00390625,
893 zero_point: 0,
894 };
895
896 let threshold: u8 = 10;
897
898 let scores_contiguous = scores_physical.clone().reversed_axes().to_owned();
900 let boxes_contiguous = boxes_physical.clone().reversed_axes().to_owned();
901 let row_result = postprocess_boxes_index_quant::<XYWH, _, _>(
902 threshold,
903 boxes_contiguous.view(),
904 scores_contiguous.view(),
905 quant,
906 );
907
908 let scores_view = scores_physical.view().reversed_axes();
910 let boxes_view = boxes_physical.view().reversed_axes();
911 assert!(scores_view.as_slice().is_none(), "should be non-contiguous");
912 assert_eq!(scores_view.strides()[0], 1);
913 let col_result =
914 postprocess_boxes_index_quant::<XYWH, _, _>(threshold, boxes_view, scores_view, quant);
915
916 assert_eq!(
918 row_result.len(),
919 col_result.len(),
920 "different number of results: row={}, col={}",
921 row_result.len(),
922 col_result.len()
923 );
924 for (i, (row, col)) in row_result.iter().zip(col_result.iter()).enumerate() {
925 assert_eq!(
926 row.0.label, col.0.label,
927 "candidate {i}: label mismatch row={} col={}",
928 row.0.label, col.0.label
929 );
930 assert_eq!(row.0.score, col.0.score, "candidate {i}: score mismatch");
931 assert_eq!(row.1, col.1, "candidate {i}: index mismatch");
932 assert_eq!(row.0.bbox, col.0.bbox, "candidate {i}: bbox mismatch");
933 }
934 }
935
936 #[test]
938 fn column_major_matches_row_major_i8() {
939 let n_classes = 80usize;
940 let n_candidates = 50usize;
941 let mut scores_physical = Array2::<i8>::zeros((n_classes, n_candidates));
942 for c in 0..n_classes {
943 for i in 0..n_candidates {
944 scores_physical[[c, i]] = ((c as i16 * 3 + i as i16 * 7) % 256 - 128) as i8;
945 }
946 }
947
948 let mut boxes_physical = Array2::<i16>::zeros((4, n_candidates));
949 for i in 0..n_candidates {
950 boxes_physical[[0, i]] = (i * 10) as i16;
951 boxes_physical[[1, i]] = (i * 20) as i16;
952 boxes_physical[[2, i]] = (i * 10 + 50) as i16;
953 boxes_physical[[3, i]] = (i * 20 + 100) as i16;
954 }
955
956 let quant = Quantization {
957 scale: 0.0256,
958 zero_point: -116,
959 };
960 let threshold: i8 = -100;
961
962 let scores_contiguous = scores_physical.clone().reversed_axes().to_owned();
963 let boxes_contiguous = boxes_physical.clone().reversed_axes().to_owned();
964 let row_result = postprocess_boxes_index_quant::<XYWH, _, _>(
965 threshold,
966 boxes_contiguous.view(),
967 scores_contiguous.view(),
968 quant,
969 );
970
971 let scores_view = scores_physical.view().reversed_axes();
972 let boxes_view = boxes_physical.view().reversed_axes();
973 let col_result =
974 postprocess_boxes_index_quant::<XYWH, _, _>(threshold, boxes_view, scores_view, quant);
975
976 assert_eq!(row_result.len(), col_result.len());
977 for (i, (row, col)) in row_result.iter().zip(col_result.iter()).enumerate() {
978 assert_eq!(row.0.label, col.0.label, "i8 candidate {i}: label mismatch");
979 assert_eq!(row.0.score, col.0.score, "i8 candidate {i}: score mismatch");
980 assert_eq!(row.1, col.1, "i8 candidate {i}: index mismatch");
981 }
982 }
983
984 fn make_nms_boxes_int(n: usize) -> Vec<DetectBoxQuantized<u8>> {
986 (0..n)
987 .map(|i| DetectBoxQuantized {
988 bbox: BoundingBox {
989 xmin: i as f32 * 100.0,
990 ymin: 0.0,
991 xmax: i as f32 * 100.0 + 10.0,
992 ymax: 10.0,
993 },
994 label: 0,
995 score: (200 - i as u32).min(255) as u8,
996 })
997 .collect()
998 }
999
1000 fn probe_boxes(n: usize) -> Array2<i16> {
1003 let mut boxes = Array2::<i16>::zeros((n, 4));
1004 for i in 0..n {
1005 boxes[[i, 0]] = i as i16 * 10;
1006 boxes[[i, 1]] = i as i16 * 10 + 1;
1007 boxes[[i, 2]] = i as i16 * 10 + 2;
1008 boxes[[i, 3]] = i as i16 * 10 + 3;
1009 }
1010 boxes
1011 }
1012
1013 fn probe_quant() -> Quantization {
1014 Quantization {
1015 scale: 1.0,
1016 zero_point: 0,
1017 }
1018 }
1019
1020 #[test]
1024 fn postprocess_boxes_quant_keeps_a_score_equal_to_the_threshold() {
1025 let boxes = probe_boxes(2);
1026 let scores = ndarray::array![[200_u8, 0], [199, 0]];
1027
1028 let kept = postprocess_boxes_quant::<XYXY, _, _>(
1029 200_u8,
1030 boxes.view(),
1031 scores.view(),
1032 probe_quant(),
1033 );
1034 assert_eq!(kept.len(), 1, "{kept:?}");
1035 assert_eq!(kept[0].score, 200);
1036
1037 let kept = postprocess_boxes_index_quant::<XYXY, _, _>(
1038 200_u8,
1039 boxes.view(),
1040 scores.view(),
1041 probe_quant(),
1042 );
1043 assert_eq!(kept.len(), 1, "{kept:?}");
1044 assert_eq!(kept[0].1, 0);
1045 }
1046
1047 #[test]
1051 fn column_major_falls_back_beyond_255_classes() {
1052 let n_classes = 300usize;
1053 let boxes = probe_boxes(2);
1054 let mut scores = Array2::<u8>::zeros((2, n_classes));
1055 scores[[0, 260]] = 200;
1056 scores[[1, 3]] = 10;
1057
1058 let kept = postprocess_boxes_quant_column_major::<XYXY, _, _>(
1059 200_u8,
1060 boxes.view(),
1061 scores.view(),
1062 probe_quant(),
1063 );
1064 assert_eq!(kept.len(), 1, "{kept:?}");
1065 assert_eq!(kept[0].label, 260);
1066 assert_eq!(kept[0].score, 200);
1067
1068 let kept = postprocess_boxes_index_quant_column_major::<XYXY, _, _>(
1069 200_u8,
1070 boxes.view(),
1071 scores.view(),
1072 probe_quant(),
1073 );
1074 assert_eq!(kept.len(), 1, "{kept:?}");
1075 assert_eq!(kept[0].0.label, 260);
1076 assert_eq!(kept[0].1, 0);
1077 }
1078
1079 #[test]
1084 fn column_major_scan_handles_row_major_input() {
1085 let boxes = probe_boxes(3);
1086 let scores = ndarray::array![[10_u8, 90, 30, 40], [5, 6, 7, 200], [1, 2, 3, 4]];
1089
1090 let kept = postprocess_boxes_quant_column_major::<XYXY, _, _>(
1091 50_u8,
1092 boxes.view(),
1093 scores.view(),
1094 probe_quant(),
1095 );
1096 assert_eq!(kept.len(), 2, "{kept:?}");
1097 assert_eq!((kept[0].label, kept[0].score), (1, 90));
1098 assert_eq!((kept[1].label, kept[1].score), (3, 200));
1099 assert_eq!(kept[1].bbox.xmin, 10.0);
1101
1102 let kept = postprocess_boxes_index_quant_column_major::<XYXY, _, _>(
1103 50_u8,
1104 boxes.view(),
1105 scores.view(),
1106 probe_quant(),
1107 );
1108 assert_eq!(kept.len(), 2, "{kept:?}");
1109 assert_eq!(kept[0].1, 0);
1110 assert_eq!(kept[1].1, 1);
1111 assert_eq!(kept[1].0.label, 3);
1112 }
1113
1114 fn overlapping_pair_int() -> Vec<DetectBoxQuantized<u8>> {
1118 vec![
1119 DetectBoxQuantized {
1120 bbox: BoundingBox {
1121 xmin: 0.0,
1122 ymin: 0.0,
1123 xmax: 10.0,
1124 ymax: 10.0,
1125 },
1126 label: 0,
1127 score: 200,
1128 },
1129 DetectBoxQuantized {
1130 bbox: BoundingBox {
1131 xmin: 1.0,
1132 ymin: 1.0,
1133 xmax: 11.0,
1134 ymax: 11.0,
1135 },
1136 label: 0,
1137 score: 180,
1138 },
1139 ]
1140 }
1141
1142 fn suppression_chain_int() -> Vec<DetectBoxQuantized<u8>> {
1144 let mk = |xmin: f32, xmax: f32, score: u8| DetectBoxQuantized {
1145 bbox: BoundingBox {
1146 xmin,
1147 ymin: 0.0,
1148 xmax,
1149 ymax: 10.0,
1150 },
1151 label: 0,
1152 score,
1153 };
1154 vec![mk(0.0, 10.0, 200), mk(2.0, 12.0, 180), mk(4.0, 14.0, 160)]
1155 }
1156
1157 #[test]
1158 fn nms_class_aware_int_suppresses_same_class_without_a_cap() {
1159 let result = nms_class_aware_int(0.5, None, overlapping_pair_int());
1160 assert_eq!(result.len(), 1, "{result:?}");
1161 assert_eq!(result[0].score, 200);
1162 }
1163
1164 #[test]
1165 fn nms_class_aware_int_keeps_overlapping_boxes_of_other_classes() {
1166 let mut boxes = overlapping_pair_int();
1167 boxes[1].label = 1;
1168 let result = nms_class_aware_int(0.5, None, boxes);
1169 assert_eq!(result.len(), 2, "{result:?}");
1170 }
1171
1172 #[test]
1173 fn nms_class_aware_int_does_not_suppress_on_behalf_of_a_suppressed_box() {
1174 let result = nms_class_aware_int(0.5, None, suppression_chain_int());
1175 assert_eq!(result.len(), 2, "{result:?}");
1176 assert_eq!(result[0].score, 200);
1177 assert_eq!(result[1].score, 160);
1178 }
1179
1180 #[test]
1184 fn nms_class_aware_int_drops_boxes_already_at_the_minimum_score() {
1185 let mut boxes = overlapping_pair_int();
1186 boxes.push(DetectBoxQuantized {
1187 bbox: BoundingBox {
1188 xmin: 50.0,
1189 ymin: 50.0,
1190 xmax: 60.0,
1191 ymax: 60.0,
1192 },
1193 label: 0,
1194 score: u8::MIN,
1195 });
1196 let result = nms_class_aware_int(0.5, None, boxes);
1197 assert_eq!(result.len(), 1, "{result:?}");
1198 assert_eq!(result[0].score, 200);
1199 }
1200
1201 fn isolated_then_overlapping_pair_int() -> Vec<DetectBoxQuantized<u8>> {
1207 let mk = |xmin: f32, xmax: f32, score: u8| DetectBoxQuantized {
1208 bbox: BoundingBox {
1209 xmin,
1210 ymin: 0.0,
1211 xmax,
1212 ymax: 10.0,
1213 },
1214 label: 0,
1215 score,
1216 };
1217 vec![
1218 mk(100.0, 110.0, 200),
1219 mk(0.0, 10.0, 180),
1220 mk(1.0, 11.0, 160),
1221 ]
1222 }
1223
1224 #[test]
1225 fn nms_class_aware_int_keeps_suppressing_after_the_first_survivor() {
1226 let result = nms_class_aware_int(0.5, None, isolated_then_overlapping_pair_int());
1227 assert_eq!(result.len(), 2, "{result:?}");
1228 assert_eq!(result[0].score, 200);
1229 assert_eq!(result[1].score, 180);
1230 }
1231
1232 fn with_index_int(boxes: Vec<DetectBoxQuantized<u8>>) -> Vec<(DetectBoxQuantized<u8>, usize)> {
1233 boxes.into_iter().enumerate().map(|(i, b)| (b, i)).collect()
1234 }
1235
1236 #[test]
1237 fn nms_extra_class_aware_int_keeps_suppressing_after_the_first_survivor() {
1238 let result = nms_extra_class_aware_int(
1239 0.5,
1240 None,
1241 with_index_int(isolated_then_overlapping_pair_int()),
1242 );
1243 assert_eq!(result.len(), 2, "{result:?}");
1244 assert_eq!(result[1].1, 1);
1245 }
1246
1247 #[test]
1248 fn nms_extra_class_aware_int_suppresses_same_class_without_a_cap() {
1249 let result = nms_extra_class_aware_int(0.5, None, with_index_int(overlapping_pair_int()));
1250 assert_eq!(result.len(), 1, "{result:?}");
1251 assert_eq!(result[0].0.score, 200);
1252 assert_eq!(result[0].1, 0, "the surviving box keeps its extra data");
1253 }
1254
1255 #[test]
1256 fn nms_extra_class_aware_int_keeps_overlapping_boxes_of_other_classes() {
1257 let mut boxes = overlapping_pair_int();
1258 boxes[1].label = 1;
1259 let result = nms_extra_class_aware_int(0.5, None, with_index_int(boxes));
1260 assert_eq!(result.len(), 2, "{result:?}");
1261 }
1262
1263 #[test]
1264 fn nms_extra_class_aware_int_does_not_suppress_on_behalf_of_a_suppressed_box() {
1265 let result = nms_extra_class_aware_int(0.5, None, with_index_int(suppression_chain_int()));
1266 assert_eq!(result.len(), 2, "{result:?}");
1267 assert_eq!(result[0].1, 0);
1268 assert_eq!(result[1].1, 2);
1269 }
1270
1271 #[test]
1272 fn nms_extra_class_aware_int_drops_boxes_already_at_the_minimum_score() {
1273 let mut boxes = overlapping_pair_int();
1274 boxes.push(DetectBoxQuantized {
1275 bbox: BoundingBox {
1276 xmin: 50.0,
1277 ymin: 50.0,
1278 xmax: 60.0,
1279 ymax: 60.0,
1280 },
1281 label: 0,
1282 score: u8::MIN,
1283 });
1284 let result = nms_extra_class_aware_int(0.5, None, with_index_int(boxes));
1285 assert_eq!(result.len(), 1, "{result:?}");
1286 assert_eq!(result[0].0.score, 200);
1287 }
1288
1289 #[test]
1293 fn nms_extra_class_aware_int_keeps_a_lone_box() {
1294 let boxes = with_index_int(vec![overlapping_pair_int().remove(0)]);
1295 let result = nms_extra_class_aware_int(0.5, None, boxes);
1296 assert_eq!(result.len(), 1, "{result:?}");
1297 assert_eq!(result[0].0.score, 200);
1298 }
1299
1300 #[test]
1301 fn nms_int_max_det_matches_full_truncated() {
1302 let boxes = make_nms_boxes_int(20);
1303 let n = 5;
1304 let full = nms_int(0.5, None, boxes.clone());
1305 let capped = nms_int(0.5, Some(n), boxes);
1306 assert_eq!(capped.len(), n);
1307 assert_eq!(&full[..n], &capped[..]);
1308 }
1309
1310 #[test]
1311 fn nms_int_max_det_zero_returns_empty() {
1312 let boxes = make_nms_boxes_int(10);
1313 let result = nms_int(0.5, Some(0), boxes);
1314 assert!(result.is_empty());
1315 }
1316
1317 #[test]
1318 fn nms_int_max_det_iou_ge_1_returns_sorted_truncated() {
1319 let boxes = make_nms_boxes_int(10);
1320 let result = nms_int(1.0, Some(3), boxes);
1321 assert_eq!(result.len(), 3);
1322 assert!(result[0].score >= result[1].score);
1324 assert!(result[1].score >= result[2].score);
1325 }
1326
1327 #[test]
1328 fn nms_int_max_det_larger_than_input() {
1329 let boxes = make_nms_boxes_int(5);
1330 let full = nms_int(0.5, None, boxes.clone());
1331 let capped = nms_int(0.5, Some(100), boxes);
1332 assert_eq!(full.len(), capped.len());
1333 }
1334}