1use std::fmt::Debug;
13
14use ndarray::{
15 parallel::prelude::{IntoParallelIterator, ParallelIterator},
16 s, Array2, Array3, ArrayView1, ArrayView2, ArrayView3,
17};
18use num_traits::{AsPrimitive, Float, PrimInt, Signed};
19
20use crate::{
21 byte::{
22 nms_class_aware_int, nms_extra_class_aware_int, nms_extra_int, nms_int,
23 postprocess_boxes_index_quant, postprocess_boxes_multilabel_index_quant,
24 postprocess_boxes_multilabel_quant, postprocess_boxes_quant, quantize_score_threshold,
25 },
26 configs::Nms,
27 dequant_detect_box,
28 float::{
29 nms_class_aware_float, nms_extra_class_aware_float, nms_extra_float, nms_float,
30 postprocess_boxes_float, postprocess_boxes_index_float, postprocess_boxes_multilabel_float,
31 postprocess_boxes_multilabel_index_float,
32 },
33 BBoxTypeTrait, BoundingBox, DetectBox, DetectBoxQuantized, ProtoData, ProtoLayout,
34 Quantization, Segmentation, XYWH, XYXY,
35};
36
37#[cfg(test)]
55pub(crate) const MAX_NMS_CANDIDATES: usize = 30_000;
56
57#[cfg(test)]
60pub(crate) const DEFAULT_MAX_DETECTIONS: usize = 300;
61
62pub(crate) trait ScoredCandidate: Sized + Send + Sync {
66 type Score: Copy;
69
70 fn score_key(&self) -> Self::Score;
71
72 fn cmp_score(a: &Self::Score, b: &Self::Score) -> std::cmp::Ordering;
74
75 fn suppress(mode: Nms, iou: f32, max_det: usize, boxes: Vec<Self>) -> Vec<Self>;
77}
78
79impl ScoredCandidate for DetectBox {
80 type Score = f32;
81 fn score_key(&self) -> f32 {
82 self.score
83 }
84 fn cmp_score(a: &f32, b: &f32) -> std::cmp::Ordering {
85 a.total_cmp(b)
86 }
87 fn suppress(mode: Nms, iou: f32, max_det: usize, boxes: Vec<Self>) -> Vec<Self> {
88 dispatch_nms_float(Some(mode), iou, Some(max_det), boxes)
89 }
90}
91
92impl<S: PrimInt + AsPrimitive<f32> + Send + Sync> ScoredCandidate for DetectBoxQuantized<S> {
93 type Score = S;
94 fn score_key(&self) -> S {
95 self.score
96 }
97 fn cmp_score(a: &S, b: &S) -> std::cmp::Ordering {
98 a.cmp(b)
99 }
100 fn suppress(mode: Nms, iou: f32, max_det: usize, boxes: Vec<Self>) -> Vec<Self> {
101 dispatch_nms_int(Some(mode), iou, Some(max_det), boxes)
102 }
103}
104
105impl<E: Send + Sync> ScoredCandidate for (DetectBox, E) {
106 type Score = f32;
107 fn score_key(&self) -> f32 {
108 self.0.score
109 }
110 fn cmp_score(a: &f32, b: &f32) -> std::cmp::Ordering {
111 a.total_cmp(b)
112 }
113 fn suppress(mode: Nms, iou: f32, max_det: usize, boxes: Vec<Self>) -> Vec<Self> {
114 dispatch_nms_extra_float(Some(mode), iou, Some(max_det), boxes)
115 }
116}
117
118impl<S: PrimInt + AsPrimitive<f32> + Send + Sync, E: Send + Sync> ScoredCandidate
119 for (DetectBoxQuantized<S>, E)
120{
121 type Score = S;
122 fn score_key(&self) -> S {
123 self.0.score
124 }
125 fn cmp_score(a: &S, b: &S) -> std::cmp::Ordering {
126 a.cmp(b)
127 }
128 fn suppress(mode: Nms, iou: f32, max_det: usize, boxes: Vec<Self>) -> Vec<Self> {
129 dispatch_nms_extra_int(Some(mode), iou, Some(max_det), boxes)
130 }
131}
132
133pub(crate) fn truncate_to_top_k_by_score<T: ScoredCandidate>(boxes: &mut Vec<T>, top_k: usize) {
141 use std::cmp::Ordering;
142 if top_k == 0 || boxes.len() <= top_k {
143 return;
144 }
145 let mut scores: Vec<T::Score> = boxes.iter().map(T::score_key).collect();
146 let (higher, kth, _) = scores.select_nth_unstable_by(top_k - 1, |a, b| T::cmp_score(b, a));
147 let kth = *kth;
148 let above = higher
149 .iter()
150 .filter(|s| T::cmp_score(s, &kth) == Ordering::Greater)
151 .count();
152 let mut ties = top_k - above;
153 boxes.retain(|b| match T::cmp_score(&b.score_key(), &kth) {
154 Ordering::Greater => true,
155 Ordering::Equal if ties > 0 => {
156 ties -= 1;
157 true
158 }
159 _ => false,
160 });
161}
162
163pub(crate) fn nms_and_cap<T: ScoredCandidate>(
171 mut boxes: Vec<T>,
172 nms: Option<Nms>,
173 iou_threshold: f32,
174 pre_nms_top_k: usize,
175 max_det: usize,
176) -> Vec<T> {
177 let mut boxes = match nms {
178 Some(mode) => {
179 {
180 let _s = tracing::trace_span!(
181 "decoder.nms_get_boxes.top_k",
182 k = pre_nms_top_k,
183 n = boxes.len()
184 )
185 .entered();
186 truncate_to_top_k_by_score(&mut boxes, pre_nms_top_k);
187 }
188 let _s =
189 tracing::trace_span!("decoder.nms_get_boxes.suppress", n = boxes.len()).entered();
190 T::suppress(mode, iou_threshold, max_det, boxes)
191 }
192 None => {
193 truncate_to_top_k_by_score(&mut boxes, max_det);
194 boxes.sort_by(|a, b| T::cmp_score(&b.score_key(), &a.score_key()));
195 boxes
196 }
197 };
198 boxes.truncate(max_det);
199 boxes
200}
201
202pub(crate) fn dispatch_nms_float(
212 nms: Option<Nms>,
213 iou: f32,
214 max_det: Option<usize>,
215 boxes: Vec<DetectBox>,
216) -> Vec<DetectBox> {
217 debug_assert_ne!(
218 nms,
219 Some(Nms::Auto),
220 "Nms::Auto must be resolved by the builder"
221 );
222 match nms {
223 Some(Nms::ClassAgnostic) => nms_float(iou, max_det, boxes),
224 Some(Nms::ClassAware | Nms::Auto) => nms_class_aware_float(iou, max_det, boxes),
225 None => boxes, }
227}
228
229pub(super) fn dispatch_nms_extra_float<E: Send + Sync>(
232 nms: Option<Nms>,
233 iou: f32,
234 max_det: Option<usize>,
235 boxes: Vec<(DetectBox, E)>,
236) -> Vec<(DetectBox, E)> {
237 debug_assert_ne!(
238 nms,
239 Some(Nms::Auto),
240 "Nms::Auto must be resolved by the builder"
241 );
242 match nms {
243 Some(Nms::ClassAgnostic) => nms_extra_float(iou, max_det, boxes),
244 Some(Nms::ClassAware | Nms::Auto) => nms_extra_class_aware_float(iou, max_det, boxes),
245 None => boxes, }
247}
248
249pub(crate) fn dispatch_nms_int<SCORE: PrimInt + AsPrimitive<f32> + Send + Sync>(
252 nms: Option<Nms>,
253 iou: f32,
254 max_det: Option<usize>,
255 boxes: Vec<DetectBoxQuantized<SCORE>>,
256) -> Vec<DetectBoxQuantized<SCORE>> {
257 debug_assert_ne!(
258 nms,
259 Some(Nms::Auto),
260 "Nms::Auto must be resolved by the builder"
261 );
262 match nms {
263 Some(Nms::ClassAgnostic) => nms_int(iou, max_det, boxes),
264 Some(Nms::ClassAware | Nms::Auto) => nms_class_aware_int(iou, max_det, boxes),
265 None => boxes, }
267}
268
269fn dispatch_nms_extra_int<SCORE: PrimInt + AsPrimitive<f32> + Send + Sync, E: Send + Sync>(
272 nms: Option<Nms>,
273 iou: f32,
274 max_det: Option<usize>,
275 boxes: Vec<(DetectBoxQuantized<SCORE>, E)>,
276) -> Vec<(DetectBoxQuantized<SCORE>, E)> {
277 debug_assert_ne!(
278 nms,
279 Some(Nms::Auto),
280 "Nms::Auto must be resolved by the builder"
281 );
282 match nms {
283 Some(Nms::ClassAgnostic) => nms_extra_int(iou, max_det, boxes),
284 Some(Nms::ClassAware | Nms::Auto) => nms_extra_class_aware_int(iou, max_det, boxes),
285 None => boxes, }
287}
288
289#[cfg(test)]
292#[inline]
293pub(crate) fn cap_or_default<T>(v: &Vec<T>) -> usize {
294 if v.capacity() > 0 {
295 v.capacity()
296 } else {
297 DEFAULT_MAX_DETECTIONS
298 }
299}
300
301#[allow(clippy::too_many_arguments)]
322pub(crate) fn decode_yolo_det<BOX: PrimInt + AsPrimitive<f32> + Send + Sync>(
323 output: (ArrayView2<BOX>, Quantization),
324 score_threshold: f32,
325 iou_threshold: f32,
326 nms: Option<Nms>,
327 pre_nms_top_k: usize,
328 max_det: usize,
329 multi_label: bool,
330 output_boxes: &mut Vec<DetectBox>,
331) -> crate::DecoderResult<()>
332where
333 f32: AsPrimitive<BOX>,
334{
335 impl_yolo_quant::<XYWH, _>(
336 output,
337 score_threshold,
338 iou_threshold,
339 nms,
340 pre_nms_top_k,
341 max_det,
342 multi_label,
343 output_boxes,
344 )
345}
346
347#[allow(clippy::too_many_arguments)]
358pub(crate) fn decode_yolo_det_float<T>(
359 output: ArrayView2<T>,
360 score_threshold: f32,
361 iou_threshold: f32,
362 nms: Option<Nms>,
363 pre_nms_top_k: usize,
364 max_det: usize,
365 multi_label: bool,
366 output_boxes: &mut Vec<DetectBox>,
367) -> crate::DecoderResult<()>
368where
369 T: Float + AsPrimitive<f32> + Send + Sync + 'static,
370 f32: AsPrimitive<T>,
371{
372 impl_yolo_float::<XYWH, _>(
373 output,
374 score_threshold,
375 iou_threshold,
376 nms,
377 pre_nms_top_k,
378 max_det,
379 multi_label,
380 output_boxes,
381 )
382}
383
384#[cfg(test)]
398pub(crate) fn decode_yolo_segdet_quant<
399 BOX: PrimInt + AsPrimitive<i64> + AsPrimitive<i128> + AsPrimitive<f32> + Send + Sync,
400 PROTO: PrimInt + AsPrimitive<i64> + AsPrimitive<i128> + AsPrimitive<f32> + Send + Sync,
401>(
402 boxes: (ArrayView2<BOX>, Quantization),
403 protos: (ArrayView3<PROTO>, Quantization),
404 score_threshold: f32,
405 iou_threshold: f32,
406 nms: Option<Nms>,
407 output_boxes: &mut Vec<DetectBox>,
408 output_masks: &mut Vec<Segmentation>,
409) -> Result<(), crate::DecoderError>
410where
411 f32: AsPrimitive<BOX>,
412{
413 let cap = cap_or_default(output_boxes);
418 impl_yolo_segdet_quant::<XYWH, _, _>(
419 boxes,
420 protos,
421 score_threshold,
422 iou_threshold,
423 nms,
424 MAX_NMS_CANDIDATES,
425 cap,
426 None,
427 None,
428 false, output_boxes,
430 output_masks,
431 )
432}
433
434#[cfg(test)]
436pub(crate) fn decode_yolo_segdet_float<T>(
437 boxes: ArrayView2<T>,
438 protos: ArrayView3<T>,
439 score_threshold: f32,
440 iou_threshold: f32,
441 nms: Option<Nms>,
442 output_boxes: &mut Vec<DetectBox>,
443 output_masks: &mut Vec<Segmentation>,
444) -> Result<(), crate::DecoderError>
445where
446 T: Float + AsPrimitive<f32> + Send + Sync + 'static,
447 f32: AsPrimitive<T>,
448{
449 let cap = cap_or_default(output_boxes);
452 impl_yolo_segdet_float::<XYWH, _, _>(
453 boxes,
454 protos,
455 score_threshold,
456 iou_threshold,
457 nms,
458 MAX_NMS_CANDIDATES,
459 cap,
460 None,
461 None,
462 false, output_boxes,
464 output_masks,
465 )
466}
467
468#[allow(clippy::too_many_arguments)]
484pub(crate) fn decode_yolo_split_det_quant<
485 BOX: PrimInt + AsPrimitive<i32> + AsPrimitive<f32> + Send + Sync,
486 SCORE: PrimInt + AsPrimitive<f32> + Send + Sync,
487>(
488 boxes: (ArrayView2<BOX>, Quantization),
489 scores: (ArrayView2<SCORE>, Quantization),
490 score_threshold: f32,
491 iou_threshold: f32,
492 nms: Option<Nms>,
493 pre_nms_top_k: usize,
494 max_det: usize,
495 multi_label: bool,
496 output_boxes: &mut Vec<DetectBox>,
497) -> crate::DecoderResult<()>
498where
499 f32: AsPrimitive<SCORE>,
500{
501 impl_yolo_split_quant::<XYWH, _, _>(
502 boxes,
503 scores,
504 score_threshold,
505 iou_threshold,
506 nms,
507 pre_nms_top_k,
508 max_det,
509 multi_label,
510 output_boxes,
511 )
512}
513
514#[allow(clippy::too_many_arguments)]
530pub(crate) fn decode_yolo_split_det_float<T>(
531 boxes: ArrayView2<T>,
532 scores: ArrayView2<T>,
533 score_threshold: f32,
534 iou_threshold: f32,
535 nms: Option<Nms>,
536 pre_nms_top_k: usize,
537 max_det: usize,
538 multi_label: bool,
539 output_boxes: &mut Vec<DetectBox>,
540) -> crate::DecoderResult<()>
541where
542 T: Float + AsPrimitive<f32> + Send + Sync + 'static,
543 f32: AsPrimitive<T>,
544{
545 impl_yolo_split_float::<XYWH, _, _>(
546 boxes,
547 scores,
548 score_threshold,
549 iou_threshold,
550 nms,
551 pre_nms_top_k,
552 max_det,
553 multi_label,
554 output_boxes,
555 )
556}
557
558pub(crate) fn decode_yolo_end_to_end_det_float<T>(
573 output: ArrayView2<T>,
574 score_threshold: f32,
575 max_det: usize,
576 output_boxes: &mut Vec<DetectBox>,
577) -> Result<(), crate::DecoderError>
578where
579 T: Float + AsPrimitive<f32> + Send + Sync + 'static,
580 f32: AsPrimitive<T>,
581{
582 if output.shape()[0] < 6 {
584 return Err(crate::DecoderError::InvalidShape(format!(
585 "End-to-end detection output requires at least 6 rows, got {}",
586 output.shape()[0]
587 )));
588 }
589
590 let boxes = output.slice(s![0..4, ..]).reversed_axes();
592 let scores = output.slice(s![4..5, ..]).reversed_axes();
593 let classes = output.slice(s![5, ..]);
594 let mut boxes =
595 postprocess_boxes_index_float::<XYXY, _, _>(score_threshold.as_(), boxes, scores);
596 boxes.truncate(max_det);
597 output_boxes.clear();
598 for (mut b, i) in boxes.into_iter() {
599 b.label = classes[i].as_() as usize;
600 output_boxes.push(b);
601 }
602 Ok(())
604}
605
606pub(crate) fn decode_yolo_end_to_end_segdet_float<T>(
624 output: ArrayView2<T>,
625 protos: ArrayView3<T>,
626 score_threshold: f32,
627 max_det: usize,
628 output_boxes: &mut Vec<DetectBox>,
629 output_masks: &mut Vec<crate::Segmentation>,
630) -> Result<(), crate::DecoderError>
631where
632 T: Float + AsPrimitive<f32> + Send + Sync + 'static,
633 f32: AsPrimitive<T>,
634{
635 let (boxes, scores, classes, mask_coeff) =
636 postprocess_yolo_end_to_end_segdet(&output, protos.dim().2)?;
637 let cap = max_det;
638 let boxes = impl_yolo_end_to_end_segdet_get_boxes::<XYXY, _, _, _>(
639 boxes,
640 scores,
641 classes,
642 score_threshold,
643 cap,
644 );
645
646 impl_yolo_split_segdet_process_masks(boxes, mask_coeff, protos, output_boxes, output_masks)
649}
650
651pub(crate) fn decode_yolo_split_end_to_end_det_float<T: Float + AsPrimitive<f32>>(
660 boxes: ArrayView2<T>,
661 scores: ArrayView2<T>,
662 classes: ArrayView2<T>,
663 score_threshold: f32,
664 max_det: usize,
665 output_boxes: &mut Vec<DetectBox>,
666) -> Result<(), crate::DecoderError> {
667 let n = boxes.shape()[1];
668
669 let cap = max_det;
670 output_boxes.clear();
671
672 let (boxes, scores, classes) = postprocess_yolo_split_end_to_end_det(boxes, scores, &classes)?;
673
674 for i in 0..n {
675 let score: f32 = scores[[i, 0]].as_();
676 if score < score_threshold {
677 continue;
678 }
679 if output_boxes.len() >= cap {
680 break;
681 }
682 output_boxes.push(DetectBox {
683 bbox: BoundingBox {
684 xmin: boxes[[i, 0]].as_(),
685 ymin: boxes[[i, 1]].as_(),
686 xmax: boxes[[i, 2]].as_(),
687 ymax: boxes[[i, 3]].as_(),
688 },
689 score,
690 label: classes[i].as_() as usize,
691 });
692 }
693 Ok(())
694}
695
696#[allow(clippy::too_many_arguments)]
705pub(crate) fn decode_yolo_split_end_to_end_segdet_float<T>(
706 boxes: ArrayView2<T>,
707 scores: ArrayView2<T>,
708 classes: ArrayView2<T>,
709 mask_coeff: ArrayView2<T>,
710 protos: ArrayView3<T>,
711 score_threshold: f32,
712 max_det: usize,
713 output_boxes: &mut Vec<DetectBox>,
714 output_masks: &mut Vec<crate::Segmentation>,
715) -> Result<(), crate::DecoderError>
716where
717 T: Float + AsPrimitive<f32> + Send + Sync + 'static,
718 f32: AsPrimitive<T>,
719{
720 let (boxes, scores, classes, mask_coeff) =
721 postprocess_yolo_split_end_to_end_segdet(boxes, scores, &classes, mask_coeff)?;
722 let cap = max_det;
723 let boxes = impl_yolo_end_to_end_segdet_get_boxes::<XYXY, _, _, _>(
724 boxes,
725 scores,
726 classes,
727 score_threshold,
728 cap,
729 );
730
731 impl_yolo_split_segdet_process_masks(boxes, mask_coeff, protos, output_boxes, output_masks)
732}
733
734#[allow(clippy::type_complexity)]
735pub(crate) fn postprocess_yolo_end_to_end_segdet<'a, T>(
736 output: &'a ArrayView2<'_, T>,
737 num_protos: usize,
738) -> Result<
739 (
740 ArrayView2<'a, T>,
741 ArrayView2<'a, T>,
742 ArrayView1<'a, T>,
743 ArrayView2<'a, T>,
744 ),
745 crate::DecoderError,
746> {
747 if output.shape()[0] < 7 {
749 return Err(crate::DecoderError::InvalidShape(format!(
750 "End-to-end segdet output requires at least 7 rows, got {}",
751 output.shape()[0]
752 )));
753 }
754
755 let num_mask_coeffs = output.shape()[0] - 6;
756 if num_mask_coeffs != num_protos {
757 return Err(crate::DecoderError::InvalidShape(format!(
758 "Mask coefficients count ({}) doesn't match protos count ({})",
759 num_mask_coeffs, num_protos
760 )));
761 }
762
763 let boxes = output.slice(s![0..4, ..]).reversed_axes();
765 let scores = output.slice(s![4..5, ..]).reversed_axes();
766 let classes = output.slice(s![5, ..]);
767 let mask_coeff = output.slice(s![6.., ..]).reversed_axes();
768 Ok((boxes, scores, classes, mask_coeff))
769}
770
771#[allow(clippy::type_complexity)]
778pub(crate) fn postprocess_yolo_split_end_to_end_det<'a, 'b, 'c, BOXES, SCORES, CLASS>(
779 boxes: ArrayView2<'a, BOXES>,
780 scores: ArrayView2<'b, SCORES>,
781 classes: &'c ArrayView2<CLASS>,
782) -> Result<
783 (
784 ArrayView2<'a, BOXES>,
785 ArrayView2<'b, SCORES>,
786 ArrayView1<'c, CLASS>,
787 ),
788 crate::DecoderError,
789> {
790 let num_boxes = boxes.shape()[1];
791 if boxes.shape()[0] != 4 {
792 return Err(crate::DecoderError::InvalidShape(format!(
793 "Split end-to-end box_coords must be 4, got {}",
794 boxes.shape()[0]
795 )));
796 }
797
798 if scores.shape()[0] != 1 {
799 return Err(crate::DecoderError::InvalidShape(format!(
800 "Split end-to-end scores num_classes must be 1, got {}",
801 scores.shape()[0]
802 )));
803 }
804
805 if classes.shape()[0] != 1 {
806 return Err(crate::DecoderError::InvalidShape(format!(
807 "Split end-to-end classes num_classes must be 1, got {}",
808 classes.shape()[0]
809 )));
810 }
811
812 if scores.shape()[1] != num_boxes {
813 return Err(crate::DecoderError::InvalidShape(format!(
814 "Split end-to-end scores must have same num_boxes as boxes ({}), got {}",
815 num_boxes,
816 scores.shape()[1]
817 )));
818 }
819
820 if classes.shape()[1] != num_boxes {
821 return Err(crate::DecoderError::InvalidShape(format!(
822 "Split end-to-end classes must have same num_boxes as boxes ({}), got {}",
823 num_boxes,
824 classes.shape()[1]
825 )));
826 }
827
828 let boxes = boxes.reversed_axes();
829 let scores = scores.reversed_axes();
830 let classes = classes.slice(s![0, ..]);
831 Ok((boxes, scores, classes))
832}
833
834#[allow(clippy::type_complexity)]
837pub(crate) fn postprocess_yolo_split_end_to_end_segdet<
838 'a,
839 'b,
840 'c,
841 'd,
842 BOXES,
843 SCORES,
844 CLASS,
845 MASK,
846>(
847 boxes: ArrayView2<'a, BOXES>,
848 scores: ArrayView2<'b, SCORES>,
849 classes: &'c ArrayView2<CLASS>,
850 mask_coeff: ArrayView2<'d, MASK>,
851) -> Result<
852 (
853 ArrayView2<'a, BOXES>,
854 ArrayView2<'b, SCORES>,
855 ArrayView1<'c, CLASS>,
856 ArrayView2<'d, MASK>,
857 ),
858 crate::DecoderError,
859> {
860 let num_boxes = boxes.shape()[1];
861 if boxes.shape()[0] != 4 {
862 return Err(crate::DecoderError::InvalidShape(format!(
863 "Split end-to-end box_coords must be 4, got {}",
864 boxes.shape()[0]
865 )));
866 }
867
868 if scores.shape()[0] != 1 {
869 return Err(crate::DecoderError::InvalidShape(format!(
870 "Split end-to-end scores num_classes must be 1, got {}",
871 scores.shape()[0]
872 )));
873 }
874
875 if classes.shape()[0] != 1 {
876 return Err(crate::DecoderError::InvalidShape(format!(
877 "Split end-to-end classes num_classes must be 1, got {}",
878 classes.shape()[0]
879 )));
880 }
881
882 if scores.shape()[1] != num_boxes {
883 return Err(crate::DecoderError::InvalidShape(format!(
884 "Split end-to-end scores must have same num_boxes as boxes ({}), got {}",
885 num_boxes,
886 scores.shape()[1]
887 )));
888 }
889
890 if classes.shape()[1] != num_boxes {
891 return Err(crate::DecoderError::InvalidShape(format!(
892 "Split end-to-end classes must have same num_boxes as boxes ({}), got {}",
893 num_boxes,
894 classes.shape()[1]
895 )));
896 }
897
898 if mask_coeff.shape()[1] != num_boxes {
899 return Err(crate::DecoderError::InvalidShape(format!(
900 "Split end-to-end mask_coeff must have same num_boxes as boxes ({}), got {}",
901 num_boxes,
902 mask_coeff.shape()[1]
903 )));
904 }
905
906 let boxes = boxes.reversed_axes();
907 let scores = scores.reversed_axes();
908 let classes = classes.slice(s![0, ..]);
909 let mask_coeff = mask_coeff.reversed_axes();
910 Ok((boxes, scores, classes, mask_coeff))
911}
912#[allow(clippy::too_many_arguments)]
921pub(crate) fn impl_yolo_quant<B: BBoxTypeTrait, T: PrimInt + AsPrimitive<f32> + Send + Sync>(
922 output: (ArrayView2<T>, Quantization),
923 score_threshold: f32,
924 iou_threshold: f32,
925 nms: Option<Nms>,
926 pre_nms_top_k: usize,
927 max_det: usize,
928 multi_label: bool,
929 output_boxes: &mut Vec<DetectBox>,
930) -> crate::DecoderResult<()>
931where
932 f32: AsPrimitive<T>,
933{
934 let _span = tracing::trace_span!("decoder.decode.yolo_quant_flat").entered();
935 let (boxes, quant_boxes) = output;
936 let (boxes_tensor, scores_tensor) = postprocess_yolo(&boxes);
937
938 let boxes = {
939 let score_threshold = quantize_score_threshold(score_threshold, quant_boxes);
940 if multi_label {
941 postprocess_boxes_multilabel_quant::<B, _, _>(
942 score_threshold,
943 boxes_tensor,
944 scores_tensor,
945 quant_boxes,
946 )?
947 } else {
948 postprocess_boxes_quant::<B, _, _>(
949 score_threshold,
950 boxes_tensor,
951 scores_tensor,
952 quant_boxes,
953 )
954 }
955 };
956
957 let boxes = nms_and_cap(boxes, nms, iou_threshold, pre_nms_top_k, max_det);
958 output_boxes.clear();
959 output_boxes.extend(boxes.iter().map(|b| dequant_detect_box(b, quant_boxes)));
960 Ok(())
961}
962
963#[allow(clippy::too_many_arguments)]
972pub(crate) fn impl_yolo_float<B: BBoxTypeTrait, T: Float + AsPrimitive<f32> + Send + Sync>(
973 output: ArrayView2<T>,
974 score_threshold: f32,
975 iou_threshold: f32,
976 nms: Option<Nms>,
977 pre_nms_top_k: usize,
978 max_det: usize,
979 multi_label: bool,
980 output_boxes: &mut Vec<DetectBox>,
981) -> crate::DecoderResult<()>
982where
983 f32: AsPrimitive<T>,
984{
985 let _span = tracing::trace_span!("decoder.decode.yolo_float_flat").entered();
986 let (boxes_tensor, scores_tensor) = postprocess_yolo(&output);
987 let boxes = if multi_label {
988 postprocess_boxes_multilabel_float::<B, _, _>(
989 score_threshold.as_(),
990 boxes_tensor,
991 scores_tensor,
992 )?
993 } else {
994 postprocess_boxes_float::<B, _, _>(score_threshold.as_(), boxes_tensor, scores_tensor)
995 };
996 output_boxes.clear();
997 output_boxes.extend(nms_and_cap(
998 boxes,
999 nms,
1000 iou_threshold,
1001 pre_nms_top_k,
1002 max_det,
1003 ));
1004 Ok(())
1005}
1006
1007#[allow(clippy::too_many_arguments)]
1021pub(crate) fn impl_yolo_split_quant<
1022 B: BBoxTypeTrait,
1023 BOX: PrimInt + AsPrimitive<f32> + Send + Sync,
1024 SCORE: PrimInt + AsPrimitive<f32> + Send + Sync,
1025>(
1026 boxes: (ArrayView2<BOX>, Quantization),
1027 scores: (ArrayView2<SCORE>, Quantization),
1028 score_threshold: f32,
1029 iou_threshold: f32,
1030 nms: Option<Nms>,
1031 pre_nms_top_k: usize,
1032 max_det: usize,
1033 multi_label: bool,
1034 output_boxes: &mut Vec<DetectBox>,
1035) -> crate::DecoderResult<()>
1036where
1037 f32: AsPrimitive<SCORE>,
1038{
1039 let _span = tracing::trace_span!("decoder.decode.yolo_quant_split").entered();
1040 let (boxes_tensor, quant_boxes) = boxes;
1041 let (scores_tensor, quant_scores) = scores;
1042
1043 let boxes_tensor = boxes_tensor.reversed_axes();
1044 let scores_tensor = scores_tensor.reversed_axes();
1045
1046 let boxes = {
1047 let score_threshold = quantize_score_threshold(score_threshold, quant_scores);
1048 if multi_label {
1049 postprocess_boxes_multilabel_quant::<B, _, _>(
1050 score_threshold,
1051 boxes_tensor,
1052 scores_tensor,
1053 quant_boxes,
1054 )?
1055 } else {
1056 postprocess_boxes_quant::<B, _, _>(
1057 score_threshold,
1058 boxes_tensor,
1059 scores_tensor,
1060 quant_boxes,
1061 )
1062 }
1063 };
1064
1065 let boxes = nms_and_cap(boxes, nms, iou_threshold, pre_nms_top_k, max_det);
1066 output_boxes.clear();
1067 output_boxes.extend(boxes.iter().map(|b| dequant_detect_box(b, quant_scores)));
1068 Ok(())
1069}
1070
1071#[allow(clippy::too_many_arguments)]
1084pub(crate) fn impl_yolo_split_float<
1085 B: BBoxTypeTrait,
1086 BOX: Float + AsPrimitive<f32> + Send + Sync,
1087 SCORE: Float + AsPrimitive<f32> + Send + Sync,
1088>(
1089 boxes_tensor: ArrayView2<BOX>,
1090 scores_tensor: ArrayView2<SCORE>,
1091 score_threshold: f32,
1092 iou_threshold: f32,
1093 nms: Option<Nms>,
1094 pre_nms_top_k: usize,
1095 max_det: usize,
1096 multi_label: bool,
1097 output_boxes: &mut Vec<DetectBox>,
1098) -> crate::DecoderResult<()>
1099where
1100 f32: AsPrimitive<SCORE>,
1101{
1102 let _span = tracing::trace_span!("decoder.decode.yolo_float_split").entered();
1103 let boxes_tensor = boxes_tensor.reversed_axes();
1104 let scores_tensor = scores_tensor.reversed_axes();
1105 let boxes = if multi_label {
1106 postprocess_boxes_multilabel_float::<B, _, _>(
1107 score_threshold.as_(),
1108 boxes_tensor,
1109 scores_tensor,
1110 )?
1111 } else {
1112 postprocess_boxes_float::<B, _, _>(score_threshold.as_(), boxes_tensor, scores_tensor)
1113 };
1114 output_boxes.clear();
1115 output_boxes.extend(nms_and_cap(
1116 boxes,
1117 nms,
1118 iou_threshold,
1119 pre_nms_top_k,
1120 max_det,
1121 ));
1122 Ok(())
1123}
1124
1125#[inline]
1133pub(crate) fn maybe_normalize_boxes_in_place(
1134 boxes: &mut [(DetectBox, usize)],
1135 normalized: Option<bool>,
1136 input_dims: Option<(usize, usize)>,
1137) {
1138 if normalized != Some(false) {
1139 return;
1140 }
1141 let Some((w, h)) = input_dims else {
1142 return;
1143 };
1144 if w == 0 || h == 0 {
1145 return;
1146 }
1147 let inv_w = 1.0 / w as f32;
1148 let inv_h = 1.0 / h as f32;
1149 for (b, _) in boxes.iter_mut() {
1150 b.bbox.xmin *= inv_w;
1151 b.bbox.ymin *= inv_h;
1152 b.bbox.xmax *= inv_w;
1153 b.bbox.ymax *= inv_h;
1154 }
1155}
1156
1157#[allow(clippy::too_many_arguments)]
1167pub(crate) fn impl_yolo_segdet_quant<
1168 B: BBoxTypeTrait,
1169 BOX: PrimInt + AsPrimitive<i64> + AsPrimitive<i128> + AsPrimitive<f32> + Send + Sync,
1170 PROTO: PrimInt + AsPrimitive<i64> + AsPrimitive<i128> + AsPrimitive<f32> + Send + Sync,
1171>(
1172 boxes: (ArrayView2<BOX>, Quantization),
1173 protos: (ArrayView3<PROTO>, Quantization),
1174 score_threshold: f32,
1175 iou_threshold: f32,
1176 nms: Option<Nms>,
1177 pre_nms_top_k: usize,
1178 max_det: usize,
1179 normalized: Option<bool>,
1180 input_dims: Option<(usize, usize)>,
1181 multi_label: bool,
1182 output_boxes: &mut Vec<DetectBox>,
1183 output_masks: &mut Vec<Segmentation>,
1184) -> Result<(), crate::DecoderError>
1185where
1186 f32: AsPrimitive<BOX>,
1187{
1188 let (boxes, quant_boxes) = boxes;
1189 let num_protos = protos.0.dim().2;
1190
1191 let (boxes_tensor, scores_tensor, mask_tensor) = postprocess_yolo_seg(&boxes, num_protos);
1192 let mut boxes = impl_yolo_split_segdet_quant_get_boxes::<B, _, _>(
1193 (boxes_tensor, quant_boxes),
1194 (scores_tensor, quant_boxes),
1195 score_threshold,
1196 iou_threshold,
1197 nms,
1198 pre_nms_top_k,
1199 max_det,
1200 multi_label,
1201 )?;
1202 maybe_normalize_boxes_in_place(&mut boxes, normalized, input_dims);
1203
1204 impl_yolo_split_segdet_quant_process_masks::<_, _>(
1205 boxes,
1206 (mask_tensor, quant_boxes),
1207 protos,
1208 output_boxes,
1209 output_masks,
1210 )
1211}
1212
1213#[allow(clippy::too_many_arguments)]
1223pub(crate) fn impl_yolo_segdet_float<
1224 B: BBoxTypeTrait,
1225 BOX: Float + AsPrimitive<f32> + Send + Sync,
1226 PROTO: Float + AsPrimitive<f32> + Send + Sync,
1227>(
1228 boxes: ArrayView2<BOX>,
1229 protos: ArrayView3<PROTO>,
1230 score_threshold: f32,
1231 iou_threshold: f32,
1232 nms: Option<Nms>,
1233 pre_nms_top_k: usize,
1234 max_det: usize,
1235 normalized: Option<bool>,
1236 input_dims: Option<(usize, usize)>,
1237 multi_label: bool,
1238 output_boxes: &mut Vec<DetectBox>,
1239 output_masks: &mut Vec<Segmentation>,
1240) -> Result<(), crate::DecoderError>
1241where
1242 f32: AsPrimitive<BOX>,
1243{
1244 let num_protos = protos.dim().2;
1245 let (boxes_tensor, scores_tensor, mask_tensor) = postprocess_yolo_seg(&boxes, num_protos);
1246 let mut boxes = impl_yolo_segdet_get_boxes::<B, _, _>(
1247 boxes_tensor,
1248 scores_tensor,
1249 score_threshold,
1250 iou_threshold,
1251 nms,
1252 pre_nms_top_k,
1253 max_det,
1254 multi_label,
1255 )?;
1256 maybe_normalize_boxes_in_place(&mut boxes, normalized, input_dims);
1257 impl_yolo_split_segdet_process_masks(boxes, mask_tensor, protos, output_boxes, output_masks)
1258}
1259
1260#[allow(clippy::too_many_arguments)]
1268pub(crate) fn impl_yolo_segdet_get_boxes<
1269 B: BBoxTypeTrait,
1270 BOX: Float + AsPrimitive<f32> + Send + Sync,
1271 SCORE: Float + AsPrimitive<f32> + Send + Sync,
1272>(
1273 boxes_tensor: ArrayView2<BOX>,
1274 scores_tensor: ArrayView2<SCORE>,
1275 score_threshold: f32,
1276 iou_threshold: f32,
1277 nms: Option<Nms>,
1278 pre_nms_top_k: usize,
1279 max_det: usize,
1280 multi_label: bool,
1281) -> crate::DecoderResult<Vec<(DetectBox, usize)>>
1282where
1283 f32: AsPrimitive<SCORE>,
1284{
1285 crate::byte::check_candidate_shapes(boxes_tensor.dim(), scores_tensor.dim())?;
1286 let span = tracing::trace_span!(
1287 "decoder.nms_get_boxes",
1288 n_candidates = tracing::field::Empty,
1289 n_detections = tracing::field::Empty,
1290 );
1291 let _guard = span.enter();
1292
1293 let boxes = {
1296 let _s = tracing::trace_span!("decoder.nms_get_boxes.score_filter").entered();
1297 if multi_label {
1298 postprocess_boxes_multilabel_index_float::<B, _, _>(
1299 score_threshold.as_(),
1300 boxes_tensor,
1301 scores_tensor,
1302 )
1303 } else {
1304 postprocess_boxes_index_float::<B, _, _>(
1305 score_threshold.as_(),
1306 boxes_tensor,
1307 scores_tensor,
1308 )
1309 }
1310 };
1311 span.record("n_candidates", boxes.len());
1312
1313 let boxes = nms_and_cap(boxes, nms, iou_threshold, pre_nms_top_k, max_det);
1314 span.record("n_detections", boxes.len());
1315
1316 Ok(boxes)
1317}
1318
1319pub(crate) fn impl_yolo_end_to_end_segdet_get_boxes<
1320 B: BBoxTypeTrait,
1321 BOX: Float + AsPrimitive<f32> + Send + Sync,
1322 SCORE: Float + AsPrimitive<f32> + Send + Sync,
1323 CLASS: AsPrimitive<f32> + Send + Sync,
1324>(
1325 boxes: ArrayView2<BOX>,
1326 scores: ArrayView2<SCORE>,
1327 classes: ArrayView1<CLASS>,
1328 score_threshold: f32,
1329 max_boxes: usize,
1330) -> Vec<(DetectBox, usize)>
1331where
1332 f32: AsPrimitive<SCORE>,
1333{
1334 let mut boxes = postprocess_boxes_index_float::<B, _, _>(score_threshold.as_(), boxes, scores);
1335 boxes.truncate(max_boxes);
1336 for (b, ind) in &mut boxes {
1337 b.label = classes[*ind].as_().round() as usize;
1338 }
1339 boxes
1340}
1341
1342pub(crate) fn impl_yolo_split_segdet_process_masks<
1343 MASK: Float + AsPrimitive<f32> + Send + Sync,
1344 PROTO: Float + AsPrimitive<f32> + Send + Sync,
1345>(
1346 boxes: Vec<(DetectBox, usize)>,
1347 masks_tensor: ArrayView2<MASK>,
1348 protos_tensor: ArrayView3<PROTO>,
1349 output_boxes: &mut Vec<DetectBox>,
1350 output_masks: &mut Vec<Segmentation>,
1351) -> Result<(), crate::DecoderError> {
1352 let _span = tracing::trace_span!(
1353 "decoder.decode.process_masks",
1354 n = boxes.len(),
1355 mode = "float"
1356 )
1357 .entered();
1358 let boxes = decode_segdet_f32(boxes, masks_tensor, protos_tensor)?;
1362 output_boxes.clear();
1363 output_masks.clear();
1364 for (b, roi, m) in boxes.into_iter() {
1365 output_boxes.push(b);
1366 output_masks.push(Segmentation {
1367 xmin: roi.xmin,
1368 ymin: roi.ymin,
1369 xmax: roi.xmax,
1370 ymax: roi.ymax,
1371 segmentation: crate::mask_to_tensor(m.view())?,
1372 });
1373 }
1374 Ok(())
1375}
1376#[allow(clippy::too_many_arguments)]
1384pub(crate) fn impl_yolo_split_segdet_quant_get_boxes<
1385 B: BBoxTypeTrait,
1386 BOX: PrimInt + AsPrimitive<f32> + Send + Sync,
1387 SCORE: PrimInt + AsPrimitive<f32> + Send + Sync,
1388>(
1389 boxes: (ArrayView2<BOX>, Quantization),
1390 scores: (ArrayView2<SCORE>, Quantization),
1391 score_threshold: f32,
1392 iou_threshold: f32,
1393 nms: Option<Nms>,
1394 pre_nms_top_k: usize,
1395 max_det: usize,
1396 multi_label: bool,
1397) -> crate::DecoderResult<Vec<(DetectBox, usize)>>
1398where
1399 f32: AsPrimitive<SCORE>,
1400{
1401 let (boxes_tensor, quant_boxes) = boxes;
1402 let (scores_tensor, quant_scores) = scores;
1403
1404 let span = tracing::trace_span!(
1405 "decoder.nms_get_boxes",
1406 n_candidates = tracing::field::Empty,
1407 n_detections = tracing::field::Empty,
1408 );
1409 let _guard = span.enter();
1410
1411 let boxes = {
1412 let _s = tracing::trace_span!("decoder.nms_get_boxes.score_filter").entered();
1413 let score_threshold = quantize_score_threshold(score_threshold, quant_scores);
1414 if multi_label {
1415 postprocess_boxes_multilabel_index_quant::<B, _, _>(
1416 score_threshold,
1417 boxes_tensor,
1418 scores_tensor,
1419 quant_boxes,
1420 )?
1421 } else {
1422 crate::byte::check_candidate_shapes(boxes_tensor.dim(), scores_tensor.dim())?;
1423 postprocess_boxes_index_quant::<B, _, _>(
1424 score_threshold,
1425 boxes_tensor,
1426 scores_tensor,
1427 quant_boxes,
1428 )
1429 }
1430 };
1431 span.record("n_candidates", boxes.len());
1432
1433 let boxes = nms_and_cap(boxes, nms, iou_threshold, pre_nms_top_k, max_det);
1434 let result: Vec<_> = {
1435 let _s =
1436 tracing::trace_span!("decoder.nms_get_boxes.dequant_boxes", n = boxes.len()).entered();
1437 boxes
1438 .into_iter()
1439 .map(|(b, i)| (dequant_detect_box(&b, quant_scores), i))
1440 .collect()
1441 };
1442 span.record("n_detections", result.len());
1443
1444 Ok(result)
1445}
1446
1447pub(crate) fn impl_yolo_split_segdet_quant_process_masks<
1448 MASK: PrimInt + AsPrimitive<i64> + AsPrimitive<i128> + AsPrimitive<f32> + Send + Sync,
1449 PROTO: PrimInt + AsPrimitive<i64> + AsPrimitive<i128> + AsPrimitive<f32> + Send + Sync,
1450>(
1451 boxes: Vec<(DetectBox, usize)>,
1452 mask_coeff: (ArrayView2<MASK>, Quantization),
1453 protos: (ArrayView3<PROTO>, Quantization),
1454 output_boxes: &mut Vec<DetectBox>,
1455 output_masks: &mut Vec<Segmentation>,
1456) -> Result<(), crate::DecoderError> {
1457 let _span = tracing::trace_span!(
1458 "decoder.decode.process_masks",
1459 n = boxes.len(),
1460 mode = "quant"
1461 )
1462 .entered();
1463 let (masks, quant_masks) = mask_coeff;
1464 let (protos, quant_protos) = protos;
1465
1466 let boxes = decode_segdet_quant(boxes, masks, protos, quant_masks, quant_protos)?;
1470 output_boxes.clear();
1471 output_masks.clear();
1472 for (b, roi, m) in boxes.into_iter() {
1473 output_boxes.push(b);
1474 output_masks.push(Segmentation {
1475 xmin: roi.xmin,
1476 ymin: roi.ymin,
1477 xmax: roi.xmax,
1478 ymax: roi.ymax,
1479 segmentation: crate::mask_to_tensor(m.view())?,
1480 });
1481 }
1482 Ok(())
1483}
1484
1485#[allow(clippy::too_many_arguments)]
1497pub(crate) fn impl_yolo_split_segdet_float<
1498 B: BBoxTypeTrait,
1499 BOX: Float + AsPrimitive<f32> + Send + Sync,
1500 SCORE: Float + AsPrimitive<f32> + Send + Sync,
1501 MASK: Float + AsPrimitive<f32> + Send + Sync,
1502 PROTO: Float + AsPrimitive<f32> + Send + Sync,
1503>(
1504 boxes_tensor: ArrayView2<BOX>,
1505 scores_tensor: ArrayView2<SCORE>,
1506 mask_tensor: ArrayView2<MASK>,
1507 protos: ArrayView3<PROTO>,
1508 score_threshold: f32,
1509 iou_threshold: f32,
1510 nms: Option<Nms>,
1511 pre_nms_top_k: usize,
1512 max_det: usize,
1513 normalized: Option<bool>,
1514 input_dims: Option<(usize, usize)>,
1515 multi_label: bool,
1516 output_boxes: &mut Vec<DetectBox>,
1517 output_masks: &mut Vec<Segmentation>,
1518) -> Result<(), crate::DecoderError>
1519where
1520 f32: AsPrimitive<SCORE>,
1521{
1522 let (boxes_tensor, scores_tensor, mask_tensor) =
1523 postprocess_yolo_split_segdet(boxes_tensor, scores_tensor, mask_tensor);
1524
1525 let mut boxes = impl_yolo_segdet_get_boxes::<B, _, _>(
1526 boxes_tensor,
1527 scores_tensor,
1528 score_threshold,
1529 iou_threshold,
1530 nms,
1531 pre_nms_top_k,
1532 max_det,
1533 multi_label,
1534 )?;
1535 maybe_normalize_boxes_in_place(&mut boxes, normalized, input_dims);
1536 impl_yolo_split_segdet_process_masks(boxes, mask_tensor, protos, output_boxes, output_masks)
1537}
1538
1539#[allow(clippy::too_many_arguments)]
1550pub(crate) fn impl_yolo_segdet_quant_proto<
1551 B: BBoxTypeTrait,
1552 BOX: PrimInt
1553 + AsPrimitive<i64>
1554 + AsPrimitive<i128>
1555 + AsPrimitive<f32>
1556 + AsPrimitive<i8>
1557 + Send
1558 + Sync,
1559 PROTO: PrimInt
1560 + AsPrimitive<i64>
1561 + AsPrimitive<i128>
1562 + AsPrimitive<f32>
1563 + AsPrimitive<i8>
1564 + Send
1565 + Sync,
1566>(
1567 boxes: (ArrayView2<BOX>, Quantization),
1568 protos: (ArrayView3<PROTO>, Quantization),
1569 score_threshold: f32,
1570 iou_threshold: f32,
1571 nms: Option<Nms>,
1572 pre_nms_top_k: usize,
1573 max_det: usize,
1574 normalized: Option<bool>,
1575 input_dims: Option<(usize, usize)>,
1576 multi_label: bool,
1577 output_boxes: &mut Vec<DetectBox>,
1578) -> crate::DecoderResult<ProtoData>
1579where
1580 f32: AsPrimitive<BOX>,
1581{
1582 let (boxes_arr, quant_boxes) = boxes;
1583 let (protos_arr, quant_protos) = protos;
1584 let num_protos = protos_arr.dim().2;
1585
1586 let (boxes_tensor, scores_tensor, mask_tensor) = postprocess_yolo_seg(&boxes_arr, num_protos);
1587
1588 let mut det_indices = impl_yolo_split_segdet_quant_get_boxes::<B, _, _>(
1589 (boxes_tensor, quant_boxes),
1590 (scores_tensor, quant_boxes),
1591 score_threshold,
1592 iou_threshold,
1593 nms,
1594 pre_nms_top_k,
1595 max_det,
1596 multi_label,
1597 )?;
1598 maybe_normalize_boxes_in_place(&mut det_indices, normalized, input_dims);
1599
1600 Ok(extract_proto_data_quant(
1601 det_indices,
1602 mask_tensor,
1603 quant_boxes,
1604 protos_arr,
1605 quant_protos,
1606 output_boxes,
1607 ))
1608}
1609
1610#[allow(clippy::too_many_arguments)]
1617pub(crate) fn impl_yolo_segdet_float_proto<
1618 B: BBoxTypeTrait,
1619 BOX: Float + AsPrimitive<f32> + Copy + Send + Sync + FloatProtoElem,
1620 PROTO: Float + AsPrimitive<f32> + Copy + Send + Sync + FloatProtoElem,
1621>(
1622 boxes: ArrayView2<BOX>,
1623 protos: ArrayView3<PROTO>,
1624 score_threshold: f32,
1625 iou_threshold: f32,
1626 nms: Option<Nms>,
1627 pre_nms_top_k: usize,
1628 max_det: usize,
1629 normalized: Option<bool>,
1630 input_dims: Option<(usize, usize)>,
1631 multi_label: bool,
1632 output_boxes: &mut Vec<DetectBox>,
1633) -> crate::DecoderResult<ProtoData>
1634where
1635 f32: AsPrimitive<BOX>,
1636{
1637 let num_protos = protos.dim().2;
1638 let (boxes_tensor, scores_tensor, mask_tensor) = postprocess_yolo_seg(&boxes, num_protos);
1639
1640 let mut boxes = impl_yolo_segdet_get_boxes::<B, _, _>(
1641 boxes_tensor,
1642 scores_tensor,
1643 score_threshold,
1644 iou_threshold,
1645 nms,
1646 pre_nms_top_k,
1647 max_det,
1648 multi_label,
1649 )?;
1650 maybe_normalize_boxes_in_place(&mut boxes, normalized, input_dims);
1651
1652 Ok(extract_proto_data_float(
1653 boxes,
1654 mask_tensor,
1655 protos,
1656 output_boxes,
1657 ))
1658}
1659
1660#[allow(clippy::too_many_arguments)]
1667pub(crate) fn impl_yolo_split_segdet_float_proto<
1668 B: BBoxTypeTrait,
1669 BOX: Float + AsPrimitive<f32> + Send + Sync,
1670 SCORE: Float + AsPrimitive<f32> + Send + Sync,
1671 MASK: Float + AsPrimitive<f32> + Copy + Send + Sync + FloatProtoElem,
1672 PROTO: Float + AsPrimitive<f32> + Copy + Send + Sync + FloatProtoElem,
1673>(
1674 boxes_tensor: ArrayView2<BOX>,
1675 scores_tensor: ArrayView2<SCORE>,
1676 mask_tensor: ArrayView2<MASK>,
1677 protos: ArrayView3<PROTO>,
1678 score_threshold: f32,
1679 iou_threshold: f32,
1680 nms: Option<Nms>,
1681 pre_nms_top_k: usize,
1682 max_det: usize,
1683 normalized: Option<bool>,
1684 input_dims: Option<(usize, usize)>,
1685 multi_label: bool,
1686 output_boxes: &mut Vec<DetectBox>,
1687) -> crate::DecoderResult<ProtoData>
1688where
1689 f32: AsPrimitive<SCORE>,
1690{
1691 let (boxes_tensor, scores_tensor, mask_tensor) =
1692 postprocess_yolo_split_segdet(boxes_tensor, scores_tensor, mask_tensor);
1693 let mut det_indices = impl_yolo_segdet_get_boxes::<B, _, _>(
1694 boxes_tensor,
1695 scores_tensor,
1696 score_threshold,
1697 iou_threshold,
1698 nms,
1699 pre_nms_top_k,
1700 max_det,
1701 multi_label,
1702 )?;
1703 maybe_normalize_boxes_in_place(&mut det_indices, normalized, input_dims);
1704
1705 Ok(extract_proto_data_float(
1706 det_indices,
1707 mask_tensor,
1708 protos,
1709 output_boxes,
1710 ))
1711}
1712
1713pub(crate) fn decode_yolo_end_to_end_segdet_float_proto<T>(
1715 output: ArrayView2<T>,
1716 protos: ArrayView3<T>,
1717 score_threshold: f32,
1718 max_det: usize,
1719 output_boxes: &mut Vec<DetectBox>,
1720) -> Result<ProtoData, crate::DecoderError>
1721where
1722 T: Float + AsPrimitive<f32> + Copy + Send + Sync + FloatProtoElem,
1723 f32: AsPrimitive<T>,
1724{
1725 let (boxes, scores, classes, mask_coeff) =
1726 postprocess_yolo_end_to_end_segdet(&output, protos.dim().2)?;
1727 let cap = max_det;
1728 let boxes = impl_yolo_end_to_end_segdet_get_boxes::<XYXY, _, _, _>(
1729 boxes,
1730 scores,
1731 classes,
1732 score_threshold,
1733 cap,
1734 );
1735
1736 Ok(extract_proto_data_float(
1737 boxes,
1738 mask_coeff,
1739 protos,
1740 output_boxes,
1741 ))
1742}
1743
1744#[allow(clippy::too_many_arguments)]
1746pub(crate) fn decode_yolo_split_end_to_end_segdet_float_proto<T>(
1747 boxes: ArrayView2<T>,
1748 scores: ArrayView2<T>,
1749 classes: ArrayView2<T>,
1750 mask_coeff: ArrayView2<T>,
1751 protos: ArrayView3<T>,
1752 score_threshold: f32,
1753 max_det: usize,
1754 output_boxes: &mut Vec<DetectBox>,
1755) -> Result<ProtoData, crate::DecoderError>
1756where
1757 T: Float + AsPrimitive<f32> + Copy + Send + Sync + FloatProtoElem,
1758 f32: AsPrimitive<T>,
1759{
1760 let (boxes, scores, classes, mask_coeff) =
1761 postprocess_yolo_split_end_to_end_segdet(boxes, scores, &classes, mask_coeff)?;
1762 let cap = max_det;
1763 let boxes = impl_yolo_end_to_end_segdet_get_boxes::<XYXY, _, _, _>(
1764 boxes,
1765 scores,
1766 classes,
1767 score_threshold,
1768 cap,
1769 );
1770
1771 Ok(extract_proto_data_float(
1772 boxes,
1773 mask_coeff,
1774 protos,
1775 output_boxes,
1776 ))
1777}
1778
1779pub(super) fn extract_proto_data_float<
1786 MASK: Float + AsPrimitive<f32> + Copy + Send + Sync + FloatProtoElem,
1787 PROTO: Float + AsPrimitive<f32> + Copy + Send + Sync + FloatProtoElem,
1788>(
1789 det_indices: Vec<(DetectBox, usize)>,
1790 mask_tensor: ArrayView2<MASK>,
1791 protos: ArrayView3<PROTO>,
1792 output_boxes: &mut Vec<DetectBox>,
1793) -> ProtoData {
1794 let _span = tracing::trace_span!(
1795 "decoder.decode_proto.extract_proto_data",
1796 mode = "float",
1797 n = det_indices.len(),
1798 num_protos = mask_tensor.ncols(),
1799 layout = "nhwc",
1800 )
1801 .entered();
1802
1803 let num_protos = mask_tensor.ncols();
1804 let n = det_indices.len();
1805
1806 let mut coeff_rows: Vec<MASK> = Vec::with_capacity(n * num_protos);
1811 output_boxes.clear();
1812 for (det, idx) in det_indices {
1813 output_boxes.push(det);
1814 let row = mask_tensor.row(idx);
1815 coeff_rows.extend(row.iter().copied());
1816 }
1817
1818 let mask_coefficients = MASK::slice_into_tensor_dyn(&coeff_rows, &[n, num_protos])
1819 .expect("allocating mask_coefficients TensorDyn");
1820 let protos_tensor =
1821 PROTO::arrayview3_into_tensor_dyn(protos).expect("allocating protos TensorDyn");
1822
1823 ProtoData {
1824 mask_coefficients,
1825 protos: protos_tensor,
1826 layout: ProtoLayout::Nhwc,
1827 }
1828}
1829
1830pub(crate) fn extract_proto_data_quant<
1841 MASK: PrimInt + AsPrimitive<f32> + AsPrimitive<i8> + Send + Sync + 'static,
1842 PROTO: PrimInt + AsPrimitive<f32> + AsPrimitive<i8> + Send + Sync + 'static,
1843>(
1844 det_indices: Vec<(DetectBox, usize)>,
1845 mask_tensor: ArrayView2<MASK>,
1846 quant_masks: Quantization,
1847 protos: ArrayView3<PROTO>,
1848 quant_protos: Quantization,
1849 output_boxes: &mut Vec<DetectBox>,
1850) -> ProtoData {
1851 use edgefirst_tensor::{Tensor, TensorDyn, TensorMapTrait, TensorMemory, TensorTrait};
1852
1853 let span = tracing::trace_span!(
1854 "decoder.decode_proto.extract_proto_data",
1855 mode = "quant",
1856 n = det_indices.len(),
1857 num_protos = tracing::field::Empty,
1858 layout = tracing::field::Empty,
1859 );
1860 let _guard = span.enter();
1861
1862 let num_protos = mask_tensor.ncols();
1863 let n = det_indices.len();
1864 span.record("num_protos", num_protos);
1865
1866 let mask_coefficients: TensorDyn = if std::any::TypeId::of::<MASK>()
1872 == std::any::TypeId::of::<i8>()
1873 {
1874 let mut coeff_i8 = Vec::<i8>::with_capacity(n * num_protos);
1875 output_boxes.clear();
1876 for (det, idx) in det_indices {
1877 output_boxes.push(det);
1878 let row = mask_tensor.row(idx);
1879 coeff_i8.extend(row.iter().map(|v| {
1880 let v_i8: i8 = v.as_();
1881 v_i8
1882 }));
1883 }
1884 let coeff_tensor = Tensor::<i8>::new(&[n, num_protos], Some(TensorMemory::Mem), None)
1885 .expect("allocating mask_coefficients tensor");
1886 if n > 0 {
1887 let mut m = coeff_tensor
1888 .map()
1889 .expect("mapping mask_coefficients tensor");
1890 m.as_mut_slice().copy_from_slice(&coeff_i8);
1891 }
1892 let coeff_quant =
1893 edgefirst_tensor::Quantization::per_tensor(quant_masks.scale, quant_masks.zero_point);
1894 let coeff_tensor = coeff_tensor
1895 .with_quantization(coeff_quant)
1896 .expect("per-tensor quantization on mask coefficients");
1897 coeff_tensor.into()
1898 } else if std::any::TypeId::of::<MASK>() == std::any::TypeId::of::<i16>() {
1899 let mut coeff_i16 = Vec::<i16>::with_capacity(n * num_protos);
1902 output_boxes.clear();
1903 for (det, idx) in det_indices {
1904 output_boxes.push(det);
1905 let row = mask_tensor.row(idx);
1906 coeff_i16.extend(row.iter().map(|v| {
1907 let v_f32: f32 = v.as_();
1908 v_f32 as i16
1909 }));
1910 }
1911 let coeff_tensor = Tensor::<i16>::new(&[n, num_protos], Some(TensorMemory::Mem), None)
1912 .expect("allocating mask_coefficients tensor");
1913 if n > 0 {
1914 let mut m = coeff_tensor
1915 .map()
1916 .expect("mapping mask_coefficients tensor");
1917 m.as_mut_slice().copy_from_slice(&coeff_i16);
1918 }
1919 let coeff_quant =
1920 edgefirst_tensor::Quantization::per_tensor(quant_masks.scale, quant_masks.zero_point);
1921 let coeff_tensor = coeff_tensor
1922 .with_quantization(coeff_quant)
1923 .expect("per-tensor quantization on mask coefficients");
1924 coeff_tensor.into()
1925 } else {
1926 let scale = quant_masks.scale;
1928 let zp = quant_masks.zero_point as f32;
1929 let mut coeff_f32 = Vec::<f32>::with_capacity(n * num_protos);
1930 output_boxes.clear();
1931 for (det, idx) in det_indices {
1932 output_boxes.push(det);
1933 let row = mask_tensor.row(idx);
1934 coeff_f32.extend(row.iter().map(|v| {
1935 let v_f32: f32 = v.as_();
1936 (v_f32 - zp) * scale
1937 }));
1938 }
1939 let coeff_tensor = Tensor::<f32>::new(&[n, num_protos], Some(TensorMemory::Mem), None)
1940 .expect("allocating mask_coefficients tensor");
1941 if n > 0 {
1942 let mut m = coeff_tensor
1943 .map()
1944 .expect("mapping mask_coefficients tensor");
1945 m.as_mut_slice().copy_from_slice(&coeff_f32);
1946 }
1947 coeff_tensor.into()
1948 };
1949
1950 let (h, w, k) = protos.dim();
1951
1952 if std::any::TypeId::of::<PROTO>() != std::any::TypeId::of::<i8>() {
1961 let scale = quant_protos.scale;
1962 let zp = quant_protos.zero_point as f32;
1963 let protos_tensor = Tensor::<f32>::new(&[h, w, k], Some(TensorMemory::Mem), None)
1964 .expect("allocating protos tensor");
1965 if n > 0 {
1966 let mut m = protos_tensor.map().expect("mapping protos tensor");
1967 for (d, s) in m.as_mut_slice().iter_mut().zip(protos.iter()) {
1968 let v: f32 = s.as_();
1969 *d = (v - zp) * scale;
1970 }
1971 }
1972 span.record("layout", tracing::field::debug(&ProtoLayout::Nhwc));
1973 return ProtoData {
1974 mask_coefficients,
1975 protos: protos_tensor.into(),
1976 layout: ProtoLayout::Nhwc,
1977 };
1978 }
1979
1980 let (proto_shape, proto_layout) = if protos.is_standard_layout() {
1984 (&[h, w, k][..], ProtoLayout::Nhwc)
1986 } else if protos.ndim() == 3 && protos.strides() == [w as isize, 1, (h * w) as isize] {
1987 (&[k, h, w][..], ProtoLayout::Nchw)
1991 } else {
1992 (&[h, w, k][..], ProtoLayout::Nhwc)
1994 };
1995
1996 let protos_tensor = Tensor::<i8>::new(proto_shape, Some(TensorMemory::Mem), None)
1997 .expect("allocating protos tensor");
1998 if n > 0 {
1999 let mut m = protos_tensor.map().expect("mapping protos tensor");
2000 let dst = m.as_mut_slice();
2001 if protos.is_standard_layout() {
2004 let src: &[i8] =
2005 unsafe { std::slice::from_raw_parts(protos.as_ptr() as *const i8, protos.len()) };
2006 dst.copy_from_slice(src);
2007 } else if protos.ndim() == 3 && protos.strides() == [w as isize, 1, (h * w) as isize] {
2008 let total = h * w * k;
2012 let src: &[i8] =
2015 unsafe { std::slice::from_raw_parts(protos.as_ptr() as *const i8, total) };
2016 dst.copy_from_slice(src);
2017 } else {
2018 for (d, s) in dst.iter_mut().zip(protos.iter()) {
2019 let v_i8: i8 = s.as_();
2020 *d = v_i8;
2021 }
2022 }
2023 }
2024 let tensor_quant =
2025 edgefirst_tensor::Quantization::per_tensor(quant_protos.scale, quant_protos.zero_point);
2026 let protos_tensor = protos_tensor
2027 .with_quantization(tensor_quant)
2028 .expect("per-tensor quantization on new Tensor<i8>");
2029
2030 span.record("layout", tracing::field::debug(&proto_layout));
2031
2032 ProtoData {
2033 mask_coefficients,
2034 protos: protos_tensor.into(),
2035 layout: proto_layout,
2036 }
2037}
2038
2039pub trait FloatProtoElem: Copy + 'static {
2049 fn slice_into_tensor_dyn(
2052 values: &[Self],
2053 shape: &[usize],
2054 ) -> edgefirst_tensor::Result<edgefirst_tensor::TensorDyn>;
2055
2056 fn arrayview3_into_tensor_dyn(
2059 view: ArrayView3<'_, Self>,
2060 ) -> edgefirst_tensor::Result<edgefirst_tensor::TensorDyn>;
2061}
2062
2063impl FloatProtoElem for f32 {
2064 fn slice_into_tensor_dyn(
2065 values: &[f32],
2066 shape: &[usize],
2067 ) -> edgefirst_tensor::Result<edgefirst_tensor::TensorDyn> {
2068 edgefirst_tensor::Tensor::<f32>::from_slice(values, shape).map(Into::into)
2069 }
2070 fn arrayview3_into_tensor_dyn(
2071 view: ArrayView3<'_, f32>,
2072 ) -> edgefirst_tensor::Result<edgefirst_tensor::TensorDyn> {
2073 edgefirst_tensor::Tensor::<f32>::from_arrayview3(view).map(Into::into)
2074 }
2075}
2076
2077impl FloatProtoElem for half::f16 {
2078 fn slice_into_tensor_dyn(
2079 values: &[half::f16],
2080 shape: &[usize],
2081 ) -> edgefirst_tensor::Result<edgefirst_tensor::TensorDyn> {
2082 edgefirst_tensor::Tensor::<half::f16>::from_slice(values, shape).map(Into::into)
2083 }
2084 fn arrayview3_into_tensor_dyn(
2085 view: ArrayView3<'_, half::f16>,
2086 ) -> edgefirst_tensor::Result<edgefirst_tensor::TensorDyn> {
2087 edgefirst_tensor::Tensor::<half::f16>::from_arrayview3(view).map(Into::into)
2088 }
2089}
2090
2091impl FloatProtoElem for f64 {
2092 fn slice_into_tensor_dyn(
2093 values: &[f64],
2094 shape: &[usize],
2095 ) -> edgefirst_tensor::Result<edgefirst_tensor::TensorDyn> {
2096 let narrowed: Vec<f32> = values.iter().map(|&v| v as f32).collect();
2098 edgefirst_tensor::Tensor::<f32>::from_slice(&narrowed, shape).map(Into::into)
2099 }
2100 fn arrayview3_into_tensor_dyn(
2101 view: ArrayView3<'_, f64>,
2102 ) -> edgefirst_tensor::Result<edgefirst_tensor::TensorDyn> {
2103 let narrowed: ndarray::Array3<f32> = view.mapv(|v| v as f32);
2104 edgefirst_tensor::Tensor::<f32>::from_arrayview3(narrowed.view()).map(Into::into)
2105 }
2106}
2107
2108fn postprocess_yolo<'a, T>(
2109 output: &'a ArrayView2<'_, T>,
2110) -> (ArrayView2<'a, T>, ArrayView2<'a, T>) {
2111 let boxes_tensor = output.slice(s![..4, ..,]).reversed_axes();
2112 let scores_tensor = output.slice(s![4.., ..,]).reversed_axes();
2113 (boxes_tensor, scores_tensor)
2114}
2115
2116pub(crate) fn postprocess_yolo_seg<'a, T>(
2117 output: &'a ArrayView2<'_, T>,
2118 num_protos: usize,
2119) -> (ArrayView2<'a, T>, ArrayView2<'a, T>, ArrayView2<'a, T>) {
2120 assert!(
2121 output.shape()[0] > num_protos + 4,
2122 "Output shape is too short: {} <= {} + 4",
2123 output.shape()[0],
2124 num_protos
2125 );
2126 let num_classes = output.shape()[0] - 4 - num_protos;
2127 let boxes_tensor = output.slice(s![..4, ..,]).reversed_axes();
2128 let scores_tensor = output.slice(s![4..(num_classes + 4), ..,]).reversed_axes();
2129 let mask_tensor = output.slice(s![(num_classes + 4).., ..,]).reversed_axes();
2130 (boxes_tensor, scores_tensor, mask_tensor)
2131}
2132
2133pub(crate) fn postprocess_yolo_split_segdet<'a, 'b, 'c, BOX, SCORE, MASK>(
2134 boxes_tensor: ArrayView2<'a, BOX>,
2135 scores_tensor: ArrayView2<'b, SCORE>,
2136 mask_tensor: ArrayView2<'c, MASK>,
2137) -> (
2138 ArrayView2<'a, BOX>,
2139 ArrayView2<'b, SCORE>,
2140 ArrayView2<'c, MASK>,
2141) {
2142 let boxes_tensor = boxes_tensor.reversed_axes();
2143 let scores_tensor = scores_tensor.reversed_axes();
2144 let mask_tensor = mask_tensor.reversed_axes();
2145 (boxes_tensor, scores_tensor, mask_tensor)
2146}
2147
2148fn decode_segdet_f32<
2149 MASK: Float + AsPrimitive<f32> + Send + Sync,
2150 PROTO: Float + AsPrimitive<f32> + Send + Sync,
2151>(
2152 boxes: Vec<(DetectBox, usize)>,
2153 masks: ArrayView2<MASK>,
2154 protos: ArrayView3<PROTO>,
2155) -> Result<Vec<(DetectBox, BoundingBox, Array3<u8>)>, crate::DecoderError> {
2156 if boxes.is_empty() {
2157 return Ok(Vec::new());
2158 }
2159 if masks.shape()[1] != protos.shape()[2] {
2160 return Err(crate::DecoderError::InvalidShape(format!(
2161 "Mask coefficients count ({}) doesn't match protos channel count ({})",
2162 masks.shape()[1],
2163 protos.shape()[2],
2164 )));
2165 }
2166 boxes
2167 .into_par_iter()
2168 .map(|b| {
2169 let ind = b.1;
2170 let (protos, roi) = protobox(&protos, &b.0.bbox)?;
2175 Ok((b.0, roi, make_segmentation(masks.row(ind), protos.view())))
2176 })
2177 .collect()
2178}
2179
2180pub(crate) fn decode_segdet_quant<
2181 MASK: PrimInt + AsPrimitive<i64> + AsPrimitive<i128> + Send + Sync,
2182 PROTO: PrimInt + AsPrimitive<i64> + AsPrimitive<i128> + Send + Sync,
2183>(
2184 boxes: Vec<(DetectBox, usize)>,
2185 masks: ArrayView2<MASK>,
2186 protos: ArrayView3<PROTO>,
2187 quant_masks: Quantization,
2188 quant_protos: Quantization,
2189) -> Result<Vec<(DetectBox, BoundingBox, Array3<u8>)>, crate::DecoderError> {
2190 if boxes.is_empty() {
2191 return Ok(Vec::new());
2192 }
2193 if masks.shape()[1] != protos.shape()[2] {
2194 return Err(crate::DecoderError::InvalidShape(format!(
2195 "Mask coefficients count ({}) doesn't match protos channel count ({})",
2196 masks.shape()[1],
2197 protos.shape()[2],
2198 )));
2199 }
2200
2201 let total_bits = MASK::zero().count_zeros() + PROTO::zero().count_zeros() + 5; boxes
2203 .into_iter()
2204 .map(|b| {
2205 let i = b.1;
2206 let (protos, roi) = protobox(&protos, &b.0.bbox.to_canonical())?;
2210 let seg = match total_bits {
2211 0..=64 => make_segmentation_quant::<MASK, PROTO, i64>(
2212 masks.row(i),
2213 protos.view(),
2214 quant_masks,
2215 quant_protos,
2216 ),
2217 65..=128 => make_segmentation_quant::<MASK, PROTO, i128>(
2218 masks.row(i),
2219 protos.view(),
2220 quant_masks,
2221 quant_protos,
2222 ),
2223 _ => {
2224 return Err(crate::DecoderError::NotSupported(format!(
2225 "Unsupported bit width ({total_bits}) for segmentation computation"
2226 )));
2227 }
2228 };
2229 Ok((b.0, roi, seg))
2230 })
2231 .collect()
2232}
2233
2234fn protobox<'a, T>(
2235 protos: &'a ArrayView3<T>,
2236 roi: &BoundingBox,
2237) -> Result<(ArrayView3<'a, T>, BoundingBox), crate::DecoderError> {
2238 let width = protos.dim().1 as f32;
2239 let height = protos.dim().0 as f32;
2240
2241 const NORM_LIMIT: f32 = 2.0;
2253 if roi.xmin > NORM_LIMIT
2254 || roi.ymin > NORM_LIMIT
2255 || roi.xmax > NORM_LIMIT
2256 || roi.ymax > NORM_LIMIT
2257 {
2258 return Err(crate::DecoderError::InvalidShape(format!(
2259 "Bounding box coordinates appear un-normalized (pixel-space). \
2260 Got bbox=({:.2}, {:.2}, {:.2}, {:.2}) but expected values in [0, 1]. \
2261 Two ways to fix this: \
2262 (1) declare `Detection::normalized = false` in the model schema \
2263 AND make sure the schema's `input.shape` / `input.dshape` carries \
2264 the model input dims so the decoder can divide by (W, H) before NMS \
2265 (EDGEAI-1303 — verify with `Decoder::input_dims().is_some()`); or \
2266 (2) normalize the boxes in-graph before decode().",
2267 roi.xmin, roi.ymin, roi.xmax, roi.ymax,
2268 )));
2269 }
2270
2271 let roi = [
2272 (roi.xmin * width).clamp(0.0, width) as usize,
2273 (roi.ymin * height).clamp(0.0, height) as usize,
2274 (roi.xmax * width).clamp(0.0, width).ceil() as usize,
2275 (roi.ymax * height).clamp(0.0, height).ceil() as usize,
2276 ];
2277
2278 let roi_norm = [
2279 roi[0] as f32 / width,
2280 roi[1] as f32 / height,
2281 roi[2] as f32 / width,
2282 roi[3] as f32 / height,
2283 ]
2284 .into();
2285
2286 let cropped = protos.slice(s![roi[1]..roi[3], roi[0]..roi[2], ..]);
2287
2288 Ok((cropped, roi_norm))
2289}
2290
2291fn make_segmentation<
2297 MASK: Float + AsPrimitive<f32> + Send + Sync,
2298 PROTO: Float + AsPrimitive<f32> + Send + Sync,
2299>(
2300 mask: ArrayView1<MASK>,
2301 protos: ArrayView3<PROTO>,
2302) -> Array3<u8> {
2303 let shape = protos.shape();
2304
2305 let mask = mask.to_shape((1, mask.len())).unwrap();
2307 let protos = protos.to_shape([shape[0] * shape[1], shape[2]]).unwrap();
2308 let protos = protos.reversed_axes();
2309 let mask = mask.map(|x| x.as_());
2310 let protos = protos.map(|x| x.as_());
2311
2312 let mask = mask
2314 .dot(&protos)
2315 .into_shape_with_order((shape[0], shape[1], 1))
2316 .unwrap();
2317
2318 mask.map(|x| {
2319 let sigmoid = 1.0 / (1.0 + (-*x).exp());
2320 (sigmoid * 255.0).round() as u8
2321 })
2322}
2323
2324fn make_segmentation_quant<
2331 MASK: PrimInt + AsPrimitive<DEST> + Send + Sync,
2332 PROTO: PrimInt + AsPrimitive<DEST> + Send + Sync,
2333 DEST: PrimInt + 'static + Signed + AsPrimitive<f32> + Debug,
2334>(
2335 mask: ArrayView1<MASK>,
2336 protos: ArrayView3<PROTO>,
2337 quant_masks: Quantization,
2338 quant_protos: Quantization,
2339) -> Array3<u8>
2340where
2341 i32: AsPrimitive<DEST>,
2342 f32: AsPrimitive<DEST>,
2343{
2344 let shape = protos.shape();
2345
2346 let mask = mask.to_shape((1, mask.len())).unwrap();
2348
2349 let protos = protos.to_shape([shape[0] * shape[1], shape[2]]).unwrap();
2350 let protos = protos.reversed_axes();
2351
2352 let zp = quant_masks.zero_point.as_();
2353
2354 let mask = mask.mapv(|x| x.as_() - zp);
2355
2356 let zp = quant_protos.zero_point.as_();
2357 let protos = protos.mapv(|x| x.as_() - zp);
2358
2359 let segmentation = mask
2361 .dot(&protos)
2362 .into_shape_with_order((shape[0], shape[1], 1))
2363 .unwrap();
2364
2365 let combined_scale = quant_masks.scale * quant_protos.scale;
2366 segmentation.map(|x| {
2367 let val: f32 = (*x).as_() * combined_scale;
2368 let sigmoid = 1.0 / (1.0 + (-val).exp());
2369 (sigmoid * 255.0).round() as u8
2370 })
2371}
2372
2373pub(crate) fn yolo_segmentation_to_mask(
2385 segmentation: ArrayView3<u8>,
2386 threshold: u8,
2387) -> Result<Array2<u8>, crate::DecoderError> {
2388 if segmentation.shape()[2] != 1 {
2389 return Err(crate::DecoderError::InvalidShape(format!(
2390 "Yolo Instance Segmentation should have shape (H, W, 1), got (H, W, {})",
2391 segmentation.shape()[2]
2392 )));
2393 }
2394 Ok(segmentation
2395 .slice(s![.., .., 0])
2396 .map(|x| if *x >= threshold { 1 } else { 0 }))
2397}
2398
2399#[cfg(test)]
2400#[cfg_attr(coverage_nightly, coverage(off))]
2401mod tests {
2402 use super::*;
2403 use ndarray::Array2;
2404
2405 fn multi_label_kernel_labels(nms: Nms) -> Vec<Vec<usize>> {
2408 let flat = Array2::from_shape_vec((6, 1), vec![0.5f32, 0.5, 0.2, 0.2, 0.9, 0.8]).unwrap();
2410 let flat_q = flat.mapv(|v| (v * 100.0).round() as i8);
2411 let q = Quantization::new(0.01, 0);
2412 let (boxes, scores) = (flat.slice(s![..4, ..]), flat.slice(s![4.., ..]));
2413 let (boxes_q, scores_q) = (flat_q.slice(s![..4, ..]), flat_q.slice(s![4.., ..]));
2414 let sorted = |mut v: Vec<usize>| {
2415 v.sort_unstable();
2416 v
2417 };
2418 let of = |b: &[DetectBox]| sorted(b.iter().map(|b| b.label).collect());
2419 let of_idx = |b: &[(DetectBox, usize)]| sorted(b.iter().map(|b| b.0.label).collect());
2420 let mut out = Vec::new();
2421 let mut v = Vec::new();
2422 impl_yolo_float::<XYWH, f32>(flat.view(), 0.5, 0.5, Some(nms), 0, 8, true, &mut v).unwrap();
2423 out.push(of(&v));
2424 impl_yolo_quant::<XYWH, i8>((flat_q.view(), q), 0.5, 0.5, Some(nms), 0, 8, true, &mut v)
2425 .unwrap();
2426 out.push(of(&v));
2427 impl_yolo_split_float::<XYWH, f32, f32>(
2428 boxes,
2429 scores,
2430 0.5,
2431 0.5,
2432 Some(nms),
2433 0,
2434 8,
2435 true,
2436 &mut v,
2437 )
2438 .unwrap();
2439 out.push(of(&v));
2440 impl_yolo_split_quant::<XYWH, i8, i8>(
2441 (boxes_q, q),
2442 (scores_q, q),
2443 0.5,
2444 0.5,
2445 Some(nms),
2446 0,
2447 8,
2448 true,
2449 &mut v,
2450 )
2451 .unwrap();
2452 out.push(of(&v));
2453 let r = impl_yolo_segdet_get_boxes::<XYWH, f32, f32>(
2454 boxes.reversed_axes(),
2455 scores.reversed_axes(),
2456 0.5,
2457 0.5,
2458 Some(nms),
2459 0,
2460 8,
2461 true,
2462 )
2463 .unwrap();
2464 out.push(of_idx(&r));
2465 let r = impl_yolo_split_segdet_quant_get_boxes::<XYWH, i8, i8>(
2466 (boxes_q.reversed_axes(), q),
2467 (scores_q.reversed_axes(), q),
2468 0.5,
2469 0.5,
2470 Some(nms),
2471 0,
2472 8,
2473 true,
2474 )
2475 .unwrap();
2476 out.push(of_idx(&r));
2477 out
2478 }
2479
2480 #[test]
2481 fn multi_label_honours_class_agnostic_nms_on_every_kernel() {
2482 for (path, labels) in multi_label_kernel_labels(Nms::ClassAgnostic)
2486 .into_iter()
2487 .enumerate()
2488 {
2489 assert_eq!(labels, vec![0], "kernel {path}: class-agnostic");
2490 }
2491 for (path, labels) in multi_label_kernel_labels(Nms::ClassAware)
2492 .into_iter()
2493 .enumerate()
2494 {
2495 assert_eq!(labels, vec![0, 1], "kernel {path}: class-aware");
2496 }
2497 }
2498
2499 #[test]
2504 fn test_end_to_end_det_basic_filtering() {
2505 let data: Vec<f32> = vec![
2509 0.1, 0.2, 0.3, 0.1, 0.2, 0.3, 0.5, 0.6, 0.7, 0.5, 0.6, 0.7, 0.9, 0.1, 0.2, 0.0, 1.0, 2.0, ];
2517 let output = Array2::from_shape_vec((6, 3), data).unwrap();
2518
2519 let mut boxes = Vec::with_capacity(10);
2520 decode_yolo_end_to_end_det_float(output.view(), 0.5, cap_or_default(&boxes), &mut boxes)
2521 .unwrap();
2522
2523 assert_eq!(boxes.len(), 1);
2525 assert_eq!(boxes[0].label, 0);
2526 assert!((boxes[0].score - 0.9).abs() < 0.01);
2527 assert!((boxes[0].bbox.xmin - 0.1).abs() < 0.01);
2528 assert!((boxes[0].bbox.ymin - 0.1).abs() < 0.01);
2529 assert!((boxes[0].bbox.xmax - 0.5).abs() < 0.01);
2530 assert!((boxes[0].bbox.ymax - 0.5).abs() < 0.01);
2531 }
2532
2533 #[test]
2534 fn test_end_to_end_det_all_pass_threshold() {
2535 let data: Vec<f32> = vec![
2537 10.0, 20.0, 10.0, 20.0, 50.0, 60.0, 50.0, 60.0, 0.8, 0.7, 1.0, 2.0, ];
2544 let output = Array2::from_shape_vec((6, 2), data).unwrap();
2545
2546 let mut boxes = Vec::with_capacity(10);
2547 decode_yolo_end_to_end_det_float(output.view(), 0.5, cap_or_default(&boxes), &mut boxes)
2548 .unwrap();
2549
2550 assert_eq!(boxes.len(), 2);
2551 assert_eq!(boxes[0].label, 1);
2552 assert_eq!(boxes[1].label, 2);
2553 }
2554
2555 #[test]
2556 fn test_end_to_end_det_none_pass_threshold() {
2557 let data: Vec<f32> = vec![
2559 10.0, 20.0, 10.0, 20.0, 50.0, 60.0, 50.0, 60.0, 0.1, 0.2, 1.0, 2.0, ];
2566 let output = Array2::from_shape_vec((6, 2), data).unwrap();
2567
2568 let mut boxes = Vec::with_capacity(10);
2569 decode_yolo_end_to_end_det_float(output.view(), 0.5, cap_or_default(&boxes), &mut boxes)
2570 .unwrap();
2571
2572 assert_eq!(boxes.len(), 0);
2573 }
2574
2575 #[test]
2576 fn test_end_to_end_det_capacity_limit() {
2577 let data: Vec<f32> = vec![
2579 0.1, 0.2, 0.3, 0.4, 0.5, 0.1, 0.2, 0.3, 0.4, 0.5, 0.5, 0.6, 0.7, 0.8, 0.9, 0.5, 0.6, 0.7, 0.8, 0.9, 0.9, 0.9, 0.9, 0.9, 0.9, 0.0, 1.0, 2.0, 3.0, 4.0, ];
2586 let output = Array2::from_shape_vec((6, 5), data).unwrap();
2587
2588 let mut boxes = Vec::with_capacity(2); decode_yolo_end_to_end_det_float(output.view(), 0.5, cap_or_default(&boxes), &mut boxes)
2590 .unwrap();
2591
2592 assert_eq!(boxes.len(), 2);
2593 }
2594
2595 #[test]
2596 fn test_end_to_end_det_empty_output() {
2597 let output = Array2::<f32>::zeros((6, 0));
2599
2600 let mut boxes = Vec::with_capacity(10);
2601 decode_yolo_end_to_end_det_float(output.view(), 0.5, cap_or_default(&boxes), &mut boxes)
2602 .unwrap();
2603
2604 assert_eq!(boxes.len(), 0);
2605 }
2606
2607 #[test]
2608 fn test_end_to_end_det_pixel_coordinates() {
2609 let data: Vec<f32> = vec![
2611 100.0, 200.0, 300.0, 400.0, 0.95, 5.0, ];
2618 let output = Array2::from_shape_vec((6, 1), data).unwrap();
2619
2620 let mut boxes = Vec::with_capacity(10);
2621 decode_yolo_end_to_end_det_float(output.view(), 0.5, cap_or_default(&boxes), &mut boxes)
2622 .unwrap();
2623
2624 assert_eq!(boxes.len(), 1);
2625 assert_eq!(boxes[0].label, 5);
2626 assert!((boxes[0].bbox.xmin - 100.0).abs() < 0.01);
2627 assert!((boxes[0].bbox.ymin - 200.0).abs() < 0.01);
2628 assert!((boxes[0].bbox.xmax - 300.0).abs() < 0.01);
2629 assert!((boxes[0].bbox.ymax - 400.0).abs() < 0.01);
2630 }
2631
2632 #[test]
2633 fn test_end_to_end_det_invalid_shape() {
2634 let output = Array2::<f32>::zeros((5, 3));
2636
2637 let mut boxes = Vec::with_capacity(10);
2638 let result = decode_yolo_end_to_end_det_float(
2639 output.view(),
2640 0.5,
2641 cap_or_default(&boxes),
2642 &mut boxes,
2643 );
2644
2645 assert!(result.is_err());
2646 assert!(matches!(
2647 result,
2648 Err(crate::DecoderError::InvalidShape(s)) if s.contains("at least 6 rows")
2649 ));
2650 }
2651
2652 #[test]
2657 fn test_end_to_end_segdet_basic() {
2658 let num_protos = 32;
2661 let num_detections = 2;
2662 let num_features = 6 + num_protos;
2663
2664 let mut data = vec![0.0f32; num_features * num_detections];
2666 data[0] = 0.1; data[1] = 0.5; data[num_detections] = 0.1; data[num_detections + 1] = 0.5; data[2 * num_detections] = 0.4; data[2 * num_detections + 1] = 0.9; data[3 * num_detections] = 0.4; data[3 * num_detections + 1] = 0.9; data[4 * num_detections] = 0.9; data[4 * num_detections + 1] = 0.3; data[5 * num_detections] = 1.0; data[5 * num_detections + 1] = 2.0; for i in 6..num_features {
2681 data[i * num_detections] = 0.1;
2682 data[i * num_detections + 1] = 0.1;
2683 }
2684
2685 let output = Array2::from_shape_vec((num_features, num_detections), data).unwrap();
2686
2687 let protos = Array3::<f32>::zeros((16, 16, num_protos));
2689
2690 let mut boxes = Vec::with_capacity(10);
2691 let mut masks = Vec::with_capacity(10);
2692 decode_yolo_end_to_end_segdet_float(
2693 output.view(),
2694 protos.view(),
2695 0.5,
2696 cap_or_default(&boxes),
2697 &mut boxes,
2698 &mut masks,
2699 )
2700 .unwrap();
2701
2702 assert_eq!(boxes.len(), 1);
2704 assert_eq!(masks.len(), 1);
2705 assert_eq!(boxes[0].label, 1);
2706 assert!((boxes[0].score - 0.9).abs() < 0.01);
2707 }
2708
2709 #[test]
2710 fn test_end_to_end_segdet_mask_coordinates() {
2711 let num_protos = 32;
2713 let num_features = 6 + num_protos;
2714
2715 let mut data = vec![0.0f32; num_features];
2716 data[0] = 0.2; data[1] = 0.2; data[2] = 0.8; data[3] = 0.8; data[4] = 0.95; data[5] = 3.0; let output = Array2::from_shape_vec((num_features, 1), data).unwrap();
2724 let protos = Array3::<f32>::zeros((16, 16, num_protos));
2725
2726 let mut boxes = Vec::with_capacity(10);
2727 let mut masks = Vec::with_capacity(10);
2728 decode_yolo_end_to_end_segdet_float(
2729 output.view(),
2730 protos.view(),
2731 0.5,
2732 cap_or_default(&boxes),
2733 &mut boxes,
2734 &mut masks,
2735 )
2736 .unwrap();
2737
2738 assert_eq!(boxes.len(), 1);
2739 assert_eq!(masks.len(), 1);
2740
2741 let step = 1.0 / 16.0;
2745 assert!(masks[0].xmin <= boxes[0].bbox.xmin);
2746 assert!(masks[0].ymin <= boxes[0].bbox.ymin);
2747 assert!(masks[0].xmax >= boxes[0].bbox.xmax);
2748 assert!(masks[0].ymax >= boxes[0].bbox.ymax);
2749 assert!((boxes[0].bbox.xmin - masks[0].xmin) < step);
2750 assert!((boxes[0].bbox.ymin - masks[0].ymin) < step);
2751 assert!((masks[0].xmax - boxes[0].bbox.xmax) < step);
2752 assert!((masks[0].ymax - boxes[0].bbox.ymax) < step);
2753 }
2754
2755 #[test]
2756 fn test_end_to_end_segdet_empty_output() {
2757 let num_protos = 32;
2758 let output = Array2::<f32>::zeros((6 + num_protos, 0));
2759 let protos = Array3::<f32>::zeros((16, 16, num_protos));
2760
2761 let mut boxes = Vec::with_capacity(10);
2762 let mut masks = Vec::with_capacity(10);
2763 decode_yolo_end_to_end_segdet_float(
2764 output.view(),
2765 protos.view(),
2766 0.5,
2767 cap_or_default(&boxes),
2768 &mut boxes,
2769 &mut masks,
2770 )
2771 .unwrap();
2772
2773 assert_eq!(boxes.len(), 0);
2774 assert_eq!(masks.len(), 0);
2775 }
2776
2777 #[test]
2778 fn test_end_to_end_segdet_capacity_limit() {
2779 let num_protos = 32;
2780 let num_detections = 5;
2781 let num_features = 6 + num_protos;
2782
2783 let mut data = vec![0.0f32; num_features * num_detections];
2784 for i in 0..num_detections {
2786 data[i] = 0.1 * (i as f32); data[num_detections + i] = 0.1 * (i as f32); data[2 * num_detections + i] = 0.1 * (i as f32) + 0.2; data[3 * num_detections + i] = 0.1 * (i as f32) + 0.2; data[4 * num_detections + i] = 0.9; data[5 * num_detections + i] = i as f32; }
2793
2794 let output = Array2::from_shape_vec((num_features, num_detections), data).unwrap();
2795 let protos = Array3::<f32>::zeros((16, 16, num_protos));
2796
2797 let mut boxes = Vec::with_capacity(2); let mut masks = Vec::with_capacity(2);
2799 decode_yolo_end_to_end_segdet_float(
2800 output.view(),
2801 protos.view(),
2802 0.5,
2803 cap_or_default(&boxes),
2804 &mut boxes,
2805 &mut masks,
2806 )
2807 .unwrap();
2808
2809 assert_eq!(boxes.len(), 2);
2810 assert_eq!(masks.len(), 2);
2811 }
2812
2813 #[test]
2814 fn test_end_to_end_segdet_invalid_shape_too_few_rows() {
2815 let output = Array2::<f32>::zeros((6, 3));
2817 let protos = Array3::<f32>::zeros((16, 16, 32));
2818
2819 let mut boxes = Vec::with_capacity(10);
2820 let mut masks = Vec::with_capacity(10);
2821 let result = decode_yolo_end_to_end_segdet_float(
2822 output.view(),
2823 protos.view(),
2824 0.5,
2825 cap_or_default(&boxes),
2826 &mut boxes,
2827 &mut masks,
2828 );
2829
2830 assert!(result.is_err());
2831 assert!(matches!(
2832 result,
2833 Err(crate::DecoderError::InvalidShape(s)) if s.contains("at least 7 rows")
2834 ));
2835 }
2836
2837 #[test]
2838 fn test_end_to_end_segdet_invalid_shape_protos_mismatch() {
2839 let num_protos = 32;
2841 let output = Array2::<f32>::zeros((6 + 16, 3)); let protos = Array3::<f32>::zeros((16, 16, num_protos)); let mut boxes = Vec::with_capacity(10);
2845 let mut masks = Vec::with_capacity(10);
2846 let result = decode_yolo_end_to_end_segdet_float(
2847 output.view(),
2848 protos.view(),
2849 0.5,
2850 cap_or_default(&boxes),
2851 &mut boxes,
2852 &mut masks,
2853 );
2854
2855 assert!(result.is_err());
2856 assert!(matches!(
2857 result,
2858 Err(crate::DecoderError::InvalidShape(s)) if s.contains("doesn't match protos count")
2859 ));
2860 }
2861
2862 #[test]
2867 fn test_split_end_to_end_segdet_basic() {
2868 let num_protos = 32;
2871 let num_detections = 2;
2872 let num_features = 6 + num_protos;
2873
2874 let mut data = vec![0.0f32; num_features * num_detections];
2876 data[0] = 0.1; data[1] = 0.5; data[num_detections] = 0.1; data[num_detections + 1] = 0.5; data[2 * num_detections] = 0.4; data[2 * num_detections + 1] = 0.9; data[3 * num_detections] = 0.4; data[3 * num_detections + 1] = 0.9; data[4 * num_detections] = 0.9; data[4 * num_detections + 1] = 0.3; data[5 * num_detections] = 1.0; data[5 * num_detections + 1] = 2.0; for i in 6..num_features {
2891 data[i * num_detections] = 0.1;
2892 data[i * num_detections + 1] = 0.1;
2893 }
2894
2895 let output = Array2::from_shape_vec((num_features, num_detections), data).unwrap();
2896 let box_coords = output.slice(s![..4, ..]);
2897 let scores = output.slice(s![4..5, ..]);
2898 let classes = output.slice(s![5..6, ..]);
2899 let mask_coeff = output.slice(s![6.., ..]);
2900 let protos = Array3::<f32>::zeros((16, 16, num_protos));
2902
2903 let mut boxes = Vec::with_capacity(10);
2904 let mut masks = Vec::with_capacity(10);
2905 decode_yolo_split_end_to_end_segdet_float(
2906 box_coords,
2907 scores,
2908 classes,
2909 mask_coeff,
2910 protos.view(),
2911 0.5,
2912 cap_or_default(&boxes),
2913 &mut boxes,
2914 &mut masks,
2915 )
2916 .unwrap();
2917
2918 assert_eq!(boxes.len(), 1);
2920 assert_eq!(masks.len(), 1);
2921 assert_eq!(boxes[0].label, 1);
2922 assert!((boxes[0].score - 0.9).abs() < 0.01);
2923 }
2924
2925 #[test]
2930 fn test_segmentation_to_mask_basic() {
2931 let data: Vec<u8> = vec![
2933 100, 200, 50, 150, 10, 255, 128, 64, 0, 127, 128, 255, 64, 64, 192, 192, ];
2938 let segmentation = Array3::from_shape_vec((4, 4, 1), data).unwrap();
2939
2940 let mask = yolo_segmentation_to_mask(segmentation.view(), 128).unwrap();
2941
2942 assert_eq!(mask[[0, 0]], 0); assert_eq!(mask[[0, 1]], 1); assert_eq!(mask[[0, 2]], 0); assert_eq!(mask[[0, 3]], 1); assert_eq!(mask[[1, 1]], 1); assert_eq!(mask[[1, 2]], 1); assert_eq!(mask[[2, 0]], 0); assert_eq!(mask[[2, 1]], 0); }
2952
2953 #[test]
2954 fn test_segmentation_to_mask_all_above() {
2955 let segmentation = Array3::from_elem((4, 4, 1), 255u8);
2956 let mask = yolo_segmentation_to_mask(segmentation.view(), 128).unwrap();
2957 assert!(mask.iter().all(|&x| x == 1));
2958 }
2959
2960 #[test]
2961 fn test_segmentation_to_mask_all_below() {
2962 let segmentation = Array3::from_elem((4, 4, 1), 64u8);
2963 let mask = yolo_segmentation_to_mask(segmentation.view(), 128).unwrap();
2964 assert!(mask.iter().all(|&x| x == 0));
2965 }
2966
2967 #[test]
2968 fn test_segmentation_to_mask_invalid_shape() {
2969 let segmentation = Array3::from_elem((4, 4, 3), 128u8);
2970 let result = yolo_segmentation_to_mask(segmentation.view(), 128);
2971
2972 assert!(result.is_err());
2973 assert!(matches!(
2974 result,
2975 Err(crate::DecoderError::InvalidShape(s)) if s.contains("(H, W, 1)")
2976 ));
2977 }
2978
2979 #[test]
2984 fn test_protobox_clamps_edge_coordinates() {
2985 let protos = Array3::<f32>::zeros((16, 16, 4));
2987 let view = protos.view();
2988 let roi = BoundingBox {
2989 xmin: 0.5,
2990 ymin: 0.5,
2991 xmax: 1.0,
2992 ymax: 1.0,
2993 };
2994 let result = protobox(&view, &roi);
2995 assert!(result.is_ok(), "protobox should accept xmax=1.0");
2996 let (cropped, _roi_norm) = result.unwrap();
2997 assert!(cropped.shape()[0] > 0);
2999 assert!(cropped.shape()[1] > 0);
3000 assert_eq!(cropped.shape()[2], 4);
3001 }
3002
3003 #[test]
3004 fn test_protobox_rejects_wildly_out_of_range() {
3005 let protos = Array3::<f32>::zeros((16, 16, 4));
3007 let view = protos.view();
3008 let roi = BoundingBox {
3009 xmin: 0.0,
3010 ymin: 0.0,
3011 xmax: 3.0,
3012 ymax: 3.0,
3013 };
3014 let result = protobox(&view, &roi);
3015 assert!(
3016 matches!(result, Err(crate::DecoderError::InvalidShape(s)) if s.contains("un-normalized")),
3017 "protobox should reject coords > NORM_LIMIT"
3018 );
3019 }
3020
3021 #[test]
3022 fn test_protobox_accepts_slightly_over_one() {
3023 let protos = Array3::<f32>::zeros((16, 16, 4));
3025 let view = protos.view();
3026 let roi = BoundingBox {
3027 xmin: 0.0,
3028 ymin: 0.0,
3029 xmax: 1.5,
3030 ymax: 1.5,
3031 };
3032 let result = protobox(&view, &roi);
3033 assert!(
3034 result.is_ok(),
3035 "protobox should accept coords <= NORM_LIMIT (2.0)"
3036 );
3037 let (cropped, _roi_norm) = result.unwrap();
3038 assert_eq!(cropped.shape()[0], 16);
3040 assert_eq!(cropped.shape()[1], 16);
3041 }
3042
3043 #[test]
3044 fn test_segdet_float_proto_no_panic() {
3045 let num_proposals = 100; let num_classes = 80;
3049 let num_mask_coeffs = 32;
3050 let rows = 4 + num_classes + num_mask_coeffs; let mut data = vec![0.0f32; rows * num_proposals];
3056 for i in 0..num_proposals {
3057 let row = |r: usize| r * num_proposals + i;
3058 data[row(0)] = 320.0; data[row(1)] = 320.0; data[row(2)] = 50.0; data[row(3)] = 50.0; data[row(4)] = 0.9; }
3064 let boxes = ndarray::Array2::from_shape_vec((rows, num_proposals), data).unwrap();
3065
3066 let protos = ndarray::Array3::<f32>::zeros((160, 160, num_mask_coeffs));
3071
3072 let mut output_boxes = Vec::with_capacity(300);
3073
3074 let proto_data = impl_yolo_segdet_float_proto::<XYWH, _, _>(
3076 boxes.view(),
3077 protos.view(),
3078 0.5,
3079 0.7,
3080 Some(Nms::ClassAgnostic),
3081 MAX_NMS_CANDIDATES,
3082 300,
3083 None,
3084 None,
3085 false, &mut output_boxes,
3087 )
3088 .unwrap();
3089
3090 assert!(!output_boxes.is_empty());
3092 let coeffs_shape = proto_data.mask_coefficients.shape();
3093 assert_eq!(coeffs_shape[0], output_boxes.len());
3094 assert_eq!(coeffs_shape[1], num_mask_coeffs);
3096 }
3097
3098 #[test]
3113 fn test_pre_nms_cap_truncates_excess_candidates() {
3114 let n: usize = 50_000;
3115 let num_classes = 1;
3116
3117 let mut boxes_data = Vec::with_capacity(n * 4);
3121 let mut scores_data = Vec::with_capacity(n * num_classes);
3122 for i in 0..n {
3123 boxes_data.extend_from_slice(&[0.1f32, 0.1, 0.5, 0.5]);
3124 scores_data.push(0.99 - (i as f32) * 1e-7);
3127 }
3128 let boxes = Array2::from_shape_vec((n, 4), boxes_data).unwrap();
3129 let scores = Array2::from_shape_vec((n, num_classes), scores_data).unwrap();
3130
3131 let result = impl_yolo_segdet_get_boxes::<XYXY, _, _>(
3132 boxes.view(),
3133 scores.view(),
3134 0.1,
3135 1.0, Some(Nms::ClassAgnostic), crate::yolo::MAX_NMS_CANDIDATES, usize::MAX, false, )
3141 .unwrap();
3142
3143 assert_eq!(
3144 result.len(),
3145 crate::yolo::MAX_NMS_CANDIDATES,
3146 "pre-NMS cap should truncate to MAX_NMS_CANDIDATES; got {}",
3147 result.len()
3148 );
3149 let top_score = result[0].0.score;
3152 assert!(
3153 top_score > 0.98,
3154 "highest-ranked survivor should have the largest score, got {top_score}"
3155 );
3156 }
3157
3158 #[test]
3163 fn test_pre_nms_cap_truncates_excess_candidates_quant() {
3164 use crate::Quantization;
3165 let n: usize = 50_000;
3166 let num_classes = 1;
3167
3168 let boxes_data = (0..n).flat_map(|_| [10i8, 10, 50, 50]).collect::<Vec<_>>();
3171 let boxes = Array2::from_shape_vec((n, 4), boxes_data).unwrap();
3172 let quant_boxes = Quantization {
3173 scale: 0.01,
3174 zero_point: 0,
3175 };
3176
3177 let scores_data: Vec<u8> = (0..n)
3182 .map(|i| 250u8.saturating_sub((i % 200) as u8))
3183 .collect();
3184 let scores = Array2::from_shape_vec((n, num_classes), scores_data).unwrap();
3185 let quant_scores = Quantization {
3186 scale: 0.00392,
3187 zero_point: 0,
3188 };
3189
3190 let result = impl_yolo_split_segdet_quant_get_boxes::<XYXY, _, _>(
3191 (boxes.view(), quant_boxes),
3192 (scores.view(), quant_scores),
3193 0.1,
3194 1.0, Some(Nms::ClassAgnostic), crate::yolo::MAX_NMS_CANDIDATES, usize::MAX, false,
3199 )
3200 .unwrap();
3201
3202 assert_eq!(
3203 result.len(),
3204 crate::yolo::MAX_NMS_CANDIDATES,
3205 "quant path pre-NMS cap should truncate to MAX_NMS_CANDIDATES; got {}",
3206 result.len()
3207 );
3208 }
3209
3210 #[test]
3224 fn segdet_combined_tensor_pairs_detection_with_matching_mask_row() {
3225 let nc = 2; let nm = 2; let n = 3; let feat = 4 + nc + nm; let mut data = vec![0.0f32; feat * n];
3248 let set = |d: &mut [f32], r: usize, c: usize, v: f32| d[r * n + c] = v;
3249 set(&mut data, 0, 0, 0.2);
3250 set(&mut data, 1, 0, 0.2);
3251 set(&mut data, 2, 0, 0.1);
3252 set(&mut data, 3, 0, 0.1);
3253 set(&mut data, 0, 1, 0.5);
3254 set(&mut data, 1, 1, 0.5);
3255 set(&mut data, 2, 1, 0.1);
3256 set(&mut data, 3, 1, 0.1);
3257 set(&mut data, 0, 2, 0.8);
3258 set(&mut data, 1, 2, 0.8);
3259 set(&mut data, 2, 2, 0.1);
3260 set(&mut data, 3, 2, 0.1);
3261 set(&mut data, 4, 0, 0.9);
3262 set(&mut data, 4, 2, 0.8);
3263 set(&mut data, 6, 0, 3.0);
3264 set(&mut data, 7, 0, 3.0);
3265 set(&mut data, 6, 2, -3.0);
3266 set(&mut data, 7, 2, -3.0);
3267
3268 let output = Array2::from_shape_vec((feat, n), data).unwrap();
3269 let protos = Array3::<f32>::from_elem((8, 8, nm), 1.0);
3270
3271 let mut boxes: Vec<DetectBox> = Vec::with_capacity(10);
3272 let mut masks: Vec<Segmentation> = Vec::with_capacity(10);
3273 decode_yolo_segdet_float(
3274 output.view(),
3275 protos.view(),
3276 0.5,
3277 0.5,
3278 Some(Nms::ClassAgnostic),
3279 &mut boxes,
3280 &mut masks,
3281 )
3282 .unwrap();
3283
3284 assert_eq!(
3285 boxes.len(),
3286 2,
3287 "two anchors above threshold should survive (a0 score=0.9, a2 score=0.8); got {}",
3288 boxes.len()
3289 );
3290
3291 for (b, m) in boxes.iter().zip(masks.iter()) {
3297 let cx = (b.bbox.xmin + b.bbox.xmax) * 0.5;
3298 let mean = {
3299 use edgefirst_tensor::{TensorMapTrait as _, TensorTrait as _};
3300 let t = m.segmentation.as_u8().expect("mask must be U8");
3301 let map = t.map_read().expect("map mask");
3302 let s = map.as_slice();
3303 let total: u32 = s.iter().map(|&v| v as u32).sum();
3304 total as f32 / s.len() as f32
3305 };
3306 if cx < 0.3 {
3307 assert!(
3309 mean > 200.0,
3310 "anchor 0 detection (centre {cx:.2}) should have high-value mask; got mean {mean}"
3311 );
3312 } else if cx > 0.7 {
3313 assert!(
3315 mean < 50.0,
3316 "anchor 2 detection (centre {cx:.2}) should have low-value mask; got mean {mean}"
3317 );
3318 } else {
3319 panic!("unexpected detection centre {cx:.2}");
3320 }
3321 }
3322 }
3323
3324 fn make_float_boxes(scores: &[f32]) -> Vec<(DetectBox, ())> {
3330 scores
3331 .iter()
3332 .enumerate()
3333 .map(|(i, &s)| {
3334 (
3335 DetectBox {
3336 bbox: BoundingBox {
3337 xmin: 0.0,
3338 ymin: 0.0,
3339 xmax: 1.0,
3340 ymax: 1.0,
3341 },
3342 score: s,
3343 label: i,
3344 },
3345 (),
3346 )
3347 })
3348 .collect()
3349 }
3350
3351 fn make_quant_boxes(scores: &[i8]) -> Vec<(DetectBoxQuantized<i8>, ())> {
3353 scores
3354 .iter()
3355 .enumerate()
3356 .map(|(i, &s)| {
3357 (
3358 DetectBoxQuantized {
3359 bbox: BoundingBox {
3360 xmin: 0.0,
3361 ymin: 0.0,
3362 xmax: 1.0,
3363 ymax: 1.0,
3364 },
3365 score: s,
3366 label: i,
3367 },
3368 (),
3369 )
3370 })
3371 .collect()
3372 }
3373
3374 #[test]
3375 fn truncate_float_top_k_zero_is_unbounded() {
3376 let mut boxes = make_float_boxes(&[0.9, 0.1, 0.5, 0.3, 0.7]);
3377 let original_len = boxes.len();
3378 truncate_to_top_k_by_score(&mut boxes, 0);
3379 assert_eq!(
3380 boxes.len(),
3381 original_len,
3382 "top_k=0 should keep all candidates (no-limit semantics)"
3383 );
3384 }
3385
3386 #[test]
3387 fn truncate_float_top_k_normal() {
3388 let mut boxes = make_float_boxes(&[0.9, 0.1, 0.5, 0.3, 0.7]);
3389 truncate_to_top_k_by_score(&mut boxes, 3);
3390 assert_eq!(boxes.len(), 3);
3391 let mut retained: Vec<f32> = boxes.iter().map(|(b, _)| b.score).collect();
3393 retained.sort_by(|a, b| b.total_cmp(a));
3394 assert_eq!(retained, vec![0.9, 0.7, 0.5]);
3395 }
3396
3397 #[test]
3398 fn truncate_float_top_k_noop_when_under_cap() {
3399 let mut boxes = make_float_boxes(&[0.9, 0.5]);
3400 truncate_to_top_k_by_score(&mut boxes, 10);
3401 assert_eq!(boxes.len(), 2, "should be no-op when len <= top_k");
3402 }
3403
3404 #[test]
3405 fn truncate_quant_top_k_zero_is_unbounded() {
3406 let mut boxes = make_quant_boxes(&[120, -50, 30, -10, 80]);
3407 let original_len = boxes.len();
3408 truncate_to_top_k_by_score(&mut boxes, 0);
3409 assert_eq!(
3410 boxes.len(),
3411 original_len,
3412 "top_k=0 should keep all candidates (no-limit semantics)"
3413 );
3414 }
3415
3416 #[test]
3417 fn truncate_quant_top_k_normal() {
3418 let mut boxes = make_quant_boxes(&[120, -50, 30, -10, 80]);
3419 truncate_to_top_k_by_score(&mut boxes, 3);
3420 assert_eq!(boxes.len(), 3);
3421 let mut retained: Vec<i8> = boxes.iter().map(|(b, _)| b.score).collect();
3422 retained.sort_by(|a, b| b.cmp(a));
3423 assert_eq!(retained, vec![120, 80, 30]);
3424 }
3425
3426 #[test]
3427 fn truncate_quant_top_k_noop_when_under_cap() {
3428 let mut boxes = make_quant_boxes(&[120, 80]);
3429 truncate_to_top_k_by_score(&mut boxes, 10);
3430 assert_eq!(boxes.len(), 2, "should be no-op when len <= top_k");
3431 }
3432
3433 fn tie_scene() -> (Vec<f32>, Vec<usize>) {
3437 let scores: Vec<f32> = (0..200)
3438 .map(|i| if (150..155).contains(&i) { 0.9 } else { 0.5 })
3439 .collect();
3440 let expected = (0..5).chain(150..155).collect();
3441 (scores, expected)
3442 }
3443
3444 #[test]
3445 fn truncate_breaks_boundary_ties_by_candidate_order() {
3446 let (scores, expected) = tie_scene();
3447 let mut boxes = make_float_boxes(&scores);
3448 truncate_to_top_k_by_score(&mut boxes, 10);
3449 let kept: Vec<usize> = boxes.iter().map(|(b, _)| b.label).collect();
3450 assert_eq!(kept, expected);
3451
3452 let q: Vec<i8> = scores.iter().map(|&s| (s * 100.0) as i8).collect();
3453 let mut boxes = make_quant_boxes(&q);
3454 truncate_to_top_k_by_score(&mut boxes, 10);
3455 let kept: Vec<usize> = boxes.iter().map(|(b, _)| b.label).collect();
3456 assert_eq!(kept, expected);
3457 }
3458
3459 #[test]
3460 fn nms_and_cap_ties_at_pre_nms_cap_keep_lowest_anchors() {
3461 let (scores, _) = tie_scene();
3464 let boxes: Vec<(DetectBox, usize)> = make_float_boxes(&scores)
3465 .into_iter()
3466 .map(|(mut b, ())| {
3467 let anchor = b.label;
3468 b.label = 0;
3469 (b, anchor)
3470 })
3471 .collect();
3472 let mut low = boxes.clone();
3473 for (b, _) in &mut low {
3474 b.score = 0.5;
3475 }
3476 let kept = nms_and_cap(low, Some(Nms::ClassAware), 0.5, 10, 300);
3477 assert_eq!(kept.iter().map(|(_, a)| *a).collect::<Vec<_>>(), vec![0]);
3478 let kept = nms_and_cap(boxes, Some(Nms::ClassAware), 0.5, 10, 300);
3479 assert_eq!(kept.iter().map(|(_, a)| *a).collect::<Vec<_>>(), vec![150]);
3480 }
3481
3482 #[test]
3483 fn nms_and_cap_bypass_keeps_highest_scores() {
3484 let boxes = make_float_boxes(&[0.1, 0.2, 0.9, 0.3, 0.8, 0.8]);
3487 let kept = nms_and_cap(boxes.clone(), None, 0.5, 0, 3);
3488 let labels: Vec<usize> = kept.iter().map(|(b, _)| b.label).collect();
3489 assert_eq!(labels, vec![2, 4, 5]);
3490 let kept = nms_and_cap(boxes, None, 0.5, 0, 300);
3492 let labels: Vec<usize> = kept.iter().map(|(b, _)| b.label).collect();
3493 assert_eq!(labels, vec![2, 4, 5, 3, 1, 0]);
3494
3495 let q = make_quant_boxes(&[10, 20, 90, 30, 80, 80]);
3496 let kept = nms_and_cap(q, None, 0.5, 0, 2);
3497 let labels: Vec<usize> = kept.iter().map(|(b, _)| b.label).collect();
3498 assert_eq!(labels, vec![2, 4]);
3499 }
3500
3501 #[test]
3502 fn nms_and_cap_pre_nms_top_k_zero_is_unbounded() {
3503 let boxes: Vec<(DetectBox, usize)> = (0..400)
3505 .map(|i| {
3506 let x = i as f32;
3507 (
3508 DetectBox {
3509 bbox: BoundingBox {
3510 xmin: x,
3511 ymin: 0.0,
3512 xmax: x + 0.5,
3513 ymax: 0.5,
3514 },
3515 score: 0.5 + (i % 7) as f32 * 0.01,
3516 label: 0,
3517 },
3518 i,
3519 )
3520 })
3521 .collect();
3522 let capped = nms_and_cap(boxes.clone(), Some(Nms::ClassAware), 0.5, 300, 1000);
3523 assert_eq!(capped.len(), 300);
3524 let all = nms_and_cap(boxes, Some(Nms::ClassAware), 0.5, 0, 1000);
3525 assert_eq!(all.len(), 400);
3526 }
3527
3528 #[test]
3529 #[cfg(debug_assertions)]
3530 #[should_panic(expected = "Nms::Auto must be resolved by the builder")]
3531 fn dispatch_nms_float_rejects_unresolved_auto() {
3532 dispatch_nms_float(Some(Nms::Auto), 0.5, None, Vec::new());
3533 }
3534
3535 #[test]
3536 #[cfg(debug_assertions)]
3537 #[should_panic(expected = "Nms::Auto must be resolved by the builder")]
3538 fn dispatch_nms_int_rejects_unresolved_auto() {
3539 dispatch_nms_int::<i8>(Some(Nms::Auto), 0.5, None, Vec::new());
3540 }
3541}