1use std::collections::HashMap;
19use std::fmt::Display;
20
21use serde::{Deserialize, Serialize};
22
23pub fn deserialize_dshape<'de, D>(deserializer: D) -> Result<Vec<(DimName, usize)>, D::Error>
29where
30 D: serde::Deserializer<'de>,
31{
32 #[derive(Deserialize)]
33 #[serde(untagged)]
34 enum DShapeItem {
35 Tuple(DimName, usize),
36 Map(HashMap<DimName, usize>),
37 }
38
39 let items: Vec<DShapeItem> = Vec::deserialize(deserializer)?;
40 items
41 .into_iter()
42 .map(|item| match item {
43 DShapeItem::Tuple(name, size) => Ok((name, size)),
44 DShapeItem::Map(map) => {
45 if map.len() != 1 {
46 return Err(serde::de::Error::custom(
47 "dshape map entry must have exactly one key",
48 ));
49 }
50 let (name, size) = map.into_iter().next().unwrap();
51 Ok((name, size))
52 }
53 })
54 .collect()
55}
56
57#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Copy)]
58pub struct QuantTuple(pub f32, pub i32);
59impl From<QuantTuple> for (f32, i32) {
60 fn from(value: QuantTuple) -> Self {
61 (value.0, value.1)
62 }
63}
64
65impl From<(f32, i32)> for QuantTuple {
66 fn from(value: (f32, i32)) -> Self {
67 QuantTuple(value.0, value.1)
68 }
69}
70
71#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default)]
72pub struct Segmentation {
73 #[serde(default)]
74 pub decoder: DecoderType,
75 #[serde(default)]
76 pub quantization: Option<QuantTuple>,
77 #[serde(default)]
78 pub shape: Vec<usize>,
79 #[serde(default, deserialize_with = "deserialize_dshape")]
80 pub dshape: Vec<(DimName, usize)>,
81}
82
83#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default)]
84pub struct Protos {
85 #[serde(default)]
86 pub decoder: DecoderType,
87 #[serde(default)]
88 pub quantization: Option<QuantTuple>,
89 #[serde(default)]
90 pub shape: Vec<usize>,
91 #[serde(default, deserialize_with = "deserialize_dshape")]
92 pub dshape: Vec<(DimName, usize)>,
93}
94
95#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default)]
96pub struct MaskCoefficients {
97 #[serde(default)]
98 pub decoder: DecoderType,
99 #[serde(default)]
100 pub quantization: Option<QuantTuple>,
101 #[serde(default)]
102 pub shape: Vec<usize>,
103 #[serde(default, deserialize_with = "deserialize_dshape")]
104 pub dshape: Vec<(DimName, usize)>,
105}
106
107#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default)]
108pub struct Mask {
109 #[serde(default)]
110 pub decoder: DecoderType,
111 #[serde(default)]
112 pub quantization: Option<QuantTuple>,
113 #[serde(default)]
114 pub shape: Vec<usize>,
115 #[serde(default, deserialize_with = "deserialize_dshape")]
116 pub dshape: Vec<(DimName, usize)>,
117}
118
119#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default)]
120pub struct Detection {
121 #[serde(default)]
122 pub anchors: Option<Vec<[f32; 2]>>,
123 #[serde(default)]
124 pub decoder: DecoderType,
125 #[serde(default)]
126 pub quantization: Option<QuantTuple>,
127 #[serde(default)]
128 pub shape: Vec<usize>,
129 #[serde(default, deserialize_with = "deserialize_dshape")]
130 pub dshape: Vec<(DimName, usize)>,
131 #[serde(default)]
138 pub normalized: Option<bool>,
139}
140
141#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default)]
142pub struct Scores {
143 #[serde(default)]
144 pub decoder: DecoderType,
145 #[serde(default)]
146 pub quantization: Option<QuantTuple>,
147 #[serde(default)]
148 pub shape: Vec<usize>,
149 #[serde(default, deserialize_with = "deserialize_dshape")]
150 pub dshape: Vec<(DimName, usize)>,
151}
152
153#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default)]
154pub struct Boxes {
155 #[serde(default)]
156 pub decoder: DecoderType,
157 #[serde(default)]
158 pub quantization: Option<QuantTuple>,
159 #[serde(default)]
160 pub shape: Vec<usize>,
161 #[serde(default, deserialize_with = "deserialize_dshape")]
162 pub dshape: Vec<(DimName, usize)>,
163 #[serde(default)]
170 pub normalized: Option<bool>,
171}
172
173#[derive(Debug, PartialEq, Serialize, Deserialize, Clone, Default)]
174pub struct Classes {
175 #[serde(default)]
176 pub decoder: DecoderType,
177 #[serde(default)]
178 pub quantization: Option<QuantTuple>,
179 #[serde(default)]
180 pub shape: Vec<usize>,
181 #[serde(default, deserialize_with = "deserialize_dshape")]
182 pub dshape: Vec<(DimName, usize)>,
183}
184
185edgefirst_tensor::ef_vocabulary! {
186 #[derive(Serialize, Deserialize)]
189 pub enum DimName {
190 #[serde(rename = "batch")]
191 Batch = 0, "batch", BATCH,
192 #[serde(rename = "height")]
193 Height = 1, "height", HEIGHT,
194 #[serde(rename = "width")]
195 Width = 2, "width", WIDTH,
196 #[serde(rename = "num_classes")]
197 NumClasses = 3, "num_classes", NUM_CLASSES,
198 #[serde(rename = "num_features")]
199 NumFeatures = 4, "num_features", NUM_FEATURES,
200 #[serde(rename = "num_boxes")]
201 NumBoxes = 5, "num_boxes", NUM_BOXES,
202 #[serde(rename = "num_protos")]
203 NumProtos = 6, "num_protos", NUM_PROTOS,
204 #[serde(rename = "num_anchors_x_features")]
205 NumAnchorsXFeatures = 7, "num_anchors_x_features", NUM_ANCHORS_X_FEATURES,
206 #[serde(rename = "padding")]
207 Padding = 8, "padding", PADDING,
208 #[serde(rename = "box_coords")]
209 BoxCoords = 9, "box_coords", BOX_COORDS,
210 #[serde(rename = "num_queries")]
213 NumQueries = 11, "num_queries", NUM_QUERIES,
214 #[serde(other)]
221 Unknown = 10, "unknown", UNKNOWN,
222 }
223 #[doc(hidden)]
224 pub mod dim_name_code;
225}
226
227impl Display for DimName {
228 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
238 f.write_str(self.as_str())
239 }
240}
241
242edgefirst_tensor::ef_vocabulary! {
243 #[derive(Serialize, Deserialize, Default)]
246 pub enum DecoderType {
247 #[default]
248 #[serde(rename = "ultralytics", alias = "yolov8")]
249 Ultralytics = 0, "ultralytics", ULTRALYTICS,
250 #[serde(rename = "modelpack")]
251 ModelPack = 1, "modelpack", MODELPACK,
252 #[serde(rename = "semantic")]
254 Semantic = 2, "semantic", SEMANTIC,
255 }
256 #[doc(hidden)]
257 pub mod decoder_type_code;
258}
259
260edgefirst_tensor::ef_vocabulary! {
261 #[derive(Serialize, Deserialize)]
275 pub enum DecoderVersion {
276 #[serde(rename = "yolov5")]
278 Yolov5 = 0, "yolov5", YOLOV5,
279 #[serde(rename = "yolov8")]
281 Yolov8 = 1, "yolov8", YOLOV8,
282 #[serde(rename = "yolo11")]
284 Yolo11 = 2, "yolo11", YOLO11,
285 #[serde(rename = "yolo26")]
288 Yolo26 = 3, "yolo26", YOLO26,
289 }
290 #[doc(hidden)]
291 pub mod decoder_version_code;
292}
293
294impl DecoderVersion {
295 pub fn is_end_to_end(&self) -> bool {
298 matches!(self, DecoderVersion::Yolo26)
299 }
300}
301
302edgefirst_tensor::ef_vocabulary! {
303 #[derive(Serialize, Deserialize, Default)]
317 #[serde(rename_all = "snake_case")]
318 pub enum Nms {
319 Auto = 1, "auto", AUTO,
325 #[default]
328 ClassAware = 2, "class_aware", CLASS_AWARE,
329 ClassAgnostic = 3, "class_agnostic", CLASS_AGNOSTIC,
331 }
332 #[doc(hidden)]
333 pub mod nms_code;
334}
335
336edgefirst_tensor::ef_vocabulary! {
337 pub enum OutputType {
341 Detection = 0, "detection", DETECTION,
342 Boxes = 1, "boxes", BOXES,
343 Scores = 2, "scores", SCORES,
344 Protos = 3, "protos", PROTOS,
345 Segmentation = 4, "segmentation", SEGMENTATION,
346 MaskCoefficients = 5, "mask_coefs", MASK_COEFFICIENTS,
347 Mask = 6, "masks", MASK,
348 Classes = 7, "classes", CLASSES,
349 }
350 #[doc(hidden)]
351 pub mod output_type_code;
352}
353
354pub const NMS_OFF_CODE: u32 = 0;
356
357impl Nms {
358 pub const fn option_code(nms: Option<Nms>) -> u32 {
360 match nms {
361 Some(n) => n.code(),
362 None => NMS_OFF_CODE,
363 }
364 }
365
366 pub fn from_option_code(code: u32) -> Option<Option<Nms>> {
369 if code == NMS_OFF_CODE {
370 Some(None)
371 } else {
372 Nms::from_code(code).map(Some)
373 }
374 }
375}
376
377#[derive(Debug, Clone, PartialEq)]
378pub enum ModelType {
379 ModelPackSegDet {
380 boxes: Boxes,
381 scores: Scores,
382 segmentation: Segmentation,
383 },
384 ModelPackSegDetSplit {
385 detection: Vec<Detection>,
386 segmentation: Segmentation,
387 },
388 ModelPackDet {
389 boxes: Boxes,
390 scores: Scores,
391 },
392 ModelPackDetSplit {
393 detection: Vec<Detection>,
394 },
395 ModelPackSeg {
396 segmentation: Segmentation,
397 },
398 YoloDet {
399 boxes: Detection,
400 },
401 YoloSegDet {
402 boxes: Detection,
403 protos: Protos,
404 },
405 YoloSplitDet {
406 boxes: Boxes,
407 scores: Scores,
408 },
409 YoloSplitSegDet {
410 boxes: Boxes,
411 scores: Scores,
412 mask_coeff: MaskCoefficients,
413 protos: Protos,
414 },
415 YoloSegDet2Way {
422 boxes: Detection,
423 mask_coeff: MaskCoefficients,
424 protos: Protos,
425 },
426 YoloEndToEndDet {
430 boxes: Detection,
431 },
432 YoloEndToEndSegDet {
436 boxes: Detection,
437 protos: Protos,
438 },
439 YoloSplitEndToEndDet {
443 boxes: Boxes,
444 scores: Scores,
445 classes: Classes,
446 },
447 YoloSplitEndToEndSegDet {
450 boxes: Boxes,
451 scores: Scores,
452 classes: Classes,
453 mask_coeff: MaskCoefficients,
454 protos: Protos,
455 },
456 PerScale,
462 Semantic,
465}
466
467pub(crate) fn validate_dshape(
471 role_name: &str,
472 shape: &[usize],
473 dshape: &[(DimName, usize)],
474) -> Result<(), crate::DecoderError> {
475 use crate::DecoderError;
476 if dshape.is_empty() {
477 return Ok(());
479 }
480
481 if dshape.len() != shape.len() {
482 return Err(DecoderError::InvalidConfig(format!(
483 "{role_name} output: dshape has {} entries but shape has {} entries \
484 — shape and dshape must describe the same axes in the same order",
485 dshape.len(),
486 shape.len()
487 )));
488 }
489
490 for (i, (name, size)) in dshape.iter().enumerate() {
491 if *size != shape[i] {
492 return Err(DecoderError::InvalidConfig(format!(
493 "{role_name} output: dshape[{i}] = ({name}, {size}) does not \
494 match shape[{i}] = {} — declare shape and dshape in the \
495 same physical order (outermost axis first)",
496 shape[i]
497 )));
498 }
499 if *name == DimName::Padding && *size != 1 {
501 return Err(DecoderError::InvalidConfig(format!(
502 "{role_name} output: `padding` axis must have size 1, got {size}"
503 )));
504 }
505 if *name == DimName::BoxCoords && *size != 4 {
506 return Err(DecoderError::InvalidConfig(format!(
507 "{role_name} output: `box_coords` axis must have size 4, got {size}"
508 )));
509 }
510 }
511
512 for i in 0..dshape.len() {
516 for j in (i + 1)..dshape.len() {
517 if dshape[i].0 == dshape[j].0 {
518 return Err(DecoderError::InvalidConfig(format!(
519 "{role_name} output: dshape axis `{}` appears at \
520 both index {i} and {j}",
521 dshape[i].0
522 )));
523 }
524 }
525 }
526
527 Ok(())
528}
529
530#[cfg(test)]
531mod vocabulary_tests {
532 use super::*;
533
534 #[test]
535 fn nms_off_is_code_zero_and_no_variant_claims_it() {
536 assert_eq!(Nms::from_code(NMS_OFF_CODE), None);
537 assert_eq!(Nms::option_code(None), NMS_OFF_CODE);
538 assert_eq!(Nms::from_option_code(NMS_OFF_CODE), Some(None));
539 for &n in Nms::all() {
540 assert_ne!(n.code(), NMS_OFF_CODE);
541 assert_eq!(Nms::from_option_code(n.code()), Some(Some(n)));
542 }
543 assert_eq!(Nms::from_option_code(99), None);
544 }
545
546 #[test]
547 fn serde_names_match_the_vocabulary_strings() {
548 for &n in Nms::all() {
549 assert_eq!(serde_json::to_value(n).unwrap(), n.as_str());
550 }
551 for &d in DecoderType::all() {
552 assert_eq!(serde_json::to_value(d).unwrap(), d.as_str());
553 }
554 for &v in DecoderVersion::all() {
555 assert_eq!(serde_json::to_value(v).unwrap(), v.as_str());
556 }
557 for &d in DimName::all() {
558 assert_eq!(d.to_string(), d.as_str());
559 if d != DimName::Unknown {
560 assert_eq!(serde_json::to_value(d).unwrap(), d.as_str());
561 }
562 }
563 }
564}