1#![cfg_attr(coverage_nightly, feature(coverage_attribute))]
68
69use ndarray::{Array, Array2, ArrayView, ArrayView1, ArrayView3, Dimension};
70use num_traits::{AsPrimitive, Float, PrimInt};
71
72pub mod byte;
73pub mod error;
74pub mod float;
75pub mod infer;
76pub mod modelpack;
77pub mod per_scale;
78pub mod schema;
79pub mod tiling;
80pub mod yolo;
81
82pub use infer::{
83 infer_ultralytics_schema, InferError, InferredSchema, ModelSignals, ModelSource, TensorInfo,
84};
85
86mod decoder;
87pub use decoder::*;
88
89pub use edgefirst_tensor::{BoundingBox, DetectBox, ProtoData, ProtoLayout, Segmentation};
93
94pub use edgefirst_decoder_abi::TilePlacement;
99
100pub use configs::{DecoderVersion, Nms};
101pub use error::{DecoderError, DecoderResult};
102pub use per_scale::DecodeDtype;
103
104use crate::{
105 decoder::configs::QuantTuple, modelpack::modelpack_segmentation_to_mask,
106 yolo::yolo_segmentation_to_mask,
107};
108
109fn mask_to_tensor(mask: ndarray::ArrayView3<'_, u8>) -> DecoderResult<edgefirst_tensor::TensorDyn> {
118 edgefirst_tensor::Tensor::from_arrayview3(mask)
119 .map(edgefirst_tensor::TensorDyn::from)
120 .map_err(|e| DecoderError::Internal(format!("mask -> TensorDyn: {e}")))
121}
122
123#[cfg(test)]
131pub(crate) trait DetectBoxTestExt {
132 fn equal_within_delta(&self, rhs: &DetectBox, eps: f32) -> bool;
133}
134
135#[cfg(test)]
136impl DetectBoxTestExt for DetectBox {
137 fn equal_within_delta(&self, rhs: &DetectBox, eps: f32) -> bool {
138 let eq_delta = |a: f32, b: f32| (a - b).abs() <= eps;
139 self.label == rhs.label
140 && eq_delta(self.score, rhs.score)
141 && eq_delta(self.bbox.xmin, rhs.bbox.xmin)
142 && eq_delta(self.bbox.ymin, rhs.bbox.ymin)
143 && eq_delta(self.bbox.xmax, rhs.bbox.xmax)
144 && eq_delta(self.bbox.ymax, rhs.bbox.ymax)
145 }
146}
147
148pub trait BBoxTypeTrait {
150 fn to_xyxy_float<A: Float + 'static, B: AsPrimitive<A>>(input: &[B; 4]) -> [A; 4];
152
153 fn to_xyxy_dequant<A: Float + 'static, B: AsPrimitive<A>>(
155 input: &[B; 4],
156 quant: Quantization,
157 ) -> [A; 4]
158 where
159 f32: AsPrimitive<A>,
160 i32: AsPrimitive<A>;
161
162 #[inline(always)]
173 fn ndarray_to_xyxy_float<A: Float + 'static, B: AsPrimitive<A>>(
174 input: ArrayView1<B>,
175 ) -> [A; 4] {
176 Self::to_xyxy_float(&[input[0], input[1], input[2], input[3]])
177 }
178
179 #[inline(always)]
180 fn ndarray_to_xyxy_dequant<A: Float + 'static, B: AsPrimitive<A>>(
182 input: ArrayView1<B>,
183 quant: Quantization,
184 ) -> [A; 4]
185 where
186 f32: AsPrimitive<A>,
187 i32: AsPrimitive<A>,
188 {
189 Self::to_xyxy_dequant(&[input[0], input[1], input[2], input[3]], quant)
190 }
191}
192
193#[derive(Debug, Clone, Copy, PartialEq, Eq)]
195pub struct XYXY {}
196
197impl BBoxTypeTrait for XYXY {
198 fn to_xyxy_float<A: Float + 'static, B: AsPrimitive<A>>(input: &[B; 4]) -> [A; 4] {
199 input.map(|b| b.as_())
200 }
201
202 fn to_xyxy_dequant<A: Float + 'static, B: AsPrimitive<A>>(
203 input: &[B; 4],
204 quant: Quantization,
205 ) -> [A; 4]
206 where
207 f32: AsPrimitive<A>,
208 i32: AsPrimitive<A>,
209 {
210 let scale = quant.scale.as_();
211 let zp = quant.zero_point.as_();
212 input.map(|b| (b.as_() - zp) * scale)
213 }
214
215 #[inline(always)]
216 fn ndarray_to_xyxy_float<A: Float + 'static, B: AsPrimitive<A>>(
217 input: ArrayView1<B>,
218 ) -> [A; 4] {
219 [
220 input[0].as_(),
221 input[1].as_(),
222 input[2].as_(),
223 input[3].as_(),
224 ]
225 }
226}
227
228#[derive(Debug, Clone, Copy, PartialEq, Eq)]
231pub struct XYWH {}
232
233impl BBoxTypeTrait for XYWH {
234 #[inline(always)]
235 fn to_xyxy_float<A: Float + 'static, B: AsPrimitive<A>>(input: &[B; 4]) -> [A; 4] {
236 let half = A::one() / (A::one() + A::one());
237 [
238 (input[0].as_()) - (input[2].as_() * half),
239 (input[1].as_()) - (input[3].as_() * half),
240 (input[0].as_()) + (input[2].as_() * half),
241 (input[1].as_()) + (input[3].as_() * half),
242 ]
243 }
244
245 #[inline(always)]
246 fn to_xyxy_dequant<A: Float + 'static, B: AsPrimitive<A>>(
247 input: &[B; 4],
248 quant: Quantization,
249 ) -> [A; 4]
250 where
251 f32: AsPrimitive<A>,
252 i32: AsPrimitive<A>,
253 {
254 let scale = quant.scale.as_();
255 let half_scale = (quant.scale * 0.5).as_();
256 let zp = quant.zero_point.as_();
257 let [x, y, w, h] = [
258 (input[0].as_() - zp) * scale,
259 (input[1].as_() - zp) * scale,
260 (input[2].as_() - zp) * half_scale,
261 (input[3].as_() - zp) * half_scale,
262 ];
263
264 [x - w, y - h, x + w, y + h]
265 }
266
267 #[inline(always)]
268 fn ndarray_to_xyxy_float<A: Float + 'static, B: AsPrimitive<A>>(
269 input: ArrayView1<B>,
270 ) -> [A; 4] {
271 let half = A::one() / (A::one() + A::one());
272 [
273 (input[0].as_()) - (input[2].as_() * half),
274 (input[1].as_()) - (input[3].as_() * half),
275 (input[0].as_()) + (input[2].as_() * half),
276 (input[1].as_()) + (input[3].as_() * half),
277 ]
278 }
279}
280
281#[derive(Debug, Clone, Copy, PartialEq)]
283pub struct Quantization {
284 pub scale: f32,
285 pub zero_point: i32,
286}
287
288impl Quantization {
289 pub fn new(scale: f32, zero_point: i32) -> Self {
298 Self { scale, zero_point }
299 }
300
301 pub fn identity() -> Self {
313 Self {
314 scale: 1.0,
315 zero_point: 0,
316 }
317 }
318}
319
320impl From<QuantTuple> for Quantization {
321 fn from(quant_tuple: QuantTuple) -> Quantization {
332 Quantization {
333 scale: quant_tuple.0,
334 zero_point: quant_tuple.1,
335 }
336 }
337}
338
339impl<S, Z> From<(S, Z)> for Quantization
340where
341 S: AsPrimitive<f32>,
342 Z: AsPrimitive<i32>,
343{
344 fn from((scale, zp): (S, Z)) -> Quantization {
353 Self {
354 scale: scale.as_(),
355 zero_point: zp.as_(),
356 }
357 }
358}
359
360impl Default for Quantization {
361 fn default() -> Self {
370 Self {
371 scale: 1.0,
372 zero_point: 0,
373 }
374 }
375}
376
377pub fn dequant_detect_box<SCORE: PrimInt + AsPrimitive<f32>>(
395 detect: &DetectBoxQuantized<SCORE>,
396 quant_scores: Quantization,
397) -> DetectBox {
398 let scaled_zp = -quant_scores.scale * quant_scores.zero_point as f32;
399 DetectBox {
400 bbox: detect.bbox,
401 score: quant_scores.scale * detect.score.as_() + scaled_zp,
402 label: detect.label,
403 }
404}
405#[derive(Debug, Clone, Copy, PartialEq)]
407pub struct DetectBoxQuantized<
408 SCORE: PrimInt + AsPrimitive<f32>,
410> {
411 pub bbox: BoundingBox,
413 pub score: SCORE,
416 pub label: usize,
418}
419
420pub fn dequantize_ndarray<T: AsPrimitive<F>, D: Dimension, F: Float + 'static>(
433 input: ArrayView<T, D>,
434 quant: Quantization,
435) -> Array<F, D>
436where
437 i32: num_traits::AsPrimitive<F>,
438 f32: num_traits::AsPrimitive<F>,
439{
440 let zero_point = quant.zero_point.as_();
441 let scale = quant.scale.as_();
442 if zero_point != F::zero() {
443 let scaled_zero = -zero_point * scale;
444 input.mapv(|d| d.as_() * scale + scaled_zero)
445 } else {
446 input.mapv(|d| d.as_() * scale)
447 }
448}
449
450pub fn dequantize_cpu<T: AsPrimitive<F>, F: Float + 'static>(
463 input: &[T],
464 quant: Quantization,
465 output: &mut [F],
466) where
467 f32: num_traits::AsPrimitive<F>,
468 i32: num_traits::AsPrimitive<F>,
469{
470 assert!(input.len() == output.len());
471 let zero_point = quant.zero_point.as_();
472 let scale = quant.scale.as_();
473 if zero_point != F::zero() {
474 let scaled_zero = -zero_point * scale; input
476 .iter()
477 .zip(output)
478 .for_each(|(d, deq)| *deq = d.as_() * scale + scaled_zero);
479 } else {
480 input
481 .iter()
482 .zip(output)
483 .for_each(|(d, deq)| *deq = d.as_() * scale);
484 }
485}
486
487pub fn dequantize_cpu_chunked<T: AsPrimitive<F>, F: Float + 'static>(
501 input: &[T],
502 quant: Quantization,
503 output: &mut [F],
504) where
505 f32: num_traits::AsPrimitive<F>,
506 i32: num_traits::AsPrimitive<F>,
507{
508 assert!(input.len() == output.len());
509 let zero_point = quant.zero_point.as_();
510 let scale = quant.scale.as_();
511
512 let input = input.as_chunks::<4>();
513 let output = output.as_chunks_mut::<4>();
514
515 if zero_point != F::zero() {
516 let scaled_zero = -zero_point * scale; input
519 .0
520 .iter()
521 .zip(output.0)
522 .for_each(|(d, deq)| *deq = d.map(|d| d.as_() * scale + scaled_zero));
523 input
524 .1
525 .iter()
526 .zip(output.1)
527 .for_each(|(d, deq)| *deq = d.as_() * scale + scaled_zero);
528 } else {
529 input
530 .0
531 .iter()
532 .zip(output.0)
533 .for_each(|(d, deq)| *deq = d.map(|d| d.as_() * scale));
534 input
535 .1
536 .iter()
537 .zip(output.1)
538 .for_each(|(d, deq)| *deq = d.as_() * scale);
539 }
540}
541
542pub fn segmentation_to_mask(segmentation: ArrayView3<u8>) -> Result<Array2<u8>, DecoderError> {
560 if segmentation.shape()[2] == 0 {
561 return Err(DecoderError::InvalidShape(
562 "Segmentation tensor must have non-zero depth".to_string(),
563 ));
564 }
565 if segmentation.shape()[2] == 1 {
566 yolo_segmentation_to_mask(segmentation, 128)
567 } else {
568 Ok(modelpack_segmentation_to_mask(segmentation))
569 }
570}
571
572fn arg_max<T: PartialOrd + Copy>(score: ArrayView1<T>) -> (T, usize) {
574 score
575 .iter()
576 .enumerate()
577 .fold((score[0], 0), |(max, arg_max), (ind, s)| {
578 if max > *s {
579 (max, arg_max)
580 } else {
581 (*s, ind)
582 }
583 })
584}
585
586#[cfg(target_arch = "aarch64")]
592pub(crate) fn arg_max_i8(scores: &[i8]) -> (i8, usize) {
593 use std::arch::aarch64::*; let n = scores.len();
596 if n < 16 {
597 let mut max = scores[0];
599 let mut idx = 0;
600 for (i, &s) in scores.iter().enumerate().skip(1) {
601 if s >= max {
602 max = s;
603 idx = i;
604 }
605 }
606 return (max, idx);
607 }
608
609 unsafe {
610 let chunks = n / 16;
612 let mut vmax = vld1q_s8(scores.as_ptr());
613 for i in 1..chunks {
614 let v = vld1q_s8(scores.as_ptr().add(i * 16));
615 vmax = vmaxq_s8(vmax, v);
616 }
617 let global_max = vmaxvq_s8(vmax);
618
619 let remainder_start = chunks * 16;
621 let mut final_max = global_max;
622 for &s in &scores[remainder_start..] {
623 if s > final_max {
624 final_max = s;
625 }
626 }
627
628 let mut idx = 0;
631 for i in (0..n).rev() {
632 if scores[i] == final_max {
633 idx = i;
634 break;
635 }
636 }
637 (final_max, idx)
638 }
639}
640#[cfg(test)]
641#[cfg_attr(coverage_nightly, coverage(off))]
642mod decoder_tests {
643 #![allow(clippy::excessive_precision)]
644 use crate::DetectBoxTestExt;
645 use crate::{
646 configs::{DecoderType, DimName, Protos},
647 modelpack::{decode_modelpack_det, decode_modelpack_split_quant},
648 yolo::{
649 decode_yolo_det, decode_yolo_det_float, decode_yolo_segdet_float,
650 decode_yolo_segdet_quant,
651 },
652 *,
653 };
654 use edgefirst_tensor::{Tensor, TensorMapTrait, TensorTrait};
655 use ndarray::Dimension;
656 use ndarray::{array, s, Array2, Array3, Array4, Axis};
657 use num_traits::{AsPrimitive, PrimInt};
658
659 fn compare_outputs(
660 boxes: (&[DetectBox], &[DetectBox]),
661 masks: (&[Segmentation], &[Segmentation]),
662 ) {
663 let (boxes0, boxes1) = boxes;
664 let (masks0, masks1) = masks;
665
666 assert_eq!(boxes0.len(), boxes1.len());
667 assert_eq!(masks0.len(), masks1.len());
668
669 for (b_i8, b_f32) in boxes0.iter().zip(boxes1) {
670 assert!(
671 b_i8.equal_within_delta(b_f32, 1e-6),
672 "{b_i8:?} is not equal to {b_f32:?}"
673 );
674 }
675
676 for (m_i8, m_f32) in masks0.iter().zip(masks1) {
677 assert_eq!(
678 [m_i8.xmin, m_i8.ymin, m_i8.xmax, m_i8.ymax],
679 [m_f32.xmin, m_f32.ymin, m_f32.xmax, m_f32.ymax],
680 );
681 let shape = m_i8.segmentation.shape().to_vec();
682 assert_eq!(shape, m_f32.segmentation.shape());
683 let (t_i8, t_f32) = (
686 m_i8.segmentation.as_u8().expect("mask must be U8"),
687 m_f32.segmentation.as_u8().expect("mask must be U8"),
688 );
689 let (map_i8, map_f32) = (
690 t_i8.map_read().expect("map i8 mask"),
691 t_f32.map_read().expect("map f32 mask"),
692 );
693 let (a, b) = (map_i8.as_slice(), map_f32.as_slice());
694 let (w, c) = (shape[1], shape[2]);
695 let mut sq_err = 0f64;
696 for (i, (&va, &vb)) in a.iter().zip(b.iter()).enumerate() {
697 let val = va as i32 - vb as i32;
698 assert!(
699 val.abs() <= 1,
700 "Difference between mask0 and mask1 is greater than 1 at ({}, {}, {}): {}",
701 i / (w * c),
702 (i / c) % w,
703 i % c,
704 val
705 );
706 sq_err += (val as f64) * (val as f64);
707 }
708 let mean_sq_err = sq_err / a.len() as f64;
709 assert!(
710 mean_sq_err < 1e-2,
711 "Mean Square Error between masks was greater than 1%: {:.2}%",
712 mean_sq_err * 100.0
713 );
714 }
715 }
716
717 fn seg_view(s: &Segmentation) -> ndarray::Array3<u8> {
720 let t = s.segmentation.as_u8().expect("mask must be U8");
721 let m = t.map_read().expect("map mask");
722 let sh = s.segmentation.shape();
723 ndarray::Array3::from_shape_vec((sh[0], sh[1], sh[2]), m.as_slice().to_vec())
724 .expect("mask shape")
725 }
726
727 fn load_yolov8_boxes() -> Array3<i8> {
730 let raw = edgefirst_bench::testdata::read("yolov8_boxes_116x8400.bin");
731 let raw = unsafe { std::slice::from_raw_parts(raw.as_ptr() as *const i8, raw.len()) };
732 Array3::from_shape_vec((1, 116, 8400), raw.to_vec()).unwrap()
733 }
734
735 fn load_yolov8_protos() -> Array4<i8> {
736 let raw = edgefirst_bench::testdata::read("yolov8_protos_160x160x32.bin");
737 let raw = unsafe { std::slice::from_raw_parts(raw.as_ptr() as *const i8, raw.len()) };
738 Array4::from_shape_vec((1, 160, 160, 32), raw.to_vec()).unwrap()
739 }
740
741 fn load_yolov8s_det() -> Array3<i8> {
742 let raw = edgefirst_bench::testdata::read("yolov8s_80_classes.bin");
743 let raw = unsafe { std::slice::from_raw_parts(raw.as_ptr() as *const i8, raw.len()) };
744 Array3::from_shape_vec((1, 84, 8400), raw.to_vec()).unwrap()
745 }
746
747 #[test]
748 fn test_decoder_modelpack() {
749 let score_threshold = 0.45;
750 let iou_threshold = 0.45;
751 let boxes = edgefirst_bench::testdata::read("modelpack_boxes_1935x1x4.bin");
752 let boxes = ndarray::Array4::from_shape_vec((1, 1935, 1, 4), boxes.to_vec()).unwrap();
753
754 let scores = edgefirst_bench::testdata::read("modelpack_scores_1935x1.bin");
755 let scores = ndarray::Array3::from_shape_vec((1, 1935, 1), scores.to_vec()).unwrap();
756
757 let quant_boxes = (0.004656755365431309, 21).into();
758 let quant_scores = (0.0019603664986789227, 0).into();
759
760 let decoder = DecoderBuilder::default()
761 .with_config_modelpack_det(
762 configs::Boxes {
763 decoder: DecoderType::ModelPack,
764 quantization: Some(quant_boxes),
765 shape: vec![1, 1935, 1, 4],
766 dshape: vec![
767 (DimName::Batch, 1),
768 (DimName::NumBoxes, 1935),
769 (DimName::Padding, 1),
770 (DimName::BoxCoords, 4),
771 ],
772 normalized: Some(true),
773 },
774 configs::Scores {
775 decoder: DecoderType::ModelPack,
776 quantization: Some(quant_scores),
777 shape: vec![1, 1935, 1],
778 dshape: vec![
779 (DimName::Batch, 1),
780 (DimName::NumBoxes, 1935),
781 (DimName::NumClasses, 1),
782 ],
783 },
784 )
785 .with_score_threshold(score_threshold)
786 .with_iou_threshold(iou_threshold)
787 .build()
788 .unwrap();
789
790 let quant_boxes = quant_boxes.into();
791 let quant_scores = quant_scores.into();
792
793 let mut output_boxes: Vec<_> = Vec::with_capacity(50);
794 decode_modelpack_det(
795 (boxes.slice(s![0, .., 0, ..]), quant_boxes),
796 (scores.slice(s![0, .., ..]), quant_scores),
797 score_threshold,
798 iou_threshold,
799 300,
800 &mut output_boxes,
801 );
802 assert!(output_boxes[0].equal_within_delta(
803 &DetectBox {
804 bbox: BoundingBox {
805 xmin: 0.40513772,
806 ymin: 0.6379755,
807 xmax: 0.5122431,
808 ymax: 0.7730214,
809 },
810 score: 0.4861709,
811 label: 0
812 },
813 1e-6
814 ));
815
816 let mut output_boxes1 = Vec::with_capacity(50);
817 let mut output_masks1 = Vec::with_capacity(50);
818
819 decoder
820 .decode_quantized(
821 &[boxes.view().into(), scores.view().into()],
822 &mut output_boxes1,
823 &mut output_masks1,
824 )
825 .unwrap();
826
827 let mut output_boxes_float = Vec::with_capacity(50);
828 let mut output_masks_float = Vec::with_capacity(50);
829
830 let boxes = dequantize_ndarray(boxes.view(), quant_boxes);
831 let scores = dequantize_ndarray(scores.view(), quant_scores);
832
833 decoder
834 .decode_float::<f32>(
835 &[boxes.view().into_dyn(), scores.view().into_dyn()],
836 &mut output_boxes_float,
837 &mut output_masks_float,
838 )
839 .unwrap();
840
841 compare_outputs((&output_boxes, &output_boxes1), (&[], &output_masks1));
842 compare_outputs(
843 (&output_boxes, &output_boxes_float),
844 (&[], &output_masks_float),
845 );
846 }
847
848 #[test]
849 fn test_decoder_modelpack_split_u8() {
850 let score_threshold = 0.45;
851 let iou_threshold = 0.45;
852 let detect0 = edgefirst_bench::testdata::read("modelpack_split_9x15x18.bin");
853 let detect0 = ndarray::Array4::from_shape_vec((1, 9, 15, 18), detect0.to_vec()).unwrap();
854
855 let detect1 = edgefirst_bench::testdata::read("modelpack_split_17x30x18.bin");
856 let detect1 = ndarray::Array4::from_shape_vec((1, 17, 30, 18), detect1.to_vec()).unwrap();
857
858 let quant0 = (0.08547406643629074, 174).into();
859 let quant1 = (0.09929127991199493, 183).into();
860 let anchors0 = vec![
861 [0.36666667461395264, 0.31481480598449707],
862 [0.38749998807907104, 0.4740740656852722],
863 [0.5333333611488342, 0.644444465637207],
864 ];
865 let anchors1 = vec![
866 [0.13750000298023224, 0.2074074000120163],
867 [0.2541666626930237, 0.21481481194496155],
868 [0.23125000298023224, 0.35185185074806213],
869 ];
870
871 let detect_config0 = configs::Detection {
872 decoder: DecoderType::ModelPack,
873 shape: vec![1, 9, 15, 18],
874 anchors: Some(anchors0.clone()),
875 quantization: Some(quant0),
876 dshape: vec![
877 (DimName::Batch, 1),
878 (DimName::Height, 9),
879 (DimName::Width, 15),
880 (DimName::NumAnchorsXFeatures, 18),
881 ],
882 normalized: Some(true),
883 };
884
885 let detect_config1 = configs::Detection {
886 decoder: DecoderType::ModelPack,
887 shape: vec![1, 17, 30, 18],
888 anchors: Some(anchors1.clone()),
889 quantization: Some(quant1),
890 dshape: vec![
891 (DimName::Batch, 1),
892 (DimName::Height, 17),
893 (DimName::Width, 30),
894 (DimName::NumAnchorsXFeatures, 18),
895 ],
896 normalized: Some(true),
897 };
898
899 let config0 = (&detect_config0).try_into().unwrap();
900 let config1 = (&detect_config1).try_into().unwrap();
901
902 let decoder = DecoderBuilder::default()
903 .with_config_modelpack_det_split(vec![detect_config1, detect_config0])
904 .with_score_threshold(score_threshold)
905 .with_iou_threshold(iou_threshold)
906 .build()
907 .unwrap();
908
909 let quant0 = quant0.into();
910 let quant1 = quant1.into();
911
912 let mut output_boxes: Vec<_> = Vec::with_capacity(10);
913 decode_modelpack_split_quant(
914 &[
915 detect0.slice(s![0, .., .., ..]),
916 detect1.slice(s![0, .., .., ..]),
917 ],
918 &[config0, config1],
919 score_threshold,
920 iou_threshold,
921 300,
922 &mut output_boxes,
923 );
924 assert!(output_boxes[0].equal_within_delta(
925 &DetectBox {
926 bbox: BoundingBox {
927 xmin: 0.43171933,
928 ymin: 0.68243736,
929 xmax: 0.5626645,
930 ymax: 0.808863,
931 },
932 score: 0.99240804,
933 label: 0
934 },
935 1e-6
936 ));
937
938 let mut output_boxes1: Vec<_> = Vec::with_capacity(10);
939 let mut output_masks1: Vec<_> = Vec::with_capacity(10);
940 decoder
941 .decode_quantized(
942 &[detect0.view().into(), detect1.view().into()],
943 &mut output_boxes1,
944 &mut output_masks1,
945 )
946 .unwrap();
947
948 let mut output_boxes1_f32: Vec<_> = Vec::with_capacity(10);
949 let mut output_masks1_f32: Vec<_> = Vec::with_capacity(10);
950
951 let detect0 = dequantize_ndarray(detect0.view(), quant0);
952 let detect1 = dequantize_ndarray(detect1.view(), quant1);
953 decoder
954 .decode_float::<f32>(
955 &[detect0.view().into_dyn(), detect1.view().into_dyn()],
956 &mut output_boxes1_f32,
957 &mut output_masks1_f32,
958 )
959 .unwrap();
960
961 compare_outputs((&output_boxes, &output_boxes1), (&[], &output_masks1));
962 compare_outputs(
963 (&output_boxes, &output_boxes1_f32),
964 (&[], &output_masks1_f32),
965 );
966 }
967
968 #[test]
969 fn test_decoder_parse_config_modelpack_split_u8() {
970 let score_threshold = 0.45;
971 let iou_threshold = 0.45;
972 let detect0 = edgefirst_bench::testdata::read("modelpack_split_9x15x18.bin");
973 let detect0 = ndarray::Array4::from_shape_vec((1, 9, 15, 18), detect0.to_vec()).unwrap();
974
975 let detect1 = edgefirst_bench::testdata::read("modelpack_split_17x30x18.bin");
976 let detect1 = ndarray::Array4::from_shape_vec((1, 17, 30, 18), detect1.to_vec()).unwrap();
977
978 let decoder = DecoderBuilder::default()
979 .with_config_yaml_str(
980 edgefirst_bench::testdata::read_to_string("modelpack_split.yaml").to_string(),
981 )
982 .with_score_threshold(score_threshold)
983 .with_iou_threshold(iou_threshold)
984 .build()
985 .unwrap();
986
987 let mut output_boxes: Vec<_> = Vec::with_capacity(10);
988 let mut output_masks: Vec<_> = Vec::with_capacity(10);
989 decoder
990 .decode_quantized(
991 &[
992 ArrayViewDQuantized::from(detect1.view()),
993 ArrayViewDQuantized::from(detect0.view()),
994 ],
995 &mut output_boxes,
996 &mut output_masks,
997 )
998 .unwrap();
999 assert!(output_boxes[0].equal_within_delta(
1000 &DetectBox {
1001 bbox: BoundingBox {
1002 xmin: 0.43171933,
1003 ymin: 0.68243736,
1004 xmax: 0.5626645,
1005 ymax: 0.808863,
1006 },
1007 score: 0.99240804,
1008 label: 0
1009 },
1010 1e-6
1011 ));
1012 }
1013
1014 #[test]
1015 fn test_modelpack_seg() {
1016 let out = edgefirst_bench::testdata::read("modelpack_seg_2x160x160.bin");
1017 let out = ndarray::Array4::from_shape_vec((1, 2, 160, 160), out.to_vec()).unwrap();
1018 let quant = (1.0 / 255.0, 0).into();
1019
1020 let decoder = DecoderBuilder::default()
1021 .with_config_modelpack_seg(configs::Segmentation {
1022 decoder: DecoderType::ModelPack,
1023 quantization: Some(quant),
1024 shape: vec![1, 2, 160, 160],
1025 dshape: vec![
1026 (DimName::Batch, 1),
1027 (DimName::NumClasses, 2),
1028 (DimName::Height, 160),
1029 (DimName::Width, 160),
1030 ],
1031 })
1032 .build()
1033 .unwrap();
1034 let mut output_boxes: Vec<_> = Vec::with_capacity(10);
1035 let mut output_masks: Vec<_> = Vec::with_capacity(10);
1036 decoder
1037 .decode_quantized(&[out.view().into()], &mut output_boxes, &mut output_masks)
1038 .unwrap();
1039
1040 let mut mask = out.slice(s![0, .., .., ..]);
1041 mask.swap_axes(0, 1);
1042 mask.swap_axes(1, 2);
1043 let mask = [Segmentation {
1044 xmin: 0.0,
1045 ymin: 0.0,
1046 xmax: 1.0,
1047 ymax: 1.0,
1048 segmentation: crate::mask_to_tensor(mask.view()).unwrap(),
1049 }];
1050 compare_outputs((&[], &output_boxes), (&mask, &output_masks));
1051
1052 decoder
1053 .decode_float::<f32>(
1054 &[dequantize_ndarray(out.view(), quant.into())
1055 .view()
1056 .into_dyn()],
1057 &mut output_boxes,
1058 &mut output_masks,
1059 )
1060 .unwrap();
1061
1062 compare_outputs((&[], &output_boxes), (&[], &[]));
1068 let mask0 = segmentation_to_mask(seg_view(&mask[0]).view()).unwrap();
1069 let mask1 = segmentation_to_mask(seg_view(&output_masks[0]).view()).unwrap();
1070
1071 assert_eq!(mask0, mask1);
1072 }
1073 #[test]
1074 fn test_modelpack_seg_quant() {
1075 let out = edgefirst_bench::testdata::read("modelpack_seg_2x160x160.bin");
1076 let out_u8 = ndarray::Array4::from_shape_vec((1, 2, 160, 160), out.to_vec()).unwrap();
1077 let out_i8 = out_u8.mapv(|x| (x as i16 - 128) as i8);
1078 let out_u16 = out_u8.mapv(|x| (x as u16) << 8);
1079 let out_i16 = out_u8.mapv(|x| (((x as i32) << 8) - 32768) as i16);
1080 let out_u32 = out_u8.mapv(|x| (x as u32) << 24);
1081 let out_i32 = out_u8.mapv(|x| (((x as i64) << 24) - 2147483648) as i32);
1082
1083 let quant = (1.0 / 255.0, 0).into();
1084
1085 let decoder = DecoderBuilder::default()
1086 .with_config_modelpack_seg(configs::Segmentation {
1087 decoder: DecoderType::ModelPack,
1088 quantization: Some(quant),
1089 shape: vec![1, 2, 160, 160],
1090 dshape: vec![
1091 (DimName::Batch, 1),
1092 (DimName::NumClasses, 2),
1093 (DimName::Height, 160),
1094 (DimName::Width, 160),
1095 ],
1096 })
1097 .build()
1098 .unwrap();
1099 let mut output_boxes: Vec<_> = Vec::with_capacity(10);
1100 let mut output_masks_u8: Vec<_> = Vec::with_capacity(10);
1101 decoder
1102 .decode_quantized(
1103 &[out_u8.view().into()],
1104 &mut output_boxes,
1105 &mut output_masks_u8,
1106 )
1107 .unwrap();
1108
1109 let mut output_masks_i8: Vec<_> = Vec::with_capacity(10);
1110 decoder
1111 .decode_quantized(
1112 &[out_i8.view().into()],
1113 &mut output_boxes,
1114 &mut output_masks_i8,
1115 )
1116 .unwrap();
1117
1118 let mut output_masks_u16: Vec<_> = Vec::with_capacity(10);
1119 decoder
1120 .decode_quantized(
1121 &[out_u16.view().into()],
1122 &mut output_boxes,
1123 &mut output_masks_u16,
1124 )
1125 .unwrap();
1126
1127 let mut output_masks_i16: Vec<_> = Vec::with_capacity(10);
1128 decoder
1129 .decode_quantized(
1130 &[out_i16.view().into()],
1131 &mut output_boxes,
1132 &mut output_masks_i16,
1133 )
1134 .unwrap();
1135
1136 let mut output_masks_u32: Vec<_> = Vec::with_capacity(10);
1137 decoder
1138 .decode_quantized(
1139 &[out_u32.view().into()],
1140 &mut output_boxes,
1141 &mut output_masks_u32,
1142 )
1143 .unwrap();
1144
1145 let mut output_masks_i32: Vec<_> = Vec::with_capacity(10);
1146 decoder
1147 .decode_quantized(
1148 &[out_i32.view().into()],
1149 &mut output_boxes,
1150 &mut output_masks_i32,
1151 )
1152 .unwrap();
1153
1154 compare_outputs((&[], &output_boxes), (&[], &[]));
1155 let mask_u8 = segmentation_to_mask(seg_view(&output_masks_u8[0]).view()).unwrap();
1156 let mask_i8 = segmentation_to_mask(seg_view(&output_masks_i8[0]).view()).unwrap();
1157 let mask_u16 = segmentation_to_mask(seg_view(&output_masks_u16[0]).view()).unwrap();
1158 let mask_i16 = segmentation_to_mask(seg_view(&output_masks_i16[0]).view()).unwrap();
1159 let mask_u32 = segmentation_to_mask(seg_view(&output_masks_u32[0]).view()).unwrap();
1160 let mask_i32 = segmentation_to_mask(seg_view(&output_masks_i32[0]).view()).unwrap();
1161 assert_eq!(mask_u8, mask_i8);
1162 assert_eq!(mask_u8, mask_u16);
1163 assert_eq!(mask_u8, mask_i16);
1164 assert_eq!(mask_u8, mask_u32);
1165 assert_eq!(mask_u8, mask_i32);
1166 }
1167
1168 #[test]
1169 fn test_modelpack_segdet() {
1170 let score_threshold = 0.45;
1171 let iou_threshold = 0.45;
1172
1173 let boxes = edgefirst_bench::testdata::read("modelpack_boxes_1935x1x4.bin");
1174 let boxes = Array4::from_shape_vec((1, 1935, 1, 4), boxes.to_vec()).unwrap();
1175
1176 let scores = edgefirst_bench::testdata::read("modelpack_scores_1935x1.bin");
1177 let scores = Array3::from_shape_vec((1, 1935, 1), scores.to_vec()).unwrap();
1178
1179 let seg = edgefirst_bench::testdata::read("modelpack_seg_2x160x160.bin");
1180 let seg = Array4::from_shape_vec((1, 2, 160, 160), seg.to_vec()).unwrap();
1181
1182 let quant_boxes = (0.004656755365431309, 21).into();
1183 let quant_scores = (0.0019603664986789227, 0).into();
1184 let quant_seg = (1.0 / 255.0, 0).into();
1185
1186 let decoder = DecoderBuilder::default()
1187 .with_config_modelpack_segdet(
1188 configs::Boxes {
1189 decoder: DecoderType::ModelPack,
1190 quantization: Some(quant_boxes),
1191 shape: vec![1, 1935, 1, 4],
1192 dshape: vec![
1193 (DimName::Batch, 1),
1194 (DimName::NumBoxes, 1935),
1195 (DimName::Padding, 1),
1196 (DimName::BoxCoords, 4),
1197 ],
1198 normalized: Some(true),
1199 },
1200 configs::Scores {
1201 decoder: DecoderType::ModelPack,
1202 quantization: Some(quant_scores),
1203 shape: vec![1, 1935, 1],
1204 dshape: vec![
1205 (DimName::Batch, 1),
1206 (DimName::NumBoxes, 1935),
1207 (DimName::NumClasses, 1),
1208 ],
1209 },
1210 configs::Segmentation {
1211 decoder: DecoderType::ModelPack,
1212 quantization: Some(quant_seg),
1213 shape: vec![1, 2, 160, 160],
1214 dshape: vec![
1215 (DimName::Batch, 1),
1216 (DimName::NumClasses, 2),
1217 (DimName::Height, 160),
1218 (DimName::Width, 160),
1219 ],
1220 },
1221 )
1222 .with_iou_threshold(iou_threshold)
1223 .with_score_threshold(score_threshold)
1224 .build()
1225 .unwrap();
1226 let mut output_boxes: Vec<_> = Vec::with_capacity(10);
1227 let mut output_masks: Vec<_> = Vec::with_capacity(10);
1228 decoder
1229 .decode_quantized(
1230 &[scores.view().into(), boxes.view().into(), seg.view().into()],
1231 &mut output_boxes,
1232 &mut output_masks,
1233 )
1234 .unwrap();
1235
1236 let mut mask = seg.slice(s![0, .., .., ..]);
1237 mask.swap_axes(0, 1);
1238 mask.swap_axes(1, 2);
1239 let mask = [Segmentation {
1240 xmin: 0.0,
1241 ymin: 0.0,
1242 xmax: 1.0,
1243 ymax: 1.0,
1244 segmentation: crate::mask_to_tensor(mask.view()).unwrap(),
1245 }];
1246 let correct_boxes = [DetectBox {
1247 bbox: BoundingBox {
1248 xmin: 0.40513772,
1249 ymin: 0.6379755,
1250 xmax: 0.5122431,
1251 ymax: 0.7730214,
1252 },
1253 score: 0.4861709,
1254 label: 0,
1255 }];
1256 compare_outputs((&correct_boxes, &output_boxes), (&mask, &output_masks));
1257
1258 let scores = dequantize_ndarray(scores.view(), quant_scores.into());
1259 let boxes = dequantize_ndarray(boxes.view(), quant_boxes.into());
1260 let seg = dequantize_ndarray(seg.view(), quant_seg.into());
1261 decoder
1262 .decode_float::<f32>(
1263 &[
1264 scores.view().into_dyn(),
1265 boxes.view().into_dyn(),
1266 seg.view().into_dyn(),
1267 ],
1268 &mut output_boxes,
1269 &mut output_masks,
1270 )
1271 .unwrap();
1272
1273 compare_outputs((&correct_boxes, &output_boxes), (&[], &[]));
1279 let mask0 = segmentation_to_mask(seg_view(&mask[0]).view()).unwrap();
1280 let mask1 = segmentation_to_mask(seg_view(&output_masks[0]).view()).unwrap();
1281
1282 assert_eq!(mask0, mask1);
1283 }
1284
1285 #[test]
1286 fn test_modelpack_segdet_split() {
1287 let score_threshold = 0.8;
1288 let iou_threshold = 0.5;
1289
1290 let seg = edgefirst_bench::testdata::read("modelpack_seg_2x160x160.bin");
1291 let seg = ndarray::Array4::from_shape_vec((1, 2, 160, 160), seg.to_vec()).unwrap();
1292
1293 let detect0 = edgefirst_bench::testdata::read("modelpack_split_9x15x18.bin");
1294 let detect0 = ndarray::Array4::from_shape_vec((1, 9, 15, 18), detect0.to_vec()).unwrap();
1295
1296 let detect1 = edgefirst_bench::testdata::read("modelpack_split_17x30x18.bin");
1297 let detect1 = ndarray::Array4::from_shape_vec((1, 17, 30, 18), detect1.to_vec()).unwrap();
1298
1299 let quant0 = (0.08547406643629074, 174).into();
1300 let quant1 = (0.09929127991199493, 183).into();
1301 let quant_seg = (1.0 / 255.0, 0).into();
1302
1303 let anchors0 = vec![
1304 [0.36666667461395264, 0.31481480598449707],
1305 [0.38749998807907104, 0.4740740656852722],
1306 [0.5333333611488342, 0.644444465637207],
1307 ];
1308 let anchors1 = vec![
1309 [0.13750000298023224, 0.2074074000120163],
1310 [0.2541666626930237, 0.21481481194496155],
1311 [0.23125000298023224, 0.35185185074806213],
1312 ];
1313
1314 let decoder = DecoderBuilder::default()
1315 .with_config_modelpack_segdet_split(
1316 vec![
1317 configs::Detection {
1318 decoder: DecoderType::ModelPack,
1319 shape: vec![1, 17, 30, 18],
1320 anchors: Some(anchors1),
1321 quantization: Some(quant1),
1322 dshape: vec![
1323 (DimName::Batch, 1),
1324 (DimName::Height, 17),
1325 (DimName::Width, 30),
1326 (DimName::NumAnchorsXFeatures, 18),
1327 ],
1328 normalized: Some(true),
1329 },
1330 configs::Detection {
1331 decoder: DecoderType::ModelPack,
1332 shape: vec![1, 9, 15, 18],
1333 anchors: Some(anchors0),
1334 quantization: Some(quant0),
1335 dshape: vec![
1336 (DimName::Batch, 1),
1337 (DimName::Height, 9),
1338 (DimName::Width, 15),
1339 (DimName::NumAnchorsXFeatures, 18),
1340 ],
1341 normalized: Some(true),
1342 },
1343 ],
1344 configs::Segmentation {
1345 decoder: DecoderType::ModelPack,
1346 quantization: Some(quant_seg),
1347 shape: vec![1, 2, 160, 160],
1348 dshape: vec![
1349 (DimName::Batch, 1),
1350 (DimName::NumClasses, 2),
1351 (DimName::Height, 160),
1352 (DimName::Width, 160),
1353 ],
1354 },
1355 )
1356 .with_score_threshold(score_threshold)
1357 .with_iou_threshold(iou_threshold)
1358 .build()
1359 .unwrap();
1360 let mut output_boxes: Vec<_> = Vec::with_capacity(10);
1361 let mut output_masks: Vec<_> = Vec::with_capacity(10);
1362 decoder
1363 .decode_quantized(
1364 &[
1365 detect0.view().into(),
1366 detect1.view().into(),
1367 seg.view().into(),
1368 ],
1369 &mut output_boxes,
1370 &mut output_masks,
1371 )
1372 .unwrap();
1373
1374 let mut mask = seg.slice(s![0, .., .., ..]);
1375 mask.swap_axes(0, 1);
1376 mask.swap_axes(1, 2);
1377 let mask = [Segmentation {
1378 xmin: 0.0,
1379 ymin: 0.0,
1380 xmax: 1.0,
1381 ymax: 1.0,
1382 segmentation: crate::mask_to_tensor(mask.view()).unwrap(),
1383 }];
1384 let correct_boxes = [DetectBox {
1385 bbox: BoundingBox {
1386 xmin: 0.43171933,
1387 ymin: 0.68243736,
1388 xmax: 0.5626645,
1389 ymax: 0.808863,
1390 },
1391 score: 0.99240804,
1392 label: 0,
1393 }];
1394 println!("Output Boxes: {:?}", output_boxes);
1395 compare_outputs((&correct_boxes, &output_boxes), (&mask, &output_masks));
1396
1397 let detect0 = dequantize_ndarray(detect0.view(), quant0.into());
1398 let detect1 = dequantize_ndarray(detect1.view(), quant1.into());
1399 let seg = dequantize_ndarray(seg.view(), quant_seg.into());
1400 decoder
1401 .decode_float::<f32>(
1402 &[
1403 detect0.view().into_dyn(),
1404 detect1.view().into_dyn(),
1405 seg.view().into_dyn(),
1406 ],
1407 &mut output_boxes,
1408 &mut output_masks,
1409 )
1410 .unwrap();
1411
1412 compare_outputs((&correct_boxes, &output_boxes), (&[], &[]));
1418 let mask0 = segmentation_to_mask(seg_view(&mask[0]).view()).unwrap();
1419 let mask1 = segmentation_to_mask(seg_view(&output_masks[0]).view()).unwrap();
1420
1421 assert_eq!(mask0, mask1);
1422 }
1423
1424 #[test]
1425 fn test_dequant_chunked() {
1426 let mut out = load_yolov8s_det().into_raw_vec_and_offset().0;
1427 out.push(123); let mut out_dequant = vec![0.0; 84 * 8400 + 1];
1430 let mut out_dequant_simd = vec![0.0; 84 * 8400 + 1];
1431 let quant = Quantization::new(0.0040811873, -123);
1432 dequantize_cpu(&out, quant, &mut out_dequant);
1433
1434 dequantize_cpu_chunked(&out, quant, &mut out_dequant_simd);
1435 assert_eq!(out_dequant, out_dequant_simd);
1436
1437 let quant = Quantization::new(0.0040811873, 0);
1438 dequantize_cpu(&out, quant, &mut out_dequant);
1439
1440 dequantize_cpu_chunked(&out, quant, &mut out_dequant_simd);
1441 assert_eq!(out_dequant, out_dequant_simd);
1442 }
1443
1444 #[test]
1445 fn test_dequant_ground_truth() {
1446 let quant = Quantization::new(0.1, -128);
1451 let input: Vec<i8> = vec![0, 127, -128, 64];
1452 let mut output = vec![0.0f32; 4];
1453 let mut output_chunked = vec![0.0f32; 4];
1454 dequantize_cpu(&input, quant, &mut output);
1455 dequantize_cpu_chunked(&input, quant, &mut output_chunked);
1456 let expected: Vec<f32> = vec![12.8, 25.5, 0.0, 19.2];
1461 for (i, (&out, &exp)) in output.iter().zip(expected.iter()).enumerate() {
1462 assert!((out - exp).abs() < 1e-5, "cpu[{i}]: {out} != {exp}");
1463 }
1464 for (i, (&out, &exp)) in output_chunked.iter().zip(expected.iter()).enumerate() {
1465 assert!((out - exp).abs() < 1e-5, "chunked[{i}]: {out} != {exp}");
1466 }
1467
1468 let quant = Quantization::new(1.0, 0);
1470 dequantize_cpu(&input, quant, &mut output);
1471 dequantize_cpu_chunked(&input, quant, &mut output_chunked);
1472 let expected: Vec<f32> = vec![0.0, 127.0, -128.0, 64.0];
1473 assert_eq!(output, expected);
1474 assert_eq!(output_chunked, expected);
1475
1476 let quant = Quantization::new(0.5, 0);
1478 dequantize_cpu(&input, quant, &mut output);
1479 dequantize_cpu_chunked(&input, quant, &mut output_chunked);
1480 let expected: Vec<f32> = vec![0.0, 63.5, -64.0, 32.0];
1481 assert_eq!(output, expected);
1482 assert_eq!(output_chunked, expected);
1483
1484 let quant = Quantization::new(0.021287762, 31);
1486 let input: Vec<i8> = vec![-128, -1, 0, 1, 31, 127];
1487 let mut output = vec![0.0f32; 6];
1488 let mut output_chunked = vec![0.0f32; 6];
1489 dequantize_cpu(&input, quant, &mut output);
1490 dequantize_cpu_chunked(&input, quant, &mut output_chunked);
1491 for i in 0..6 {
1492 let expected = (input[i] as f32 - 31.0) * 0.021287762;
1493 assert!(
1494 (output[i] - expected).abs() < 1e-5,
1495 "cpu[{i}]: {} != {expected}",
1496 output[i]
1497 );
1498 assert!(
1499 (output_chunked[i] - expected).abs() < 1e-5,
1500 "chunked[{i}]: {} != {expected}",
1501 output_chunked[i]
1502 );
1503 }
1504 }
1505
1506 #[test]
1507 fn test_decoder_yolo_det() {
1508 let score_threshold = 0.25;
1509 let iou_threshold = 0.7;
1510 let out = load_yolov8s_det();
1511 let quant = (0.0040811873, -123).into();
1512
1513 let decoder = DecoderBuilder::default()
1514 .with_config_yolo_det(
1515 configs::Detection {
1516 decoder: DecoderType::Ultralytics,
1517 shape: vec![1, 84, 8400],
1518 anchors: None,
1519 quantization: Some(quant),
1520 dshape: vec![
1521 (DimName::Batch, 1),
1522 (DimName::NumFeatures, 84),
1523 (DimName::NumBoxes, 8400),
1524 ],
1525 normalized: Some(true),
1526 },
1527 Some(DecoderVersion::Yolo11),
1528 )
1529 .with_score_threshold(score_threshold)
1530 .with_iou_threshold(iou_threshold)
1531 .build()
1532 .unwrap();
1533
1534 let mut output_boxes: Vec<_> = Vec::with_capacity(50);
1535 decode_yolo_det(
1536 (out.slice(s![0, .., ..]), quant.into()),
1537 score_threshold,
1538 iou_threshold,
1539 Some(configs::Nms::ClassAgnostic),
1540 &mut output_boxes,
1541 );
1542 assert!(output_boxes[0].equal_within_delta(
1543 &DetectBox {
1544 bbox: BoundingBox {
1545 xmin: 0.5285137,
1546 ymin: 0.05305544,
1547 xmax: 0.87541467,
1548 ymax: 0.9998909,
1549 },
1550 score: 0.5591227,
1551 label: 0
1552 },
1553 1e-6
1554 ));
1555
1556 assert!(output_boxes[1].equal_within_delta(
1557 &DetectBox {
1558 bbox: BoundingBox {
1559 xmin: 0.130598,
1560 ymin: 0.43260583,
1561 xmax: 0.35098213,
1562 ymax: 0.9958097,
1563 },
1564 score: 0.33057618,
1565 label: 75
1566 },
1567 1e-6
1568 ));
1569
1570 let mut output_boxes1: Vec<_> = Vec::with_capacity(50);
1571 let mut output_masks1: Vec<_> = Vec::with_capacity(50);
1572 decoder
1573 .decode_quantized(&[out.view().into()], &mut output_boxes1, &mut output_masks1)
1574 .unwrap();
1575
1576 let out = dequantize_ndarray(out.view(), quant.into());
1577 let mut output_boxes_f32: Vec<_> = Vec::with_capacity(50);
1578 let mut output_masks_f32: Vec<_> = Vec::with_capacity(50);
1579 decoder
1580 .decode_float::<f32>(
1581 &[out.view().into_dyn()],
1582 &mut output_boxes_f32,
1583 &mut output_masks_f32,
1584 )
1585 .unwrap();
1586
1587 compare_outputs((&output_boxes, &output_boxes1), (&[], &output_masks1));
1588 compare_outputs((&output_boxes, &output_boxes_f32), (&[], &output_masks_f32));
1589 }
1590
1591 #[test]
1592 fn test_decoder_masks() {
1593 let score_threshold = 0.45;
1594 let iou_threshold = 0.45;
1595 let boxes = load_yolov8_boxes();
1596 let quant_boxes = Quantization::new(0.021287761628627777, 31);
1597
1598 let protos = load_yolov8_protos();
1599 let quant_protos = Quantization::new(0.02491161972284317, -117);
1600 let protos = dequantize_ndarray::<_, _, f32>(protos.view(), quant_protos);
1601 let seg = dequantize_ndarray::<_, _, f32>(boxes.view(), quant_boxes);
1602 let mut output_boxes: Vec<_> = Vec::with_capacity(10);
1603 let mut output_masks: Vec<_> = Vec::with_capacity(10);
1604 decode_yolo_segdet_float(
1605 seg.slice(s![0, .., ..]),
1606 protos.slice(s![0, .., .., ..]),
1607 score_threshold,
1608 iou_threshold,
1609 Some(configs::Nms::ClassAgnostic),
1610 &mut output_boxes,
1611 &mut output_masks,
1612 )
1613 .unwrap();
1614 assert_eq!(output_boxes.len(), 2);
1615 assert_eq!(output_boxes.len(), output_masks.len());
1616
1617 for (b, m) in output_boxes.iter().zip(&output_masks) {
1618 assert!(b.bbox.xmin >= m.xmin);
1621 assert!(b.bbox.ymin >= m.ymin);
1622 assert!(b.bbox.xmax <= m.xmax);
1623 assert!(b.bbox.ymax <= m.ymax);
1624 }
1625 assert!(output_boxes[0].equal_within_delta(
1626 &DetectBox {
1627 bbox: BoundingBox {
1628 xmin: 0.08515105,
1629 ymin: 0.7131401,
1630 xmax: 0.29802868,
1631 ymax: 0.8195788,
1632 },
1633 score: 0.91537374,
1634 label: 23
1635 },
1636 1.0 / 160.0, ));
1638
1639 assert!(output_boxes[1].equal_within_delta(
1640 &DetectBox {
1641 bbox: BoundingBox {
1642 xmin: 0.59605736,
1643 ymin: 0.25545314,
1644 xmax: 0.93666154,
1645 ymax: 0.72378385,
1646 },
1647 score: 0.91537374,
1648 label: 23
1649 },
1650 1.0 / 160.0, ));
1652
1653 let full_mask = edgefirst_bench::testdata::read("yolov8_mask_results.bin");
1654 let full_mask = ndarray::Array2::from_shape_vec((160, 160), full_mask.to_vec()).unwrap();
1655
1656 let cropped_mask = full_mask.slice(ndarray::s![
1657 (output_masks[1].ymin * 160.0) as usize..(output_masks[1].ymax * 160.0) as usize,
1658 (output_masks[1].xmin * 160.0) as usize..(output_masks[1].xmax * 160.0) as usize,
1659 ]);
1660
1661 assert_eq!(
1662 cropped_mask,
1663 segmentation_to_mask(seg_view(&output_masks[1]).view()).unwrap()
1664 );
1665 }
1666
1667 #[test]
1677 fn test_decoder_masks_nchw_protos() {
1678 let score_threshold = 0.45;
1679 let iou_threshold = 0.45;
1680
1681 let boxes_2d = load_yolov8_boxes().slice_move(s![0, .., ..]);
1683 let quant_boxes = Quantization::new(0.021287761628627777, 31);
1684
1685 let protos_hwc = load_yolov8_protos().slice_move(s![0, .., .., ..]);
1687 let quant_protos = Quantization::new(0.02491161972284317, -117);
1688 let protos_f32_hwc = dequantize_ndarray::<_, _, f32>(protos_hwc.view(), quant_protos);
1689
1690 let seg = dequantize_ndarray::<_, _, f32>(boxes_2d.view(), quant_boxes);
1692 let mut ref_boxes: Vec<_> = Vec::with_capacity(10);
1693 let mut ref_masks: Vec<_> = Vec::with_capacity(10);
1694 decode_yolo_segdet_float(
1695 seg.view(),
1696 protos_f32_hwc.view(),
1697 score_threshold,
1698 iou_threshold,
1699 Some(configs::Nms::ClassAgnostic),
1700 &mut ref_boxes,
1701 &mut ref_masks,
1702 )
1703 .unwrap();
1704 assert_eq!(ref_boxes.len(), 2);
1705
1706 let protos_f32_chw_view = protos_f32_hwc.view().permuted_axes([2, 0, 1]); let protos_f32_chw = protos_f32_chw_view.to_owned();
1713 let protos_nchw = protos_f32_chw.insert_axis(ndarray::Axis(0)); let seg_3d = seg.insert_axis(ndarray::Axis(0)); let decoder = DecoderBuilder::default()
1722 .with_config_yolo_segdet(
1723 configs::Detection {
1724 decoder: configs::DecoderType::Ultralytics,
1725 quantization: None,
1726 shape: vec![1, 116, 8400],
1727 dshape: vec![
1728 (configs::DimName::Batch, 1),
1729 (configs::DimName::NumFeatures, 116),
1730 (configs::DimName::NumBoxes, 8400),
1731 ],
1732 normalized: Some(true),
1733 anchors: None,
1734 },
1735 configs::Protos {
1736 decoder: configs::DecoderType::Ultralytics,
1737 quantization: None,
1738 shape: vec![1, 32, 160, 160],
1739 dshape: vec![
1740 (configs::DimName::Batch, 1),
1741 (configs::DimName::NumProtos, 32),
1742 (configs::DimName::Height, 160),
1743 (configs::DimName::Width, 160),
1744 ],
1745 },
1746 None, )
1748 .with_score_threshold(score_threshold)
1749 .with_iou_threshold(iou_threshold)
1750 .build()
1751 .unwrap();
1752
1753 let mut cfg_boxes: Vec<_> = Vec::with_capacity(10);
1754 let mut cfg_masks: Vec<_> = Vec::with_capacity(10);
1755 decoder
1756 .decode_float(
1757 &[seg_3d.view().into_dyn(), protos_nchw.view().into_dyn()],
1758 &mut cfg_boxes,
1759 &mut cfg_masks,
1760 )
1761 .unwrap();
1762
1763 assert_eq!(
1765 cfg_boxes.len(),
1766 ref_boxes.len(),
1767 "config path produced {} boxes, reference produced {}",
1768 cfg_boxes.len(),
1769 ref_boxes.len()
1770 );
1771
1772 for (i, (cb, rb)) in cfg_boxes.iter().zip(&ref_boxes).enumerate() {
1774 assert!(
1775 cb.equal_within_delta(rb, 0.01),
1776 "box {i} mismatch: config={cb:?}, reference={rb:?}"
1777 );
1778 }
1779
1780 for (i, (cm, rm)) in cfg_masks.iter().zip(&ref_masks).enumerate() {
1782 let cm_arr = segmentation_to_mask(seg_view(cm).view()).unwrap();
1783 let rm_arr = segmentation_to_mask(seg_view(rm).view()).unwrap();
1784 assert_eq!(
1785 cm_arr, rm_arr,
1786 "mask {i} pixel mismatch between config-driven and reference paths"
1787 );
1788 }
1789 }
1790
1791 #[test]
1792 fn test_decoder_masks_i8() {
1793 let score_threshold = 0.45;
1794 let iou_threshold = 0.45;
1795 let boxes = load_yolov8_boxes();
1796 let quant_boxes = (0.021287761628627777, 31).into();
1797
1798 let protos = load_yolov8_protos();
1799 let quant_protos = (0.02491161972284317, -117).into();
1800 let mut output_boxes: Vec<_> = Vec::with_capacity(500);
1801 let mut output_masks: Vec<_> = Vec::with_capacity(500);
1802
1803 let decoder = DecoderBuilder::default()
1804 .with_config_yolo_segdet(
1805 configs::Detection {
1806 decoder: configs::DecoderType::Ultralytics,
1807 quantization: Some(quant_boxes),
1808 shape: vec![1, 116, 8400],
1809 anchors: None,
1810 dshape: vec![
1811 (DimName::Batch, 1),
1812 (DimName::NumFeatures, 116),
1813 (DimName::NumBoxes, 8400),
1814 ],
1815 normalized: Some(true),
1816 },
1817 Protos {
1818 decoder: configs::DecoderType::Ultralytics,
1819 quantization: Some(quant_protos),
1820 shape: vec![1, 160, 160, 32],
1821 dshape: vec![
1822 (DimName::Batch, 1),
1823 (DimName::Height, 160),
1824 (DimName::Width, 160),
1825 (DimName::NumProtos, 32),
1826 ],
1827 },
1828 Some(DecoderVersion::Yolo11),
1829 )
1830 .with_score_threshold(score_threshold)
1831 .with_iou_threshold(iou_threshold)
1832 .build()
1833 .unwrap();
1834
1835 let quant_boxes = quant_boxes.into();
1836 let quant_protos = quant_protos.into();
1837
1838 decode_yolo_segdet_quant(
1839 (boxes.slice(s![0, .., ..]), quant_boxes),
1840 (protos.slice(s![0, .., .., ..]), quant_protos),
1841 score_threshold,
1842 iou_threshold,
1843 Some(configs::Nms::ClassAgnostic),
1844 &mut output_boxes,
1845 &mut output_masks,
1846 )
1847 .unwrap();
1848
1849 let mut output_boxes1: Vec<_> = Vec::with_capacity(500);
1850 let mut output_masks1: Vec<_> = Vec::with_capacity(500);
1851
1852 decoder
1853 .decode_quantized(
1854 &[boxes.view().into(), protos.view().into()],
1855 &mut output_boxes1,
1856 &mut output_masks1,
1857 )
1858 .unwrap();
1859
1860 let protos = dequantize_ndarray::<_, _, f32>(protos.view(), quant_protos);
1861 let seg = dequantize_ndarray::<_, _, f32>(boxes.view(), quant_boxes);
1862
1863 let mut output_boxes_f32: Vec<_> = Vec::with_capacity(500);
1864 let mut output_masks_f32: Vec<_> = Vec::with_capacity(500);
1865 decode_yolo_segdet_float(
1866 seg.slice(s![0, .., ..]),
1867 protos.slice(s![0, .., .., ..]),
1868 score_threshold,
1869 iou_threshold,
1870 Some(configs::Nms::ClassAgnostic),
1871 &mut output_boxes_f32,
1872 &mut output_masks_f32,
1873 )
1874 .unwrap();
1875
1876 let mut output_boxes1_f32: Vec<_> = Vec::with_capacity(500);
1877 let mut output_masks1_f32: Vec<_> = Vec::with_capacity(500);
1878
1879 decoder
1880 .decode_float(
1881 &[seg.view().into_dyn(), protos.view().into_dyn()],
1882 &mut output_boxes1_f32,
1883 &mut output_masks1_f32,
1884 )
1885 .unwrap();
1886
1887 compare_outputs(
1888 (&output_boxes, &output_boxes1),
1889 (&output_masks, &output_masks1),
1890 );
1891
1892 compare_outputs(
1893 (&output_boxes, &output_boxes_f32),
1894 (&output_masks, &output_masks_f32),
1895 );
1896
1897 compare_outputs(
1898 (&output_boxes_f32, &output_boxes1_f32),
1899 (&output_masks_f32, &output_masks1_f32),
1900 );
1901 }
1902
1903 #[test]
1904 fn test_decoder_yolo_split() {
1905 let score_threshold = 0.45;
1906 let iou_threshold = 0.45;
1907 let boxes = load_yolov8_boxes();
1908 let boxes: Vec<_> = boxes.iter().map(|x| *x as i16 * 256).collect();
1909 let boxes = ndarray::Array3::from_shape_vec((1, 116, 8400), boxes).unwrap();
1910
1911 let quant_boxes = Quantization::new(0.021287761628627777 / 256.0, 31 * 256);
1912
1913 let decoder = DecoderBuilder::default()
1914 .with_config_yolo_split_det(
1915 configs::Boxes {
1916 decoder: configs::DecoderType::Ultralytics,
1917 quantization: Some(QuantTuple(quant_boxes.scale, quant_boxes.zero_point)),
1918 shape: vec![1, 4, 8400],
1919 dshape: vec![
1920 (DimName::Batch, 1),
1921 (DimName::BoxCoords, 4),
1922 (DimName::NumBoxes, 8400),
1923 ],
1924 normalized: Some(true),
1925 },
1926 configs::Scores {
1927 decoder: configs::DecoderType::Ultralytics,
1928 quantization: Some(QuantTuple(quant_boxes.scale, quant_boxes.zero_point)),
1929 shape: vec![1, 80, 8400],
1930 dshape: vec![
1931 (DimName::Batch, 1),
1932 (DimName::NumClasses, 80),
1933 (DimName::NumBoxes, 8400),
1934 ],
1935 },
1936 )
1937 .with_score_threshold(score_threshold)
1938 .with_iou_threshold(iou_threshold)
1939 .build()
1940 .unwrap();
1941
1942 let mut output_boxes: Vec<_> = Vec::with_capacity(500);
1943 let mut output_masks: Vec<_> = Vec::with_capacity(500);
1944
1945 decoder
1946 .decode_quantized(
1947 &[
1948 boxes.slice(s![.., ..4, ..]).into(),
1949 boxes.slice(s![.., 4..84, ..]).into(),
1950 ],
1951 &mut output_boxes,
1952 &mut output_masks,
1953 )
1954 .unwrap();
1955
1956 let seg = dequantize_ndarray::<_, _, f32>(boxes.view(), quant_boxes);
1957 let mut output_boxes_f32: Vec<_> = Vec::with_capacity(500);
1958 decode_yolo_det_float(
1959 seg.slice(s![0, ..84, ..]),
1960 score_threshold,
1961 iou_threshold,
1962 Some(configs::Nms::ClassAgnostic),
1963 &mut output_boxes_f32,
1964 );
1965
1966 let mut output_boxes1: Vec<_> = Vec::with_capacity(500);
1967 let mut output_masks1: Vec<_> = Vec::with_capacity(500);
1968
1969 decoder
1970 .decode_float(
1971 &[
1972 seg.slice(s![.., ..4, ..]).into_dyn(),
1973 seg.slice(s![.., 4..84, ..]).into_dyn(),
1974 ],
1975 &mut output_boxes1,
1976 &mut output_masks1,
1977 )
1978 .unwrap();
1979 compare_outputs((&output_boxes, &output_boxes_f32), (&output_masks, &[]));
1980 compare_outputs((&output_boxes_f32, &output_boxes1), (&[], &output_masks1));
1981 }
1982
1983 #[test]
1984 fn test_decoder_masks_config_mixed() {
1985 let score_threshold = 0.45;
1986 let iou_threshold = 0.45;
1987 let boxes_raw = load_yolov8_boxes();
1988 let boxes: Vec<_> = boxes_raw.iter().map(|x| *x as i16 * 256).collect();
1989 let boxes = ndarray::Array3::from_shape_vec((1, 116, 8400), boxes).unwrap();
1990
1991 let quant_boxes = (0.021287761628627777 / 256.0, 31 * 256);
1992
1993 let protos = load_yolov8_protos();
1994 let quant_protos = (0.02491161972284317, -117);
1995
1996 let decoder = build_yolo_split_segdet_decoder(
1997 score_threshold,
1998 iou_threshold,
1999 quant_boxes,
2000 quant_protos,
2001 );
2002 let mut output_boxes: Vec<_> = Vec::with_capacity(500);
2003 let mut output_masks: Vec<_> = Vec::with_capacity(500);
2004
2005 decoder
2006 .decode_quantized(
2007 &[
2008 boxes.slice(s![.., ..4, ..]).into(),
2009 boxes.slice(s![.., 4..84, ..]).into(),
2010 boxes.slice(s![.., 84.., ..]).into(),
2011 protos.view().into(),
2012 ],
2013 &mut output_boxes,
2014 &mut output_masks,
2015 )
2016 .unwrap();
2017
2018 let protos = dequantize_ndarray::<_, _, f32>(protos.view(), quant_protos.into());
2019 let seg = dequantize_ndarray::<_, _, f32>(boxes.view(), quant_boxes.into());
2020 let mut output_boxes_f32: Vec<_> = Vec::with_capacity(500);
2021 let mut output_masks_f32: Vec<_> = Vec::with_capacity(500);
2022 decode_yolo_segdet_float(
2023 seg.slice(s![0, .., ..]),
2024 protos.slice(s![0, .., .., ..]),
2025 score_threshold,
2026 iou_threshold,
2027 Some(configs::Nms::ClassAgnostic),
2028 &mut output_boxes_f32,
2029 &mut output_masks_f32,
2030 )
2031 .unwrap();
2032
2033 let mut output_boxes1: Vec<_> = Vec::with_capacity(500);
2034 let mut output_masks1: Vec<_> = Vec::with_capacity(500);
2035
2036 decoder
2037 .decode_float(
2038 &[
2039 seg.slice(s![.., ..4, ..]).into_dyn(),
2040 seg.slice(s![.., 4..84, ..]).into_dyn(),
2041 seg.slice(s![.., 84.., ..]).into_dyn(),
2042 protos.view().into_dyn(),
2043 ],
2044 &mut output_boxes1,
2045 &mut output_masks1,
2046 )
2047 .unwrap();
2048 compare_outputs(
2049 (&output_boxes, &output_boxes_f32),
2050 (&output_masks, &output_masks_f32),
2051 );
2052 compare_outputs(
2053 (&output_boxes_f32, &output_boxes1),
2054 (&output_masks_f32, &output_masks1),
2055 );
2056 }
2057
2058 fn build_yolo_split_segdet_decoder(
2059 score_threshold: f32,
2060 iou_threshold: f32,
2061 quant_boxes: (f32, i32),
2062 quant_protos: (f32, i32),
2063 ) -> crate::Decoder {
2064 DecoderBuilder::default()
2065 .with_config_yolo_split_segdet(
2066 configs::Boxes {
2067 decoder: configs::DecoderType::Ultralytics,
2068 quantization: Some(quant_boxes.into()),
2069 shape: vec![1, 4, 8400],
2070 dshape: vec![
2071 (DimName::Batch, 1),
2072 (DimName::BoxCoords, 4),
2073 (DimName::NumBoxes, 8400),
2074 ],
2075 normalized: Some(true),
2076 },
2077 configs::Scores {
2078 decoder: configs::DecoderType::Ultralytics,
2079 quantization: Some(quant_boxes.into()),
2080 shape: vec![1, 80, 8400],
2081 dshape: vec![
2082 (DimName::Batch, 1),
2083 (DimName::NumClasses, 80),
2084 (DimName::NumBoxes, 8400),
2085 ],
2086 },
2087 configs::MaskCoefficients {
2088 decoder: configs::DecoderType::Ultralytics,
2089 quantization: Some(quant_boxes.into()),
2090 shape: vec![1, 32, 8400],
2091 dshape: vec![
2092 (DimName::Batch, 1),
2093 (DimName::NumProtos, 32),
2094 (DimName::NumBoxes, 8400),
2095 ],
2096 },
2097 configs::Protos {
2098 decoder: configs::DecoderType::Ultralytics,
2099 quantization: Some(quant_protos.into()),
2100 shape: vec![1, 160, 160, 32],
2101 dshape: vec![
2102 (DimName::Batch, 1),
2103 (DimName::Height, 160),
2104 (DimName::Width, 160),
2105 (DimName::NumProtos, 32),
2106 ],
2107 },
2108 )
2109 .with_score_threshold(score_threshold)
2110 .with_iou_threshold(iou_threshold)
2111 .build()
2112 .unwrap()
2113 }
2114
2115 fn build_yolov8_seg_decoder(score_threshold: f32, iou_threshold: f32) -> crate::Decoder {
2116 let config_yaml = edgefirst_bench::testdata::read_to_string("yolov8_seg.yaml");
2117 DecoderBuilder::default()
2118 .with_config_yaml_str(config_yaml.to_string())
2119 .with_score_threshold(score_threshold)
2120 .with_iou_threshold(iou_threshold)
2121 .build()
2122 .unwrap()
2123 }
2124 #[test]
2125 fn test_decoder_masks_config_i32() {
2126 let score_threshold = 0.45;
2127 let iou_threshold = 0.45;
2128 let boxes_raw = load_yolov8_boxes();
2129 let scale = 1 << 23;
2130 let boxes: Vec<_> = boxes_raw.iter().map(|x| *x as i32 * scale).collect();
2131 let boxes = ndarray::Array3::from_shape_vec((1, 116, 8400), boxes).unwrap();
2132
2133 let quant_boxes = (0.021287761628627777 / scale as f32, 31 * scale);
2134
2135 let protos_raw = load_yolov8_protos();
2136 let protos: Vec<_> = protos_raw.iter().map(|x| *x as i32 * scale).collect();
2137 let protos = ndarray::Array4::from_shape_vec((1, 160, 160, 32), protos).unwrap();
2138 let quant_protos = (0.02491161972284317 / scale as f32, -117 * scale);
2139
2140 let decoder = build_yolo_split_segdet_decoder(
2141 score_threshold,
2142 iou_threshold,
2143 quant_boxes,
2144 quant_protos,
2145 );
2146
2147 let mut output_boxes: Vec<_> = Vec::with_capacity(500);
2148 let mut output_masks: Vec<_> = Vec::with_capacity(500);
2149
2150 decoder
2151 .decode_quantized(
2152 &[
2153 boxes.slice(s![.., ..4, ..]).into(),
2154 boxes.slice(s![.., 4..84, ..]).into(),
2155 boxes.slice(s![.., 84.., ..]).into(),
2156 protos.view().into(),
2157 ],
2158 &mut output_boxes,
2159 &mut output_masks,
2160 )
2161 .unwrap();
2162
2163 let protos = dequantize_ndarray::<_, _, f32>(protos.view(), quant_protos.into());
2164 let seg = dequantize_ndarray::<_, _, f32>(boxes.view(), quant_boxes.into());
2165 let mut output_boxes_f32: Vec<_> = Vec::with_capacity(500);
2166 let mut output_masks_f32: Vec<Segmentation> = Vec::with_capacity(500);
2167 decode_yolo_segdet_float(
2168 seg.slice(s![0, .., ..]),
2169 protos.slice(s![0, .., .., ..]),
2170 score_threshold,
2171 iou_threshold,
2172 Some(configs::Nms::ClassAgnostic),
2173 &mut output_boxes_f32,
2174 &mut output_masks_f32,
2175 )
2176 .unwrap();
2177
2178 assert_eq!(output_boxes.len(), output_boxes_f32.len());
2179 assert_eq!(output_masks.len(), output_masks_f32.len());
2180
2181 compare_outputs(
2182 (&output_boxes, &output_boxes_f32),
2183 (&output_masks, &output_masks_f32),
2184 );
2185 }
2186
2187 #[test]
2189 fn test_context_switch() {
2190 let yolo_det = || {
2191 let score_threshold = 0.25;
2192 let iou_threshold = 0.7;
2193 let out = load_yolov8s_det();
2194 let quant = (0.0040811873, -123).into();
2195
2196 let decoder = DecoderBuilder::default()
2197 .with_config_yolo_det(
2198 configs::Detection {
2199 decoder: DecoderType::Ultralytics,
2200 shape: vec![1, 84, 8400],
2201 anchors: None,
2202 quantization: Some(quant),
2203 dshape: vec![
2204 (DimName::Batch, 1),
2205 (DimName::NumFeatures, 84),
2206 (DimName::NumBoxes, 8400),
2207 ],
2208 normalized: None,
2209 },
2210 None,
2211 )
2212 .with_score_threshold(score_threshold)
2213 .with_iou_threshold(iou_threshold)
2214 .build()
2215 .unwrap();
2216
2217 let mut output_boxes: Vec<_> = Vec::with_capacity(50);
2218 let mut output_masks: Vec<_> = Vec::with_capacity(50);
2219
2220 for _ in 0..100 {
2221 decoder
2222 .decode_quantized(&[out.view().into()], &mut output_boxes, &mut output_masks)
2223 .unwrap();
2224
2225 assert!(output_boxes[0].equal_within_delta(
2226 &DetectBox {
2227 bbox: BoundingBox {
2228 xmin: 0.5285137,
2229 ymin: 0.05305544,
2230 xmax: 0.87541467,
2231 ymax: 0.9998909,
2232 },
2233 score: 0.5591227,
2234 label: 0
2235 },
2236 1e-6
2237 ));
2238
2239 assert!(output_boxes[1].equal_within_delta(
2240 &DetectBox {
2241 bbox: BoundingBox {
2242 xmin: 0.130598,
2243 ymin: 0.43260583,
2244 xmax: 0.35098213,
2245 ymax: 0.9958097,
2246 },
2247 score: 0.33057618,
2248 label: 75
2249 },
2250 1e-6
2251 ));
2252 assert!(output_masks.is_empty());
2253 }
2254 };
2255
2256 let modelpack_det_split = || {
2257 let score_threshold = 0.8;
2258 let iou_threshold = 0.5;
2259
2260 let seg = edgefirst_bench::testdata::read("modelpack_seg_2x160x160.bin");
2261 let seg = ndarray::Array4::from_shape_vec((1, 2, 160, 160), seg.to_vec()).unwrap();
2262
2263 let detect0 = edgefirst_bench::testdata::read("modelpack_split_9x15x18.bin");
2264 let detect0 =
2265 ndarray::Array4::from_shape_vec((1, 9, 15, 18), detect0.to_vec()).unwrap();
2266
2267 let detect1 = edgefirst_bench::testdata::read("modelpack_split_17x30x18.bin");
2268 let detect1 =
2269 ndarray::Array4::from_shape_vec((1, 17, 30, 18), detect1.to_vec()).unwrap();
2270
2271 let mut mask = seg.slice(s![0, .., .., ..]);
2272 mask.swap_axes(0, 1);
2273 mask.swap_axes(1, 2);
2274 let mask = [Segmentation {
2275 xmin: 0.0,
2276 ymin: 0.0,
2277 xmax: 1.0,
2278 ymax: 1.0,
2279 segmentation: crate::mask_to_tensor(mask.view()).unwrap(),
2280 }];
2281 let correct_boxes = [DetectBox {
2282 bbox: BoundingBox {
2283 xmin: 0.43171933,
2284 ymin: 0.68243736,
2285 xmax: 0.5626645,
2286 ymax: 0.808863,
2287 },
2288 score: 0.99240804,
2289 label: 0,
2290 }];
2291
2292 let quant0 = (0.08547406643629074, 174).into();
2293 let quant1 = (0.09929127991199493, 183).into();
2294 let quant_seg = (1.0 / 255.0, 0).into();
2295
2296 let anchors0 = vec![
2297 [0.36666667461395264, 0.31481480598449707],
2298 [0.38749998807907104, 0.4740740656852722],
2299 [0.5333333611488342, 0.644444465637207],
2300 ];
2301 let anchors1 = vec![
2302 [0.13750000298023224, 0.2074074000120163],
2303 [0.2541666626930237, 0.21481481194496155],
2304 [0.23125000298023224, 0.35185185074806213],
2305 ];
2306
2307 let decoder = DecoderBuilder::default()
2308 .with_config_modelpack_segdet_split(
2309 vec![
2310 configs::Detection {
2311 decoder: DecoderType::ModelPack,
2312 shape: vec![1, 17, 30, 18],
2313 anchors: Some(anchors1),
2314 quantization: Some(quant1),
2315 dshape: vec![
2316 (DimName::Batch, 1),
2317 (DimName::Height, 17),
2318 (DimName::Width, 30),
2319 (DimName::NumAnchorsXFeatures, 18),
2320 ],
2321 normalized: None,
2322 },
2323 configs::Detection {
2324 decoder: DecoderType::ModelPack,
2325 shape: vec![1, 9, 15, 18],
2326 anchors: Some(anchors0),
2327 quantization: Some(quant0),
2328 dshape: vec![
2329 (DimName::Batch, 1),
2330 (DimName::Height, 9),
2331 (DimName::Width, 15),
2332 (DimName::NumAnchorsXFeatures, 18),
2333 ],
2334 normalized: None,
2335 },
2336 ],
2337 configs::Segmentation {
2338 decoder: DecoderType::ModelPack,
2339 quantization: Some(quant_seg),
2340 shape: vec![1, 2, 160, 160],
2341 dshape: vec![
2342 (DimName::Batch, 1),
2343 (DimName::NumClasses, 2),
2344 (DimName::Height, 160),
2345 (DimName::Width, 160),
2346 ],
2347 },
2348 )
2349 .with_score_threshold(score_threshold)
2350 .with_iou_threshold(iou_threshold)
2351 .build()
2352 .unwrap();
2353 let mut output_boxes: Vec<_> = Vec::with_capacity(10);
2354 let mut output_masks: Vec<_> = Vec::with_capacity(10);
2355
2356 for _ in 0..100 {
2357 decoder
2358 .decode_quantized(
2359 &[
2360 detect0.view().into(),
2361 detect1.view().into(),
2362 seg.view().into(),
2363 ],
2364 &mut output_boxes,
2365 &mut output_masks,
2366 )
2367 .unwrap();
2368
2369 compare_outputs((&correct_boxes, &output_boxes), (&mask, &output_masks));
2370 }
2371 };
2372
2373 let handles = vec![
2374 std::thread::spawn(yolo_det),
2375 std::thread::spawn(modelpack_det_split),
2376 std::thread::spawn(yolo_det),
2377 std::thread::spawn(modelpack_det_split),
2378 std::thread::spawn(yolo_det),
2379 std::thread::spawn(modelpack_det_split),
2380 std::thread::spawn(yolo_det),
2381 std::thread::spawn(modelpack_det_split),
2382 ];
2383 for handle in handles {
2384 handle.join().unwrap();
2385 }
2386 }
2387
2388 #[test]
2389 fn test_ndarray_to_xyxy_float() {
2390 let arr = array![10.0_f32, 20.0, 20.0, 20.0];
2391 let xyxy: [f32; 4] = XYWH::ndarray_to_xyxy_float(arr.view());
2392 assert_eq!(xyxy, [0.0_f32, 10.0, 20.0, 30.0]);
2393
2394 let arr = array![10.0_f32, 20.0, 20.0, 20.0];
2395 let xyxy: [f32; 4] = XYXY::ndarray_to_xyxy_float(arr.view());
2396 assert_eq!(xyxy, [10.0_f32, 20.0, 20.0, 20.0]);
2397 }
2398
2399 #[test]
2400 fn test_class_aware_nms_float() {
2401 use crate::float::nms_class_aware_float;
2402
2403 let boxes = vec![
2405 DetectBox {
2406 bbox: BoundingBox {
2407 xmin: 0.0,
2408 ymin: 0.0,
2409 xmax: 0.5,
2410 ymax: 0.5,
2411 },
2412 score: 0.9,
2413 label: 0, },
2415 DetectBox {
2416 bbox: BoundingBox {
2417 xmin: 0.1,
2418 ymin: 0.1,
2419 xmax: 0.6,
2420 ymax: 0.6,
2421 },
2422 score: 0.8,
2423 label: 1, },
2425 ];
2426
2427 let result = nms_class_aware_float(0.3, None, boxes.clone());
2430 assert_eq!(
2431 result.len(),
2432 2,
2433 "Class-aware NMS should keep both boxes with different classes"
2434 );
2435
2436 let same_class_boxes = vec![
2438 DetectBox {
2439 bbox: BoundingBox {
2440 xmin: 0.0,
2441 ymin: 0.0,
2442 xmax: 0.5,
2443 ymax: 0.5,
2444 },
2445 score: 0.9,
2446 label: 0,
2447 },
2448 DetectBox {
2449 bbox: BoundingBox {
2450 xmin: 0.1,
2451 ymin: 0.1,
2452 xmax: 0.6,
2453 ymax: 0.6,
2454 },
2455 score: 0.8,
2456 label: 0, },
2458 ];
2459
2460 let result = nms_class_aware_float(0.3, None, same_class_boxes);
2461 assert_eq!(
2462 result.len(),
2463 1,
2464 "Class-aware NMS should suppress overlapping box with same class"
2465 );
2466 assert_eq!(result[0].label, 0);
2467 assert!((result[0].score - 0.9).abs() < 1e-6);
2468 }
2469
2470 #[test]
2471 fn test_class_agnostic_vs_aware_nms() {
2472 use crate::float::{nms_class_aware_float, nms_float};
2473
2474 let boxes = vec![
2476 DetectBox {
2477 bbox: BoundingBox {
2478 xmin: 0.0,
2479 ymin: 0.0,
2480 xmax: 0.5,
2481 ymax: 0.5,
2482 },
2483 score: 0.9,
2484 label: 0,
2485 },
2486 DetectBox {
2487 bbox: BoundingBox {
2488 xmin: 0.1,
2489 ymin: 0.1,
2490 xmax: 0.6,
2491 ymax: 0.6,
2492 },
2493 score: 0.8,
2494 label: 1,
2495 },
2496 ];
2497
2498 let agnostic_result = nms_float(0.3, None, boxes.clone());
2500 assert_eq!(
2501 agnostic_result.len(),
2502 1,
2503 "Class-agnostic NMS should suppress overlapping boxes"
2504 );
2505
2506 let aware_result = nms_class_aware_float(0.3, None, boxes);
2508 assert_eq!(
2509 aware_result.len(),
2510 2,
2511 "Class-aware NMS should keep boxes with different classes"
2512 );
2513 }
2514
2515 #[test]
2516 fn test_class_aware_nms_int() {
2517 use crate::byte::nms_class_aware_int;
2518
2519 let boxes = vec![
2521 DetectBoxQuantized {
2522 bbox: BoundingBox {
2523 xmin: 0.0,
2524 ymin: 0.0,
2525 xmax: 0.5,
2526 ymax: 0.5,
2527 },
2528 score: 200_u8,
2529 label: 0,
2530 },
2531 DetectBoxQuantized {
2532 bbox: BoundingBox {
2533 xmin: 0.1,
2534 ymin: 0.1,
2535 xmax: 0.6,
2536 ymax: 0.6,
2537 },
2538 score: 180_u8,
2539 label: 1, },
2541 ];
2542
2543 let result = nms_class_aware_int(0.5, None, boxes);
2545 assert_eq!(
2546 result.len(),
2547 2,
2548 "Class-aware NMS (int) should keep boxes with different classes"
2549 );
2550 }
2551
2552 #[test]
2553 fn test_nms_enum_default() {
2554 let default_nms: configs::Nms = Default::default();
2556 assert_eq!(default_nms, configs::Nms::ClassAgnostic);
2557 }
2558
2559 #[test]
2560 fn test_decoder_nms_mode() {
2561 let decoder = DecoderBuilder::default()
2563 .with_config_yolo_det(
2564 configs::Detection {
2565 anchors: None,
2566 decoder: DecoderType::Ultralytics,
2567 quantization: None,
2568 shape: vec![1, 84, 8400],
2569 dshape: Vec::new(),
2570 normalized: Some(true),
2571 },
2572 None,
2573 )
2574 .with_nms(Some(configs::Nms::ClassAware))
2575 .build()
2576 .unwrap();
2577
2578 assert_eq!(decoder.nms, Some(configs::Nms::ClassAware));
2579 }
2580
2581 #[test]
2582 fn test_decoder_nms_bypass() {
2583 let decoder = DecoderBuilder::default()
2585 .with_config_yolo_det(
2586 configs::Detection {
2587 anchors: None,
2588 decoder: DecoderType::Ultralytics,
2589 quantization: None,
2590 shape: vec![1, 84, 8400],
2591 dshape: Vec::new(),
2592 normalized: Some(true),
2593 },
2594 None,
2595 )
2596 .with_nms(None)
2597 .build()
2598 .unwrap();
2599
2600 assert_eq!(decoder.nms, None);
2601 }
2602
2603 #[test]
2604 fn test_decoder_normalized_boxes_true() {
2605 let decoder = DecoderBuilder::default()
2607 .with_config_yolo_det(
2608 configs::Detection {
2609 anchors: None,
2610 decoder: DecoderType::Ultralytics,
2611 quantization: None,
2612 shape: vec![1, 84, 8400],
2613 dshape: Vec::new(),
2614 normalized: Some(true),
2615 },
2616 None,
2617 )
2618 .build()
2619 .unwrap();
2620
2621 assert_eq!(decoder.normalized_boxes(), Some(true));
2622 }
2623
2624 #[test]
2625 fn test_decoder_normalized_boxes_false() {
2626 let decoder = DecoderBuilder::default()
2629 .with_config_yolo_det(
2630 configs::Detection {
2631 anchors: None,
2632 decoder: DecoderType::Ultralytics,
2633 quantization: None,
2634 shape: vec![1, 84, 8400],
2635 dshape: Vec::new(),
2636 normalized: Some(false),
2637 },
2638 None,
2639 )
2640 .build()
2641 .unwrap();
2642
2643 assert_eq!(decoder.normalized_boxes(), Some(false));
2644 }
2645
2646 #[test]
2647 fn test_decoder_normalized_boxes_unknown() {
2648 let decoder = DecoderBuilder::default()
2650 .with_config_yolo_det(
2651 configs::Detection {
2652 anchors: None,
2653 decoder: DecoderType::Ultralytics,
2654 quantization: None,
2655 shape: vec![1, 84, 8400],
2656 dshape: Vec::new(),
2657 normalized: None,
2658 },
2659 Some(DecoderVersion::Yolo11),
2660 )
2661 .build()
2662 .unwrap();
2663
2664 assert_eq!(decoder.normalized_boxes(), None);
2665 }
2666
2667 pub fn quantize_ndarray<T: PrimInt + 'static, D: Dimension, F: Float + AsPrimitive<T>>(
2668 input: ArrayView<F, D>,
2669 quant: Quantization,
2670 ) -> Array<T, D>
2671 where
2672 i32: num_traits::AsPrimitive<F>,
2673 f32: num_traits::AsPrimitive<F>,
2674 {
2675 let zero_point = quant.zero_point.as_();
2676 let div_scale = F::one() / quant.scale.as_();
2677 if zero_point != F::zero() {
2678 input.mapv(|d| (d * div_scale + zero_point).round().as_())
2679 } else {
2680 input.mapv(|d| (d * div_scale).round().as_())
2681 }
2682 }
2683
2684 fn real_data_expected_boxes() -> [DetectBox; 2] {
2685 [
2686 DetectBox {
2687 bbox: BoundingBox {
2688 xmin: 0.08515105,
2689 ymin: 0.7131401,
2690 xmax: 0.29802868,
2691 ymax: 0.8195788,
2692 },
2693 score: 0.91537374,
2694 label: 23,
2695 },
2696 DetectBox {
2697 bbox: BoundingBox {
2698 xmin: 0.59605736,
2699 ymin: 0.25545314,
2700 xmax: 0.93666154,
2701 ymax: 0.72378385,
2702 },
2703 score: 0.91537374,
2704 label: 23,
2705 },
2706 ]
2707 }
2708
2709 fn e2e_expected_boxes_quant() -> [DetectBox; 1] {
2710 [DetectBox {
2711 bbox: BoundingBox {
2712 xmin: 0.12549022,
2713 ymin: 0.12549022,
2714 xmax: 0.23529413,
2715 ymax: 0.23529413,
2716 },
2717 score: 0.98823535,
2718 label: 2,
2719 }]
2720 }
2721
2722 fn e2e_expected_boxes_float() -> [DetectBox; 1] {
2723 [DetectBox {
2724 bbox: BoundingBox {
2725 xmin: 0.1234,
2726 ymin: 0.1234,
2727 xmax: 0.2345,
2728 ymax: 0.2345,
2729 },
2730 score: 0.9876,
2731 label: 2,
2732 }]
2733 }
2734
2735 macro_rules! real_data_proto_test {
2736 ($name:ident, quantized, $layout:ident) => {
2737 #[test]
2738 fn $name() {
2739 let is_split = matches!(stringify!($layout), "split");
2740
2741 let score_threshold = 0.45;
2742 let iou_threshold = 0.45;
2743 let quant_boxes = (0.021287762_f32, 31_i32);
2744 let quant_protos = (0.02491162_f32, -117_i32);
2745
2746 let raw_boxes = edgefirst_bench::testdata::read("yolov8_boxes_116x8400.bin");
2747 let raw_boxes = unsafe {
2748 std::slice::from_raw_parts(raw_boxes.as_ptr() as *const i8, raw_boxes.len())
2749 };
2750 let boxes_i8 =
2751 ndarray::Array3::from_shape_vec((1, 116, 8400), raw_boxes.to_vec()).unwrap();
2752
2753 let raw_protos = edgefirst_bench::testdata::read("yolov8_protos_160x160x32.bin");
2754 let raw_protos = unsafe {
2755 std::slice::from_raw_parts(raw_protos.as_ptr() as *const i8, raw_protos.len())
2756 };
2757 let protos_i8 =
2758 ndarray::Array4::from_shape_vec((1, 160, 160, 32), raw_protos.to_vec())
2759 .unwrap();
2760
2761 let mask_split = boxes_i8.slice(s![.., 84.., ..]).to_owned();
2763 let scores_split = boxes_i8.slice(s![.., 4..84, ..]).to_owned();
2764 let boxes_split = boxes_i8.slice(s![.., ..4, ..]).to_owned();
2765 let boxes_combined = boxes_i8;
2766
2767 let decoder = if is_split {
2768 build_yolo_split_segdet_decoder(
2769 score_threshold,
2770 iou_threshold,
2771 quant_boxes,
2772 quant_protos,
2773 )
2774 } else {
2775 build_yolov8_seg_decoder(score_threshold, iou_threshold)
2776 };
2777
2778 let expected = real_data_expected_boxes();
2779 let mut output_boxes = Vec::with_capacity(50);
2780
2781 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> = if is_split {
2782 vec![
2783 boxes_split.view().into(),
2784 scores_split.view().into(),
2785 mask_split.view().into(),
2786 protos_i8.view().into(),
2787 ]
2788 } else {
2789 vec![boxes_combined.view().into(), protos_i8.view().into()]
2790 };
2791 decoder
2792 .decode_quantized_proto(&inputs, &mut output_boxes)
2793 .unwrap();
2794
2795 assert_eq!(output_boxes.len(), 2);
2796 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
2797 assert!(output_boxes[1].equal_within_delta(&expected[1], 1.0 / 160.0));
2798 }
2799 };
2800 ($name:ident, float, $layout:ident) => {
2801 #[test]
2802 fn $name() {
2803 let is_split = matches!(stringify!($layout), "split");
2804
2805 let score_threshold = 0.45;
2806 let iou_threshold = 0.45;
2807 let quant_boxes = (0.021287762_f32, 31_i32);
2808 let quant_protos = (0.02491162_f32, -117_i32);
2809
2810 let raw_boxes = edgefirst_bench::testdata::read("yolov8_boxes_116x8400.bin");
2811 let raw_boxes = unsafe {
2812 std::slice::from_raw_parts(raw_boxes.as_ptr() as *const i8, raw_boxes.len())
2813 };
2814 let boxes_i8 =
2815 ndarray::Array3::from_shape_vec((1, 116, 8400), raw_boxes.to_vec()).unwrap();
2816 let boxes_f32: Array3<f32> =
2817 dequantize_ndarray(boxes_i8.view(), quant_boxes.into());
2818
2819 let raw_protos = edgefirst_bench::testdata::read("yolov8_protos_160x160x32.bin");
2820 let raw_protos = unsafe {
2821 std::slice::from_raw_parts(raw_protos.as_ptr() as *const i8, raw_protos.len())
2822 };
2823 let protos_i8 =
2824 ndarray::Array4::from_shape_vec((1, 160, 160, 32), raw_protos.to_vec())
2825 .unwrap();
2826 let protos_f32: Array4<f32> =
2827 dequantize_ndarray(protos_i8.view(), quant_protos.into());
2828
2829 let mask_split = boxes_f32.slice(s![.., 84.., ..]).to_owned();
2831 let scores_split = boxes_f32.slice(s![.., 4..84, ..]).to_owned();
2832 let boxes_split = boxes_f32.slice(s![.., ..4, ..]).to_owned();
2833 let boxes_combined = boxes_f32;
2834
2835 let decoder = if is_split {
2836 build_yolo_split_segdet_decoder(
2837 score_threshold,
2838 iou_threshold,
2839 quant_boxes,
2840 quant_protos,
2841 )
2842 } else {
2843 build_yolov8_seg_decoder(score_threshold, iou_threshold)
2844 };
2845
2846 let expected = real_data_expected_boxes();
2847 let mut output_boxes = Vec::with_capacity(50);
2848
2849 let inputs = if is_split {
2850 vec![
2851 boxes_split.view().into_dyn(),
2852 scores_split.view().into_dyn(),
2853 mask_split.view().into_dyn(),
2854 protos_f32.view().into_dyn(),
2855 ]
2856 } else {
2857 vec![
2858 boxes_combined.view().into_dyn(),
2859 protos_f32.view().into_dyn(),
2860 ]
2861 };
2862 decoder
2863 .decode_float_proto(&inputs, &mut output_boxes)
2864 .unwrap();
2865
2866 assert_eq!(output_boxes.len(), 2);
2867 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
2868 assert!(output_boxes[1].equal_within_delta(&expected[1], 1.0 / 160.0));
2869 }
2870 };
2871 }
2872
2873 real_data_proto_test!(test_decoder_segdet_proto, quantized, combined);
2874 real_data_proto_test!(test_decoder_segdet_proto_float, float, combined);
2875 real_data_proto_test!(test_decoder_segdet_split_proto, quantized, split);
2876 real_data_proto_test!(test_decoder_segdet_split_proto_float, float, split);
2877
2878 const E2E_COMBINED_DET_CONFIG: &str = "
2879decoder_version: yolo26
2880outputs:
2881 - type: detection
2882 decoder: ultralytics
2883 quantization: [0.00784313725490196, 0]
2884 shape: [1, 10, 6]
2885 dshape:
2886 - [batch, 1]
2887 - [num_boxes, 10]
2888 - [num_features, 6]
2889 normalized: true
2890";
2891
2892 const E2E_COMBINED_SEGDET_CONFIG: &str = "
2893decoder_version: yolo26
2894outputs:
2895 - type: detection
2896 decoder: ultralytics
2897 quantization: [0.00784313725490196, 0]
2898 shape: [1, 10, 38]
2899 dshape:
2900 - [batch, 1]
2901 - [num_boxes, 10]
2902 - [num_features, 38]
2903 normalized: true
2904 - type: protos
2905 decoder: ultralytics
2906 quantization: [0.0039215686274509803921568627451, 128]
2907 shape: [1, 160, 160, 32]
2908 dshape:
2909 - [batch, 1]
2910 - [height, 160]
2911 - [width, 160]
2912 - [num_protos, 32]
2913";
2914
2915 const E2E_SPLIT_DET_CONFIG: &str = "
2916decoder_version: yolo26
2917outputs:
2918 - type: boxes
2919 decoder: ultralytics
2920 quantization: [0.00784313725490196, 0]
2921 shape: [1, 10, 4]
2922 dshape:
2923 - [batch, 1]
2924 - [num_boxes, 10]
2925 - [box_coords, 4]
2926 normalized: true
2927 - type: scores
2928 decoder: ultralytics
2929 quantization: [0.00784313725490196, 0]
2930 shape: [1, 10, 1]
2931 dshape:
2932 - [batch, 1]
2933 - [num_boxes, 10]
2934 - [num_classes, 1]
2935 - type: classes
2936 decoder: ultralytics
2937 quantization: [0.00784313725490196, 0]
2938 shape: [1, 10, 1]
2939 dshape:
2940 - [batch, 1]
2941 - [num_boxes, 10]
2942 - [num_classes, 1]
2943";
2944
2945 const E2E_SPLIT_SEGDET_CONFIG: &str = "
2946decoder_version: yolo26
2947outputs:
2948 - type: boxes
2949 decoder: ultralytics
2950 quantization: [0.00784313725490196, 0]
2951 shape: [1, 10, 4]
2952 dshape:
2953 - [batch, 1]
2954 - [num_boxes, 10]
2955 - [box_coords, 4]
2956 normalized: true
2957 - type: scores
2958 decoder: ultralytics
2959 quantization: [0.00784313725490196, 0]
2960 shape: [1, 10, 1]
2961 dshape:
2962 - [batch, 1]
2963 - [num_boxes, 10]
2964 - [num_classes, 1]
2965 - type: classes
2966 decoder: ultralytics
2967 quantization: [0.00784313725490196, 0]
2968 shape: [1, 10, 1]
2969 dshape:
2970 - [batch, 1]
2971 - [num_boxes, 10]
2972 - [num_classes, 1]
2973 - type: mask_coefficients
2974 decoder: ultralytics
2975 quantization: [0.00784313725490196, 0]
2976 shape: [1, 10, 32]
2977 dshape:
2978 - [batch, 1]
2979 - [num_boxes, 10]
2980 - [num_protos, 32]
2981 - type: protos
2982 decoder: ultralytics
2983 quantization: [0.0039215686274509803921568627451, 128]
2984 shape: [1, 160, 160, 32]
2985 dshape:
2986 - [batch, 1]
2987 - [height, 160]
2988 - [width, 160]
2989 - [num_protos, 32]
2990";
2991
2992 macro_rules! e2e_segdet_test {
2993 ($name:ident, quantized, $layout:ident, $output:ident) => {
2994 #[test]
2995 fn $name() {
2996 let is_split = matches!(stringify!($layout), "split");
2997 let is_proto = matches!(stringify!($output), "proto");
2998
2999 let score_threshold = 0.45;
3000 let iou_threshold = 0.45;
3001
3002 let mut boxes = Array2::zeros((10, 4));
3003 let mut scores = Array2::zeros((10, 1));
3004 let mut classes = Array2::zeros((10, 1));
3005 let mask = Array2::zeros((10, 32));
3006 let protos = Array3::<f64>::zeros((160, 160, 32));
3007 let protos = protos.insert_axis(Axis(0));
3008 let protos_quant = (1.0 / 255.0, 0.0);
3009 let protos: Array4<u8> = quantize_ndarray(protos.view(), protos_quant.into());
3010
3011 boxes
3012 .slice_mut(s![0, ..])
3013 .assign(&array![0.1234, 0.1234, 0.2345, 0.2345]);
3014 scores.slice_mut(s![0, ..]).assign(&array![0.9876]);
3015 classes.slice_mut(s![0, ..]).assign(&array![2.0]);
3016
3017 let detect_quant = (2.0 / 255.0, 0.0);
3018
3019 let decoder = if is_split {
3020 DecoderBuilder::default()
3021 .with_config_yaml_str(E2E_SPLIT_SEGDET_CONFIG.to_string())
3022 .with_score_threshold(score_threshold)
3023 .with_iou_threshold(iou_threshold)
3024 .build()
3025 .unwrap()
3026 } else {
3027 DecoderBuilder::default()
3028 .with_config_yaml_str(E2E_COMBINED_SEGDET_CONFIG.to_string())
3029 .with_score_threshold(score_threshold)
3030 .with_iou_threshold(iou_threshold)
3031 .build()
3032 .unwrap()
3033 };
3034
3035 let expected = e2e_expected_boxes_quant();
3036 let mut output_boxes = Vec::with_capacity(50);
3037
3038 if is_split {
3039 let boxes = boxes.insert_axis(Axis(0));
3040 let scores = scores.insert_axis(Axis(0));
3041 let classes = classes.insert_axis(Axis(0));
3042 let mask = mask.insert_axis(Axis(0));
3043
3044 let boxes: Array3<u8> = quantize_ndarray(boxes.view(), detect_quant.into());
3045 let scores: Array3<u8> = quantize_ndarray(scores.view(), detect_quant.into());
3046 let classes: Array3<u8> = quantize_ndarray(classes.view(), detect_quant.into());
3047 let mask: Array3<u8> = quantize_ndarray(mask.view(), detect_quant.into());
3048
3049 if is_proto {
3050 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> = vec![
3051 boxes.view().into(),
3052 scores.view().into(),
3053 classes.view().into(),
3054 mask.view().into(),
3055 protos.view().into(),
3056 ];
3057 decoder
3058 .decode_quantized_proto(&inputs, &mut output_boxes)
3059 .unwrap();
3060
3061 assert_eq!(output_boxes.len(), 1);
3062 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
3063 } else {
3064 let mut output_masks = Vec::with_capacity(50);
3065 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> = vec![
3066 boxes.view().into(),
3067 scores.view().into(),
3068 classes.view().into(),
3069 mask.view().into(),
3070 protos.view().into(),
3071 ];
3072 decoder
3073 .decode_quantized(&inputs, &mut output_boxes, &mut output_masks)
3074 .unwrap();
3075
3076 assert_eq!(output_boxes.len(), 1);
3077 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
3078 }
3079 } else {
3080 let detect = ndarray::concatenate![
3082 Axis(1),
3083 boxes.view(),
3084 scores.view(),
3085 classes.view(),
3086 mask.view()
3087 ];
3088 let detect = detect.insert_axis(Axis(0));
3089 assert_eq!(detect.shape(), &[1, 10, 38]);
3090 let detect: Array3<u8> = quantize_ndarray(detect.view(), detect_quant.into());
3091
3092 if is_proto {
3093 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> =
3094 vec![detect.view().into(), protos.view().into()];
3095 decoder
3096 .decode_quantized_proto(&inputs, &mut output_boxes)
3097 .unwrap();
3098
3099 assert_eq!(output_boxes.len(), 1);
3100 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
3101 } else {
3102 let mut output_masks = Vec::with_capacity(50);
3103 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> =
3104 vec![detect.view().into(), protos.view().into()];
3105 decoder
3106 .decode_quantized(&inputs, &mut output_boxes, &mut output_masks)
3107 .unwrap();
3108
3109 assert_eq!(output_boxes.len(), 1);
3110 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
3111 }
3112 }
3113 }
3114 };
3115 ($name:ident, float, $layout:ident, $output:ident) => {
3116 #[test]
3117 fn $name() {
3118 let is_split = matches!(stringify!($layout), "split");
3119 let is_proto = matches!(stringify!($output), "proto");
3120
3121 let score_threshold = 0.45;
3122 let iou_threshold = 0.45;
3123
3124 let mut boxes = Array2::zeros((10, 4));
3125 let mut scores = Array2::zeros((10, 1));
3126 let mut classes = Array2::zeros((10, 1));
3127 let mask: Array2<f64> = Array2::zeros((10, 32));
3128 let protos = Array3::<f64>::zeros((160, 160, 32));
3129 let protos = protos.insert_axis(Axis(0));
3130
3131 boxes
3132 .slice_mut(s![0, ..])
3133 .assign(&array![0.1234, 0.1234, 0.2345, 0.2345]);
3134 scores.slice_mut(s![0, ..]).assign(&array![0.9876]);
3135 classes.slice_mut(s![0, ..]).assign(&array![2.0]);
3136
3137 let decoder = if is_split {
3138 DecoderBuilder::default()
3139 .with_config_yaml_str(E2E_SPLIT_SEGDET_CONFIG.to_string())
3140 .with_score_threshold(score_threshold)
3141 .with_iou_threshold(iou_threshold)
3142 .build()
3143 .unwrap()
3144 } else {
3145 DecoderBuilder::default()
3146 .with_config_yaml_str(E2E_COMBINED_SEGDET_CONFIG.to_string())
3147 .with_score_threshold(score_threshold)
3148 .with_iou_threshold(iou_threshold)
3149 .build()
3150 .unwrap()
3151 };
3152
3153 let expected = e2e_expected_boxes_float();
3154 let mut output_boxes = Vec::with_capacity(50);
3155
3156 if is_split {
3157 let boxes = boxes.insert_axis(Axis(0));
3158 let scores = scores.insert_axis(Axis(0));
3159 let classes = classes.insert_axis(Axis(0));
3160 let mask = mask.insert_axis(Axis(0));
3161
3162 if is_proto {
3163 let inputs = vec![
3164 boxes.view().into_dyn(),
3165 scores.view().into_dyn(),
3166 classes.view().into_dyn(),
3167 mask.view().into_dyn(),
3168 protos.view().into_dyn(),
3169 ];
3170 decoder
3171 .decode_float_proto(&inputs, &mut output_boxes)
3172 .unwrap();
3173
3174 assert_eq!(output_boxes.len(), 1);
3175 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
3176 } else {
3177 let mut output_masks = Vec::with_capacity(50);
3178 let inputs = vec![
3179 boxes.view().into_dyn(),
3180 scores.view().into_dyn(),
3181 classes.view().into_dyn(),
3182 mask.view().into_dyn(),
3183 protos.view().into_dyn(),
3184 ];
3185 decoder
3186 .decode_float(&inputs, &mut output_boxes, &mut output_masks)
3187 .unwrap();
3188
3189 assert_eq!(output_boxes.len(), 1);
3190 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
3191 }
3192 } else {
3193 let detect = ndarray::concatenate![
3195 Axis(1),
3196 boxes.view(),
3197 scores.view(),
3198 classes.view(),
3199 mask.view()
3200 ];
3201 let detect = detect.insert_axis(Axis(0));
3202 assert_eq!(detect.shape(), &[1, 10, 38]);
3203
3204 if is_proto {
3205 let inputs = vec![detect.view().into_dyn(), protos.view().into_dyn()];
3206 decoder
3207 .decode_float_proto(&inputs, &mut output_boxes)
3208 .unwrap();
3209
3210 assert_eq!(output_boxes.len(), 1);
3211 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
3212 } else {
3213 let mut output_masks = Vec::with_capacity(50);
3214 let inputs = vec![detect.view().into_dyn(), protos.view().into_dyn()];
3215 decoder
3216 .decode_float(&inputs, &mut output_boxes, &mut output_masks)
3217 .unwrap();
3218
3219 assert_eq!(output_boxes.len(), 1);
3220 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
3221 }
3222 }
3223 }
3224 };
3225 }
3226
3227 e2e_segdet_test!(test_decoder_end_to_end_segdet, quantized, combined, masks);
3228 e2e_segdet_test!(test_decoder_end_to_end_segdet_float, float, combined, masks);
3229 e2e_segdet_test!(
3230 test_decoder_end_to_end_segdet_proto,
3231 quantized,
3232 combined,
3233 proto
3234 );
3235 e2e_segdet_test!(
3236 test_decoder_end_to_end_segdet_proto_float,
3237 float,
3238 combined,
3239 proto
3240 );
3241 e2e_segdet_test!(
3242 test_decoder_end_to_end_segdet_split,
3243 quantized,
3244 split,
3245 masks
3246 );
3247 e2e_segdet_test!(
3248 test_decoder_end_to_end_segdet_split_float,
3249 float,
3250 split,
3251 masks
3252 );
3253 e2e_segdet_test!(
3254 test_decoder_end_to_end_segdet_split_proto,
3255 quantized,
3256 split,
3257 proto
3258 );
3259 e2e_segdet_test!(
3260 test_decoder_end_to_end_segdet_split_proto_float,
3261 float,
3262 split,
3263 proto
3264 );
3265
3266 macro_rules! e2e_det_test {
3267 ($name:ident, quantized, $layout:ident) => {
3268 #[test]
3269 fn $name() {
3270 let is_split = matches!(stringify!($layout), "split");
3271
3272 let score_threshold = 0.45;
3273 let iou_threshold = 0.45;
3274
3275 let mut boxes = Array3::zeros((1, 10, 4));
3276 let mut scores = Array3::zeros((1, 10, 1));
3277 let mut classes = Array3::zeros((1, 10, 1));
3278
3279 boxes
3280 .slice_mut(s![0, 0, ..])
3281 .assign(&array![0.1234, 0.1234, 0.2345, 0.2345]);
3282 scores.slice_mut(s![0, 0, ..]).assign(&array![0.9876]);
3283 classes.slice_mut(s![0, 0, ..]).assign(&array![2.0]);
3284
3285 let detect_quant = (2.0 / 255.0, 0_i32);
3286
3287 let decoder = if is_split {
3288 DecoderBuilder::default()
3289 .with_config_yaml_str(E2E_SPLIT_DET_CONFIG.to_string())
3290 .with_score_threshold(score_threshold)
3291 .with_iou_threshold(iou_threshold)
3292 .build()
3293 .unwrap()
3294 } else {
3295 DecoderBuilder::default()
3296 .with_config_yaml_str(E2E_COMBINED_DET_CONFIG.to_string())
3297 .with_score_threshold(score_threshold)
3298 .with_iou_threshold(iou_threshold)
3299 .build()
3300 .unwrap()
3301 };
3302
3303 let expected = e2e_expected_boxes_quant();
3304 let mut output_boxes = Vec::with_capacity(50);
3305
3306 if is_split {
3307 let boxes: Array<u8, _> = quantize_ndarray(boxes.view(), detect_quant.into());
3308 let scores: Array<u8, _> = quantize_ndarray(scores.view(), detect_quant.into());
3309 let classes: Array<u8, _> =
3310 quantize_ndarray(classes.view(), detect_quant.into());
3311 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> = vec![
3312 boxes.view().into(),
3313 scores.view().into(),
3314 classes.view().into(),
3315 ];
3316 decoder
3317 .decode_quantized(&inputs, &mut output_boxes, &mut Vec::new())
3318 .unwrap();
3319 } else {
3320 let detect =
3321 ndarray::concatenate![Axis(2), boxes.view(), scores.view(), classes.view()];
3322 assert_eq!(detect.shape(), &[1, 10, 6]);
3323 let detect: Array3<u8> = quantize_ndarray(detect.view(), detect_quant.into());
3324 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> =
3325 vec![detect.view().into()];
3326 decoder
3327 .decode_quantized(&inputs, &mut output_boxes, &mut Vec::new())
3328 .unwrap();
3329 }
3330
3331 assert_eq!(output_boxes.len(), 1);
3332 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
3333 }
3334 };
3335 ($name:ident, float, $layout:ident) => {
3336 #[test]
3337 fn $name() {
3338 let is_split = matches!(stringify!($layout), "split");
3339
3340 let score_threshold = 0.45;
3341 let iou_threshold = 0.45;
3342
3343 let mut boxes = Array3::zeros((1, 10, 4));
3344 let mut scores = Array3::zeros((1, 10, 1));
3345 let mut classes = Array3::zeros((1, 10, 1));
3346
3347 boxes
3348 .slice_mut(s![0, 0, ..])
3349 .assign(&array![0.1234, 0.1234, 0.2345, 0.2345]);
3350 scores.slice_mut(s![0, 0, ..]).assign(&array![0.9876]);
3351 classes.slice_mut(s![0, 0, ..]).assign(&array![2.0]);
3352
3353 let decoder = if is_split {
3354 DecoderBuilder::default()
3355 .with_config_yaml_str(E2E_SPLIT_DET_CONFIG.to_string())
3356 .with_score_threshold(score_threshold)
3357 .with_iou_threshold(iou_threshold)
3358 .build()
3359 .unwrap()
3360 } else {
3361 DecoderBuilder::default()
3362 .with_config_yaml_str(E2E_COMBINED_DET_CONFIG.to_string())
3363 .with_score_threshold(score_threshold)
3364 .with_iou_threshold(iou_threshold)
3365 .build()
3366 .unwrap()
3367 };
3368
3369 let expected = e2e_expected_boxes_float();
3370 let mut output_boxes = Vec::with_capacity(50);
3371
3372 if is_split {
3373 let inputs = vec![
3374 boxes.view().into_dyn(),
3375 scores.view().into_dyn(),
3376 classes.view().into_dyn(),
3377 ];
3378 decoder
3379 .decode_float(&inputs, &mut output_boxes, &mut Vec::new())
3380 .unwrap();
3381 } else {
3382 let detect =
3383 ndarray::concatenate![Axis(2), boxes.view(), scores.view(), classes.view()];
3384 assert_eq!(detect.shape(), &[1, 10, 6]);
3385 let inputs = vec![detect.view().into_dyn()];
3386 decoder
3387 .decode_float(&inputs, &mut output_boxes, &mut Vec::new())
3388 .unwrap();
3389 }
3390
3391 assert_eq!(output_boxes.len(), 1);
3392 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
3393 }
3394 };
3395 }
3396
3397 e2e_det_test!(test_decoder_end_to_end_combined_det, quantized, combined);
3398 e2e_det_test!(test_decoder_end_to_end_combined_det_float, float, combined);
3399 e2e_det_test!(test_decoder_end_to_end_split_det, quantized, split);
3400 e2e_det_test!(test_decoder_end_to_end_split_det_float, float, split);
3401
3402 #[test]
3403 fn test_decode_tensor() {
3404 let score_threshold = 0.45;
3405 let iou_threshold = 0.45;
3406
3407 let raw_boxes = edgefirst_bench::testdata::read("yolov8_boxes_116x8400.bin");
3408 let raw_boxes =
3409 unsafe { std::slice::from_raw_parts(raw_boxes.as_ptr() as *const i8, raw_boxes.len()) };
3410 let boxes_i8: Tensor<i8> = Tensor::new(&[1, 116, 8400], None, None).unwrap();
3411 boxes_i8
3412 .map()
3413 .unwrap()
3414 .as_mut_slice()
3415 .copy_from_slice(raw_boxes);
3416 let boxes_i8 = boxes_i8.into();
3417
3418 let raw_protos = edgefirst_bench::testdata::read("yolov8_protos_160x160x32.bin");
3419 let raw_protos = unsafe {
3420 std::slice::from_raw_parts(raw_protos.as_ptr() as *const i8, raw_protos.len())
3421 };
3422 let protos_i8: Tensor<i8> = Tensor::new(&[1, 160, 160, 32], None, None).unwrap();
3423 protos_i8
3424 .map()
3425 .unwrap()
3426 .as_mut_slice()
3427 .copy_from_slice(raw_protos);
3428 let protos_i8 = protos_i8.into();
3429
3430 let decoder = build_yolov8_seg_decoder(score_threshold, iou_threshold);
3431 let expected = real_data_expected_boxes();
3432 let mut output_boxes = Vec::with_capacity(50);
3433
3434 decoder
3435 .decode(&[&boxes_i8, &protos_i8], &mut output_boxes, &mut Vec::new())
3436 .unwrap();
3437
3438 assert_eq!(output_boxes.len(), 2);
3439 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
3440 assert!(output_boxes[1].equal_within_delta(&expected[1], 1.0 / 160.0));
3441 }
3442
3443 #[test]
3444 fn test_decode_tensor_f32() {
3445 let score_threshold = 0.45;
3446 let iou_threshold = 0.45;
3447
3448 let quant_boxes = (0.021287762_f32, 31_i32);
3449 let quant_protos = (0.02491162_f32, -117_i32);
3450 let raw_boxes = edgefirst_bench::testdata::read("yolov8_boxes_116x8400.bin");
3451 let raw_boxes =
3452 unsafe { std::slice::from_raw_parts(raw_boxes.as_ptr() as *const i8, raw_boxes.len()) };
3453 let mut raw_boxes_f32 = vec![0f32; raw_boxes.len()];
3454 dequantize_cpu(raw_boxes, quant_boxes.into(), &mut raw_boxes_f32);
3455 let boxes_f32: Tensor<f32> = Tensor::new(&[1, 116, 8400], None, None).unwrap();
3456 boxes_f32
3457 .map()
3458 .unwrap()
3459 .as_mut_slice()
3460 .copy_from_slice(&raw_boxes_f32);
3461 let boxes_f32 = boxes_f32.into();
3462
3463 let raw_protos = edgefirst_bench::testdata::read("yolov8_protos_160x160x32.bin");
3464 let raw_protos = unsafe {
3465 std::slice::from_raw_parts(raw_protos.as_ptr() as *const i8, raw_protos.len())
3466 };
3467 let mut raw_protos_f32 = vec![0f32; raw_protos.len()];
3468 dequantize_cpu(raw_protos, quant_protos.into(), &mut raw_protos_f32);
3469 let protos_f32: Tensor<f32> = Tensor::new(&[1, 160, 160, 32], None, None).unwrap();
3470 protos_f32
3471 .map()
3472 .unwrap()
3473 .as_mut_slice()
3474 .copy_from_slice(&raw_protos_f32);
3475 let protos_f32 = protos_f32.into();
3476
3477 let decoder = build_yolov8_seg_decoder(score_threshold, iou_threshold);
3478
3479 let expected = real_data_expected_boxes();
3480 let mut output_boxes = Vec::with_capacity(50);
3481
3482 decoder
3483 .decode(
3484 &[&boxes_f32, &protos_f32],
3485 &mut output_boxes,
3486 &mut Vec::new(),
3487 )
3488 .unwrap();
3489
3490 assert_eq!(output_boxes.len(), 2);
3491 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
3492 assert!(output_boxes[1].equal_within_delta(&expected[1], 1.0 / 160.0));
3493 }
3494
3495 #[test]
3496 fn test_decode_tensor_f64() {
3497 let score_threshold = 0.45;
3498 let iou_threshold = 0.45;
3499
3500 let quant_boxes = (0.021287762_f32, 31_i32);
3501 let quant_protos = (0.02491162_f32, -117_i32);
3502 let raw_boxes = edgefirst_bench::testdata::read("yolov8_boxes_116x8400.bin");
3503 let raw_boxes =
3504 unsafe { std::slice::from_raw_parts(raw_boxes.as_ptr() as *const i8, raw_boxes.len()) };
3505 let mut raw_boxes_f64 = vec![0f64; raw_boxes.len()];
3506 dequantize_cpu(raw_boxes, quant_boxes.into(), &mut raw_boxes_f64);
3507 let boxes_f64: Tensor<f64> = Tensor::new(&[1, 116, 8400], None, None).unwrap();
3508 boxes_f64
3509 .map()
3510 .unwrap()
3511 .as_mut_slice()
3512 .copy_from_slice(&raw_boxes_f64);
3513 let boxes_f64 = boxes_f64.into();
3514
3515 let raw_protos = edgefirst_bench::testdata::read("yolov8_protos_160x160x32.bin");
3516 let raw_protos = unsafe {
3517 std::slice::from_raw_parts(raw_protos.as_ptr() as *const i8, raw_protos.len())
3518 };
3519 let mut raw_protos_f64 = vec![0f64; raw_protos.len()];
3520 dequantize_cpu(raw_protos, quant_protos.into(), &mut raw_protos_f64);
3521 let protos_f64: Tensor<f64> = Tensor::new(&[1, 160, 160, 32], None, None).unwrap();
3522 protos_f64
3523 .map()
3524 .unwrap()
3525 .as_mut_slice()
3526 .copy_from_slice(&raw_protos_f64);
3527 let protos_f64 = protos_f64.into();
3528
3529 let decoder = build_yolov8_seg_decoder(score_threshold, iou_threshold);
3530
3531 let expected = real_data_expected_boxes();
3532 let mut output_boxes = Vec::with_capacity(50);
3533
3534 decoder
3535 .decode(
3536 &[&boxes_f64, &protos_f64],
3537 &mut output_boxes,
3538 &mut Vec::new(),
3539 )
3540 .unwrap();
3541
3542 assert_eq!(output_boxes.len(), 2);
3543 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
3544 assert!(output_boxes[1].equal_within_delta(&expected[1], 1.0 / 160.0));
3545 }
3546
3547 #[test]
3548 fn test_decode_tensor_proto() {
3549 let score_threshold = 0.45;
3550 let iou_threshold = 0.45;
3551
3552 let raw_boxes = edgefirst_bench::testdata::read("yolov8_boxes_116x8400.bin");
3553 let raw_boxes =
3554 unsafe { std::slice::from_raw_parts(raw_boxes.as_ptr() as *const i8, raw_boxes.len()) };
3555 let boxes_i8: Tensor<i8> = Tensor::new(&[1, 116, 8400], None, None).unwrap();
3556 boxes_i8
3557 .map()
3558 .unwrap()
3559 .as_mut_slice()
3560 .copy_from_slice(raw_boxes);
3561 let boxes_i8 = boxes_i8.into();
3562
3563 let raw_protos = edgefirst_bench::testdata::read("yolov8_protos_160x160x32.bin");
3564 let raw_protos = unsafe {
3565 std::slice::from_raw_parts(raw_protos.as_ptr() as *const i8, raw_protos.len())
3566 };
3567 let protos_i8: Tensor<i8> = Tensor::new(&[1, 160, 160, 32], None, None).unwrap();
3568 protos_i8
3569 .map()
3570 .unwrap()
3571 .as_mut_slice()
3572 .copy_from_slice(raw_protos);
3573 let protos_i8 = protos_i8.into();
3574
3575 let decoder = build_yolov8_seg_decoder(score_threshold, iou_threshold);
3576
3577 let expected = real_data_expected_boxes();
3578 let mut output_boxes = Vec::with_capacity(50);
3579
3580 let proto_data = decoder
3581 .decode_proto(&[&boxes_i8, &protos_i8], &mut output_boxes)
3582 .unwrap();
3583
3584 assert_eq!(output_boxes.len(), 2);
3585 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
3586 assert!(output_boxes[1].equal_within_delta(&expected[1], 1.0 / 160.0));
3587
3588 let proto_data = proto_data.expect("segmentation model should return ProtoData");
3589 let coeffs_shape = proto_data.mask_coefficients.shape();
3590 assert_eq!(
3591 coeffs_shape[0],
3592 output_boxes.len(),
3593 "mask_coefficients count must match detection count"
3594 );
3595 assert_eq!(
3596 coeffs_shape[1], 32,
3597 "each detection should have 32 mask coefficients"
3598 );
3599 }
3600
3601 #[test]
3619 fn test_physical_order_tflite_nhwc_protos() {
3620 let score_threshold = 0.45;
3621 let iou_threshold = 0.45;
3622
3623 let protos_hwc = load_yolov8_protos().slice_move(s![0, .., .., ..]);
3625 let quant_protos = Quantization::new(0.02491161972284317, -117);
3626 let protos_f32_hwc = dequantize_ndarray::<_, _, f32>(protos_hwc.view(), quant_protos);
3627
3628 let boxes_2d = load_yolov8_boxes().slice_move(s![0, .., ..]);
3629 let quant_boxes = Quantization::new(0.021287761628627777, 31);
3630 let seg = dequantize_ndarray::<_, _, f32>(boxes_2d.view(), quant_boxes);
3631
3632 let mut ref_boxes: Vec<_> = Vec::with_capacity(10);
3634 let mut ref_masks: Vec<_> = Vec::with_capacity(10);
3635 decode_yolo_segdet_float(
3636 seg.view(),
3637 protos_f32_hwc.view(),
3638 score_threshold,
3639 iou_threshold,
3640 Some(configs::Nms::ClassAgnostic),
3641 &mut ref_boxes,
3642 &mut ref_masks,
3643 )
3644 .unwrap();
3645
3646 let protos_nhwc = protos_f32_hwc.clone().insert_axis(Axis(0)); let seg_3d = seg.insert_axis(Axis(0)); let decoder = DecoderBuilder::default()
3652 .with_config_yolo_segdet(
3653 configs::Detection {
3654 decoder: configs::DecoderType::Ultralytics,
3655 quantization: None,
3656 shape: vec![1, 116, 8400],
3657 dshape: vec![
3658 (DimName::Batch, 1),
3659 (DimName::NumFeatures, 116),
3660 (DimName::NumBoxes, 8400),
3661 ],
3662 normalized: Some(true),
3663 anchors: None,
3664 },
3665 configs::Protos {
3666 decoder: configs::DecoderType::Ultralytics,
3667 quantization: None,
3668 shape: vec![1, 160, 160, 32],
3669 dshape: vec![
3671 (DimName::Batch, 1),
3672 (DimName::Height, 160),
3673 (DimName::Width, 160),
3674 (DimName::NumProtos, 32),
3675 ],
3676 },
3677 None,
3678 )
3679 .with_score_threshold(score_threshold)
3680 .with_iou_threshold(iou_threshold)
3681 .build()
3682 .expect("config with NHWC protos dshape must build");
3683
3684 let mut cfg_boxes = Vec::with_capacity(10);
3685 let mut cfg_masks = Vec::with_capacity(10);
3686 decoder
3687 .decode_float(
3688 &[seg_3d.view().into_dyn(), protos_nhwc.view().into_dyn()],
3689 &mut cfg_boxes,
3690 &mut cfg_masks,
3691 )
3692 .unwrap();
3693
3694 assert_eq!(cfg_boxes.len(), ref_boxes.len(), "box count mismatch");
3695 for (c, r) in cfg_boxes.iter().zip(&ref_boxes) {
3696 assert!(
3697 c.equal_within_delta(r, 0.01),
3698 "NHWC-declared box does not match reference: {c:?} vs {r:?}"
3699 );
3700 }
3701 for (cm, rm) in cfg_masks.iter().zip(&ref_masks) {
3702 let cm_arr = segmentation_to_mask(seg_view(cm).view()).unwrap();
3703 let rm_arr = segmentation_to_mask(seg_view(rm).view()).unwrap();
3704 assert_eq!(
3705 cm_arr, rm_arr,
3706 "NHWC-declared mask must match reference pixel-for-pixel"
3707 );
3708 }
3709 }
3710
3711 #[test]
3720 fn test_physical_order_ara2_anchor_first_split_boxes() {
3721 use configs::{Boxes, Scores};
3722
3723 const N: usize = 8400;
3726 let mut boxes_canonical = Array3::<f32>::zeros((1, 4, N));
3727 let target_anchor = 42usize;
3728 boxes_canonical[[0, 0, target_anchor]] = 0.4; boxes_canonical[[0, 1, target_anchor]] = 0.5; boxes_canonical[[0, 2, target_anchor]] = 0.2; boxes_canonical[[0, 3, target_anchor]] = 0.2; let mut scores_canonical = Array3::<f32>::zeros((1, 80, N));
3735 scores_canonical[[0, 0, target_anchor]] = 0.9;
3736
3737 let ref_decoder = DecoderBuilder::default()
3739 .with_config_yolo_split_det(
3740 Boxes {
3741 decoder: configs::DecoderType::Ultralytics,
3742 quantization: None,
3743 shape: vec![1, 4, N],
3744 dshape: vec![
3745 (DimName::Batch, 1),
3746 (DimName::BoxCoords, 4),
3747 (DimName::NumBoxes, N),
3748 ],
3749 normalized: Some(true),
3750 },
3751 Scores {
3752 decoder: configs::DecoderType::Ultralytics,
3753 quantization: None,
3754 shape: vec![1, 80, N],
3755 dshape: vec![
3756 (DimName::Batch, 1),
3757 (DimName::NumClasses, 80),
3758 (DimName::NumBoxes, N),
3759 ],
3760 },
3761 )
3762 .with_score_threshold(0.5)
3763 .with_iou_threshold(0.5)
3764 .with_nms(Some(configs::Nms::ClassAgnostic))
3765 .build()
3766 .expect("reference canonical split decoder must build");
3767
3768 let mut ref_boxes = Vec::with_capacity(4);
3769 let mut ref_masks = Vec::with_capacity(0);
3770 ref_decoder
3771 .decode_float(
3772 &[
3773 boxes_canonical.view().into_dyn(),
3774 scores_canonical.view().into_dyn(),
3775 ],
3776 &mut ref_boxes,
3777 &mut ref_masks,
3778 )
3779 .unwrap();
3780 assert_eq!(ref_boxes.len(), 1, "reference should produce one box");
3781
3782 let boxes_ara2 = boxes_canonical.view().permuted_axes([0, 2, 1]).to_owned(); let scores_ara2 = scores_canonical.view().permuted_axes([0, 2, 1]).to_owned(); let ara2_decoder = DecoderBuilder::default()
3790 .with_config_yolo_split_det(
3791 Boxes {
3792 decoder: configs::DecoderType::Ultralytics,
3793 quantization: None,
3794 shape: vec![1, N, 4],
3795 dshape: vec![
3796 (DimName::Batch, 1),
3797 (DimName::NumBoxes, N),
3798 (DimName::BoxCoords, 4),
3799 ],
3800 normalized: Some(true),
3801 },
3802 Scores {
3803 decoder: configs::DecoderType::Ultralytics,
3804 quantization: None,
3805 shape: vec![1, N, 80],
3806 dshape: vec![
3807 (DimName::Batch, 1),
3808 (DimName::NumBoxes, N),
3809 (DimName::NumClasses, 80),
3810 ],
3811 },
3812 )
3813 .with_score_threshold(0.5)
3814 .with_iou_threshold(0.5)
3815 .with_nms(Some(configs::Nms::ClassAgnostic))
3816 .build()
3817 .expect("Ara-2 anchor-first decoder must build");
3818
3819 let mut ara2_boxes = Vec::with_capacity(4);
3820 let mut ara2_masks = Vec::with_capacity(0);
3821 ara2_decoder
3822 .decode_float(
3823 &[boxes_ara2.view().into_dyn(), scores_ara2.view().into_dyn()],
3824 &mut ara2_boxes,
3825 &mut ara2_masks,
3826 )
3827 .unwrap();
3828
3829 assert_eq!(
3830 ara2_boxes.len(),
3831 ref_boxes.len(),
3832 "Ara-2 anchor-first declaration must produce the same number \
3833 of boxes as the canonical features-first reference"
3834 );
3835 for (a, r) in ara2_boxes.iter().zip(&ref_boxes) {
3836 assert!(
3837 a.equal_within_delta(r, 1e-4),
3838 "Ara-2 box differs from reference: {a:?} vs {r:?}"
3839 );
3840 }
3841 }
3842
3843 #[test]
3847 fn test_physical_order_rejects_shape_dshape_mismatch() {
3848 let result = DecoderBuilder::default()
3849 .with_config_yolo_segdet(
3850 configs::Detection {
3851 decoder: configs::DecoderType::Ultralytics,
3852 quantization: None,
3853 shape: vec![1, 116, 8400],
3854 dshape: vec![
3855 (DimName::Batch, 1),
3856 (DimName::NumFeatures, 116),
3857 (DimName::NumBoxes, 8400),
3858 ],
3859 normalized: Some(true),
3860 anchors: None,
3861 },
3862 configs::Protos {
3863 decoder: configs::DecoderType::Ultralytics,
3864 quantization: None,
3865 shape: vec![1, 32, 160, 160],
3867 dshape: vec![
3870 (DimName::Batch, 1),
3871 (DimName::Height, 160),
3872 (DimName::Width, 160),
3873 (DimName::NumProtos, 32),
3874 ],
3875 },
3876 None,
3877 )
3878 .build();
3879
3880 match result {
3881 Err(DecoderError::InvalidConfig(msg)) => {
3882 assert!(
3883 msg.contains("does not match shape"),
3884 "expected shape/dshape size mismatch error, got: {msg}"
3885 );
3886 }
3887 other => panic!("expected InvalidConfig, got {other:?}"),
3888 }
3889 }
3890
3891 #[test]
3894 fn test_physical_order_rejects_duplicate_dshape_axis() {
3895 let result = DecoderBuilder::default()
3896 .with_config_yolo_split_det(
3897 configs::Boxes {
3898 decoder: configs::DecoderType::Ultralytics,
3899 quantization: None,
3900 shape: vec![1, 4, 8400],
3901 dshape: vec![
3902 (DimName::Batch, 1),
3903 (DimName::BoxCoords, 4),
3904 (DimName::BoxCoords, 4), ],
3906 normalized: Some(true),
3907 },
3908 configs::Scores {
3909 decoder: configs::DecoderType::Ultralytics,
3910 quantization: None,
3911 shape: vec![1, 80, 8400],
3912 dshape: vec![
3913 (DimName::Batch, 1),
3914 (DimName::NumClasses, 80),
3915 (DimName::NumBoxes, 8400),
3916 ],
3917 },
3918 )
3919 .build();
3920
3921 match result {
3926 Err(DecoderError::InvalidConfig(msg)) => {
3927 assert!(
3928 msg.contains("appears at both index") || msg.contains("does not match shape"),
3929 "expected positional or duplicate-axis error, got: {msg}"
3930 );
3931 }
3932 other => panic!("expected InvalidConfig, got {other:?}"),
3933 }
3934
3935 let result = DecoderBuilder::default()
3940 .with_config_yolo_split_det(
3941 configs::Boxes {
3942 decoder: configs::DecoderType::Ultralytics,
3943 quantization: None,
3944 shape: vec![1, 1, 4, 8400],
3945 dshape: vec![
3946 (DimName::Batch, 1),
3947 (DimName::Batch, 1), (DimName::BoxCoords, 4),
3949 (DimName::NumBoxes, 8400),
3950 ],
3951 normalized: Some(true),
3952 },
3953 configs::Scores {
3954 decoder: configs::DecoderType::Ultralytics,
3955 quantization: None,
3956 shape: vec![1, 80, 8400],
3957 dshape: vec![
3958 (DimName::Batch, 1),
3959 (DimName::NumClasses, 80),
3960 (DimName::NumBoxes, 8400),
3961 ],
3962 },
3963 )
3964 .build();
3965 match result {
3966 Err(DecoderError::InvalidConfig(msg)) => {
3967 assert!(
3968 msg.contains("appears at both index"),
3969 "expected duplicate-axis error, got: {msg}"
3970 );
3971 }
3972 other => panic!("expected InvalidConfig, got {other:?}"),
3973 }
3974 }
3975
3976 #[test]
3982 fn test_physical_order_dshape_omitted_decodes_numerically() {
3983 let score_threshold = 0.45;
3984 let iou_threshold = 0.45;
3985
3986 let protos_hwc = load_yolov8_protos().slice_move(s![0, .., .., ..]);
3987 let quant_protos = Quantization::new(0.02491161972284317, -117);
3988 let protos_f32_hwc = dequantize_ndarray::<_, _, f32>(protos_hwc.view(), quant_protos);
3989
3990 let boxes_2d = load_yolov8_boxes().slice_move(s![0, .., ..]);
3991 let quant_boxes = Quantization::new(0.021287761628627777, 31);
3992 let seg = dequantize_ndarray::<_, _, f32>(boxes_2d.view(), quant_boxes);
3993
3994 let protos_nhwc = protos_f32_hwc.clone().insert_axis(Axis(0));
3995 let seg_3d = seg.insert_axis(Axis(0));
3996
3997 let build_decoder = |det_dshape: Vec<(DimName, usize)>,
3998 proto_dshape: Vec<(DimName, usize)>| {
3999 DecoderBuilder::default()
4000 .with_config_yolo_segdet(
4001 configs::Detection {
4002 decoder: configs::DecoderType::Ultralytics,
4003 quantization: None,
4004 shape: vec![1, 116, 8400],
4005 dshape: det_dshape,
4006 normalized: Some(true),
4007 anchors: None,
4008 },
4009 configs::Protos {
4010 decoder: configs::DecoderType::Ultralytics,
4011 quantization: None,
4012 shape: vec![1, 160, 160, 32],
4013 dshape: proto_dshape,
4014 },
4015 None,
4016 )
4017 .with_score_threshold(score_threshold)
4018 .with_iou_threshold(iou_threshold)
4019 .build()
4020 .unwrap()
4021 };
4022
4023 let dshaped = build_decoder(
4025 vec![
4026 (DimName::Batch, 1),
4027 (DimName::NumFeatures, 116),
4028 (DimName::NumBoxes, 8400),
4029 ],
4030 vec![
4031 (DimName::Batch, 1),
4032 (DimName::Height, 160),
4033 (DimName::Width, 160),
4034 (DimName::NumProtos, 32),
4035 ],
4036 );
4037 let mut dshaped_boxes = Vec::new();
4038 let mut dshaped_masks = Vec::new();
4039 dshaped
4040 .decode_float(
4041 &[seg_3d.view().into_dyn(), protos_nhwc.view().into_dyn()],
4042 &mut dshaped_boxes,
4043 &mut dshaped_masks,
4044 )
4045 .unwrap();
4046
4047 let bare = build_decoder(vec![], vec![]);
4050 let mut bare_boxes = Vec::new();
4051 let mut bare_masks = Vec::new();
4052 bare.decode_float(
4053 &[seg_3d.view().into_dyn(), protos_nhwc.view().into_dyn()],
4054 &mut bare_boxes,
4055 &mut bare_masks,
4056 )
4057 .unwrap();
4058
4059 assert_eq!(bare_boxes.len(), dshaped_boxes.len());
4060 for (b, d) in bare_boxes.iter().zip(&dshaped_boxes) {
4061 assert!(
4062 b.equal_within_delta(d, 1e-4),
4063 "dshape-omitted box {b:?} differs from dshape-populated {d:?}"
4064 );
4065 }
4066 for (bm, dm) in bare_masks.iter().zip(&dshaped_masks) {
4067 let bm_arr = segmentation_to_mask(seg_view(bm).view()).unwrap();
4068 let dm_arr = segmentation_to_mask(seg_view(dm).view()).unwrap();
4069 assert_eq!(
4070 bm_arr, dm_arr,
4071 "dshape-omitted mask must match dshape-populated pixel-for-pixel"
4072 );
4073 }
4074 }
4075
4076 #[test]
4086 fn test_physical_order_ara2_4d_anchor_first_with_padding() {
4087 const N: usize = 8400;
4092 let mut boxes = Array3::<f32>::zeros((1, N, 4));
4093 let target = 42usize;
4094 boxes[[0, target, 0]] = 0.4;
4095 boxes[[0, target, 1]] = 0.5;
4096 boxes[[0, target, 2]] = 0.2;
4097 boxes[[0, target, 3]] = 0.2;
4098 let mut scores = Array3::<f32>::zeros((1, N, 80));
4099 scores[[0, target, 0]] = 0.9;
4100
4101 let json = r#"{
4107 "schema_version": 2,
4108 "decoder_version": "yolov8",
4109 "nms": "class_agnostic",
4110 "outputs": [
4111 {"name": "boxes", "type": "boxes",
4112 "shape": [1, 8400, 1, 4],
4113 "dshape": [{"batch":1},{"num_boxes":8400},{"padding":1},{"box_coords":4}],
4114 "encoding": "direct",
4115 "decoder": "ultralytics",
4116 "normalized": true},
4117 {"name": "scores", "type": "scores",
4118 "shape": [1, 8400, 1, 80],
4119 "dshape": [{"batch":1},{"num_boxes":8400},{"padding":1},{"num_classes":80}],
4120 "decoder": "ultralytics",
4121 "score_format": "per_class"}
4122 ]
4123 }"#;
4124 let decoder = DecoderBuilder::default()
4125 .with_config_json_str(json.to_string())
4126 .with_score_threshold(0.5)
4127 .with_iou_threshold(0.5)
4128 .build()
4129 .expect("4D anchor-first schema should build via squeeze_padding_dims");
4130
4131 let mut out_boxes = Vec::with_capacity(4);
4132 let mut out_masks = Vec::with_capacity(0);
4133 decoder
4134 .decode_float(
4135 &[boxes.view().into_dyn(), scores.view().into_dyn()],
4136 &mut out_boxes,
4137 &mut out_masks,
4138 )
4139 .unwrap();
4140
4141 assert_eq!(
4142 out_boxes.len(),
4143 1,
4144 "4D anchor-first with padding should decode exactly one box from the seeded anchor"
4145 );
4146 let b = &out_boxes[0];
4147 assert!((b.bbox.xmin - 0.3).abs() < 1e-3, "xmin wrong: {b:?}");
4149 assert!((b.bbox.ymin - 0.4).abs() < 1e-3, "ymin wrong: {b:?}");
4150 assert!((b.bbox.xmax - 0.5).abs() < 1e-3, "xmax wrong: {b:?}");
4151 assert!((b.bbox.ymax - 0.6).abs() < 1e-3, "ymax wrong: {b:?}");
4152 assert_eq!(b.label, 0);
4153 assert!(b.score > 0.85, "score {}: {b:?}", b.score);
4154 }
4155}
4156
4157#[cfg(feature = "tracker")]
4158#[cfg(test)]
4159#[cfg_attr(coverage_nightly, coverage(off))]
4160mod decoder_tracked_tests {
4161
4162 use crate::DetectBoxTestExt;
4163 use edgefirst_tracker::{ByteTrackBuilder, Tracker};
4164 use ndarray::{array, s, Array, Array2, Array3, Array4, ArrayView, Axis, Dimension};
4165 use num_traits::{AsPrimitive, Float, PrimInt};
4166 use rand::{RngExt, SeedableRng};
4167 use rand_distr::StandardNormal;
4168
4169 use crate::{
4170 configs::{self, DimName},
4171 dequantize_ndarray, BoundingBox, DecoderBuilder, DetectBox, Quantization,
4172 };
4173
4174 pub fn quantize_ndarray<T: PrimInt + 'static, D: Dimension, F: Float + AsPrimitive<T>>(
4175 input: ArrayView<F, D>,
4176 quant: Quantization,
4177 ) -> Array<T, D>
4178 where
4179 i32: num_traits::AsPrimitive<F>,
4180 f32: num_traits::AsPrimitive<F>,
4181 {
4182 let zero_point = quant.zero_point.as_();
4183 let div_scale = F::one() / quant.scale.as_();
4184 if zero_point != F::zero() {
4185 input.mapv(|d| (d * div_scale + zero_point).round().as_())
4186 } else {
4187 input.mapv(|d| (d * div_scale).round().as_())
4188 }
4189 }
4190
4191 #[test]
4192 fn test_decoder_tracked_random_jitter() {
4193 use crate::configs::{DecoderType, Nms};
4194 use crate::DecoderBuilder;
4195
4196 let score_threshold = 0.25;
4197 let iou_threshold = 0.1;
4198 let out = edgefirst_bench::testdata::read("yolov8s_80_classes.bin");
4199 let out = unsafe { std::slice::from_raw_parts(out.as_ptr() as *const i8, out.len()) };
4200 let out = Array3::from_shape_vec((1, 84, 8400), out.to_vec()).unwrap();
4201 let quant = (0.0040811873, -123).into();
4202
4203 let decoder = DecoderBuilder::default()
4204 .with_config_yolo_det(
4205 crate::configs::Detection {
4206 decoder: DecoderType::Ultralytics,
4207 shape: vec![1, 84, 8400],
4208 anchors: None,
4209 quantization: Some(quant),
4210 dshape: vec![
4211 (crate::configs::DimName::Batch, 1),
4212 (crate::configs::DimName::NumFeatures, 84),
4213 (crate::configs::DimName::NumBoxes, 8400),
4214 ],
4215 normalized: Some(true),
4216 },
4217 None,
4218 )
4219 .with_score_threshold(score_threshold)
4220 .with_iou_threshold(iou_threshold)
4221 .with_nms(Some(Nms::ClassAgnostic))
4222 .build()
4223 .unwrap();
4224 let mut rng = rand::rngs::StdRng::seed_from_u64(0xAB_BEEF); let expected_boxes = [
4227 crate::DetectBox {
4228 bbox: crate::BoundingBox {
4229 xmin: 0.5285137,
4230 ymin: 0.05305544,
4231 xmax: 0.87541467,
4232 ymax: 0.9998909,
4233 },
4234 score: 0.5591227,
4235 label: 0,
4236 },
4237 crate::DetectBox {
4238 bbox: crate::BoundingBox {
4239 xmin: 0.130598,
4240 ymin: 0.43260583,
4241 xmax: 0.35098213,
4242 ymax: 0.9958097,
4243 },
4244 score: 0.33057618,
4245 label: 75,
4246 },
4247 ];
4248
4249 let mut tracker = ByteTrackBuilder::new()
4250 .track_update(0.1)
4251 .track_high_conf(0.3)
4252 .build();
4253
4254 let mut output_boxes = Vec::with_capacity(50);
4255 let mut output_masks = Vec::with_capacity(50);
4256 let mut output_tracks = Vec::with_capacity(50);
4257
4258 decoder
4259 .decode_tracked_quantized(
4260 &mut tracker,
4261 0,
4262 &[out.view().into()],
4263 &mut output_boxes,
4264 &mut output_masks,
4265 &mut output_tracks,
4266 )
4267 .unwrap();
4268
4269 assert_eq!(output_boxes.len(), 2);
4270 assert!(output_boxes[0].equal_within_delta(&expected_boxes[0], 1e-6));
4271 assert!(output_boxes[1].equal_within_delta(&expected_boxes[1], 1e-6));
4272
4273 let mut last_boxes = output_boxes.clone();
4274
4275 for i in 1..=100 {
4276 let mut out = out.clone();
4277 let mut x_values = out.slice_mut(s![0, 0, ..]);
4279 for x in x_values.iter_mut() {
4280 let r: f32 = rng.sample(StandardNormal);
4281 let r = r.clamp(-2.0, 2.0) / 2.0;
4282 *x = x.saturating_add((r * 1e-2 / quant.0) as i8);
4283 }
4284
4285 let mut y_values = out.slice_mut(s![0, 1, ..]);
4286 for y in y_values.iter_mut() {
4287 let r: f32 = rng.sample(StandardNormal);
4288 let r = r.clamp(-2.0, 2.0) / 2.0;
4289 *y = y.saturating_add((r * 1e-2 / quant.0) as i8);
4290 }
4291
4292 decoder
4293 .decode_tracked_quantized(
4294 &mut tracker,
4295 100_000_000 * i / 3, &[out.view().into()],
4297 &mut output_boxes,
4298 &mut output_masks,
4299 &mut output_tracks,
4300 )
4301 .unwrap();
4302
4303 assert_eq!(output_boxes.len(), 2);
4304 assert!(output_boxes[0].equal_within_delta(&expected_boxes[0], 5e-3));
4305 assert!(output_boxes[1].equal_within_delta(&expected_boxes[1], 5e-3));
4306
4307 assert!(output_boxes[0].equal_within_delta(&last_boxes[0], 1e-3));
4308 assert!(output_boxes[1].equal_within_delta(&last_boxes[1], 1e-3));
4309 last_boxes = output_boxes.clone();
4310 }
4311 }
4312
4313 fn real_data_expected_boxes() -> [DetectBox; 2] {
4316 [
4317 DetectBox {
4318 bbox: BoundingBox {
4319 xmin: 0.08515105,
4320 ymin: 0.7131401,
4321 xmax: 0.29802868,
4322 ymax: 0.8195788,
4323 },
4324 score: 0.91537374,
4325 label: 23,
4326 },
4327 DetectBox {
4328 bbox: BoundingBox {
4329 xmin: 0.59605736,
4330 ymin: 0.25545314,
4331 xmax: 0.93666154,
4332 ymax: 0.72378385,
4333 },
4334 score: 0.91537374,
4335 label: 23,
4336 },
4337 ]
4338 }
4339
4340 fn e2e_expected_boxes_quant() -> [DetectBox; 1] {
4341 [DetectBox {
4342 bbox: BoundingBox {
4343 xmin: 0.12549022,
4344 ymin: 0.12549022,
4345 xmax: 0.23529413,
4346 ymax: 0.23529413,
4347 },
4348 score: 0.98823535,
4349 label: 2,
4350 }]
4351 }
4352
4353 fn e2e_expected_boxes_float() -> [DetectBox; 1] {
4354 [DetectBox {
4355 bbox: BoundingBox {
4356 xmin: 0.1234,
4357 ymin: 0.1234,
4358 xmax: 0.2345,
4359 ymax: 0.2345,
4360 },
4361 score: 0.9876,
4362 label: 2,
4363 }]
4364 }
4365
4366 fn build_yolo_split_segdet_decoder(
4367 score_threshold: f32,
4368 iou_threshold: f32,
4369 quant_boxes: (f32, i32),
4370 quant_protos: (f32, i32),
4371 ) -> crate::Decoder {
4372 DecoderBuilder::default()
4373 .with_config_yolo_split_segdet(
4374 configs::Boxes {
4375 decoder: configs::DecoderType::Ultralytics,
4376 quantization: Some(quant_boxes.into()),
4377 shape: vec![1, 4, 8400],
4378 dshape: vec![
4379 (DimName::Batch, 1),
4380 (DimName::BoxCoords, 4),
4381 (DimName::NumBoxes, 8400),
4382 ],
4383 normalized: Some(true),
4384 },
4385 configs::Scores {
4386 decoder: configs::DecoderType::Ultralytics,
4387 quantization: Some(quant_boxes.into()),
4388 shape: vec![1, 80, 8400],
4389 dshape: vec![
4390 (DimName::Batch, 1),
4391 (DimName::NumClasses, 80),
4392 (DimName::NumBoxes, 8400),
4393 ],
4394 },
4395 configs::MaskCoefficients {
4396 decoder: configs::DecoderType::Ultralytics,
4397 quantization: Some(quant_boxes.into()),
4398 shape: vec![1, 32, 8400],
4399 dshape: vec![
4400 (DimName::Batch, 1),
4401 (DimName::NumProtos, 32),
4402 (DimName::NumBoxes, 8400),
4403 ],
4404 },
4405 configs::Protos {
4406 decoder: configs::DecoderType::Ultralytics,
4407 quantization: Some(quant_protos.into()),
4408 shape: vec![1, 160, 160, 32],
4409 dshape: vec![
4410 (DimName::Batch, 1),
4411 (DimName::Height, 160),
4412 (DimName::Width, 160),
4413 (DimName::NumProtos, 32),
4414 ],
4415 },
4416 )
4417 .with_score_threshold(score_threshold)
4418 .with_iou_threshold(iou_threshold)
4419 .build()
4420 .unwrap()
4421 }
4422
4423 fn build_yolov8_seg_decoder(score_threshold: f32, iou_threshold: f32) -> crate::Decoder {
4424 let config_yaml = edgefirst_bench::testdata::read_to_string("yolov8_seg.yaml");
4425 DecoderBuilder::default()
4426 .with_config_yaml_str(config_yaml.to_string())
4427 .with_score_threshold(score_threshold)
4428 .with_iou_threshold(iou_threshold)
4429 .build()
4430 .unwrap()
4431 }
4432
4433 macro_rules! real_data_tracked_test {
4440 ($name:ident, quantized, $layout:ident, $output:ident) => {
4441 #[test]
4442 fn $name() {
4443 let is_split = matches!(stringify!($layout), "split");
4444 let is_proto = matches!(stringify!($output), "proto");
4445
4446 let score_threshold = 0.45;
4447 let iou_threshold = 0.45;
4448 let quant_boxes = (0.021287762_f32, 31_i32);
4449 let quant_protos = (0.02491162_f32, -117_i32);
4450
4451 let raw_boxes = edgefirst_bench::testdata::read("yolov8_boxes_116x8400.bin");
4452 let raw_boxes = unsafe {
4453 std::slice::from_raw_parts(raw_boxes.as_ptr() as *const i8, raw_boxes.len())
4454 };
4455 let boxes_i8 =
4456 ndarray::Array3::from_shape_vec((1, 116, 8400), raw_boxes.to_vec()).unwrap();
4457
4458 let raw_protos = edgefirst_bench::testdata::read("yolov8_protos_160x160x32.bin");
4459 let raw_protos = unsafe {
4460 std::slice::from_raw_parts(raw_protos.as_ptr() as *const i8, raw_protos.len())
4461 };
4462 let protos_i8 =
4463 ndarray::Array4::from_shape_vec((1, 160, 160, 32), raw_protos.to_vec())
4464 .unwrap();
4465
4466 let mask_split = boxes_i8.slice(s![.., 84.., ..]).to_owned();
4468 let mut scores_split = boxes_i8.slice(s![.., 4..84, ..]).to_owned();
4469 let boxes_split = boxes_i8.slice(s![.., ..4, ..]).to_owned();
4470 let mut boxes_combined = boxes_i8;
4471
4472 let decoder = if is_split {
4473 build_yolo_split_segdet_decoder(
4474 score_threshold,
4475 iou_threshold,
4476 quant_boxes,
4477 quant_protos,
4478 )
4479 } else {
4480 build_yolov8_seg_decoder(score_threshold, iou_threshold)
4481 };
4482
4483 let expected = real_data_expected_boxes();
4484 let mut tracker = ByteTrackBuilder::new()
4485 .track_update(0.1)
4486 .track_high_conf(0.7)
4487 .build();
4488 let mut output_boxes = Vec::with_capacity(50);
4489 let mut output_tracks = Vec::with_capacity(50);
4490
4491 if is_proto {
4493 {
4494 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> = if is_split {
4495 vec![
4496 boxes_split.view().into(),
4497 scores_split.view().into(),
4498 mask_split.view().into(),
4499 protos_i8.view().into(),
4500 ]
4501 } else {
4502 vec![boxes_combined.view().into(), protos_i8.view().into()]
4503 };
4504 decoder
4505 .decode_tracked_quantized_proto(
4506 &mut tracker,
4507 0,
4508 &inputs,
4509 &mut output_boxes,
4510 &mut output_tracks,
4511 )
4512 .unwrap();
4513 }
4514 assert_eq!(output_boxes.len(), 2);
4515 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
4516 assert!(output_boxes[1].equal_within_delta(&expected[1], 1.0 / 160.0));
4517
4518 if is_split {
4520 for score in scores_split.iter_mut() {
4521 *score = i8::MIN;
4522 }
4523 } else {
4524 for score in boxes_combined.slice_mut(s![0, 4..84, ..]).iter_mut() {
4525 *score = i8::MIN;
4526 }
4527 }
4528
4529 let proto_result = {
4530 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> = if is_split {
4531 vec![
4532 boxes_split.view().into(),
4533 scores_split.view().into(),
4534 mask_split.view().into(),
4535 protos_i8.view().into(),
4536 ]
4537 } else {
4538 vec![boxes_combined.view().into(), protos_i8.view().into()]
4539 };
4540 decoder
4541 .decode_tracked_quantized_proto(
4542 &mut tracker,
4543 100_000_000 / 3,
4544 &inputs,
4545 &mut output_boxes,
4546 &mut output_tracks,
4547 )
4548 .unwrap()
4549 };
4550 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
4551 assert!(output_boxes[1].equal_within_delta(&expected[1], 1e-6));
4552 assert!(proto_result.is_some_and(|x| x.mask_coefficients.shape()[0] == 0));
4553 } else {
4554 let mut output_masks = Vec::with_capacity(50);
4555 {
4556 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> = if is_split {
4557 vec![
4558 boxes_split.view().into(),
4559 scores_split.view().into(),
4560 mask_split.view().into(),
4561 protos_i8.view().into(),
4562 ]
4563 } else {
4564 vec![boxes_combined.view().into(), protos_i8.view().into()]
4565 };
4566 decoder
4567 .decode_tracked_quantized(
4568 &mut tracker,
4569 0,
4570 &inputs,
4571 &mut output_boxes,
4572 &mut output_masks,
4573 &mut output_tracks,
4574 )
4575 .unwrap();
4576 }
4577 assert_eq!(output_boxes.len(), 2);
4578 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
4579 assert!(output_boxes[1].equal_within_delta(&expected[1], 1.0 / 160.0));
4580
4581 if is_split {
4582 for score in scores_split.iter_mut() {
4583 *score = i8::MIN;
4584 }
4585 } else {
4586 for score in boxes_combined.slice_mut(s![0, 4..84, ..]).iter_mut() {
4587 *score = i8::MIN;
4588 }
4589 }
4590
4591 {
4592 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> = if is_split {
4593 vec![
4594 boxes_split.view().into(),
4595 scores_split.view().into(),
4596 mask_split.view().into(),
4597 protos_i8.view().into(),
4598 ]
4599 } else {
4600 vec![boxes_combined.view().into(), protos_i8.view().into()]
4601 };
4602 decoder
4603 .decode_tracked_quantized(
4604 &mut tracker,
4605 100_000_000 / 3,
4606 &inputs,
4607 &mut output_boxes,
4608 &mut output_masks,
4609 &mut output_tracks,
4610 )
4611 .unwrap();
4612 }
4613 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
4614 assert!(output_boxes[1].equal_within_delta(&expected[1], 1e-6));
4615 assert!(output_masks.is_empty());
4616 }
4617 }
4618 };
4619 ($name:ident, float, $layout:ident, $output:ident) => {
4620 #[test]
4621 fn $name() {
4622 let is_split = matches!(stringify!($layout), "split");
4623 let is_proto = matches!(stringify!($output), "proto");
4624
4625 let score_threshold = 0.45;
4626 let iou_threshold = 0.45;
4627 let quant_boxes = (0.021287762_f32, 31_i32);
4628 let quant_protos = (0.02491162_f32, -117_i32);
4629
4630 let raw_boxes = edgefirst_bench::testdata::read("yolov8_boxes_116x8400.bin");
4631 let raw_boxes = unsafe {
4632 std::slice::from_raw_parts(raw_boxes.as_ptr() as *const i8, raw_boxes.len())
4633 };
4634 let boxes_i8 =
4635 ndarray::Array3::from_shape_vec((1, 116, 8400), raw_boxes.to_vec()).unwrap();
4636 let boxes_f32 = dequantize_ndarray(boxes_i8.view(), quant_boxes.into());
4637
4638 let raw_protos = edgefirst_bench::testdata::read("yolov8_protos_160x160x32.bin");
4639 let raw_protos = unsafe {
4640 std::slice::from_raw_parts(raw_protos.as_ptr() as *const i8, raw_protos.len())
4641 };
4642 let protos_i8 =
4643 ndarray::Array4::from_shape_vec((1, 160, 160, 32), raw_protos.to_vec())
4644 .unwrap();
4645 let protos_f32 = dequantize_ndarray(protos_i8.view(), quant_protos.into());
4646
4647 let mask_split = boxes_f32.slice(s![.., 84.., ..]).to_owned();
4649 let mut scores_split = boxes_f32.slice(s![.., 4..84, ..]).to_owned();
4650 let boxes_split = boxes_f32.slice(s![.., ..4, ..]).to_owned();
4651 let mut boxes_combined = boxes_f32;
4652
4653 let decoder = if is_split {
4654 build_yolo_split_segdet_decoder(
4655 score_threshold,
4656 iou_threshold,
4657 quant_boxes,
4658 quant_protos,
4659 )
4660 } else {
4661 build_yolov8_seg_decoder(score_threshold, iou_threshold)
4662 };
4663
4664 let expected = real_data_expected_boxes();
4665 let mut tracker = ByteTrackBuilder::new()
4666 .track_update(0.1)
4667 .track_high_conf(0.7)
4668 .build();
4669 let mut output_boxes = Vec::with_capacity(50);
4670 let mut output_tracks = Vec::with_capacity(50);
4671
4672 if is_proto {
4673 {
4674 let inputs = if is_split {
4675 vec![
4676 boxes_split.view().into_dyn(),
4677 scores_split.view().into_dyn(),
4678 mask_split.view().into_dyn(),
4679 protos_f32.view().into_dyn(),
4680 ]
4681 } else {
4682 vec![
4683 boxes_combined.view().into_dyn(),
4684 protos_f32.view().into_dyn(),
4685 ]
4686 };
4687 decoder
4688 .decode_tracked_float_proto(
4689 &mut tracker,
4690 0,
4691 &inputs,
4692 &mut output_boxes,
4693 &mut output_tracks,
4694 )
4695 .unwrap();
4696 }
4697 assert_eq!(output_boxes.len(), 2);
4698 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
4699 assert!(output_boxes[1].equal_within_delta(&expected[1], 1.0 / 160.0));
4700
4701 if is_split {
4702 for score in scores_split.iter_mut() {
4703 *score = 0.0;
4704 }
4705 } else {
4706 for score in boxes_combined.slice_mut(s![0, 4..84, ..]).iter_mut() {
4707 *score = 0.0;
4708 }
4709 }
4710
4711 let proto_result = {
4712 let inputs = if is_split {
4713 vec![
4714 boxes_split.view().into_dyn(),
4715 scores_split.view().into_dyn(),
4716 mask_split.view().into_dyn(),
4717 protos_f32.view().into_dyn(),
4718 ]
4719 } else {
4720 vec![
4721 boxes_combined.view().into_dyn(),
4722 protos_f32.view().into_dyn(),
4723 ]
4724 };
4725 decoder
4726 .decode_tracked_float_proto(
4727 &mut tracker,
4728 100_000_000 / 3,
4729 &inputs,
4730 &mut output_boxes,
4731 &mut output_tracks,
4732 )
4733 .unwrap()
4734 };
4735 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
4736 assert!(output_boxes[1].equal_within_delta(&expected[1], 1e-6));
4737 assert!(proto_result.is_some_and(|x| x.mask_coefficients.shape()[0] == 0));
4738 } else {
4739 let mut output_masks = Vec::with_capacity(50);
4740 {
4741 let inputs = if is_split {
4742 vec![
4743 boxes_split.view().into_dyn(),
4744 scores_split.view().into_dyn(),
4745 mask_split.view().into_dyn(),
4746 protos_f32.view().into_dyn(),
4747 ]
4748 } else {
4749 vec![
4750 boxes_combined.view().into_dyn(),
4751 protos_f32.view().into_dyn(),
4752 ]
4753 };
4754 decoder
4755 .decode_tracked_float(
4756 &mut tracker,
4757 0,
4758 &inputs,
4759 &mut output_boxes,
4760 &mut output_masks,
4761 &mut output_tracks,
4762 )
4763 .unwrap();
4764 }
4765 assert_eq!(output_boxes.len(), 2);
4766 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
4767 assert!(output_boxes[1].equal_within_delta(&expected[1], 1.0 / 160.0));
4768
4769 if is_split {
4770 for score in scores_split.iter_mut() {
4771 *score = 0.0;
4772 }
4773 } else {
4774 for score in boxes_combined.slice_mut(s![0, 4..84, ..]).iter_mut() {
4775 *score = 0.0;
4776 }
4777 }
4778
4779 {
4780 let inputs = if is_split {
4781 vec![
4782 boxes_split.view().into_dyn(),
4783 scores_split.view().into_dyn(),
4784 mask_split.view().into_dyn(),
4785 protos_f32.view().into_dyn(),
4786 ]
4787 } else {
4788 vec![
4789 boxes_combined.view().into_dyn(),
4790 protos_f32.view().into_dyn(),
4791 ]
4792 };
4793 decoder
4794 .decode_tracked_float(
4795 &mut tracker,
4796 100_000_000 / 3,
4797 &inputs,
4798 &mut output_boxes,
4799 &mut output_masks,
4800 &mut output_tracks,
4801 )
4802 .unwrap();
4803 }
4804 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
4805 assert!(output_boxes[1].equal_within_delta(&expected[1], 1e-6));
4806 assert!(output_masks.is_empty());
4807 }
4808 }
4809 };
4810 }
4811
4812 real_data_tracked_test!(test_decoder_tracked_segdet, quantized, combined, masks);
4813 real_data_tracked_test!(test_decoder_tracked_segdet_float, float, combined, masks);
4814 real_data_tracked_test!(
4815 test_decoder_tracked_segdet_proto,
4816 quantized,
4817 combined,
4818 proto
4819 );
4820 real_data_tracked_test!(
4821 test_decoder_tracked_segdet_proto_float,
4822 float,
4823 combined,
4824 proto
4825 );
4826 real_data_tracked_test!(test_decoder_tracked_segdet_split, quantized, split, masks);
4827 real_data_tracked_test!(test_decoder_tracked_segdet_split_float, float, split, masks);
4828 real_data_tracked_test!(
4829 test_decoder_tracked_segdet_split_proto,
4830 quantized,
4831 split,
4832 proto
4833 );
4834 real_data_tracked_test!(
4835 test_decoder_tracked_segdet_split_proto_float,
4836 float,
4837 split,
4838 proto
4839 );
4840
4841 const E2E_COMBINED_CONFIG: &str = "
4847decoder_version: yolo26
4848outputs:
4849 - type: detection
4850 decoder: ultralytics
4851 quantization: [0.00784313725490196, 0]
4852 shape: [1, 10, 38]
4853 dshape:
4854 - [batch, 1]
4855 - [num_boxes, 10]
4856 - [num_features, 38]
4857 normalized: true
4858 - type: protos
4859 decoder: ultralytics
4860 quantization: [0.0039215686274509803921568627451, 128]
4861 shape: [1, 160, 160, 32]
4862 dshape:
4863 - [batch, 1]
4864 - [height, 160]
4865 - [width, 160]
4866 - [num_protos, 32]
4867";
4868
4869 const E2E_SPLIT_CONFIG: &str = "
4870decoder_version: yolo26
4871outputs:
4872 - type: boxes
4873 decoder: ultralytics
4874 quantization: [0.00784313725490196, 0]
4875 shape: [1, 10, 4]
4876 dshape:
4877 - [batch, 1]
4878 - [num_boxes, 10]
4879 - [box_coords, 4]
4880 normalized: true
4881 - type: scores
4882 decoder: ultralytics
4883 quantization: [0.00784313725490196, 0]
4884 shape: [1, 10, 1]
4885 dshape:
4886 - [batch, 1]
4887 - [num_boxes, 10]
4888 - [num_classes, 1]
4889 - type: classes
4890 decoder: ultralytics
4891 quantization: [0.00784313725490196, 0]
4892 shape: [1, 10, 1]
4893 dshape:
4894 - [batch, 1]
4895 - [num_boxes, 10]
4896 - [num_classes, 1]
4897 - type: mask_coefficients
4898 decoder: ultralytics
4899 quantization: [0.00784313725490196, 0]
4900 shape: [1, 10, 32]
4901 dshape:
4902 - [batch, 1]
4903 - [num_boxes, 10]
4904 - [num_protos, 32]
4905 - type: protos
4906 decoder: ultralytics
4907 quantization: [0.0039215686274509803921568627451, 128]
4908 shape: [1, 160, 160, 32]
4909 dshape:
4910 - [batch, 1]
4911 - [height, 160]
4912 - [width, 160]
4913 - [num_protos, 32]
4914";
4915
4916 macro_rules! e2e_tracked_test {
4917 ($name:ident, quantized, $layout:ident, $output:ident) => {
4918 #[test]
4919 fn $name() {
4920 let is_split = matches!(stringify!($layout), "split");
4921 let is_proto = matches!(stringify!($output), "proto");
4922
4923 let score_threshold = 0.45;
4924 let iou_threshold = 0.45;
4925
4926 let mut boxes = Array2::zeros((10, 4));
4927 let mut scores = Array2::zeros((10, 1));
4928 let mut classes = Array2::zeros((10, 1));
4929 let mask = Array2::zeros((10, 32));
4930 let protos = Array3::<f64>::zeros((160, 160, 32));
4931 let protos = protos.insert_axis(Axis(0));
4932 let protos_quant = (1.0 / 255.0, 0.0);
4933 let protos: Array4<u8> = quantize_ndarray(protos.view(), protos_quant.into());
4934
4935 boxes
4936 .slice_mut(s![0, ..])
4937 .assign(&array![0.1234, 0.1234, 0.2345, 0.2345]);
4938 scores.slice_mut(s![0, ..]).assign(&array![0.9876]);
4939 classes.slice_mut(s![0, ..]).assign(&array![2.0]);
4940
4941 let detect_quant = (2.0 / 255.0, 0.0);
4942
4943 let decoder = if is_split {
4944 DecoderBuilder::default()
4945 .with_config_yaml_str(E2E_SPLIT_CONFIG.to_string())
4946 .with_score_threshold(score_threshold)
4947 .with_iou_threshold(iou_threshold)
4948 .build()
4949 .unwrap()
4950 } else {
4951 DecoderBuilder::default()
4952 .with_config_yaml_str(E2E_COMBINED_CONFIG.to_string())
4953 .with_score_threshold(score_threshold)
4954 .with_iou_threshold(iou_threshold)
4955 .build()
4956 .unwrap()
4957 };
4958
4959 let expected = e2e_expected_boxes_quant();
4960 let mut tracker = ByteTrackBuilder::new()
4961 .track_update(0.1)
4962 .track_high_conf(0.7)
4963 .build();
4964 let mut output_boxes = Vec::with_capacity(50);
4965 let mut output_tracks = Vec::with_capacity(50);
4966
4967 if is_split {
4968 let boxes = boxes.insert_axis(Axis(0));
4969 let scores = scores.insert_axis(Axis(0));
4970 let classes = classes.insert_axis(Axis(0));
4971 let mask = mask.insert_axis(Axis(0));
4972
4973 let boxes: Array3<u8> = quantize_ndarray(boxes.view(), detect_quant.into());
4974 let mut scores: Array3<u8> =
4975 quantize_ndarray(scores.view(), detect_quant.into());
4976 let classes: Array3<u8> = quantize_ndarray(classes.view(), detect_quant.into());
4977 let mask: Array3<u8> = quantize_ndarray(mask.view(), detect_quant.into());
4978
4979 if is_proto {
4980 {
4981 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> = vec![
4982 boxes.view().into(),
4983 scores.view().into(),
4984 classes.view().into(),
4985 mask.view().into(),
4986 protos.view().into(),
4987 ];
4988 decoder
4989 .decode_tracked_quantized_proto(
4990 &mut tracker,
4991 0,
4992 &inputs,
4993 &mut output_boxes,
4994 &mut output_tracks,
4995 )
4996 .unwrap();
4997 }
4998 assert_eq!(output_boxes.len(), 1);
4999 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
5000
5001 for score in scores.slice_mut(s![.., .., ..]).iter_mut() {
5002 *score = u8::MIN;
5003 }
5004 let proto_result = {
5005 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> = vec![
5006 boxes.view().into(),
5007 scores.view().into(),
5008 classes.view().into(),
5009 mask.view().into(),
5010 protos.view().into(),
5011 ];
5012 decoder
5013 .decode_tracked_quantized_proto(
5014 &mut tracker,
5015 100_000_000 / 3,
5016 &inputs,
5017 &mut output_boxes,
5018 &mut output_tracks,
5019 )
5020 .unwrap()
5021 };
5022 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
5023 assert!(proto_result.is_some_and(|x| x.mask_coefficients.shape()[0] == 0));
5024 } else {
5025 let mut output_masks = Vec::with_capacity(50);
5026 {
5027 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> = vec![
5028 boxes.view().into(),
5029 scores.view().into(),
5030 classes.view().into(),
5031 mask.view().into(),
5032 protos.view().into(),
5033 ];
5034 decoder
5035 .decode_tracked_quantized(
5036 &mut tracker,
5037 0,
5038 &inputs,
5039 &mut output_boxes,
5040 &mut output_masks,
5041 &mut output_tracks,
5042 )
5043 .unwrap();
5044 }
5045 assert_eq!(output_boxes.len(), 1);
5046 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
5047
5048 for score in scores.slice_mut(s![.., .., ..]).iter_mut() {
5049 *score = u8::MIN;
5050 }
5051 {
5052 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> = vec![
5053 boxes.view().into(),
5054 scores.view().into(),
5055 classes.view().into(),
5056 mask.view().into(),
5057 protos.view().into(),
5058 ];
5059 decoder
5060 .decode_tracked_quantized(
5061 &mut tracker,
5062 100_000_000 / 3,
5063 &inputs,
5064 &mut output_boxes,
5065 &mut output_masks,
5066 &mut output_tracks,
5067 )
5068 .unwrap();
5069 }
5070 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
5071 assert!(output_masks.is_empty());
5072 }
5073 } else {
5074 let detect = ndarray::concatenate![
5076 Axis(1),
5077 boxes.view(),
5078 scores.view(),
5079 classes.view(),
5080 mask.view()
5081 ];
5082 let detect = detect.insert_axis(Axis(0));
5083 assert_eq!(detect.shape(), &[1, 10, 38]);
5084 let mut detect: Array3<u8> =
5085 quantize_ndarray(detect.view(), detect_quant.into());
5086
5087 if is_proto {
5088 {
5089 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> =
5090 vec![detect.view().into(), protos.view().into()];
5091 decoder
5092 .decode_tracked_quantized_proto(
5093 &mut tracker,
5094 0,
5095 &inputs,
5096 &mut output_boxes,
5097 &mut output_tracks,
5098 )
5099 .unwrap();
5100 }
5101 assert_eq!(output_boxes.len(), 1);
5102 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
5103
5104 for score in detect.slice_mut(s![.., .., 4]).iter_mut() {
5105 *score = u8::MIN;
5106 }
5107 let proto_result = {
5108 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> =
5109 vec![detect.view().into(), protos.view().into()];
5110 decoder
5111 .decode_tracked_quantized_proto(
5112 &mut tracker,
5113 100_000_000 / 3,
5114 &inputs,
5115 &mut output_boxes,
5116 &mut output_tracks,
5117 )
5118 .unwrap()
5119 };
5120 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
5121 assert!(proto_result.is_some_and(|x| x.mask_coefficients.shape()[0] == 0));
5122 } else {
5123 let mut output_masks = Vec::with_capacity(50);
5124 {
5125 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> =
5126 vec![detect.view().into(), protos.view().into()];
5127 decoder
5128 .decode_tracked_quantized(
5129 &mut tracker,
5130 0,
5131 &inputs,
5132 &mut output_boxes,
5133 &mut output_masks,
5134 &mut output_tracks,
5135 )
5136 .unwrap();
5137 }
5138 assert_eq!(output_boxes.len(), 1);
5139 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
5140
5141 for score in detect.slice_mut(s![.., .., 4]).iter_mut() {
5142 *score = u8::MIN;
5143 }
5144 {
5145 let inputs: Vec<crate::decoder::ArrayViewDQuantized<'_>> =
5146 vec![detect.view().into(), protos.view().into()];
5147 decoder
5148 .decode_tracked_quantized(
5149 &mut tracker,
5150 100_000_000 / 3,
5151 &inputs,
5152 &mut output_boxes,
5153 &mut output_masks,
5154 &mut output_tracks,
5155 )
5156 .unwrap();
5157 }
5158 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
5159 assert!(output_masks.is_empty());
5160 }
5161 }
5162 }
5163 };
5164 ($name:ident, float, $layout:ident, $output:ident) => {
5165 #[test]
5166 fn $name() {
5167 let is_split = matches!(stringify!($layout), "split");
5168 let is_proto = matches!(stringify!($output), "proto");
5169
5170 let score_threshold = 0.45;
5171 let iou_threshold = 0.45;
5172
5173 let mut boxes = Array2::zeros((10, 4));
5174 let mut scores = Array2::zeros((10, 1));
5175 let mut classes = Array2::zeros((10, 1));
5176 let mask: Array2<f64> = Array2::zeros((10, 32));
5177 let protos = Array3::<f64>::zeros((160, 160, 32));
5178 let protos = protos.insert_axis(Axis(0));
5179
5180 boxes
5181 .slice_mut(s![0, ..])
5182 .assign(&array![0.1234, 0.1234, 0.2345, 0.2345]);
5183 scores.slice_mut(s![0, ..]).assign(&array![0.9876]);
5184 classes.slice_mut(s![0, ..]).assign(&array![2.0]);
5185
5186 let decoder = if is_split {
5187 DecoderBuilder::default()
5188 .with_config_yaml_str(E2E_SPLIT_CONFIG.to_string())
5189 .with_score_threshold(score_threshold)
5190 .with_iou_threshold(iou_threshold)
5191 .build()
5192 .unwrap()
5193 } else {
5194 DecoderBuilder::default()
5195 .with_config_yaml_str(E2E_COMBINED_CONFIG.to_string())
5196 .with_score_threshold(score_threshold)
5197 .with_iou_threshold(iou_threshold)
5198 .build()
5199 .unwrap()
5200 };
5201
5202 let expected = e2e_expected_boxes_float();
5203 let mut tracker = ByteTrackBuilder::new()
5204 .track_update(0.1)
5205 .track_high_conf(0.7)
5206 .build();
5207 let mut output_boxes = Vec::with_capacity(50);
5208 let mut output_tracks = Vec::with_capacity(50);
5209
5210 if is_split {
5211 let boxes = boxes.insert_axis(Axis(0));
5212 let mut scores = scores.insert_axis(Axis(0));
5213 let classes = classes.insert_axis(Axis(0));
5214 let mask = mask.insert_axis(Axis(0));
5215
5216 if is_proto {
5217 {
5218 let inputs = vec![
5219 boxes.view().into_dyn(),
5220 scores.view().into_dyn(),
5221 classes.view().into_dyn(),
5222 mask.view().into_dyn(),
5223 protos.view().into_dyn(),
5224 ];
5225 decoder
5226 .decode_tracked_float_proto(
5227 &mut tracker,
5228 0,
5229 &inputs,
5230 &mut output_boxes,
5231 &mut output_tracks,
5232 )
5233 .unwrap();
5234 }
5235 assert_eq!(output_boxes.len(), 1);
5236 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
5237
5238 for score in scores.slice_mut(s![.., .., ..]).iter_mut() {
5239 *score = 0.0;
5240 }
5241 let proto_result = {
5242 let inputs = vec![
5243 boxes.view().into_dyn(),
5244 scores.view().into_dyn(),
5245 classes.view().into_dyn(),
5246 mask.view().into_dyn(),
5247 protos.view().into_dyn(),
5248 ];
5249 decoder
5250 .decode_tracked_float_proto(
5251 &mut tracker,
5252 100_000_000 / 3,
5253 &inputs,
5254 &mut output_boxes,
5255 &mut output_tracks,
5256 )
5257 .unwrap()
5258 };
5259 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
5260 assert!(proto_result.is_some_and(|x| x.mask_coefficients.shape()[0] == 0));
5261 } else {
5262 let mut output_masks = Vec::with_capacity(50);
5263 {
5264 let inputs = vec![
5265 boxes.view().into_dyn(),
5266 scores.view().into_dyn(),
5267 classes.view().into_dyn(),
5268 mask.view().into_dyn(),
5269 protos.view().into_dyn(),
5270 ];
5271 decoder
5272 .decode_tracked_float(
5273 &mut tracker,
5274 0,
5275 &inputs,
5276 &mut output_boxes,
5277 &mut output_masks,
5278 &mut output_tracks,
5279 )
5280 .unwrap();
5281 }
5282 assert_eq!(output_boxes.len(), 1);
5283 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
5284
5285 for score in scores.slice_mut(s![.., .., ..]).iter_mut() {
5286 *score = 0.0;
5287 }
5288 {
5289 let inputs = vec![
5290 boxes.view().into_dyn(),
5291 scores.view().into_dyn(),
5292 classes.view().into_dyn(),
5293 mask.view().into_dyn(),
5294 protos.view().into_dyn(),
5295 ];
5296 decoder
5297 .decode_tracked_float(
5298 &mut tracker,
5299 100_000_000 / 3,
5300 &inputs,
5301 &mut output_boxes,
5302 &mut output_masks,
5303 &mut output_tracks,
5304 )
5305 .unwrap();
5306 }
5307 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
5308 assert!(output_masks.is_empty());
5309 }
5310 } else {
5311 let detect = ndarray::concatenate![
5313 Axis(1),
5314 boxes.view(),
5315 scores.view(),
5316 classes.view(),
5317 mask.view()
5318 ];
5319 let mut detect = detect.insert_axis(Axis(0));
5320 assert_eq!(detect.shape(), &[1, 10, 38]);
5321
5322 if is_proto {
5323 {
5324 let inputs = vec![detect.view().into_dyn(), protos.view().into_dyn()];
5325 decoder
5326 .decode_tracked_float_proto(
5327 &mut tracker,
5328 0,
5329 &inputs,
5330 &mut output_boxes,
5331 &mut output_tracks,
5332 )
5333 .unwrap();
5334 }
5335 assert_eq!(output_boxes.len(), 1);
5336 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
5337
5338 for score in detect.slice_mut(s![.., .., 4]).iter_mut() {
5339 *score = 0.0;
5340 }
5341 let proto_result = {
5342 let inputs = vec![detect.view().into_dyn(), protos.view().into_dyn()];
5343 decoder
5344 .decode_tracked_float_proto(
5345 &mut tracker,
5346 100_000_000 / 3,
5347 &inputs,
5348 &mut output_boxes,
5349 &mut output_tracks,
5350 )
5351 .unwrap()
5352 };
5353 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
5354 assert!(proto_result.is_some_and(|x| x.mask_coefficients.shape()[0] == 0));
5355 } else {
5356 let mut output_masks = Vec::with_capacity(50);
5357 {
5358 let inputs = vec![detect.view().into_dyn(), protos.view().into_dyn()];
5359 decoder
5360 .decode_tracked_float(
5361 &mut tracker,
5362 0,
5363 &inputs,
5364 &mut output_boxes,
5365 &mut output_masks,
5366 &mut output_tracks,
5367 )
5368 .unwrap();
5369 }
5370 assert_eq!(output_boxes.len(), 1);
5371 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
5372
5373 for score in detect.slice_mut(s![.., .., 4]).iter_mut() {
5374 *score = 0.0;
5375 }
5376 {
5377 let inputs = vec![detect.view().into_dyn(), protos.view().into_dyn()];
5378 decoder
5379 .decode_tracked_float(
5380 &mut tracker,
5381 100_000_000 / 3,
5382 &inputs,
5383 &mut output_boxes,
5384 &mut output_masks,
5385 &mut output_tracks,
5386 )
5387 .unwrap();
5388 }
5389 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
5390 assert!(output_masks.is_empty());
5391 }
5392 }
5393 }
5394 };
5395 }
5396
5397 e2e_tracked_test!(
5398 test_decoder_tracked_end_to_end_segdet,
5399 quantized,
5400 combined,
5401 masks
5402 );
5403 e2e_tracked_test!(
5404 test_decoder_tracked_end_to_end_segdet_float,
5405 float,
5406 combined,
5407 masks
5408 );
5409 e2e_tracked_test!(
5410 test_decoder_tracked_end_to_end_segdet_proto,
5411 quantized,
5412 combined,
5413 proto
5414 );
5415 e2e_tracked_test!(
5416 test_decoder_tracked_end_to_end_segdet_proto_float,
5417 float,
5418 combined,
5419 proto
5420 );
5421 e2e_tracked_test!(
5422 test_decoder_tracked_end_to_end_segdet_split,
5423 quantized,
5424 split,
5425 masks
5426 );
5427 e2e_tracked_test!(
5428 test_decoder_tracked_end_to_end_segdet_split_float,
5429 float,
5430 split,
5431 masks
5432 );
5433 e2e_tracked_test!(
5434 test_decoder_tracked_end_to_end_segdet_split_proto,
5435 quantized,
5436 split,
5437 proto
5438 );
5439 e2e_tracked_test!(
5440 test_decoder_tracked_end_to_end_segdet_split_proto_float,
5441 float,
5442 split,
5443 proto
5444 );
5445
5446 macro_rules! e2e_tracked_tensor_test {
5452 ($name:ident, quantized, $layout:ident, $output:ident) => {
5453 #[test]
5454 fn $name() {
5455 use edgefirst_tensor::{Tensor, TensorMapTrait, TensorTrait};
5456
5457 let is_split = matches!(stringify!($layout), "split");
5458 let is_proto = matches!(stringify!($output), "proto");
5459
5460 let score_threshold = 0.45;
5461 let iou_threshold = 0.45;
5462
5463 let mut boxes = Array2::zeros((10, 4));
5464 let mut scores = Array2::zeros((10, 1));
5465 let mut classes = Array2::zeros((10, 1));
5466 let mask = Array2::zeros((10, 32));
5467 let protos_f64 = Array3::<f64>::zeros((160, 160, 32));
5468 let protos_f64 = protos_f64.insert_axis(Axis(0));
5469 let protos_quant = (1.0 / 255.0, 0.0);
5470 let protos_u8: Array4<u8> =
5471 quantize_ndarray(protos_f64.view(), protos_quant.into());
5472
5473 boxes
5474 .slice_mut(s![0, ..])
5475 .assign(&array![0.1234, 0.1234, 0.2345, 0.2345]);
5476 scores.slice_mut(s![0, ..]).assign(&array![0.9876]);
5477 classes.slice_mut(s![0, ..]).assign(&array![2.0]);
5478
5479 let detect_quant = (2.0 / 255.0, 0.0);
5480
5481 let decoder = if is_split {
5482 DecoderBuilder::default()
5483 .with_config_yaml_str(E2E_SPLIT_CONFIG.to_string())
5484 .with_score_threshold(score_threshold)
5485 .with_iou_threshold(iou_threshold)
5486 .build()
5487 .unwrap()
5488 } else {
5489 DecoderBuilder::default()
5490 .with_config_yaml_str(E2E_COMBINED_CONFIG.to_string())
5491 .with_score_threshold(score_threshold)
5492 .with_iou_threshold(iou_threshold)
5493 .build()
5494 .unwrap()
5495 };
5496
5497 let make_u8_tensor =
5499 |shape: &[usize], data: &[u8]| -> edgefirst_tensor::TensorDyn {
5500 let t = Tensor::<u8>::new(shape, None, None).unwrap();
5501 t.map().unwrap().as_mut_slice()[..data.len()].copy_from_slice(data);
5502 t.into()
5503 };
5504
5505 let expected = e2e_expected_boxes_quant();
5506 let mut tracker = ByteTrackBuilder::new()
5507 .track_update(0.1)
5508 .track_high_conf(0.7)
5509 .build();
5510 let mut output_boxes = Vec::with_capacity(50);
5511 let mut output_tracks = Vec::with_capacity(50);
5512
5513 let protos_td = make_u8_tensor(protos_u8.shape(), protos_u8.as_slice().unwrap());
5514
5515 if is_split {
5516 let boxes = boxes.insert_axis(Axis(0));
5517 let scores = scores.insert_axis(Axis(0));
5518 let classes = classes.insert_axis(Axis(0));
5519 let mask = mask.insert_axis(Axis(0));
5520
5521 let boxes_q: Array3<u8> = quantize_ndarray(boxes.view(), detect_quant.into());
5522 let mut scores_q: Array3<u8> =
5523 quantize_ndarray(scores.view(), detect_quant.into());
5524 let classes_q: Array3<u8> =
5525 quantize_ndarray(classes.view(), detect_quant.into());
5526 let mask_q: Array3<u8> = quantize_ndarray(mask.view(), detect_quant.into());
5527
5528 let boxes_td = make_u8_tensor(boxes_q.shape(), boxes_q.as_slice().unwrap());
5529 let classes_td =
5530 make_u8_tensor(classes_q.shape(), classes_q.as_slice().unwrap());
5531 let mask_td = make_u8_tensor(mask_q.shape(), mask_q.as_slice().unwrap());
5532
5533 if is_proto {
5534 let scores_td =
5535 make_u8_tensor(scores_q.shape(), scores_q.as_slice().unwrap());
5536 decoder
5537 .decode_proto_tracked(
5538 &mut tracker,
5539 0,
5540 &[&boxes_td, &scores_td, &classes_td, &mask_td, &protos_td],
5541 &mut output_boxes,
5542 &mut output_tracks,
5543 )
5544 .unwrap();
5545
5546 assert_eq!(output_boxes.len(), 1);
5547 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
5548
5549 for score in scores_q.slice_mut(s![.., .., ..]).iter_mut() {
5550 *score = u8::MIN;
5551 }
5552 let scores_td =
5553 make_u8_tensor(scores_q.shape(), scores_q.as_slice().unwrap());
5554 let proto_result = decoder
5555 .decode_proto_tracked(
5556 &mut tracker,
5557 100_000_000 / 3,
5558 &[&boxes_td, &scores_td, &classes_td, &mask_td, &protos_td],
5559 &mut output_boxes,
5560 &mut output_tracks,
5561 )
5562 .unwrap();
5563 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
5564 assert!(proto_result.is_some_and(|x| x.mask_coefficients.shape()[0] == 0));
5565 } else {
5566 let scores_td =
5567 make_u8_tensor(scores_q.shape(), scores_q.as_slice().unwrap());
5568 let mut output_masks = Vec::with_capacity(50);
5569 decoder
5570 .decode_tracked(
5571 &mut tracker,
5572 0,
5573 &[&boxes_td, &scores_td, &classes_td, &mask_td, &protos_td],
5574 &mut output_boxes,
5575 &mut output_masks,
5576 &mut output_tracks,
5577 )
5578 .unwrap();
5579
5580 assert_eq!(output_boxes.len(), 1);
5581 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
5582
5583 for score in scores_q.slice_mut(s![.., .., ..]).iter_mut() {
5584 *score = u8::MIN;
5585 }
5586 let scores_td =
5587 make_u8_tensor(scores_q.shape(), scores_q.as_slice().unwrap());
5588 decoder
5589 .decode_tracked(
5590 &mut tracker,
5591 100_000_000 / 3,
5592 &[&boxes_td, &scores_td, &classes_td, &mask_td, &protos_td],
5593 &mut output_boxes,
5594 &mut output_masks,
5595 &mut output_tracks,
5596 )
5597 .unwrap();
5598 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
5599 assert!(output_masks.is_empty());
5600 }
5601 } else {
5602 let detect = ndarray::concatenate![
5604 Axis(1),
5605 boxes.view(),
5606 scores.view(),
5607 classes.view(),
5608 mask.view()
5609 ];
5610 let detect = detect.insert_axis(Axis(0));
5611 assert_eq!(detect.shape(), &[1, 10, 38]);
5612 let detect =
5614 Array3::from_shape_vec(detect.raw_dim(), detect.iter().copied().collect())
5615 .unwrap();
5616 let mut detect_q: Array3<u8> =
5617 quantize_ndarray(detect.view(), detect_quant.into());
5618
5619 if is_proto {
5620 let detect_td =
5621 make_u8_tensor(detect_q.shape(), detect_q.as_slice().unwrap());
5622 decoder
5623 .decode_proto_tracked(
5624 &mut tracker,
5625 0,
5626 &[&detect_td, &protos_td],
5627 &mut output_boxes,
5628 &mut output_tracks,
5629 )
5630 .unwrap();
5631
5632 assert_eq!(output_boxes.len(), 1);
5633 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
5634
5635 for score in detect_q.slice_mut(s![.., .., 4]).iter_mut() {
5636 *score = u8::MIN;
5637 }
5638 let detect_td =
5639 make_u8_tensor(detect_q.shape(), detect_q.as_slice().unwrap());
5640 let proto_result = decoder
5641 .decode_proto_tracked(
5642 &mut tracker,
5643 100_000_000 / 3,
5644 &[&detect_td, &protos_td],
5645 &mut output_boxes,
5646 &mut output_tracks,
5647 )
5648 .unwrap();
5649 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
5650 assert!(proto_result.is_some_and(|x| x.mask_coefficients.shape()[0] == 0));
5651 } else {
5652 let detect_td =
5653 make_u8_tensor(detect_q.shape(), detect_q.as_slice().unwrap());
5654 let mut output_masks = Vec::with_capacity(50);
5655 decoder
5656 .decode_tracked(
5657 &mut tracker,
5658 0,
5659 &[&detect_td, &protos_td],
5660 &mut output_boxes,
5661 &mut output_masks,
5662 &mut output_tracks,
5663 )
5664 .unwrap();
5665
5666 assert_eq!(output_boxes.len(), 1);
5667 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
5668
5669 for score in detect_q.slice_mut(s![.., .., 4]).iter_mut() {
5670 *score = u8::MIN;
5671 }
5672 let detect_td =
5673 make_u8_tensor(detect_q.shape(), detect_q.as_slice().unwrap());
5674 decoder
5675 .decode_tracked(
5676 &mut tracker,
5677 100_000_000 / 3,
5678 &[&detect_td, &protos_td],
5679 &mut output_boxes,
5680 &mut output_masks,
5681 &mut output_tracks,
5682 )
5683 .unwrap();
5684 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
5685 assert!(output_masks.is_empty());
5686 }
5687 }
5688 }
5689 };
5690 ($name:ident, float, $layout:ident, $output:ident) => {
5691 #[test]
5692 fn $name() {
5693 use edgefirst_tensor::{Tensor, TensorMapTrait, TensorTrait};
5694
5695 let is_split = matches!(stringify!($layout), "split");
5696 let is_proto = matches!(stringify!($output), "proto");
5697
5698 let score_threshold = 0.45;
5699 let iou_threshold = 0.45;
5700
5701 let mut boxes = Array2::zeros((10, 4));
5702 let mut scores = Array2::zeros((10, 1));
5703 let mut classes = Array2::zeros((10, 1));
5704 let mask: Array2<f64> = Array2::zeros((10, 32));
5705 let protos = Array3::<f64>::zeros((160, 160, 32));
5706 let protos = protos.insert_axis(Axis(0));
5707
5708 boxes
5709 .slice_mut(s![0, ..])
5710 .assign(&array![0.1234, 0.1234, 0.2345, 0.2345]);
5711 scores.slice_mut(s![0, ..]).assign(&array![0.9876]);
5712 classes.slice_mut(s![0, ..]).assign(&array![2.0]);
5713
5714 let decoder = if is_split {
5715 DecoderBuilder::default()
5716 .with_config_yaml_str(E2E_SPLIT_CONFIG.to_string())
5717 .with_score_threshold(score_threshold)
5718 .with_iou_threshold(iou_threshold)
5719 .build()
5720 .unwrap()
5721 } else {
5722 DecoderBuilder::default()
5723 .with_config_yaml_str(E2E_COMBINED_CONFIG.to_string())
5724 .with_score_threshold(score_threshold)
5725 .with_iou_threshold(iou_threshold)
5726 .build()
5727 .unwrap()
5728 };
5729
5730 let make_f64_tensor =
5732 |shape: &[usize], data: &[f64]| -> edgefirst_tensor::TensorDyn {
5733 let t = Tensor::<f64>::new(shape, None, None).unwrap();
5734 t.map().unwrap().as_mut_slice()[..data.len()].copy_from_slice(data);
5735 t.into()
5736 };
5737
5738 let expected = e2e_expected_boxes_float();
5739 let mut tracker = ByteTrackBuilder::new()
5740 .track_update(0.1)
5741 .track_high_conf(0.7)
5742 .build();
5743 let mut output_boxes = Vec::with_capacity(50);
5744 let mut output_tracks = Vec::with_capacity(50);
5745
5746 let protos_td = make_f64_tensor(protos.shape(), protos.as_slice().unwrap());
5747
5748 if is_split {
5749 let boxes = boxes.insert_axis(Axis(0));
5750 let mut scores = scores.insert_axis(Axis(0));
5751 let classes = classes.insert_axis(Axis(0));
5752 let mask = mask.insert_axis(Axis(0));
5753
5754 let boxes_td = make_f64_tensor(boxes.shape(), boxes.as_slice().unwrap());
5755 let classes_td = make_f64_tensor(classes.shape(), classes.as_slice().unwrap());
5756 let mask_td = make_f64_tensor(mask.shape(), mask.as_slice().unwrap());
5757
5758 if is_proto {
5759 let scores_td = make_f64_tensor(scores.shape(), scores.as_slice().unwrap());
5760 decoder
5761 .decode_proto_tracked(
5762 &mut tracker,
5763 0,
5764 &[&boxes_td, &scores_td, &classes_td, &mask_td, &protos_td],
5765 &mut output_boxes,
5766 &mut output_tracks,
5767 )
5768 .unwrap();
5769
5770 assert_eq!(output_boxes.len(), 1);
5771 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
5772
5773 for score in scores.slice_mut(s![.., .., ..]).iter_mut() {
5774 *score = 0.0;
5775 }
5776 let scores_td = make_f64_tensor(scores.shape(), scores.as_slice().unwrap());
5777 let proto_result = decoder
5778 .decode_proto_tracked(
5779 &mut tracker,
5780 100_000_000 / 3,
5781 &[&boxes_td, &scores_td, &classes_td, &mask_td, &protos_td],
5782 &mut output_boxes,
5783 &mut output_tracks,
5784 )
5785 .unwrap();
5786 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
5787 assert!(proto_result.is_some_and(|x| x.mask_coefficients.shape()[0] == 0));
5788 } else {
5789 let scores_td = make_f64_tensor(scores.shape(), scores.as_slice().unwrap());
5790 let mut output_masks = Vec::with_capacity(50);
5791 decoder
5792 .decode_tracked(
5793 &mut tracker,
5794 0,
5795 &[&boxes_td, &scores_td, &classes_td, &mask_td, &protos_td],
5796 &mut output_boxes,
5797 &mut output_masks,
5798 &mut output_tracks,
5799 )
5800 .unwrap();
5801
5802 assert_eq!(output_boxes.len(), 1);
5803 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
5804
5805 for score in scores.slice_mut(s![.., .., ..]).iter_mut() {
5806 *score = 0.0;
5807 }
5808 let scores_td = make_f64_tensor(scores.shape(), scores.as_slice().unwrap());
5809 decoder
5810 .decode_tracked(
5811 &mut tracker,
5812 100_000_000 / 3,
5813 &[&boxes_td, &scores_td, &classes_td, &mask_td, &protos_td],
5814 &mut output_boxes,
5815 &mut output_masks,
5816 &mut output_tracks,
5817 )
5818 .unwrap();
5819 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
5820 assert!(output_masks.is_empty());
5821 }
5822 } else {
5823 let detect = ndarray::concatenate![
5825 Axis(1),
5826 boxes.view(),
5827 scores.view(),
5828 classes.view(),
5829 mask.view()
5830 ];
5831 let detect = detect.insert_axis(Axis(0));
5832 assert_eq!(detect.shape(), &[1, 10, 38]);
5833 let mut detect =
5835 Array3::from_shape_vec(detect.raw_dim(), detect.iter().copied().collect())
5836 .unwrap();
5837
5838 if is_proto {
5839 let detect_td = make_f64_tensor(detect.shape(), detect.as_slice().unwrap());
5840 decoder
5841 .decode_proto_tracked(
5842 &mut tracker,
5843 0,
5844 &[&detect_td, &protos_td],
5845 &mut output_boxes,
5846 &mut output_tracks,
5847 )
5848 .unwrap();
5849
5850 assert_eq!(output_boxes.len(), 1);
5851 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
5852
5853 for score in detect.slice_mut(s![.., .., 4]).iter_mut() {
5854 *score = 0.0;
5855 }
5856 let detect_td = make_f64_tensor(detect.shape(), detect.as_slice().unwrap());
5857 let proto_result = decoder
5858 .decode_proto_tracked(
5859 &mut tracker,
5860 100_000_000 / 3,
5861 &[&detect_td, &protos_td],
5862 &mut output_boxes,
5863 &mut output_tracks,
5864 )
5865 .unwrap();
5866 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
5867 assert!(proto_result.is_some_and(|x| x.mask_coefficients.shape()[0] == 0));
5868 } else {
5869 let detect_td = make_f64_tensor(detect.shape(), detect.as_slice().unwrap());
5870 let mut output_masks = Vec::with_capacity(50);
5871 decoder
5872 .decode_tracked(
5873 &mut tracker,
5874 0,
5875 &[&detect_td, &protos_td],
5876 &mut output_boxes,
5877 &mut output_masks,
5878 &mut output_tracks,
5879 )
5880 .unwrap();
5881
5882 assert_eq!(output_boxes.len(), 1);
5883 assert!(output_boxes[0].equal_within_delta(&expected[0], 1.0 / 160.0));
5884
5885 for score in detect.slice_mut(s![.., .., 4]).iter_mut() {
5886 *score = 0.0;
5887 }
5888 let detect_td = make_f64_tensor(detect.shape(), detect.as_slice().unwrap());
5889 decoder
5890 .decode_tracked(
5891 &mut tracker,
5892 100_000_000 / 3,
5893 &[&detect_td, &protos_td],
5894 &mut output_boxes,
5895 &mut output_masks,
5896 &mut output_tracks,
5897 )
5898 .unwrap();
5899 assert!(output_boxes[0].equal_within_delta(&expected[0], 1e-6));
5900 assert!(output_masks.is_empty());
5901 }
5902 }
5903 }
5904 };
5905 }
5906
5907 e2e_tracked_tensor_test!(
5908 test_decoder_tracked_tensor_end_to_end_segdet,
5909 quantized,
5910 combined,
5911 masks
5912 );
5913 e2e_tracked_tensor_test!(
5914 test_decoder_tracked_tensor_end_to_end_segdet_float,
5915 float,
5916 combined,
5917 masks
5918 );
5919 e2e_tracked_tensor_test!(
5920 test_decoder_tracked_tensor_end_to_end_segdet_proto,
5921 quantized,
5922 combined,
5923 proto
5924 );
5925 e2e_tracked_tensor_test!(
5926 test_decoder_tracked_tensor_end_to_end_segdet_proto_float,
5927 float,
5928 combined,
5929 proto
5930 );
5931 e2e_tracked_tensor_test!(
5932 test_decoder_tracked_tensor_end_to_end_segdet_split,
5933 quantized,
5934 split,
5935 masks
5936 );
5937 e2e_tracked_tensor_test!(
5938 test_decoder_tracked_tensor_end_to_end_segdet_split_float,
5939 float,
5940 split,
5941 masks
5942 );
5943 e2e_tracked_tensor_test!(
5944 test_decoder_tracked_tensor_end_to_end_segdet_split_proto,
5945 quantized,
5946 split,
5947 proto
5948 );
5949 e2e_tracked_tensor_test!(
5950 test_decoder_tracked_tensor_end_to_end_segdet_split_proto_float,
5951 float,
5952 split,
5953 proto
5954 );
5955
5956 #[test]
5957 fn test_decoder_tracked_linear_motion() {
5958 use crate::configs::{DecoderType, Nms};
5959 use crate::DecoderBuilder;
5960
5961 let score_threshold = 0.25;
5962 let iou_threshold = 0.1;
5963 let out = edgefirst_bench::testdata::read("yolov8s_80_classes.bin");
5964 let out = unsafe { std::slice::from_raw_parts(out.as_ptr() as *const i8, out.len()) };
5965 let mut out = Array3::from_shape_vec((1, 84, 8400), out.to_vec()).unwrap();
5966 let quant = (0.0040811873, -123).into();
5967
5968 let decoder = DecoderBuilder::default()
5969 .with_config_yolo_det(
5970 crate::configs::Detection {
5971 decoder: DecoderType::Ultralytics,
5972 shape: vec![1, 84, 8400],
5973 anchors: None,
5974 quantization: Some(quant),
5975 dshape: vec![
5976 (crate::configs::DimName::Batch, 1),
5977 (crate::configs::DimName::NumFeatures, 84),
5978 (crate::configs::DimName::NumBoxes, 8400),
5979 ],
5980 normalized: Some(true),
5981 },
5982 None,
5983 )
5984 .with_score_threshold(score_threshold)
5985 .with_iou_threshold(iou_threshold)
5986 .with_nms(Some(Nms::ClassAgnostic))
5987 .build()
5988 .unwrap();
5989
5990 let mut expected_boxes = [
5991 DetectBox {
5992 bbox: BoundingBox {
5993 xmin: 0.5285137,
5994 ymin: 0.05305544,
5995 xmax: 0.87541467,
5996 ymax: 0.9998909,
5997 },
5998 score: 0.5591227,
5999 label: 0,
6000 },
6001 DetectBox {
6002 bbox: BoundingBox {
6003 xmin: 0.130598,
6004 ymin: 0.43260583,
6005 xmax: 0.35098213,
6006 ymax: 0.9958097,
6007 },
6008 score: 0.33057618,
6009 label: 75,
6010 },
6011 ];
6012
6013 let mut tracker = ByteTrackBuilder::new()
6014 .track_update(0.1)
6015 .track_high_conf(0.3)
6016 .build();
6017
6018 let mut output_boxes = Vec::with_capacity(50);
6019 let mut output_masks = Vec::with_capacity(50);
6020 let mut output_tracks = Vec::with_capacity(50);
6021
6022 decoder
6023 .decode_tracked_quantized(
6024 &mut tracker,
6025 0,
6026 &[out.view().into()],
6027 &mut output_boxes,
6028 &mut output_masks,
6029 &mut output_tracks,
6030 )
6031 .unwrap();
6032
6033 assert_eq!(output_boxes.len(), 2);
6034 assert!(output_boxes[0].equal_within_delta(&expected_boxes[0], 1e-6));
6035 assert!(output_boxes[1].equal_within_delta(&expected_boxes[1], 1e-6));
6036
6037 for i in 1..=100 {
6038 let mut out = out.clone();
6039 let mut x_values = out.slice_mut(s![0, 0, ..]);
6041 for x in x_values.iter_mut() {
6042 *x = x.saturating_add((i as f32 * 1e-3 / quant.0).round() as i8);
6043 }
6044
6045 decoder
6046 .decode_tracked_quantized(
6047 &mut tracker,
6048 100_000_000 * i / 3, &[out.view().into()],
6050 &mut output_boxes,
6051 &mut output_masks,
6052 &mut output_tracks,
6053 )
6054 .unwrap();
6055
6056 assert_eq!(output_boxes.len(), 2);
6057 }
6058 let tracks = tracker.get_active_tracks();
6059 let predicted_boxes: Vec<_> = tracks
6060 .iter()
6061 .map(|track| {
6062 let mut l = track.last_box;
6063 l.bbox = track.info.tracked_location.into();
6064 l
6065 })
6066 .collect();
6067 expected_boxes[0].bbox.xmin += 0.1; expected_boxes[0].bbox.xmax += 0.1;
6069 expected_boxes[1].bbox.xmin += 0.1;
6070 expected_boxes[1].bbox.xmax += 0.1;
6071
6072 assert!(predicted_boxes[0].equal_within_delta(&expected_boxes[0], 1e-3));
6073 assert!(predicted_boxes[1].equal_within_delta(&expected_boxes[1], 1e-3));
6074
6075 let mut scores_values = out.slice_mut(s![0, 4.., ..]);
6077 for score in scores_values.iter_mut() {
6078 *score = i8::MIN; }
6080 decoder
6081 .decode_tracked_quantized(
6082 &mut tracker,
6083 100_000_000 * 101 / 3,
6084 &[out.view().into()],
6085 &mut output_boxes,
6086 &mut output_masks,
6087 &mut output_tracks,
6088 )
6089 .unwrap();
6090 expected_boxes[0].bbox.xmin += 0.001; expected_boxes[0].bbox.xmax += 0.001;
6092 expected_boxes[1].bbox.xmin += 0.001;
6093 expected_boxes[1].bbox.xmax += 0.001;
6094
6095 assert!(output_boxes[0].equal_within_delta(&expected_boxes[0], 1e-3));
6096 assert!(output_boxes[1].equal_within_delta(&expected_boxes[1], 1e-3));
6097 }
6098
6099 #[test]
6100 fn test_decoder_tracked_end_to_end_float() {
6101 let score_threshold = 0.45;
6102 let iou_threshold = 0.45;
6103
6104 let mut boxes = Array2::zeros((10, 4));
6105 let mut scores = Array2::zeros((10, 1));
6106 let mut classes = Array2::zeros((10, 1));
6107
6108 boxes
6109 .slice_mut(s![0, ..,])
6110 .assign(&array![0.1234, 0.1234, 0.2345, 0.2345]);
6111 scores.slice_mut(s![0, ..]).assign(&array![0.9876]);
6112 classes.slice_mut(s![0, ..]).assign(&array![2.0]);
6113
6114 let detect = ndarray::concatenate![Axis(1), boxes.view(), scores.view(), classes.view(),];
6115 let mut detect = detect.insert_axis(Axis(0));
6116 assert_eq!(detect.shape(), &[1, 10, 6]);
6117 let config = "
6118decoder_version: yolo26
6119outputs:
6120 - type: detection
6121 decoder: ultralytics
6122 quantization: [0.00784313725490196, 0]
6123 shape: [1, 10, 6]
6124 dshape:
6125 - [batch, 1]
6126 - [num_boxes, 10]
6127 - [num_features, 6]
6128 normalized: true
6129";
6130
6131 let decoder = DecoderBuilder::default()
6132 .with_config_yaml_str(config.to_string())
6133 .with_score_threshold(score_threshold)
6134 .with_iou_threshold(iou_threshold)
6135 .build()
6136 .unwrap();
6137
6138 let expected_boxes = [DetectBox {
6139 bbox: BoundingBox {
6140 xmin: 0.1234,
6141 ymin: 0.1234,
6142 xmax: 0.2345,
6143 ymax: 0.2345,
6144 },
6145 score: 0.9876,
6146 label: 2,
6147 }];
6148
6149 let mut tracker = ByteTrackBuilder::new()
6150 .track_update(0.1)
6151 .track_high_conf(0.7)
6152 .build();
6153
6154 let mut output_boxes = Vec::with_capacity(50);
6155 let mut output_masks = Vec::with_capacity(50);
6156 let mut output_tracks = Vec::with_capacity(50);
6157
6158 decoder
6159 .decode_tracked_float(
6160 &mut tracker,
6161 0,
6162 &[detect.view().into_dyn()],
6163 &mut output_boxes,
6164 &mut output_masks,
6165 &mut output_tracks,
6166 )
6167 .unwrap();
6168
6169 assert_eq!(output_boxes.len(), 1);
6170 assert!(output_boxes[0].equal_within_delta(&expected_boxes[0], 1e-6));
6171
6172 for score in detect.slice_mut(s![.., .., 4]).iter_mut() {
6175 *score = 0.0; }
6177
6178 decoder
6179 .decode_tracked_float(
6180 &mut tracker,
6181 100_000_000 / 3,
6182 &[detect.view().into_dyn()],
6183 &mut output_boxes,
6184 &mut output_masks,
6185 &mut output_tracks,
6186 )
6187 .unwrap();
6188 assert!(output_boxes[0].equal_within_delta(&expected_boxes[0], 1e-6));
6189 }
6190}