Skip to main content

edgefirst_decoder/decoder/
config.rs

1// SPDX-FileCopyrightText: Copyright 2025 Au-Zone Technologies
2// SPDX-License-Identifier: Apache-2.0
3
4use super::configs::{self, DimName, QuantTuple};
5use serde::{Deserialize, Serialize};
6
7/// Used to represent the outputs in the model configuration.
8/// # Examples
9/// ```rust
10/// # use edgefirst_decoder::{DecoderBuilder, DecoderResult, ConfigOutputs};
11/// # fn main() -> DecoderResult<()> {
12/// let config_json = include_str!(concat!(env!("CARGO_MANIFEST_DIR"), "/../../testdata/modelpack_split.json"));
13/// let config: ConfigOutputs = serde_json::from_str(config_json)?;
14/// let decoder = DecoderBuilder::new().with_config(config).build()?;
15///
16/// # Ok(())
17/// # }
18#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default)]
19pub struct ConfigOutputs {
20    #[serde(default)]
21    pub outputs: Vec<ConfigOutput>,
22    /// NMS mode from config file. When present, overrides the builder's NMS
23    /// setting.
24    /// - `Some(Nms::ClassAgnostic)` — class-agnostic NMS: suppress overlapping
25    ///   boxes regardless of class
26    /// - `Some(Nms::ClassAware)` — class-aware NMS: only suppress boxes with
27    ///   the same class
28    /// - `None` — use builder default or skip NMS (user handles it externally)
29    #[serde(default, skip_serializing_if = "Option::is_none")]
30    pub nms: Option<configs::Nms>,
31    /// Decoder version for Ultralytics models. Determines the decoding
32    /// strategy.
33    /// - `Some(Yolo26)` — end-to-end model with embedded NMS
34    /// - `Some(Yolov5/Yolov8/Yolo11)` — traditional models requiring external
35    ///   NMS
36    /// - `None` — infer from other settings (legacy behavior)
37    #[serde(default, skip_serializing_if = "Option::is_none")]
38    pub decoder_version: Option<configs::DecoderVersion>,
39}
40
41#[derive(Debug, PartialEq, Serialize, Deserialize, Clone)]
42#[serde(tag = "type")]
43pub enum ConfigOutput {
44    #[serde(rename = "detection")]
45    Detection(configs::Detection),
46    #[serde(rename = "masks")]
47    Mask(configs::Mask),
48    #[serde(rename = "segmentation")]
49    Segmentation(configs::Segmentation),
50    #[serde(rename = "protos")]
51    Protos(configs::Protos),
52    #[serde(rename = "scores")]
53    Scores(configs::Scores),
54    #[serde(rename = "boxes")]
55    Boxes(configs::Boxes),
56    #[serde(rename = "mask_coefficients")]
57    MaskCoefficients(configs::MaskCoefficients),
58    #[serde(rename = "classes")]
59    Classes(configs::Classes),
60}
61
62#[derive(Debug, PartialEq, Clone)]
63pub enum ConfigOutputRef<'a> {
64    Detection(&'a configs::Detection),
65    Mask(&'a configs::Mask),
66    Segmentation(&'a configs::Segmentation),
67    Protos(&'a configs::Protos),
68    Scores(&'a configs::Scores),
69    Boxes(&'a configs::Boxes),
70    MaskCoefficients(&'a configs::MaskCoefficients),
71    Classes(&'a configs::Classes),
72}
73
74impl<'a> ConfigOutputRef<'a> {
75    pub(super) fn decoder(&self) -> configs::DecoderType {
76        match self {
77            ConfigOutputRef::Detection(v) => v.decoder,
78            ConfigOutputRef::Mask(v) => v.decoder,
79            ConfigOutputRef::Segmentation(v) => v.decoder,
80            ConfigOutputRef::Protos(v) => v.decoder,
81            ConfigOutputRef::Scores(v) => v.decoder,
82            ConfigOutputRef::Boxes(v) => v.decoder,
83            ConfigOutputRef::MaskCoefficients(v) => v.decoder,
84            ConfigOutputRef::Classes(v) => v.decoder,
85        }
86    }
87
88    pub(super) fn dshape(&self) -> &[(DimName, usize)] {
89        match self {
90            ConfigOutputRef::Detection(v) => &v.dshape,
91            ConfigOutputRef::Mask(v) => &v.dshape,
92            ConfigOutputRef::Segmentation(v) => &v.dshape,
93            ConfigOutputRef::Protos(v) => &v.dshape,
94            ConfigOutputRef::Scores(v) => &v.dshape,
95            ConfigOutputRef::Boxes(v) => &v.dshape,
96            ConfigOutputRef::MaskCoefficients(v) => &v.dshape,
97            ConfigOutputRef::Classes(v) => &v.dshape,
98        }
99    }
100}
101
102impl<'a> From<&'a configs::Detection> for ConfigOutputRef<'a> {
103    /// Converts from references of config structs to ConfigOutputRef
104    /// # Examples
105    /// ```rust
106    /// # use edgefirst_decoder::{configs, ConfigOutputRef};
107    /// let detection_config = configs::Detection {
108    ///     anchors: None,
109    ///     decoder: configs::DecoderType::Ultralytics,
110    ///     quantization: None,
111    ///     shape: vec![1, 84, 8400],
112    ///     dshape: Vec::new(),
113    ///     normalized: Some(true),
114    /// };
115    /// let output: ConfigOutputRef = (&detection_config).into();
116    /// ```
117    fn from(v: &'a configs::Detection) -> ConfigOutputRef<'a> {
118        ConfigOutputRef::Detection(v)
119    }
120}
121
122impl<'a> From<&'a configs::Mask> for ConfigOutputRef<'a> {
123    /// Converts from references of config structs to ConfigOutputRef
124    /// # Examples
125    /// ```rust
126    /// # use edgefirst_decoder::{configs, ConfigOutputRef};
127    /// let mask = configs::Mask {
128    ///     decoder: configs::DecoderType::ModelPack,
129    ///     quantization: None,
130    ///     shape: vec![1, 160, 160, 1],
131    ///     dshape: Vec::new(),
132    /// };
133    /// let output: ConfigOutputRef = (&mask).into();
134    /// ```
135    fn from(v: &'a configs::Mask) -> ConfigOutputRef<'a> {
136        ConfigOutputRef::Mask(v)
137    }
138}
139
140impl<'a> From<&'a configs::Segmentation> for ConfigOutputRef<'a> {
141    /// Converts from references of config structs to ConfigOutputRef
142    /// # Examples
143    /// ```rust
144    /// # use edgefirst_decoder::{configs, ConfigOutputRef};
145    /// let seg = configs::Segmentation {
146    ///     decoder: configs::DecoderType::ModelPack,
147    ///     quantization: None,
148    ///     shape: vec![1, 160, 160, 3],
149    ///     dshape: Vec::new(),
150    /// };
151    /// let output: ConfigOutputRef = (&seg).into();
152    /// ```
153    fn from(v: &'a configs::Segmentation) -> ConfigOutputRef<'a> {
154        ConfigOutputRef::Segmentation(v)
155    }
156}
157
158impl<'a> From<&'a configs::Protos> for ConfigOutputRef<'a> {
159    /// Converts from references of config structs to ConfigOutputRef
160    /// # Examples
161    /// ```rust
162    /// # use edgefirst_decoder::{configs, ConfigOutputRef};
163    /// let protos = configs::Protos {
164    ///     decoder: configs::DecoderType::Ultralytics,
165    ///     quantization: None,
166    ///     shape: vec![1, 160, 160, 32],
167    ///     dshape: Vec::new(),
168    /// };
169    /// let output: ConfigOutputRef = (&protos).into();
170    /// ```
171    fn from(v: &'a configs::Protos) -> ConfigOutputRef<'a> {
172        ConfigOutputRef::Protos(v)
173    }
174}
175
176impl<'a> From<&'a configs::Scores> for ConfigOutputRef<'a> {
177    /// Converts from references of config structs to ConfigOutputRef
178    /// # Examples
179    /// ```rust
180    /// # use edgefirst_decoder::{configs, ConfigOutputRef};
181    /// let scores = configs::Scores {
182    ///     decoder: configs::DecoderType::Ultralytics,
183    ///     quantization: None,
184    ///     shape: vec![1, 40, 8400],
185    ///     dshape: Vec::new(),
186    /// };
187    /// let output: ConfigOutputRef = (&scores).into();
188    /// ```
189    fn from(v: &'a configs::Scores) -> ConfigOutputRef<'a> {
190        ConfigOutputRef::Scores(v)
191    }
192}
193
194impl<'a> From<&'a configs::Boxes> for ConfigOutputRef<'a> {
195    /// Converts from references of config structs to ConfigOutputRef
196    /// # Examples
197    /// ```rust
198    /// # use edgefirst_decoder::{configs, ConfigOutputRef};
199    /// let boxes = configs::Boxes {
200    ///     decoder: configs::DecoderType::Ultralytics,
201    ///     quantization: None,
202    ///     shape: vec![1, 4, 8400],
203    ///     dshape: Vec::new(),
204    ///     normalized: Some(true),
205    /// };
206    /// let output: ConfigOutputRef = (&boxes).into();
207    /// ```
208    fn from(v: &'a configs::Boxes) -> ConfigOutputRef<'a> {
209        ConfigOutputRef::Boxes(v)
210    }
211}
212
213impl<'a> From<&'a configs::MaskCoefficients> for ConfigOutputRef<'a> {
214    /// Converts from references of config structs to ConfigOutputRef
215    /// # Examples
216    /// ```rust
217    /// # use edgefirst_decoder::{configs, ConfigOutputRef};
218    /// let mask_coefficients = configs::MaskCoefficients {
219    ///     decoder: configs::DecoderType::Ultralytics,
220    ///     quantization: None,
221    ///     shape: vec![1, 32, 8400],
222    ///     dshape: Vec::new(),
223    /// };
224    /// let output: ConfigOutputRef = (&mask_coefficients).into();
225    /// ```
226    fn from(v: &'a configs::MaskCoefficients) -> ConfigOutputRef<'a> {
227        ConfigOutputRef::MaskCoefficients(v)
228    }
229}
230
231impl<'a> From<&'a configs::Classes> for ConfigOutputRef<'a> {
232    fn from(v: &'a configs::Classes) -> ConfigOutputRef<'a> {
233        ConfigOutputRef::Classes(v)
234    }
235}
236
237impl ConfigOutput {
238    /// Returns the shape of the output.
239    ///
240    /// # Examples
241    /// ```rust
242    /// # use edgefirst_decoder::{configs, ConfigOutput};
243    /// let detection_config = configs::Detection {
244    ///     anchors: None,
245    ///     decoder: configs::DecoderType::Ultralytics,
246    ///     quantization: None,
247    ///     shape: vec![1, 84, 8400],
248    ///     dshape: Vec::new(),
249    ///     normalized: Some(true),
250    /// };
251    /// let output = ConfigOutput::Detection(detection_config);
252    /// assert_eq!(output.shape(), &[1, 84, 8400]);
253    /// ```
254    pub fn shape(&self) -> &[usize] {
255        match self {
256            ConfigOutput::Detection(detection) => &detection.shape,
257            ConfigOutput::Mask(mask) => &mask.shape,
258            ConfigOutput::Segmentation(segmentation) => &segmentation.shape,
259            ConfigOutput::Scores(scores) => &scores.shape,
260            ConfigOutput::Boxes(boxes) => &boxes.shape,
261            ConfigOutput::Protos(protos) => &protos.shape,
262            ConfigOutput::MaskCoefficients(mask_coefficients) => &mask_coefficients.shape,
263            ConfigOutput::Classes(classes) => &classes.shape,
264        }
265    }
266
267    /// Returns the decoder type of the output.
268    ///    
269    /// # Examples
270    /// ```rust
271    /// # use edgefirst_decoder::{configs, ConfigOutput};
272    /// let detection_config = configs::Detection {
273    ///     anchors: None,
274    ///     decoder: configs::DecoderType::Ultralytics,
275    ///     quantization: None,
276    ///     shape: vec![1, 84, 8400],
277    ///     dshape: Vec::new(),
278    ///     normalized: Some(true),
279    /// };
280    /// let output = ConfigOutput::Detection(detection_config);
281    /// assert_eq!(output.decoder(), &configs::DecoderType::Ultralytics);
282    /// ```
283    pub fn decoder(&self) -> &configs::DecoderType {
284        match self {
285            ConfigOutput::Detection(detection) => &detection.decoder,
286            ConfigOutput::Mask(mask) => &mask.decoder,
287            ConfigOutput::Segmentation(segmentation) => &segmentation.decoder,
288            ConfigOutput::Scores(scores) => &scores.decoder,
289            ConfigOutput::Boxes(boxes) => &boxes.decoder,
290            ConfigOutput::Protos(protos) => &protos.decoder,
291            ConfigOutput::MaskCoefficients(mask_coefficients) => &mask_coefficients.decoder,
292            ConfigOutput::Classes(classes) => &classes.decoder,
293        }
294    }
295
296    /// Returns the quantization of the output.
297    ///
298    /// # Examples
299    /// ```rust
300    /// # use edgefirst_decoder::{configs, ConfigOutput};
301    /// let detection_config = configs::Detection {
302    ///   anchors: None,
303    ///   decoder: configs::DecoderType::Ultralytics,
304    ///   quantization: Some(configs::QuantTuple(0.012345, 26)),
305    ///   shape: vec![1, 84, 8400],
306    ///   dshape: Vec::new(),
307    ///   normalized: Some(true),
308    /// };
309    /// let output = ConfigOutput::Detection(detection_config);
310    /// assert_eq!(output.quantization(),
311    /// Some(configs::QuantTuple(0.012345,26))); ```
312    pub fn quantization(&self) -> Option<QuantTuple> {
313        match self {
314            ConfigOutput::Detection(detection) => detection.quantization,
315            ConfigOutput::Mask(mask) => mask.quantization,
316            ConfigOutput::Segmentation(segmentation) => segmentation.quantization,
317            ConfigOutput::Scores(scores) => scores.quantization,
318            ConfigOutput::Boxes(boxes) => boxes.quantization,
319            ConfigOutput::Protos(protos) => protos.quantization,
320            ConfigOutput::MaskCoefficients(mask_coefficients) => mask_coefficients.quantization,
321            ConfigOutput::Classes(classes) => classes.quantization,
322        }
323    }
324}