Skip to main content

edgefirst_decoder/decoder/
mod.rs

1// SPDX-FileCopyrightText: Copyright 2025 Au-Zone Technologies
2// SPDX-License-Identifier: Apache-2.0
3
4use ndarray::{ArrayView, ArrayViewD, Dimension};
5use num_traits::{AsPrimitive, Float};
6
7use crate::{DecoderError, DetectBox, ProtoData, Segmentation};
8
9pub mod config;
10pub mod configs;
11
12use configs::ModelType;
13
14#[derive(Debug, Clone, PartialEq)]
15pub struct Decoder {
16    model_type: ModelType,
17    pub iou_threshold: f32,
18    pub score_threshold: f32,
19    /// NMS mode: Some(mode) applies NMS, None bypasses NMS (for end-to-end
20    /// models)
21    pub nms: Option<configs::Nms>,
22    /// Whether decoded boxes are in normalized [0,1] coordinates.
23    /// - `Some(true)`: Coordinates in [0,1] range
24    /// - `Some(false)`: Pixel coordinates
25    /// - `None`: Unknown, caller must infer (e.g., check if any coordinate >
26    ///   1.0)
27    normalized: Option<bool>,
28}
29
30#[derive(Debug)]
31pub enum ArrayViewDQuantized<'a> {
32    UInt8(ArrayViewD<'a, u8>),
33    Int8(ArrayViewD<'a, i8>),
34    UInt16(ArrayViewD<'a, u16>),
35    Int16(ArrayViewD<'a, i16>),
36    UInt32(ArrayViewD<'a, u32>),
37    Int32(ArrayViewD<'a, i32>),
38}
39
40impl<'a, D> From<ArrayView<'a, u8, D>> for ArrayViewDQuantized<'a>
41where
42    D: Dimension,
43{
44    fn from(arr: ArrayView<'a, u8, D>) -> Self {
45        Self::UInt8(arr.into_dyn())
46    }
47}
48
49impl<'a, D> From<ArrayView<'a, i8, D>> for ArrayViewDQuantized<'a>
50where
51    D: Dimension,
52{
53    fn from(arr: ArrayView<'a, i8, D>) -> Self {
54        Self::Int8(arr.into_dyn())
55    }
56}
57
58impl<'a, D> From<ArrayView<'a, u16, D>> for ArrayViewDQuantized<'a>
59where
60    D: Dimension,
61{
62    fn from(arr: ArrayView<'a, u16, D>) -> Self {
63        Self::UInt16(arr.into_dyn())
64    }
65}
66
67impl<'a, D> From<ArrayView<'a, i16, D>> for ArrayViewDQuantized<'a>
68where
69    D: Dimension,
70{
71    fn from(arr: ArrayView<'a, i16, D>) -> Self {
72        Self::Int16(arr.into_dyn())
73    }
74}
75
76impl<'a, D> From<ArrayView<'a, u32, D>> for ArrayViewDQuantized<'a>
77where
78    D: Dimension,
79{
80    fn from(arr: ArrayView<'a, u32, D>) -> Self {
81        Self::UInt32(arr.into_dyn())
82    }
83}
84
85impl<'a, D> From<ArrayView<'a, i32, D>> for ArrayViewDQuantized<'a>
86where
87    D: Dimension,
88{
89    fn from(arr: ArrayView<'a, i32, D>) -> Self {
90        Self::Int32(arr.into_dyn())
91    }
92}
93
94impl<'a> ArrayViewDQuantized<'a> {
95    /// Returns the shape of the underlying array.
96    ///
97    /// # Examples
98    /// ```rust
99    /// # use edgefirst_decoder::ArrayViewDQuantized;
100    /// # use ndarray::Array2;
101    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
102    /// let arr = Array2::from_shape_vec((2, 3), vec![1u8, 2, 3, 4, 5, 6])?;
103    /// let view = ArrayViewDQuantized::from(arr.view().into_dyn());
104    /// assert_eq!(view.shape(), &[2, 3]);
105    /// # Ok(())
106    /// # }
107    /// ```
108    pub fn shape(&self) -> &[usize] {
109        match self {
110            ArrayViewDQuantized::UInt8(a) => a.shape(),
111            ArrayViewDQuantized::Int8(a) => a.shape(),
112            ArrayViewDQuantized::UInt16(a) => a.shape(),
113            ArrayViewDQuantized::Int16(a) => a.shape(),
114            ArrayViewDQuantized::UInt32(a) => a.shape(),
115            ArrayViewDQuantized::Int32(a) => a.shape(),
116        }
117    }
118}
119
120/// WARNING: Do NOT nest `with_quantized!` calls. Each level multiplies
121/// monomorphized code paths by 6 (one per integer variant), so nesting
122/// N levels deep produces 6^N instantiations.
123///
124/// Instead, dequantize each tensor sequentially with `dequant_3d!`/`dequant_4d!`
125/// (6*N paths) or split into independent phases that each nest at most 2 levels.
126macro_rules! with_quantized {
127    ($x:expr, $var:ident, $body:expr) => {
128        match $x {
129            ArrayViewDQuantized::UInt8(x) => {
130                let $var = x;
131                $body
132            }
133            ArrayViewDQuantized::Int8(x) => {
134                let $var = x;
135                $body
136            }
137            ArrayViewDQuantized::UInt16(x) => {
138                let $var = x;
139                $body
140            }
141            ArrayViewDQuantized::Int16(x) => {
142                let $var = x;
143                $body
144            }
145            ArrayViewDQuantized::UInt32(x) => {
146                let $var = x;
147                $body
148            }
149            ArrayViewDQuantized::Int32(x) => {
150                let $var = x;
151                $body
152            }
153        }
154    };
155}
156
157mod builder;
158mod helpers;
159mod postprocess;
160mod tests;
161
162pub use builder::DecoderBuilder;
163pub use config::{ConfigOutput, ConfigOutputRef, ConfigOutputs};
164
165impl Decoder {
166    /// This function returns the parsed model type of the decoder.
167    ///
168    /// # Examples
169    ///
170    /// ```rust
171    /// # use edgefirst_decoder::{DecoderBuilder, DecoderResult, configs::ModelType};
172    /// # fn main() -> DecoderResult<()> {
173    /// #    let config_yaml = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata/modelpack_split.yaml")).to_string();
174    ///     let decoder = DecoderBuilder::default()
175    ///         .with_config_yaml_str(config_yaml)
176    ///         .build()?;
177    ///     assert!(matches!(
178    ///         decoder.model_type(),
179    ///         ModelType::ModelPackDetSplit { .. }
180    ///     ));
181    /// #    Ok(())
182    /// # }
183    /// ```
184    pub fn model_type(&self) -> &ModelType {
185        &self.model_type
186    }
187
188    /// Returns the box coordinate format if known from the model config.
189    ///
190    /// - `Some(true)`: Boxes are in normalized [0,1] coordinates
191    /// - `Some(false)`: Boxes are in pixel coordinates relative to model input
192    /// - `None`: Unknown, caller must infer (e.g., check if any coordinate >
193    ///   1.0)
194    ///
195    /// This is determined by the model config's `normalized` field, not the NMS
196    /// mode. When coordinates are in pixels or unknown, the caller may need
197    /// to normalize using the model input dimensions.
198    ///
199    /// # Examples
200    ///
201    /// ```rust
202    /// # use edgefirst_decoder::{DecoderBuilder, DecoderResult};
203    /// # fn main() -> DecoderResult<()> {
204    /// #    let config_yaml = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata/modelpack_split.yaml")).to_string();
205    ///     let decoder = DecoderBuilder::default()
206    ///         .with_config_yaml_str(config_yaml)
207    ///         .build()?;
208    ///     // Config doesn't specify normalized, so it's None
209    ///     assert!(decoder.normalized_boxes().is_none());
210    /// #    Ok(())
211    /// # }
212    /// ```
213    pub fn normalized_boxes(&self) -> Option<bool> {
214        self.normalized
215    }
216
217    /// This function decodes quantized model outputs into detection boxes and
218    /// segmentation masks. The quantized outputs can be of u8, i8, u16, i16,
219    /// u32, or i32 types. Up to `output_boxes.capacity()` boxes and masks
220    /// will be decoded. The function clears the provided output vectors
221    /// before populating them with the decoded results.
222    ///
223    /// This function returns a `DecoderError` if the the provided outputs don't
224    /// match the configuration provided by the user when building the decoder.
225    ///
226    /// # Examples
227    ///
228    /// ```rust
229    /// # use edgefirst_decoder::{BoundingBox, DecoderBuilder, DetectBox, DecoderResult};
230    /// # use ndarray::Array4;
231    /// # fn main() -> DecoderResult<()> {
232    /// #    let detect0 = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata/modelpack_split_9x15x18.bin"));
233    /// #    let detect0 = ndarray::Array4::from_shape_vec((1, 9, 15, 18), detect0.to_vec())?;
234    /// #
235    /// #    let detect1 = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata/modelpack_split_17x30x18.bin"));
236    /// #    let detect1 = ndarray::Array4::from_shape_vec((1, 17, 30, 18), detect1.to_vec())?;
237    /// #    let model_output = vec![
238    /// #        detect1.view().into_dyn().into(),
239    /// #        detect0.view().into_dyn().into(),
240    /// #    ];
241    /// let decoder = DecoderBuilder::default()
242    ///     .with_config_yaml_str(include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata/modelpack_split.yaml")).to_string())
243    ///     .with_score_threshold(0.45)
244    ///     .with_iou_threshold(0.45)
245    ///     .build()?;
246    ///
247    /// let mut output_boxes: Vec<_> = Vec::with_capacity(10);
248    /// let mut output_masks: Vec<_> = Vec::with_capacity(10);
249    /// decoder.decode_quantized(&model_output, &mut output_boxes, &mut output_masks)?;
250    /// assert!(output_boxes[0].equal_within_delta(
251    ///     &DetectBox {
252    ///         bbox: BoundingBox {
253    ///             xmin: 0.43171933,
254    ///             ymin: 0.68243736,
255    ///             xmax: 0.5626645,
256    ///             ymax: 0.808863,
257    ///         },
258    ///         score: 0.99240804,
259    ///         label: 0
260    ///     },
261    ///     1e-6
262    /// ));
263    /// #    Ok(())
264    /// # }
265    /// ```
266    pub fn decode_quantized(
267        &self,
268        outputs: &[ArrayViewDQuantized],
269        output_boxes: &mut Vec<DetectBox>,
270        output_masks: &mut Vec<Segmentation>,
271    ) -> Result<(), DecoderError> {
272        output_boxes.clear();
273        output_masks.clear();
274        match &self.model_type {
275            ModelType::ModelPackSegDet {
276                boxes,
277                scores,
278                segmentation,
279            } => {
280                self.decode_modelpack_det_quantized(outputs, boxes, scores, output_boxes)?;
281                self.decode_modelpack_seg_quantized(outputs, segmentation, output_masks)
282            }
283            ModelType::ModelPackSegDetSplit {
284                detection,
285                segmentation,
286            } => {
287                self.decode_modelpack_det_split_quantized(outputs, detection, output_boxes)?;
288                self.decode_modelpack_seg_quantized(outputs, segmentation, output_masks)
289            }
290            ModelType::ModelPackDet { boxes, scores } => {
291                self.decode_modelpack_det_quantized(outputs, boxes, scores, output_boxes)
292            }
293            ModelType::ModelPackDetSplit { detection } => {
294                self.decode_modelpack_det_split_quantized(outputs, detection, output_boxes)
295            }
296            ModelType::ModelPackSeg { segmentation } => {
297                self.decode_modelpack_seg_quantized(outputs, segmentation, output_masks)
298            }
299            ModelType::YoloDet { boxes } => {
300                self.decode_yolo_det_quantized(outputs, boxes, output_boxes)
301            }
302            ModelType::YoloSegDet { boxes, protos } => self.decode_yolo_segdet_quantized(
303                outputs,
304                boxes,
305                protos,
306                output_boxes,
307                output_masks,
308            ),
309            ModelType::YoloSplitDet { boxes, scores } => {
310                self.decode_yolo_split_det_quantized(outputs, boxes, scores, output_boxes)
311            }
312            ModelType::YoloSplitSegDet {
313                boxes,
314                scores,
315                mask_coeff,
316                protos,
317            } => self.decode_yolo_split_segdet_quantized(
318                outputs,
319                boxes,
320                scores,
321                mask_coeff,
322                protos,
323                output_boxes,
324                output_masks,
325            ),
326            ModelType::YoloEndToEndDet { boxes } => {
327                self.decode_yolo_end_to_end_det_quantized(outputs, boxes, output_boxes)
328            }
329            ModelType::YoloEndToEndSegDet { boxes, protos } => self
330                .decode_yolo_end_to_end_segdet_quantized(
331                    outputs,
332                    boxes,
333                    protos,
334                    output_boxes,
335                    output_masks,
336                ),
337            ModelType::YoloSplitEndToEndDet {
338                boxes,
339                scores,
340                classes,
341            } => self.decode_yolo_split_end_to_end_det_quantized(
342                outputs,
343                boxes,
344                scores,
345                classes,
346                output_boxes,
347            ),
348            ModelType::YoloSplitEndToEndSegDet {
349                boxes,
350                scores,
351                classes,
352                mask_coeff,
353                protos,
354            } => self.decode_yolo_split_end_to_end_segdet_quantized(
355                outputs,
356                boxes,
357                scores,
358                classes,
359                mask_coeff,
360                protos,
361                output_boxes,
362                output_masks,
363            ),
364        }
365    }
366
367    /// This function decodes floating point model outputs into detection boxes
368    /// and segmentation masks. Up to `output_boxes.capacity()` boxes and
369    /// masks will be decoded. The function clears the provided output
370    /// vectors before populating them with the decoded results.
371    ///
372    /// This function returns an `Error` if the the provided outputs don't
373    /// match the configuration provided by the user when building the decoder.
374    ///
375    /// Any quantization information in the configuration will be ignored.
376    ///
377    /// # Examples
378    ///
379    /// ```rust
380    /// # use edgefirst_decoder::{BoundingBox, DecoderBuilder, DetectBox, DecoderResult, configs, configs::{DecoderType, DecoderVersion}, dequantize_cpu, Quantization};
381    /// # use ndarray::Array3;
382    /// # fn main() -> DecoderResult<()> {
383    /// #   let out = include_bytes!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata/yolov8s_80_classes.bin"));
384    /// #   let out = unsafe { std::slice::from_raw_parts(out.as_ptr() as *const i8, out.len()) };
385    /// #   let mut out_dequant = vec![0.0_f64; 84 * 8400];
386    /// #   let quant = Quantization::new(0.0040811873, -123);
387    /// #   dequantize_cpu(out, quant, &mut out_dequant);
388    /// #   let model_output_f64 = Array3::from_shape_vec((1, 84, 8400), out_dequant)?.into_dyn();
389    ///    let decoder = DecoderBuilder::default()
390    ///     .with_config_yolo_det(configs::Detection {
391    ///         decoder: DecoderType::Ultralytics,
392    ///         quantization: None,
393    ///         shape: vec![1, 84, 8400],
394    ///         anchors: None,
395    ///         dshape: Vec::new(),
396    ///         normalized: Some(true),
397    ///     },
398    ///     Some(DecoderVersion::Yolo11))
399    ///     .with_score_threshold(0.25)
400    ///     .with_iou_threshold(0.7)
401    ///     .build()?;
402    ///
403    /// let mut output_boxes: Vec<_> = Vec::with_capacity(10);
404    /// let mut output_masks: Vec<_> = Vec::with_capacity(10);
405    /// let model_output_f64 = vec![model_output_f64.view().into()];
406    /// decoder.decode_float(&model_output_f64, &mut output_boxes, &mut output_masks)?;    
407    /// assert!(output_boxes[0].equal_within_delta(
408    ///        &DetectBox {
409    ///            bbox: BoundingBox {
410    ///                xmin: 0.5285137,
411    ///                ymin: 0.05305544,
412    ///                xmax: 0.87541467,
413    ///                ymax: 0.9998909,
414    ///            },
415    ///            score: 0.5591227,
416    ///            label: 0
417    ///        },
418    ///        1e-6
419    ///    ));
420    ///
421    /// #    Ok(())
422    /// # }
423    pub fn decode_float<T>(
424        &self,
425        outputs: &[ArrayViewD<T>],
426        output_boxes: &mut Vec<DetectBox>,
427        output_masks: &mut Vec<Segmentation>,
428    ) -> Result<(), DecoderError>
429    where
430        T: Float + AsPrimitive<f32> + AsPrimitive<u8> + Send + Sync + 'static,
431        f32: AsPrimitive<T>,
432    {
433        output_boxes.clear();
434        output_masks.clear();
435        match &self.model_type {
436            ModelType::ModelPackSegDet {
437                boxes,
438                scores,
439                segmentation,
440            } => {
441                self.decode_modelpack_det_float(outputs, boxes, scores, output_boxes)?;
442                self.decode_modelpack_seg_float(outputs, segmentation, output_masks)?;
443            }
444            ModelType::ModelPackSegDetSplit {
445                detection,
446                segmentation,
447            } => {
448                self.decode_modelpack_det_split_float(outputs, detection, output_boxes)?;
449                self.decode_modelpack_seg_float(outputs, segmentation, output_masks)?;
450            }
451            ModelType::ModelPackDet { boxes, scores } => {
452                self.decode_modelpack_det_float(outputs, boxes, scores, output_boxes)?;
453            }
454            ModelType::ModelPackDetSplit { detection } => {
455                self.decode_modelpack_det_split_float(outputs, detection, output_boxes)?;
456            }
457            ModelType::ModelPackSeg { segmentation } => {
458                self.decode_modelpack_seg_float(outputs, segmentation, output_masks)?;
459            }
460            ModelType::YoloDet { boxes } => {
461                self.decode_yolo_det_float(outputs, boxes, output_boxes)?;
462            }
463            ModelType::YoloSegDet { boxes, protos } => {
464                self.decode_yolo_segdet_float(outputs, boxes, protos, output_boxes, output_masks)?;
465            }
466            ModelType::YoloSplitDet { boxes, scores } => {
467                self.decode_yolo_split_det_float(outputs, boxes, scores, output_boxes)?;
468            }
469            ModelType::YoloSplitSegDet {
470                boxes,
471                scores,
472                mask_coeff,
473                protos,
474            } => {
475                self.decode_yolo_split_segdet_float(
476                    outputs,
477                    boxes,
478                    scores,
479                    mask_coeff,
480                    protos,
481                    output_boxes,
482                    output_masks,
483                )?;
484            }
485            ModelType::YoloEndToEndDet { boxes } => {
486                self.decode_yolo_end_to_end_det_float(outputs, boxes, output_boxes)?;
487            }
488            ModelType::YoloEndToEndSegDet { boxes, protos } => {
489                self.decode_yolo_end_to_end_segdet_float(
490                    outputs,
491                    boxes,
492                    protos,
493                    output_boxes,
494                    output_masks,
495                )?;
496            }
497            ModelType::YoloSplitEndToEndDet {
498                boxes,
499                scores,
500                classes,
501            } => {
502                self.decode_yolo_split_end_to_end_det_float(
503                    outputs,
504                    boxes,
505                    scores,
506                    classes,
507                    output_boxes,
508                )?;
509            }
510            ModelType::YoloSplitEndToEndSegDet {
511                boxes,
512                scores,
513                classes,
514                mask_coeff,
515                protos,
516            } => {
517                self.decode_yolo_split_end_to_end_segdet_float(
518                    outputs,
519                    boxes,
520                    scores,
521                    classes,
522                    mask_coeff,
523                    protos,
524                    output_boxes,
525                    output_masks,
526                )?;
527            }
528        }
529        Ok(())
530    }
531
532    /// Decodes quantized model outputs into detection boxes, returning raw
533    /// `ProtoData` for segmentation models instead of materialized masks.
534    ///
535    /// Returns `Ok(None)` for detection-only and ModelPack models (use
536    /// `decode_quantized` for those). Returns `Ok(Some(ProtoData))` for
537    /// YOLO segmentation models.
538    pub fn decode_quantized_proto(
539        &self,
540        outputs: &[ArrayViewDQuantized],
541        output_boxes: &mut Vec<DetectBox>,
542    ) -> Result<Option<ProtoData>, DecoderError> {
543        output_boxes.clear();
544        match &self.model_type {
545            // Detection-only and ModelPack variants: no proto data
546            ModelType::ModelPackSegDet { .. }
547            | ModelType::ModelPackSegDetSplit { .. }
548            | ModelType::ModelPackDet { .. }
549            | ModelType::ModelPackDetSplit { .. }
550            | ModelType::ModelPackSeg { .. }
551            | ModelType::YoloDet { .. }
552            | ModelType::YoloSplitDet { .. }
553            | ModelType::YoloEndToEndDet { .. }
554            | ModelType::YoloSplitEndToEndDet { .. } => Ok(None),
555
556            ModelType::YoloSegDet { boxes, protos } => {
557                let proto =
558                    self.decode_yolo_segdet_quantized_proto(outputs, boxes, protos, output_boxes)?;
559                Ok(Some(proto))
560            }
561            ModelType::YoloSplitSegDet {
562                boxes,
563                scores,
564                mask_coeff,
565                protos,
566            } => {
567                let proto = self.decode_yolo_split_segdet_quantized_proto(
568                    outputs,
569                    boxes,
570                    scores,
571                    mask_coeff,
572                    protos,
573                    output_boxes,
574                )?;
575                Ok(Some(proto))
576            }
577            ModelType::YoloEndToEndSegDet { boxes, protos } => {
578                let proto = self.decode_yolo_end_to_end_segdet_quantized_proto(
579                    outputs,
580                    boxes,
581                    protos,
582                    output_boxes,
583                )?;
584                Ok(Some(proto))
585            }
586            ModelType::YoloSplitEndToEndSegDet {
587                boxes,
588                scores,
589                classes,
590                mask_coeff,
591                protos,
592            } => {
593                let proto = self.decode_yolo_split_end_to_end_segdet_quantized_proto(
594                    outputs,
595                    boxes,
596                    scores,
597                    classes,
598                    mask_coeff,
599                    protos,
600                    output_boxes,
601                )?;
602                Ok(Some(proto))
603            }
604        }
605    }
606
607    /// Decodes floating-point model outputs into detection boxes, returning
608    /// raw `ProtoData` for segmentation models instead of materialized masks.
609    ///
610    /// Returns `Ok(None)` for detection-only and ModelPack models. Returns
611    /// `Ok(Some(ProtoData))` for YOLO segmentation models.
612    pub fn decode_float_proto<T>(
613        &self,
614        outputs: &[ArrayViewD<T>],
615        output_boxes: &mut Vec<DetectBox>,
616    ) -> Result<Option<ProtoData>, DecoderError>
617    where
618        T: Float + AsPrimitive<f32> + AsPrimitive<u8> + Send + Sync + 'static,
619        f32: AsPrimitive<T>,
620    {
621        output_boxes.clear();
622        match &self.model_type {
623            // Detection-only and ModelPack variants: no proto data
624            ModelType::ModelPackSegDet { .. }
625            | ModelType::ModelPackSegDetSplit { .. }
626            | ModelType::ModelPackDet { .. }
627            | ModelType::ModelPackDetSplit { .. }
628            | ModelType::ModelPackSeg { .. }
629            | ModelType::YoloDet { .. }
630            | ModelType::YoloSplitDet { .. }
631            | ModelType::YoloEndToEndDet { .. }
632            | ModelType::YoloSplitEndToEndDet { .. } => Ok(None),
633
634            ModelType::YoloSegDet { boxes, protos } => {
635                let proto =
636                    self.decode_yolo_segdet_float_proto(outputs, boxes, protos, output_boxes)?;
637                Ok(Some(proto))
638            }
639            ModelType::YoloSplitSegDet {
640                boxes,
641                scores,
642                mask_coeff,
643                protos,
644            } => {
645                let proto = self.decode_yolo_split_segdet_float_proto(
646                    outputs,
647                    boxes,
648                    scores,
649                    mask_coeff,
650                    protos,
651                    output_boxes,
652                )?;
653                Ok(Some(proto))
654            }
655            ModelType::YoloEndToEndSegDet { boxes, protos } => {
656                let proto = self.decode_yolo_end_to_end_segdet_float_proto(
657                    outputs,
658                    boxes,
659                    protos,
660                    output_boxes,
661                )?;
662                Ok(Some(proto))
663            }
664            ModelType::YoloSplitEndToEndSegDet {
665                boxes,
666                scores,
667                classes,
668                mask_coeff,
669                protos,
670            } => {
671                let proto = self.decode_yolo_split_end_to_end_segdet_float_proto(
672                    outputs,
673                    boxes,
674                    scores,
675                    classes,
676                    mask_coeff,
677                    protos,
678                    output_boxes,
679                )?;
680                Ok(Some(proto))
681            }
682        }
683    }
684}