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<
1839 MASK: PrimInt + AsPrimitive<f32> + AsPrimitive<i8> + Send + Sync + 'static,
1840 PROTO: PrimInt + AsPrimitive<f32> + AsPrimitive<i8> + Send + Sync + 'static,
1841>(
1842 det_indices: Vec<(DetectBox, usize)>,
1843 mask_tensor: ArrayView2<MASK>,
1844 quant_masks: Quantization,
1845 protos: ArrayView3<PROTO>,
1846 quant_protos: Quantization,
1847 output_boxes: &mut Vec<DetectBox>,
1848) -> ProtoData {
1849 use edgefirst_tensor::{Tensor, TensorDyn, TensorMapTrait, TensorMemory, TensorTrait};
1850
1851 let span = tracing::trace_span!(
1852 "decoder.decode_proto.extract_proto_data",
1853 mode = "quant",
1854 n = det_indices.len(),
1855 num_protos = tracing::field::Empty,
1856 layout = tracing::field::Empty,
1857 );
1858 let _guard = span.enter();
1859
1860 let num_protos = mask_tensor.ncols();
1861 let n = det_indices.len();
1862 span.record("num_protos", num_protos);
1863
1864 if n == 0 {
1870 output_boxes.clear();
1871 let (h, w, k) = protos.dim();
1872
1873 let (proto_shape, proto_layout) = if std::any::TypeId::of::<PROTO>()
1875 == std::any::TypeId::of::<i8>()
1876 {
1877 if protos.is_standard_layout() {
1878 (&[h, w, k][..], ProtoLayout::Nhwc)
1879 } else if protos.ndim() == 3 && protos.strides() == [w as isize, 1, (h * w) as isize] {
1880 (&[k, h, w][..], ProtoLayout::Nchw)
1881 } else {
1882 (&[h, w, k][..], ProtoLayout::Nhwc)
1883 }
1884 } else {
1885 (&[h, w, k][..], ProtoLayout::Nhwc)
1886 };
1887
1888 let coeff_tensor = Tensor::<i8>::new(&[0, num_protos], Some(TensorMemory::Mem), None)
1889 .expect("allocating empty mask_coefficients tensor");
1890 let coeff_quant =
1891 edgefirst_tensor::Quantization::per_tensor(quant_masks.scale, quant_masks.zero_point);
1892 let coeff_tensor = coeff_tensor
1893 .with_quantization(coeff_quant)
1894 .expect("per-tensor quantization on mask coefficients");
1895 let protos_tensor = Tensor::<i8>::new(proto_shape, Some(TensorMemory::Mem), None)
1896 .expect("allocating protos tensor");
1897 let tensor_quant =
1898 edgefirst_tensor::Quantization::per_tensor(quant_protos.scale, quant_protos.zero_point);
1899 let protos_tensor = protos_tensor
1900 .with_quantization(tensor_quant)
1901 .expect("per-tensor quantization on protos tensor");
1902 return ProtoData {
1903 mask_coefficients: coeff_tensor.into(),
1904 protos: protos_tensor.into(),
1905 layout: proto_layout,
1906 };
1907 }
1908
1909 let mask_coefficients: TensorDyn = if std::any::TypeId::of::<MASK>()
1915 == std::any::TypeId::of::<i8>()
1916 {
1917 let mut coeff_i8 = Vec::<i8>::with_capacity(n * num_protos);
1918 output_boxes.clear();
1919 for (det, idx) in det_indices {
1920 output_boxes.push(det);
1921 let row = mask_tensor.row(idx);
1922 coeff_i8.extend(row.iter().map(|v| {
1923 let v_i8: i8 = v.as_();
1924 v_i8
1925 }));
1926 }
1927 let coeff_tensor = Tensor::<i8>::new(&[n, num_protos], Some(TensorMemory::Mem), None)
1928 .expect("allocating mask_coefficients tensor");
1929 if n > 0 {
1930 let mut m = coeff_tensor
1931 .map()
1932 .expect("mapping mask_coefficients tensor");
1933 m.as_mut_slice().copy_from_slice(&coeff_i8);
1934 }
1935 let coeff_quant =
1936 edgefirst_tensor::Quantization::per_tensor(quant_masks.scale, quant_masks.zero_point);
1937 let coeff_tensor = coeff_tensor
1938 .with_quantization(coeff_quant)
1939 .expect("per-tensor quantization on mask coefficients");
1940 coeff_tensor.into()
1941 } else if std::any::TypeId::of::<MASK>() == std::any::TypeId::of::<i16>() {
1942 let mut coeff_i16 = Vec::<i16>::with_capacity(n * num_protos);
1945 output_boxes.clear();
1946 for (det, idx) in det_indices {
1947 output_boxes.push(det);
1948 let row = mask_tensor.row(idx);
1949 coeff_i16.extend(row.iter().map(|v| {
1950 let v_f32: f32 = v.as_();
1951 v_f32 as i16
1952 }));
1953 }
1954 let coeff_tensor = Tensor::<i16>::new(&[n, num_protos], Some(TensorMemory::Mem), None)
1955 .expect("allocating mask_coefficients tensor");
1956 if n > 0 {
1957 let mut m = coeff_tensor
1958 .map()
1959 .expect("mapping mask_coefficients tensor");
1960 m.as_mut_slice().copy_from_slice(&coeff_i16);
1961 }
1962 let coeff_quant =
1963 edgefirst_tensor::Quantization::per_tensor(quant_masks.scale, quant_masks.zero_point);
1964 let coeff_tensor = coeff_tensor
1965 .with_quantization(coeff_quant)
1966 .expect("per-tensor quantization on mask coefficients");
1967 coeff_tensor.into()
1968 } else {
1969 let scale = quant_masks.scale;
1971 let zp = quant_masks.zero_point as f32;
1972 let mut coeff_f32 = Vec::<f32>::with_capacity(n * num_protos);
1973 output_boxes.clear();
1974 for (det, idx) in det_indices {
1975 output_boxes.push(det);
1976 let row = mask_tensor.row(idx);
1977 coeff_f32.extend(row.iter().map(|v| {
1978 let v_f32: f32 = v.as_();
1979 (v_f32 - zp) * scale
1980 }));
1981 }
1982 let coeff_tensor = Tensor::<f32>::new(&[n, num_protos], Some(TensorMemory::Mem), None)
1983 .expect("allocating mask_coefficients tensor");
1984 if n > 0 {
1985 let mut m = coeff_tensor
1986 .map()
1987 .expect("mapping mask_coefficients tensor");
1988 m.as_mut_slice().copy_from_slice(&coeff_f32);
1989 }
1990 coeff_tensor.into()
1991 };
1992
1993 let (h, w, k) = protos.dim();
1997
1998 let (proto_shape, proto_layout) =
2000 if std::any::TypeId::of::<PROTO>() == std::any::TypeId::of::<i8>() {
2001 if protos.is_standard_layout() {
2002 (&[h, w, k][..], ProtoLayout::Nhwc)
2004 } else if protos.ndim() == 3 && protos.strides() == [w as isize, 1, (h * w) as isize] {
2005 (&[k, h, w][..], ProtoLayout::Nchw)
2009 } else {
2010 (&[h, w, k][..], ProtoLayout::Nhwc)
2012 }
2013 } else {
2014 (&[h, w, k][..], ProtoLayout::Nhwc)
2015 };
2016
2017 let protos_tensor = Tensor::<i8>::new(proto_shape, Some(TensorMemory::Mem), None)
2018 .expect("allocating protos tensor");
2019 {
2020 let mut m = protos_tensor.map().expect("mapping protos tensor");
2021 let dst = m.as_mut_slice();
2022 if std::any::TypeId::of::<PROTO>() == std::any::TypeId::of::<i8>() {
2023 if protos.is_standard_layout() {
2026 let src: &[i8] = unsafe {
2027 std::slice::from_raw_parts(protos.as_ptr() as *const i8, protos.len())
2028 };
2029 dst.copy_from_slice(src);
2030 } else if protos.ndim() == 3 && protos.strides() == [w as isize, 1, (h * w) as isize] {
2031 let total = h * w * k;
2035 let src: &[i8] =
2038 unsafe { std::slice::from_raw_parts(protos.as_ptr() as *const i8, total) };
2039 dst.copy_from_slice(src);
2040 } else {
2041 for (d, s) in dst.iter_mut().zip(protos.iter()) {
2042 let v_i8: i8 = s.as_();
2043 *d = v_i8;
2044 }
2045 }
2046 } else {
2047 for (d, s) in dst.iter_mut().zip(protos.iter()) {
2048 let v_i8: i8 = s.as_();
2049 *d = v_i8;
2050 }
2051 }
2052 }
2053 let tensor_quant =
2054 edgefirst_tensor::Quantization::per_tensor(quant_protos.scale, quant_protos.zero_point);
2055 let protos_tensor = protos_tensor
2056 .with_quantization(tensor_quant)
2057 .expect("per-tensor quantization on new Tensor<i8>");
2058
2059 span.record("layout", tracing::field::debug(&proto_layout));
2060
2061 ProtoData {
2062 mask_coefficients,
2063 protos: protos_tensor.into(),
2064 layout: proto_layout,
2065 }
2066}
2067
2068pub trait FloatProtoElem: Copy + 'static {
2078 fn slice_into_tensor_dyn(
2081 values: &[Self],
2082 shape: &[usize],
2083 ) -> edgefirst_tensor::Result<edgefirst_tensor::TensorDyn>;
2084
2085 fn arrayview3_into_tensor_dyn(
2088 view: ArrayView3<'_, Self>,
2089 ) -> edgefirst_tensor::Result<edgefirst_tensor::TensorDyn>;
2090}
2091
2092impl FloatProtoElem for f32 {
2093 fn slice_into_tensor_dyn(
2094 values: &[f32],
2095 shape: &[usize],
2096 ) -> edgefirst_tensor::Result<edgefirst_tensor::TensorDyn> {
2097 edgefirst_tensor::Tensor::<f32>::from_slice(values, shape).map(Into::into)
2098 }
2099 fn arrayview3_into_tensor_dyn(
2100 view: ArrayView3<'_, f32>,
2101 ) -> edgefirst_tensor::Result<edgefirst_tensor::TensorDyn> {
2102 edgefirst_tensor::Tensor::<f32>::from_arrayview3(view).map(Into::into)
2103 }
2104}
2105
2106impl FloatProtoElem for half::f16 {
2107 fn slice_into_tensor_dyn(
2108 values: &[half::f16],
2109 shape: &[usize],
2110 ) -> edgefirst_tensor::Result<edgefirst_tensor::TensorDyn> {
2111 edgefirst_tensor::Tensor::<half::f16>::from_slice(values, shape).map(Into::into)
2112 }
2113 fn arrayview3_into_tensor_dyn(
2114 view: ArrayView3<'_, half::f16>,
2115 ) -> edgefirst_tensor::Result<edgefirst_tensor::TensorDyn> {
2116 edgefirst_tensor::Tensor::<half::f16>::from_arrayview3(view).map(Into::into)
2117 }
2118}
2119
2120impl FloatProtoElem for f64 {
2121 fn slice_into_tensor_dyn(
2122 values: &[f64],
2123 shape: &[usize],
2124 ) -> edgefirst_tensor::Result<edgefirst_tensor::TensorDyn> {
2125 let narrowed: Vec<f32> = values.iter().map(|&v| v as f32).collect();
2127 edgefirst_tensor::Tensor::<f32>::from_slice(&narrowed, shape).map(Into::into)
2128 }
2129 fn arrayview3_into_tensor_dyn(
2130 view: ArrayView3<'_, f64>,
2131 ) -> edgefirst_tensor::Result<edgefirst_tensor::TensorDyn> {
2132 let narrowed: ndarray::Array3<f32> = view.mapv(|v| v as f32);
2133 edgefirst_tensor::Tensor::<f32>::from_arrayview3(narrowed.view()).map(Into::into)
2134 }
2135}
2136
2137fn postprocess_yolo<'a, T>(
2138 output: &'a ArrayView2<'_, T>,
2139) -> (ArrayView2<'a, T>, ArrayView2<'a, T>) {
2140 let boxes_tensor = output.slice(s![..4, ..,]).reversed_axes();
2141 let scores_tensor = output.slice(s![4.., ..,]).reversed_axes();
2142 (boxes_tensor, scores_tensor)
2143}
2144
2145pub(crate) fn postprocess_yolo_seg<'a, T>(
2146 output: &'a ArrayView2<'_, T>,
2147 num_protos: usize,
2148) -> (ArrayView2<'a, T>, ArrayView2<'a, T>, ArrayView2<'a, T>) {
2149 assert!(
2150 output.shape()[0] > num_protos + 4,
2151 "Output shape is too short: {} <= {} + 4",
2152 output.shape()[0],
2153 num_protos
2154 );
2155 let num_classes = output.shape()[0] - 4 - num_protos;
2156 let boxes_tensor = output.slice(s![..4, ..,]).reversed_axes();
2157 let scores_tensor = output.slice(s![4..(num_classes + 4), ..,]).reversed_axes();
2158 let mask_tensor = output.slice(s![(num_classes + 4).., ..,]).reversed_axes();
2159 (boxes_tensor, scores_tensor, mask_tensor)
2160}
2161
2162pub(crate) fn postprocess_yolo_split_segdet<'a, 'b, 'c, BOX, SCORE, MASK>(
2163 boxes_tensor: ArrayView2<'a, BOX>,
2164 scores_tensor: ArrayView2<'b, SCORE>,
2165 mask_tensor: ArrayView2<'c, MASK>,
2166) -> (
2167 ArrayView2<'a, BOX>,
2168 ArrayView2<'b, SCORE>,
2169 ArrayView2<'c, MASK>,
2170) {
2171 let boxes_tensor = boxes_tensor.reversed_axes();
2172 let scores_tensor = scores_tensor.reversed_axes();
2173 let mask_tensor = mask_tensor.reversed_axes();
2174 (boxes_tensor, scores_tensor, mask_tensor)
2175}
2176
2177fn decode_segdet_f32<
2178 MASK: Float + AsPrimitive<f32> + Send + Sync,
2179 PROTO: Float + AsPrimitive<f32> + Send + Sync,
2180>(
2181 boxes: Vec<(DetectBox, usize)>,
2182 masks: ArrayView2<MASK>,
2183 protos: ArrayView3<PROTO>,
2184) -> Result<Vec<(DetectBox, BoundingBox, Array3<u8>)>, crate::DecoderError> {
2185 if boxes.is_empty() {
2186 return Ok(Vec::new());
2187 }
2188 if masks.shape()[1] != protos.shape()[2] {
2189 return Err(crate::DecoderError::InvalidShape(format!(
2190 "Mask coefficients count ({}) doesn't match protos channel count ({})",
2191 masks.shape()[1],
2192 protos.shape()[2],
2193 )));
2194 }
2195 boxes
2196 .into_par_iter()
2197 .map(|b| {
2198 let ind = b.1;
2199 let (protos, roi) = protobox(&protos, &b.0.bbox)?;
2204 Ok((b.0, roi, make_segmentation(masks.row(ind), protos.view())))
2205 })
2206 .collect()
2207}
2208
2209pub(crate) fn decode_segdet_quant<
2210 MASK: PrimInt + AsPrimitive<i64> + AsPrimitive<i128> + Send + Sync,
2211 PROTO: PrimInt + AsPrimitive<i64> + AsPrimitive<i128> + Send + Sync,
2212>(
2213 boxes: Vec<(DetectBox, usize)>,
2214 masks: ArrayView2<MASK>,
2215 protos: ArrayView3<PROTO>,
2216 quant_masks: Quantization,
2217 quant_protos: Quantization,
2218) -> Result<Vec<(DetectBox, BoundingBox, Array3<u8>)>, crate::DecoderError> {
2219 if boxes.is_empty() {
2220 return Ok(Vec::new());
2221 }
2222 if masks.shape()[1] != protos.shape()[2] {
2223 return Err(crate::DecoderError::InvalidShape(format!(
2224 "Mask coefficients count ({}) doesn't match protos channel count ({})",
2225 masks.shape()[1],
2226 protos.shape()[2],
2227 )));
2228 }
2229
2230 let total_bits = MASK::zero().count_zeros() + PROTO::zero().count_zeros() + 5; boxes
2232 .into_iter()
2233 .map(|b| {
2234 let i = b.1;
2235 let (protos, roi) = protobox(&protos, &b.0.bbox.to_canonical())?;
2239 let seg = match total_bits {
2240 0..=64 => make_segmentation_quant::<MASK, PROTO, i64>(
2241 masks.row(i),
2242 protos.view(),
2243 quant_masks,
2244 quant_protos,
2245 ),
2246 65..=128 => make_segmentation_quant::<MASK, PROTO, i128>(
2247 masks.row(i),
2248 protos.view(),
2249 quant_masks,
2250 quant_protos,
2251 ),
2252 _ => {
2253 return Err(crate::DecoderError::NotSupported(format!(
2254 "Unsupported bit width ({total_bits}) for segmentation computation"
2255 )));
2256 }
2257 };
2258 Ok((b.0, roi, seg))
2259 })
2260 .collect()
2261}
2262
2263fn protobox<'a, T>(
2264 protos: &'a ArrayView3<T>,
2265 roi: &BoundingBox,
2266) -> Result<(ArrayView3<'a, T>, BoundingBox), crate::DecoderError> {
2267 let width = protos.dim().1 as f32;
2268 let height = protos.dim().0 as f32;
2269
2270 const NORM_LIMIT: f32 = 2.0;
2282 if roi.xmin > NORM_LIMIT
2283 || roi.ymin > NORM_LIMIT
2284 || roi.xmax > NORM_LIMIT
2285 || roi.ymax > NORM_LIMIT
2286 {
2287 return Err(crate::DecoderError::InvalidShape(format!(
2288 "Bounding box coordinates appear un-normalized (pixel-space). \
2289 Got bbox=({:.2}, {:.2}, {:.2}, {:.2}) but expected values in [0, 1]. \
2290 Two ways to fix this: \
2291 (1) declare `Detection::normalized = false` in the model schema \
2292 AND make sure the schema's `input.shape` / `input.dshape` carries \
2293 the model input dims so the decoder can divide by (W, H) before NMS \
2294 (EDGEAI-1303 — verify with `Decoder::input_dims().is_some()`); or \
2295 (2) normalize the boxes in-graph before decode().",
2296 roi.xmin, roi.ymin, roi.xmax, roi.ymax,
2297 )));
2298 }
2299
2300 let roi = [
2301 (roi.xmin * width).clamp(0.0, width) as usize,
2302 (roi.ymin * height).clamp(0.0, height) as usize,
2303 (roi.xmax * width).clamp(0.0, width).ceil() as usize,
2304 (roi.ymax * height).clamp(0.0, height).ceil() as usize,
2305 ];
2306
2307 let roi_norm = [
2308 roi[0] as f32 / width,
2309 roi[1] as f32 / height,
2310 roi[2] as f32 / width,
2311 roi[3] as f32 / height,
2312 ]
2313 .into();
2314
2315 let cropped = protos.slice(s![roi[1]..roi[3], roi[0]..roi[2], ..]);
2316
2317 Ok((cropped, roi_norm))
2318}
2319
2320fn make_segmentation<
2326 MASK: Float + AsPrimitive<f32> + Send + Sync,
2327 PROTO: Float + AsPrimitive<f32> + Send + Sync,
2328>(
2329 mask: ArrayView1<MASK>,
2330 protos: ArrayView3<PROTO>,
2331) -> Array3<u8> {
2332 let shape = protos.shape();
2333
2334 let mask = mask.to_shape((1, mask.len())).unwrap();
2336 let protos = protos.to_shape([shape[0] * shape[1], shape[2]]).unwrap();
2337 let protos = protos.reversed_axes();
2338 let mask = mask.map(|x| x.as_());
2339 let protos = protos.map(|x| x.as_());
2340
2341 let mask = mask
2343 .dot(&protos)
2344 .into_shape_with_order((shape[0], shape[1], 1))
2345 .unwrap();
2346
2347 mask.map(|x| {
2348 let sigmoid = 1.0 / (1.0 + (-*x).exp());
2349 (sigmoid * 255.0).round() as u8
2350 })
2351}
2352
2353fn make_segmentation_quant<
2360 MASK: PrimInt + AsPrimitive<DEST> + Send + Sync,
2361 PROTO: PrimInt + AsPrimitive<DEST> + Send + Sync,
2362 DEST: PrimInt + 'static + Signed + AsPrimitive<f32> + Debug,
2363>(
2364 mask: ArrayView1<MASK>,
2365 protos: ArrayView3<PROTO>,
2366 quant_masks: Quantization,
2367 quant_protos: Quantization,
2368) -> Array3<u8>
2369where
2370 i32: AsPrimitive<DEST>,
2371 f32: AsPrimitive<DEST>,
2372{
2373 let shape = protos.shape();
2374
2375 let mask = mask.to_shape((1, mask.len())).unwrap();
2377
2378 let protos = protos.to_shape([shape[0] * shape[1], shape[2]]).unwrap();
2379 let protos = protos.reversed_axes();
2380
2381 let zp = quant_masks.zero_point.as_();
2382
2383 let mask = mask.mapv(|x| x.as_() - zp);
2384
2385 let zp = quant_protos.zero_point.as_();
2386 let protos = protos.mapv(|x| x.as_() - zp);
2387
2388 let segmentation = mask
2390 .dot(&protos)
2391 .into_shape_with_order((shape[0], shape[1], 1))
2392 .unwrap();
2393
2394 let combined_scale = quant_masks.scale * quant_protos.scale;
2395 segmentation.map(|x| {
2396 let val: f32 = (*x).as_() * combined_scale;
2397 let sigmoid = 1.0 / (1.0 + (-val).exp());
2398 (sigmoid * 255.0).round() as u8
2399 })
2400}
2401
2402pub(crate) fn yolo_segmentation_to_mask(
2414 segmentation: ArrayView3<u8>,
2415 threshold: u8,
2416) -> Result<Array2<u8>, crate::DecoderError> {
2417 if segmentation.shape()[2] != 1 {
2418 return Err(crate::DecoderError::InvalidShape(format!(
2419 "Yolo Instance Segmentation should have shape (H, W, 1), got (H, W, {})",
2420 segmentation.shape()[2]
2421 )));
2422 }
2423 Ok(segmentation
2424 .slice(s![.., .., 0])
2425 .map(|x| if *x >= threshold { 1 } else { 0 }))
2426}
2427
2428#[cfg(test)]
2429#[cfg_attr(coverage_nightly, coverage(off))]
2430mod tests {
2431 use super::*;
2432 use ndarray::Array2;
2433
2434 fn multi_label_kernel_labels(nms: Nms) -> Vec<Vec<usize>> {
2437 let flat = Array2::from_shape_vec((6, 1), vec![0.5f32, 0.5, 0.2, 0.2, 0.9, 0.8]).unwrap();
2439 let flat_q = flat.mapv(|v| (v * 100.0).round() as i8);
2440 let q = Quantization::new(0.01, 0);
2441 let (boxes, scores) = (flat.slice(s![..4, ..]), flat.slice(s![4.., ..]));
2442 let (boxes_q, scores_q) = (flat_q.slice(s![..4, ..]), flat_q.slice(s![4.., ..]));
2443 let sorted = |mut v: Vec<usize>| {
2444 v.sort_unstable();
2445 v
2446 };
2447 let of = |b: &[DetectBox]| sorted(b.iter().map(|b| b.label).collect());
2448 let of_idx = |b: &[(DetectBox, usize)]| sorted(b.iter().map(|b| b.0.label).collect());
2449 let mut out = Vec::new();
2450 let mut v = Vec::new();
2451 impl_yolo_float::<XYWH, f32>(flat.view(), 0.5, 0.5, Some(nms), 0, 8, true, &mut v).unwrap();
2452 out.push(of(&v));
2453 impl_yolo_quant::<XYWH, i8>((flat_q.view(), q), 0.5, 0.5, Some(nms), 0, 8, true, &mut v)
2454 .unwrap();
2455 out.push(of(&v));
2456 impl_yolo_split_float::<XYWH, f32, f32>(
2457 boxes,
2458 scores,
2459 0.5,
2460 0.5,
2461 Some(nms),
2462 0,
2463 8,
2464 true,
2465 &mut v,
2466 )
2467 .unwrap();
2468 out.push(of(&v));
2469 impl_yolo_split_quant::<XYWH, i8, i8>(
2470 (boxes_q, q),
2471 (scores_q, q),
2472 0.5,
2473 0.5,
2474 Some(nms),
2475 0,
2476 8,
2477 true,
2478 &mut v,
2479 )
2480 .unwrap();
2481 out.push(of(&v));
2482 let r = impl_yolo_segdet_get_boxes::<XYWH, f32, f32>(
2483 boxes.reversed_axes(),
2484 scores.reversed_axes(),
2485 0.5,
2486 0.5,
2487 Some(nms),
2488 0,
2489 8,
2490 true,
2491 )
2492 .unwrap();
2493 out.push(of_idx(&r));
2494 let r = impl_yolo_split_segdet_quant_get_boxes::<XYWH, i8, i8>(
2495 (boxes_q.reversed_axes(), q),
2496 (scores_q.reversed_axes(), q),
2497 0.5,
2498 0.5,
2499 Some(nms),
2500 0,
2501 8,
2502 true,
2503 )
2504 .unwrap();
2505 out.push(of_idx(&r));
2506 out
2507 }
2508
2509 #[test]
2510 fn multi_label_honours_class_agnostic_nms_on_every_kernel() {
2511 for (path, labels) in multi_label_kernel_labels(Nms::ClassAgnostic)
2515 .into_iter()
2516 .enumerate()
2517 {
2518 assert_eq!(labels, vec![0], "kernel {path}: class-agnostic");
2519 }
2520 for (path, labels) in multi_label_kernel_labels(Nms::ClassAware)
2521 .into_iter()
2522 .enumerate()
2523 {
2524 assert_eq!(labels, vec![0, 1], "kernel {path}: class-aware");
2525 }
2526 }
2527
2528 #[test]
2533 fn test_end_to_end_det_basic_filtering() {
2534 let data: Vec<f32> = vec![
2538 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, ];
2546 let output = Array2::from_shape_vec((6, 3), data).unwrap();
2547
2548 let mut boxes = Vec::with_capacity(10);
2549 decode_yolo_end_to_end_det_float(output.view(), 0.5, cap_or_default(&boxes), &mut boxes)
2550 .unwrap();
2551
2552 assert_eq!(boxes.len(), 1);
2554 assert_eq!(boxes[0].label, 0);
2555 assert!((boxes[0].score - 0.9).abs() < 0.01);
2556 assert!((boxes[0].bbox.xmin - 0.1).abs() < 0.01);
2557 assert!((boxes[0].bbox.ymin - 0.1).abs() < 0.01);
2558 assert!((boxes[0].bbox.xmax - 0.5).abs() < 0.01);
2559 assert!((boxes[0].bbox.ymax - 0.5).abs() < 0.01);
2560 }
2561
2562 #[test]
2563 fn test_end_to_end_det_all_pass_threshold() {
2564 let data: Vec<f32> = vec![
2566 10.0, 20.0, 10.0, 20.0, 50.0, 60.0, 50.0, 60.0, 0.8, 0.7, 1.0, 2.0, ];
2573 let output = Array2::from_shape_vec((6, 2), data).unwrap();
2574
2575 let mut boxes = Vec::with_capacity(10);
2576 decode_yolo_end_to_end_det_float(output.view(), 0.5, cap_or_default(&boxes), &mut boxes)
2577 .unwrap();
2578
2579 assert_eq!(boxes.len(), 2);
2580 assert_eq!(boxes[0].label, 1);
2581 assert_eq!(boxes[1].label, 2);
2582 }
2583
2584 #[test]
2585 fn test_end_to_end_det_none_pass_threshold() {
2586 let data: Vec<f32> = vec![
2588 10.0, 20.0, 10.0, 20.0, 50.0, 60.0, 50.0, 60.0, 0.1, 0.2, 1.0, 2.0, ];
2595 let output = Array2::from_shape_vec((6, 2), data).unwrap();
2596
2597 let mut boxes = Vec::with_capacity(10);
2598 decode_yolo_end_to_end_det_float(output.view(), 0.5, cap_or_default(&boxes), &mut boxes)
2599 .unwrap();
2600
2601 assert_eq!(boxes.len(), 0);
2602 }
2603
2604 #[test]
2605 fn test_end_to_end_det_capacity_limit() {
2606 let data: Vec<f32> = vec![
2608 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, ];
2615 let output = Array2::from_shape_vec((6, 5), data).unwrap();
2616
2617 let mut boxes = Vec::with_capacity(2); decode_yolo_end_to_end_det_float(output.view(), 0.5, cap_or_default(&boxes), &mut boxes)
2619 .unwrap();
2620
2621 assert_eq!(boxes.len(), 2);
2622 }
2623
2624 #[test]
2625 fn test_end_to_end_det_empty_output() {
2626 let output = Array2::<f32>::zeros((6, 0));
2628
2629 let mut boxes = Vec::with_capacity(10);
2630 decode_yolo_end_to_end_det_float(output.view(), 0.5, cap_or_default(&boxes), &mut boxes)
2631 .unwrap();
2632
2633 assert_eq!(boxes.len(), 0);
2634 }
2635
2636 #[test]
2637 fn test_end_to_end_det_pixel_coordinates() {
2638 let data: Vec<f32> = vec![
2640 100.0, 200.0, 300.0, 400.0, 0.95, 5.0, ];
2647 let output = Array2::from_shape_vec((6, 1), data).unwrap();
2648
2649 let mut boxes = Vec::with_capacity(10);
2650 decode_yolo_end_to_end_det_float(output.view(), 0.5, cap_or_default(&boxes), &mut boxes)
2651 .unwrap();
2652
2653 assert_eq!(boxes.len(), 1);
2654 assert_eq!(boxes[0].label, 5);
2655 assert!((boxes[0].bbox.xmin - 100.0).abs() < 0.01);
2656 assert!((boxes[0].bbox.ymin - 200.0).abs() < 0.01);
2657 assert!((boxes[0].bbox.xmax - 300.0).abs() < 0.01);
2658 assert!((boxes[0].bbox.ymax - 400.0).abs() < 0.01);
2659 }
2660
2661 #[test]
2662 fn test_end_to_end_det_invalid_shape() {
2663 let output = Array2::<f32>::zeros((5, 3));
2665
2666 let mut boxes = Vec::with_capacity(10);
2667 let result = decode_yolo_end_to_end_det_float(
2668 output.view(),
2669 0.5,
2670 cap_or_default(&boxes),
2671 &mut boxes,
2672 );
2673
2674 assert!(result.is_err());
2675 assert!(matches!(
2676 result,
2677 Err(crate::DecoderError::InvalidShape(s)) if s.contains("at least 6 rows")
2678 ));
2679 }
2680
2681 #[test]
2686 fn test_end_to_end_segdet_basic() {
2687 let num_protos = 32;
2690 let num_detections = 2;
2691 let num_features = 6 + num_protos;
2692
2693 let mut data = vec![0.0f32; num_features * num_detections];
2695 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 {
2710 data[i * num_detections] = 0.1;
2711 data[i * num_detections + 1] = 0.1;
2712 }
2713
2714 let output = Array2::from_shape_vec((num_features, num_detections), data).unwrap();
2715
2716 let protos = Array3::<f32>::zeros((16, 16, num_protos));
2718
2719 let mut boxes = Vec::with_capacity(10);
2720 let mut masks = Vec::with_capacity(10);
2721 decode_yolo_end_to_end_segdet_float(
2722 output.view(),
2723 protos.view(),
2724 0.5,
2725 cap_or_default(&boxes),
2726 &mut boxes,
2727 &mut masks,
2728 )
2729 .unwrap();
2730
2731 assert_eq!(boxes.len(), 1);
2733 assert_eq!(masks.len(), 1);
2734 assert_eq!(boxes[0].label, 1);
2735 assert!((boxes[0].score - 0.9).abs() < 0.01);
2736 }
2737
2738 #[test]
2739 fn test_end_to_end_segdet_mask_coordinates() {
2740 let num_protos = 32;
2742 let num_features = 6 + num_protos;
2743
2744 let mut data = vec![0.0f32; num_features];
2745 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();
2753 let protos = Array3::<f32>::zeros((16, 16, num_protos));
2754
2755 let mut boxes = Vec::with_capacity(10);
2756 let mut masks = Vec::with_capacity(10);
2757 decode_yolo_end_to_end_segdet_float(
2758 output.view(),
2759 protos.view(),
2760 0.5,
2761 cap_or_default(&boxes),
2762 &mut boxes,
2763 &mut masks,
2764 )
2765 .unwrap();
2766
2767 assert_eq!(boxes.len(), 1);
2768 assert_eq!(masks.len(), 1);
2769
2770 let step = 1.0 / 16.0;
2774 assert!(masks[0].xmin <= boxes[0].bbox.xmin);
2775 assert!(masks[0].ymin <= boxes[0].bbox.ymin);
2776 assert!(masks[0].xmax >= boxes[0].bbox.xmax);
2777 assert!(masks[0].ymax >= boxes[0].bbox.ymax);
2778 assert!((boxes[0].bbox.xmin - masks[0].xmin) < step);
2779 assert!((boxes[0].bbox.ymin - masks[0].ymin) < step);
2780 assert!((masks[0].xmax - boxes[0].bbox.xmax) < step);
2781 assert!((masks[0].ymax - boxes[0].bbox.ymax) < step);
2782 }
2783
2784 #[test]
2785 fn test_end_to_end_segdet_empty_output() {
2786 let num_protos = 32;
2787 let output = Array2::<f32>::zeros((6 + num_protos, 0));
2788 let protos = Array3::<f32>::zeros((16, 16, num_protos));
2789
2790 let mut boxes = Vec::with_capacity(10);
2791 let mut masks = Vec::with_capacity(10);
2792 decode_yolo_end_to_end_segdet_float(
2793 output.view(),
2794 protos.view(),
2795 0.5,
2796 cap_or_default(&boxes),
2797 &mut boxes,
2798 &mut masks,
2799 )
2800 .unwrap();
2801
2802 assert_eq!(boxes.len(), 0);
2803 assert_eq!(masks.len(), 0);
2804 }
2805
2806 #[test]
2807 fn test_end_to_end_segdet_capacity_limit() {
2808 let num_protos = 32;
2809 let num_detections = 5;
2810 let num_features = 6 + num_protos;
2811
2812 let mut data = vec![0.0f32; num_features * num_detections];
2813 for i in 0..num_detections {
2815 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; }
2822
2823 let output = Array2::from_shape_vec((num_features, num_detections), data).unwrap();
2824 let protos = Array3::<f32>::zeros((16, 16, num_protos));
2825
2826 let mut boxes = Vec::with_capacity(2); let mut masks = Vec::with_capacity(2);
2828 decode_yolo_end_to_end_segdet_float(
2829 output.view(),
2830 protos.view(),
2831 0.5,
2832 cap_or_default(&boxes),
2833 &mut boxes,
2834 &mut masks,
2835 )
2836 .unwrap();
2837
2838 assert_eq!(boxes.len(), 2);
2839 assert_eq!(masks.len(), 2);
2840 }
2841
2842 #[test]
2843 fn test_end_to_end_segdet_invalid_shape_too_few_rows() {
2844 let output = Array2::<f32>::zeros((6, 3));
2846 let protos = Array3::<f32>::zeros((16, 16, 32));
2847
2848 let mut boxes = Vec::with_capacity(10);
2849 let mut masks = Vec::with_capacity(10);
2850 let result = decode_yolo_end_to_end_segdet_float(
2851 output.view(),
2852 protos.view(),
2853 0.5,
2854 cap_or_default(&boxes),
2855 &mut boxes,
2856 &mut masks,
2857 );
2858
2859 assert!(result.is_err());
2860 assert!(matches!(
2861 result,
2862 Err(crate::DecoderError::InvalidShape(s)) if s.contains("at least 7 rows")
2863 ));
2864 }
2865
2866 #[test]
2867 fn test_end_to_end_segdet_invalid_shape_protos_mismatch() {
2868 let num_protos = 32;
2870 let output = Array2::<f32>::zeros((6 + 16, 3)); let protos = Array3::<f32>::zeros((16, 16, num_protos)); let mut boxes = Vec::with_capacity(10);
2874 let mut masks = Vec::with_capacity(10);
2875 let result = decode_yolo_end_to_end_segdet_float(
2876 output.view(),
2877 protos.view(),
2878 0.5,
2879 cap_or_default(&boxes),
2880 &mut boxes,
2881 &mut masks,
2882 );
2883
2884 assert!(result.is_err());
2885 assert!(matches!(
2886 result,
2887 Err(crate::DecoderError::InvalidShape(s)) if s.contains("doesn't match protos count")
2888 ));
2889 }
2890
2891 #[test]
2896 fn test_split_end_to_end_segdet_basic() {
2897 let num_protos = 32;
2900 let num_detections = 2;
2901 let num_features = 6 + num_protos;
2902
2903 let mut data = vec![0.0f32; num_features * num_detections];
2905 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 {
2920 data[i * num_detections] = 0.1;
2921 data[i * num_detections + 1] = 0.1;
2922 }
2923
2924 let output = Array2::from_shape_vec((num_features, num_detections), data).unwrap();
2925 let box_coords = output.slice(s![..4, ..]);
2926 let scores = output.slice(s![4..5, ..]);
2927 let classes = output.slice(s![5..6, ..]);
2928 let mask_coeff = output.slice(s![6.., ..]);
2929 let protos = Array3::<f32>::zeros((16, 16, num_protos));
2931
2932 let mut boxes = Vec::with_capacity(10);
2933 let mut masks = Vec::with_capacity(10);
2934 decode_yolo_split_end_to_end_segdet_float(
2935 box_coords,
2936 scores,
2937 classes,
2938 mask_coeff,
2939 protos.view(),
2940 0.5,
2941 cap_or_default(&boxes),
2942 &mut boxes,
2943 &mut masks,
2944 )
2945 .unwrap();
2946
2947 assert_eq!(boxes.len(), 1);
2949 assert_eq!(masks.len(), 1);
2950 assert_eq!(boxes[0].label, 1);
2951 assert!((boxes[0].score - 0.9).abs() < 0.01);
2952 }
2953
2954 #[test]
2959 fn test_segmentation_to_mask_basic() {
2960 let data: Vec<u8> = vec![
2962 100, 200, 50, 150, 10, 255, 128, 64, 0, 127, 128, 255, 64, 64, 192, 192, ];
2967 let segmentation = Array3::from_shape_vec((4, 4, 1), data).unwrap();
2968
2969 let mask = yolo_segmentation_to_mask(segmentation.view(), 128).unwrap();
2970
2971 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); }
2981
2982 #[test]
2983 fn test_segmentation_to_mask_all_above() {
2984 let segmentation = Array3::from_elem((4, 4, 1), 255u8);
2985 let mask = yolo_segmentation_to_mask(segmentation.view(), 128).unwrap();
2986 assert!(mask.iter().all(|&x| x == 1));
2987 }
2988
2989 #[test]
2990 fn test_segmentation_to_mask_all_below() {
2991 let segmentation = Array3::from_elem((4, 4, 1), 64u8);
2992 let mask = yolo_segmentation_to_mask(segmentation.view(), 128).unwrap();
2993 assert!(mask.iter().all(|&x| x == 0));
2994 }
2995
2996 #[test]
2997 fn test_segmentation_to_mask_invalid_shape() {
2998 let segmentation = Array3::from_elem((4, 4, 3), 128u8);
2999 let result = yolo_segmentation_to_mask(segmentation.view(), 128);
3000
3001 assert!(result.is_err());
3002 assert!(matches!(
3003 result,
3004 Err(crate::DecoderError::InvalidShape(s)) if s.contains("(H, W, 1)")
3005 ));
3006 }
3007
3008 #[test]
3013 fn test_protobox_clamps_edge_coordinates() {
3014 let protos = Array3::<f32>::zeros((16, 16, 4));
3016 let view = protos.view();
3017 let roi = BoundingBox {
3018 xmin: 0.5,
3019 ymin: 0.5,
3020 xmax: 1.0,
3021 ymax: 1.0,
3022 };
3023 let result = protobox(&view, &roi);
3024 assert!(result.is_ok(), "protobox should accept xmax=1.0");
3025 let (cropped, _roi_norm) = result.unwrap();
3026 assert!(cropped.shape()[0] > 0);
3028 assert!(cropped.shape()[1] > 0);
3029 assert_eq!(cropped.shape()[2], 4);
3030 }
3031
3032 #[test]
3033 fn test_protobox_rejects_wildly_out_of_range() {
3034 let protos = Array3::<f32>::zeros((16, 16, 4));
3036 let view = protos.view();
3037 let roi = BoundingBox {
3038 xmin: 0.0,
3039 ymin: 0.0,
3040 xmax: 3.0,
3041 ymax: 3.0,
3042 };
3043 let result = protobox(&view, &roi);
3044 assert!(
3045 matches!(result, Err(crate::DecoderError::InvalidShape(s)) if s.contains("un-normalized")),
3046 "protobox should reject coords > NORM_LIMIT"
3047 );
3048 }
3049
3050 #[test]
3051 fn test_protobox_accepts_slightly_over_one() {
3052 let protos = Array3::<f32>::zeros((16, 16, 4));
3054 let view = protos.view();
3055 let roi = BoundingBox {
3056 xmin: 0.0,
3057 ymin: 0.0,
3058 xmax: 1.5,
3059 ymax: 1.5,
3060 };
3061 let result = protobox(&view, &roi);
3062 assert!(
3063 result.is_ok(),
3064 "protobox should accept coords <= NORM_LIMIT (2.0)"
3065 );
3066 let (cropped, _roi_norm) = result.unwrap();
3067 assert_eq!(cropped.shape()[0], 16);
3069 assert_eq!(cropped.shape()[1], 16);
3070 }
3071
3072 #[test]
3073 fn test_segdet_float_proto_no_panic() {
3074 let num_proposals = 100; let num_classes = 80;
3078 let num_mask_coeffs = 32;
3079 let rows = 4 + num_classes + num_mask_coeffs; let mut data = vec![0.0f32; rows * num_proposals];
3085 for i in 0..num_proposals {
3086 let row = |r: usize| r * num_proposals + i;
3087 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; }
3093 let boxes = ndarray::Array2::from_shape_vec((rows, num_proposals), data).unwrap();
3094
3095 let protos = ndarray::Array3::<f32>::zeros((160, 160, num_mask_coeffs));
3100
3101 let mut output_boxes = Vec::with_capacity(300);
3102
3103 let proto_data = impl_yolo_segdet_float_proto::<XYWH, _, _>(
3105 boxes.view(),
3106 protos.view(),
3107 0.5,
3108 0.7,
3109 Some(Nms::ClassAgnostic),
3110 MAX_NMS_CANDIDATES,
3111 300,
3112 None,
3113 None,
3114 false, &mut output_boxes,
3116 )
3117 .unwrap();
3118
3119 assert!(!output_boxes.is_empty());
3121 let coeffs_shape = proto_data.mask_coefficients.shape();
3122 assert_eq!(coeffs_shape[0], output_boxes.len());
3123 assert_eq!(coeffs_shape[1], num_mask_coeffs);
3125 }
3126
3127 #[test]
3142 fn test_pre_nms_cap_truncates_excess_candidates() {
3143 let n: usize = 50_000;
3144 let num_classes = 1;
3145
3146 let mut boxes_data = Vec::with_capacity(n * 4);
3150 let mut scores_data = Vec::with_capacity(n * num_classes);
3151 for i in 0..n {
3152 boxes_data.extend_from_slice(&[0.1f32, 0.1, 0.5, 0.5]);
3153 scores_data.push(0.99 - (i as f32) * 1e-7);
3156 }
3157 let boxes = Array2::from_shape_vec((n, 4), boxes_data).unwrap();
3158 let scores = Array2::from_shape_vec((n, num_classes), scores_data).unwrap();
3159
3160 let result = impl_yolo_segdet_get_boxes::<XYXY, _, _>(
3161 boxes.view(),
3162 scores.view(),
3163 0.1,
3164 1.0, Some(Nms::ClassAgnostic), crate::yolo::MAX_NMS_CANDIDATES, usize::MAX, false, )
3170 .unwrap();
3171
3172 assert_eq!(
3173 result.len(),
3174 crate::yolo::MAX_NMS_CANDIDATES,
3175 "pre-NMS cap should truncate to MAX_NMS_CANDIDATES; got {}",
3176 result.len()
3177 );
3178 let top_score = result[0].0.score;
3181 assert!(
3182 top_score > 0.98,
3183 "highest-ranked survivor should have the largest score, got {top_score}"
3184 );
3185 }
3186
3187 #[test]
3192 fn test_pre_nms_cap_truncates_excess_candidates_quant() {
3193 use crate::Quantization;
3194 let n: usize = 50_000;
3195 let num_classes = 1;
3196
3197 let boxes_data = (0..n).flat_map(|_| [10i8, 10, 50, 50]).collect::<Vec<_>>();
3200 let boxes = Array2::from_shape_vec((n, 4), boxes_data).unwrap();
3201 let quant_boxes = Quantization {
3202 scale: 0.01,
3203 zero_point: 0,
3204 };
3205
3206 let scores_data: Vec<u8> = (0..n)
3211 .map(|i| 250u8.saturating_sub((i % 200) as u8))
3212 .collect();
3213 let scores = Array2::from_shape_vec((n, num_classes), scores_data).unwrap();
3214 let quant_scores = Quantization {
3215 scale: 0.00392,
3216 zero_point: 0,
3217 };
3218
3219 let result = impl_yolo_split_segdet_quant_get_boxes::<XYXY, _, _>(
3220 (boxes.view(), quant_boxes),
3221 (scores.view(), quant_scores),
3222 0.1,
3223 1.0, Some(Nms::ClassAgnostic), crate::yolo::MAX_NMS_CANDIDATES, usize::MAX, false,
3228 )
3229 .unwrap();
3230
3231 assert_eq!(
3232 result.len(),
3233 crate::yolo::MAX_NMS_CANDIDATES,
3234 "quant path pre-NMS cap should truncate to MAX_NMS_CANDIDATES; got {}",
3235 result.len()
3236 );
3237 }
3238
3239 #[test]
3253 fn segdet_combined_tensor_pairs_detection_with_matching_mask_row() {
3254 let nc = 2; let nm = 2; let n = 3; let feat = 4 + nc + nm; let mut data = vec![0.0f32; feat * n];
3277 let set = |d: &mut [f32], r: usize, c: usize, v: f32| d[r * n + c] = v;
3278 set(&mut data, 0, 0, 0.2);
3279 set(&mut data, 1, 0, 0.2);
3280 set(&mut data, 2, 0, 0.1);
3281 set(&mut data, 3, 0, 0.1);
3282 set(&mut data, 0, 1, 0.5);
3283 set(&mut data, 1, 1, 0.5);
3284 set(&mut data, 2, 1, 0.1);
3285 set(&mut data, 3, 1, 0.1);
3286 set(&mut data, 0, 2, 0.8);
3287 set(&mut data, 1, 2, 0.8);
3288 set(&mut data, 2, 2, 0.1);
3289 set(&mut data, 3, 2, 0.1);
3290 set(&mut data, 4, 0, 0.9);
3291 set(&mut data, 4, 2, 0.8);
3292 set(&mut data, 6, 0, 3.0);
3293 set(&mut data, 7, 0, 3.0);
3294 set(&mut data, 6, 2, -3.0);
3295 set(&mut data, 7, 2, -3.0);
3296
3297 let output = Array2::from_shape_vec((feat, n), data).unwrap();
3298 let protos = Array3::<f32>::from_elem((8, 8, nm), 1.0);
3299
3300 let mut boxes: Vec<DetectBox> = Vec::with_capacity(10);
3301 let mut masks: Vec<Segmentation> = Vec::with_capacity(10);
3302 decode_yolo_segdet_float(
3303 output.view(),
3304 protos.view(),
3305 0.5,
3306 0.5,
3307 Some(Nms::ClassAgnostic),
3308 &mut boxes,
3309 &mut masks,
3310 )
3311 .unwrap();
3312
3313 assert_eq!(
3314 boxes.len(),
3315 2,
3316 "two anchors above threshold should survive (a0 score=0.9, a2 score=0.8); got {}",
3317 boxes.len()
3318 );
3319
3320 for (b, m) in boxes.iter().zip(masks.iter()) {
3326 let cx = (b.bbox.xmin + b.bbox.xmax) * 0.5;
3327 let mean = {
3328 use edgefirst_tensor::{TensorMapTrait as _, TensorTrait as _};
3329 let t = m.segmentation.as_u8().expect("mask must be U8");
3330 let map = t.map_read().expect("map mask");
3331 let s = map.as_slice();
3332 let total: u32 = s.iter().map(|&v| v as u32).sum();
3333 total as f32 / s.len() as f32
3334 };
3335 if cx < 0.3 {
3336 assert!(
3338 mean > 200.0,
3339 "anchor 0 detection (centre {cx:.2}) should have high-value mask; got mean {mean}"
3340 );
3341 } else if cx > 0.7 {
3342 assert!(
3344 mean < 50.0,
3345 "anchor 2 detection (centre {cx:.2}) should have low-value mask; got mean {mean}"
3346 );
3347 } else {
3348 panic!("unexpected detection centre {cx:.2}");
3349 }
3350 }
3351 }
3352
3353 fn make_float_boxes(scores: &[f32]) -> Vec<(DetectBox, ())> {
3359 scores
3360 .iter()
3361 .enumerate()
3362 .map(|(i, &s)| {
3363 (
3364 DetectBox {
3365 bbox: BoundingBox {
3366 xmin: 0.0,
3367 ymin: 0.0,
3368 xmax: 1.0,
3369 ymax: 1.0,
3370 },
3371 score: s,
3372 label: i,
3373 },
3374 (),
3375 )
3376 })
3377 .collect()
3378 }
3379
3380 fn make_quant_boxes(scores: &[i8]) -> Vec<(DetectBoxQuantized<i8>, ())> {
3382 scores
3383 .iter()
3384 .enumerate()
3385 .map(|(i, &s)| {
3386 (
3387 DetectBoxQuantized {
3388 bbox: BoundingBox {
3389 xmin: 0.0,
3390 ymin: 0.0,
3391 xmax: 1.0,
3392 ymax: 1.0,
3393 },
3394 score: s,
3395 label: i,
3396 },
3397 (),
3398 )
3399 })
3400 .collect()
3401 }
3402
3403 #[test]
3404 fn truncate_float_top_k_zero_is_unbounded() {
3405 let mut boxes = make_float_boxes(&[0.9, 0.1, 0.5, 0.3, 0.7]);
3406 let original_len = boxes.len();
3407 truncate_to_top_k_by_score(&mut boxes, 0);
3408 assert_eq!(
3409 boxes.len(),
3410 original_len,
3411 "top_k=0 should keep all candidates (no-limit semantics)"
3412 );
3413 }
3414
3415 #[test]
3416 fn truncate_float_top_k_normal() {
3417 let mut boxes = make_float_boxes(&[0.9, 0.1, 0.5, 0.3, 0.7]);
3418 truncate_to_top_k_by_score(&mut boxes, 3);
3419 assert_eq!(boxes.len(), 3);
3420 let mut retained: Vec<f32> = boxes.iter().map(|(b, _)| b.score).collect();
3422 retained.sort_by(|a, b| b.total_cmp(a));
3423 assert_eq!(retained, vec![0.9, 0.7, 0.5]);
3424 }
3425
3426 #[test]
3427 fn truncate_float_top_k_noop_when_under_cap() {
3428 let mut boxes = make_float_boxes(&[0.9, 0.5]);
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 #[test]
3434 fn truncate_quant_top_k_zero_is_unbounded() {
3435 let mut boxes = make_quant_boxes(&[120, -50, 30, -10, 80]);
3436 let original_len = boxes.len();
3437 truncate_to_top_k_by_score(&mut boxes, 0);
3438 assert_eq!(
3439 boxes.len(),
3440 original_len,
3441 "top_k=0 should keep all candidates (no-limit semantics)"
3442 );
3443 }
3444
3445 #[test]
3446 fn truncate_quant_top_k_normal() {
3447 let mut boxes = make_quant_boxes(&[120, -50, 30, -10, 80]);
3448 truncate_to_top_k_by_score(&mut boxes, 3);
3449 assert_eq!(boxes.len(), 3);
3450 let mut retained: Vec<i8> = boxes.iter().map(|(b, _)| b.score).collect();
3451 retained.sort_by(|a, b| b.cmp(a));
3452 assert_eq!(retained, vec![120, 80, 30]);
3453 }
3454
3455 #[test]
3456 fn truncate_quant_top_k_noop_when_under_cap() {
3457 let mut boxes = make_quant_boxes(&[120, 80]);
3458 truncate_to_top_k_by_score(&mut boxes, 10);
3459 assert_eq!(boxes.len(), 2, "should be no-op when len <= top_k");
3460 }
3461
3462 fn tie_scene() -> (Vec<f32>, Vec<usize>) {
3466 let scores: Vec<f32> = (0..200)
3467 .map(|i| if (150..155).contains(&i) { 0.9 } else { 0.5 })
3468 .collect();
3469 let expected = (0..5).chain(150..155).collect();
3470 (scores, expected)
3471 }
3472
3473 #[test]
3474 fn truncate_breaks_boundary_ties_by_candidate_order() {
3475 let (scores, expected) = tie_scene();
3476 let mut boxes = make_float_boxes(&scores);
3477 truncate_to_top_k_by_score(&mut boxes, 10);
3478 let kept: Vec<usize> = boxes.iter().map(|(b, _)| b.label).collect();
3479 assert_eq!(kept, expected);
3480
3481 let q: Vec<i8> = scores.iter().map(|&s| (s * 100.0) as i8).collect();
3482 let mut boxes = make_quant_boxes(&q);
3483 truncate_to_top_k_by_score(&mut boxes, 10);
3484 let kept: Vec<usize> = boxes.iter().map(|(b, _)| b.label).collect();
3485 assert_eq!(kept, expected);
3486 }
3487
3488 #[test]
3489 fn nms_and_cap_ties_at_pre_nms_cap_keep_lowest_anchors() {
3490 let (scores, _) = tie_scene();
3493 let boxes: Vec<(DetectBox, usize)> = make_float_boxes(&scores)
3494 .into_iter()
3495 .map(|(mut b, ())| {
3496 let anchor = b.label;
3497 b.label = 0;
3498 (b, anchor)
3499 })
3500 .collect();
3501 let mut low = boxes.clone();
3502 for (b, _) in &mut low {
3503 b.score = 0.5;
3504 }
3505 let kept = nms_and_cap(low, Some(Nms::ClassAware), 0.5, 10, 300);
3506 assert_eq!(kept.iter().map(|(_, a)| *a).collect::<Vec<_>>(), vec![0]);
3507 let kept = nms_and_cap(boxes, Some(Nms::ClassAware), 0.5, 10, 300);
3508 assert_eq!(kept.iter().map(|(_, a)| *a).collect::<Vec<_>>(), vec![150]);
3509 }
3510
3511 #[test]
3512 fn nms_and_cap_bypass_keeps_highest_scores() {
3513 let boxes = make_float_boxes(&[0.1, 0.2, 0.9, 0.3, 0.8, 0.8]);
3516 let kept = nms_and_cap(boxes.clone(), None, 0.5, 0, 3);
3517 let labels: Vec<usize> = kept.iter().map(|(b, _)| b.label).collect();
3518 assert_eq!(labels, vec![2, 4, 5]);
3519 let kept = nms_and_cap(boxes, None, 0.5, 0, 300);
3521 let labels: Vec<usize> = kept.iter().map(|(b, _)| b.label).collect();
3522 assert_eq!(labels, vec![2, 4, 5, 3, 1, 0]);
3523
3524 let q = make_quant_boxes(&[10, 20, 90, 30, 80, 80]);
3525 let kept = nms_and_cap(q, None, 0.5, 0, 2);
3526 let labels: Vec<usize> = kept.iter().map(|(b, _)| b.label).collect();
3527 assert_eq!(labels, vec![2, 4]);
3528 }
3529
3530 #[test]
3531 fn nms_and_cap_pre_nms_top_k_zero_is_unbounded() {
3532 let boxes: Vec<(DetectBox, usize)> = (0..400)
3534 .map(|i| {
3535 let x = i as f32;
3536 (
3537 DetectBox {
3538 bbox: BoundingBox {
3539 xmin: x,
3540 ymin: 0.0,
3541 xmax: x + 0.5,
3542 ymax: 0.5,
3543 },
3544 score: 0.5 + (i % 7) as f32 * 0.01,
3545 label: 0,
3546 },
3547 i,
3548 )
3549 })
3550 .collect();
3551 let capped = nms_and_cap(boxes.clone(), Some(Nms::ClassAware), 0.5, 300, 1000);
3552 assert_eq!(capped.len(), 300);
3553 let all = nms_and_cap(boxes, Some(Nms::ClassAware), 0.5, 0, 1000);
3554 assert_eq!(all.len(), 400);
3555 }
3556
3557 #[test]
3558 #[cfg(debug_assertions)]
3559 #[should_panic(expected = "Nms::Auto must be resolved by the builder")]
3560 fn dispatch_nms_float_rejects_unresolved_auto() {
3561 dispatch_nms_float(Some(Nms::Auto), 0.5, None, Vec::new());
3562 }
3563
3564 #[test]
3565 #[cfg(debug_assertions)]
3566 #[should_panic(expected = "Nms::Auto must be resolved by the builder")]
3567 fn dispatch_nms_int_rejects_unresolved_auto() {
3568 dispatch_nms_int::<i8>(Some(Nms::Auto), 0.5, None, Vec::new());
3569 }
3570}