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}