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