Skip to main content

edgefirst_decoder/model_config/
provenance.rs

1// SPDX-FileCopyrightText: Copyright 2026 Au-Zone Technologies
2// SPDX-License-Identifier: Apache-2.0
3
4//! Where each resolved value came from, and the model family it implies.
5
6use std::fmt;
7
8edgefirst_tensor::ef_vocabulary! {
9    /// The source that supplied a resolved value. The code is the one the C
10    /// `EF_CONFIG_SOURCE_*` enumerators and the Python `ConfigSource` enum
11    /// use.
12    #[non_exhaustive]
13    pub enum ConfigSource {
14        /// `edgefirst.json` embedded in the model file.
15        EmbeddedEdgefirst = 0, "embedded_edgefirst", EMBEDDED_EDGEFIRST,
16        /// `edgefirst.json` (and `labels.txt`) beside the model file.
17        SidecarEdgefirst = 1, "sidecar_edgefirst", SIDECAR_EDGEFIRST,
18        /// Hugging Face `config.json` / `preprocessor_config.json` embedded
19        /// in the model file.
20        EmbeddedHf = 2, "embedded_hf", EMBEDDED_HF,
21        /// Hugging Face `config.json` / `preprocessor_config.json` beside the
22        /// model file.
23        SidecarHf = 3, "sidecar_hf", SIDECAR_HF,
24        /// Ultralytics export metadata, through
25        /// [`infer_ultralytics_schema`](crate::infer_ultralytics_schema).
26        UltralyticsMetadata = 4, "ultralytics_metadata", ULTRALYTICS_METADATA,
27        /// Guessed from the model's input and output names and shapes.
28        Heuristic = 5, "heuristic", HEURISTIC,
29        /// The documented default.
30        Default = 6, "default", DEFAULT,
31        /// A caller override ([`ModelOverrides`](super::ModelOverrides)).
32        Override = 7, "override", OVERRIDE,
33    }
34    #[doc(hidden)]
35    pub mod config_source_code;
36}
37
38edgefirst_tensor::ef_vocabulary! {
39    /// The family a model's configuration identifies. It selects the
40    /// convention a bare `letterbox` override follows. The code is the one
41    /// the C `EF_MODEL_FAMILY_*` enumerators and the Python `ModelFamily`
42    /// enum use.
43    #[non_exhaustive]
44    pub enum ModelFamily {
45        /// Described by an `edgefirst.json`.
46        EdgeFirst = 0, "edgefirst", EDGEFIRST,
47        /// An Ultralytics export, described by its metadata.
48        Ultralytics = 1, "ultralytics", ULTRALYTICS,
49        /// A Hugging Face export, described by its configs or recognised by
50        /// its input and output names.
51        HuggingFace = 2, "huggingface", HUGGINGFACE,
52        /// Configured only by overrides.
53        Unknown = 3, "unknown", UNKNOWN,
54    }
55    #[doc(hidden)]
56    pub mod model_family_code;
57}
58
59edgefirst_tensor::ef_vocabulary! {
60    /// What a model's outputs decode to. The code is the one the C
61    /// `EF_MODEL_TASK_*` enumerators and the Python `ModelTask` enum use;
62    /// the string is the `task` override grammar.
63    #[non_exhaustive]
64    pub enum ModelTask {
65        /// Bounding boxes.
66        Detect = 0, "detect", DETECT,
67        /// Bounding boxes with instance masks.
68        Segment = 1, "segment", SEGMENT,
69        /// A per-pixel class map.
70        Semantic = 2, "semantic", SEMANTIC,
71    }
72    #[doc(hidden)]
73    pub mod model_task_code;
74}
75
76edgefirst_tensor::ef_vocabulary! {
77    /// A resolved value whose source is recorded. The code is the one the C
78    /// `EF_CONFIG_FIELD_*` enumerators and the Python `ConfigField` enum use;
79    /// the string is the name [`ResolvedModelConfig::describe`] prints.
80    ///
81    /// [`ResolvedModelConfig::describe`]: super::ResolvedModelConfig::describe
82    #[derive(PartialOrd, Ord)]
83    #[non_exhaustive]
84    pub enum ConfigField {
85        /// Input normalization.
86        Normalization = 0, "normalize", NORMALIZATION,
87        /// Resize mode.
88        Resize = 1, "resize", RESIZE,
89        /// Letterbox placement.
90        Placement = 2, "placement", PLACEMENT,
91        /// Letterbox pad value and the space it is expressed in.
92        Pad = 3, "pad", PAD,
93        /// Resampling filter.
94        Interpolation = 4, "interpolation", INTERPOLATION,
95        /// Model task.
96        Task = 5, "task", TASK,
97        /// Semantic decode mode.
98        DecodeMode = 6, "decode", DECODE_MODE,
99        /// Class labels.
100        Labels = 7, "labels", LABELS,
101        /// Model input size.
102        Size = 8, "size", SIZE,
103    }
104    #[doc(hidden)]
105    pub mod config_field_code;
106}
107
108/// The source of one resolved value, with an optional detail naming the file
109/// or the reason for a guess.
110///
111/// Its [`Display`](fmt::Display) form is the bracketed text of
112/// [`ResolvedModelConfig::describe`](super::ResolvedModelConfig::describe):
113///
114/// ```
115/// use edgefirst_decoder::model_config::{ConfigSource, Provenance};
116///
117/// let p = Provenance::new(ConfigSource::Heuristic, Some("SegFormer default"));
118/// assert_eq!(p.to_string(), "guess: SegFormer default");
119/// assert_eq!(Provenance::new(ConfigSource::Override, None).to_string(), "override");
120/// ```
121#[derive(Debug, Clone, PartialEq, Eq)]
122#[non_exhaustive]
123pub struct Provenance {
124    /// The source that supplied the value.
125    pub source: ConfigSource,
126    /// The file or field it was read from, or the reason for a guess.
127    pub detail: Option<String>,
128}
129
130impl Provenance {
131    /// A provenance record.
132    pub fn new(source: ConfigSource, detail: Option<&str>) -> Self {
133        Self {
134            source,
135            detail: detail.map(str::to_owned),
136        }
137    }
138}
139
140impl fmt::Display for Provenance {
141    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
142        let detail = self.detail.as_deref();
143        match (self.source, detail) {
144            (ConfigSource::Heuristic, Some(d)) => write!(f, "guess: {d}"),
145            (ConfigSource::Heuristic, None) => f.write_str("guess"),
146            (ConfigSource::Default, Some(d)) => write!(f, "default: {d}"),
147            (ConfigSource::Default, None) => f.write_str("default"),
148            (ConfigSource::Override, _) => f.write_str("override"),
149            (ConfigSource::UltralyticsMetadata, _) => f.write_str("ultralytics metadata"),
150            (_, Some(d)) => f.write_str(d),
151            (ConfigSource::EmbeddedEdgefirst, None) => f.write_str("edgefirst.json"),
152            (ConfigSource::SidecarEdgefirst, None) => f.write_str("sidecar edgefirst.json"),
153            (ConfigSource::EmbeddedHf, None) => f.write_str("embedded hugging face config"),
154            (ConfigSource::SidecarHf, None) => f.write_str("sidecar hugging face config"),
155        }
156    }
157}
158
159#[cfg(test)]
160mod tests {
161    use super::*;
162
163    #[test]
164    fn vocabularies_round_trip_codes_and_strings() {
165        for &s in ConfigSource::all() {
166            assert_eq!(ConfigSource::from_code(s.code()), Some(s));
167            assert_eq!(ConfigSource::from_str_code(s.as_str()), Some(s));
168        }
169        for &m in ModelFamily::all() {
170            assert_eq!(ModelFamily::from_str_code(m.as_str()), Some(m));
171        }
172        for &t in ModelTask::all() {
173            assert_eq!(ModelTask::from_str_code(t.as_str()), Some(t));
174        }
175        for (i, &c) in ConfigField::all().iter().enumerate() {
176            assert_eq!(c.code() as usize, i, "fields describe in code order");
177            assert_eq!(ConfigField::from_str_code(c.as_str()), Some(c));
178        }
179    }
180
181    #[test]
182    fn provenance_displays_bracket_text() {
183        let p = |s, d| Provenance::new(s, d).to_string();
184        assert_eq!(
185            p(ConfigSource::SidecarHf, Some("preprocessor_config.json")),
186            "preprocessor_config.json"
187        );
188        assert_eq!(
189            p(ConfigSource::Heuristic, Some("SegFormer default")),
190            "guess: SegFormer default"
191        );
192        assert_eq!(p(ConfigSource::Override, None), "override");
193        assert_eq!(p(ConfigSource::Default, None), "default");
194        assert_eq!(p(ConfigSource::EmbeddedEdgefirst, None), "edgefirst.json");
195        assert_eq!(
196            p(ConfigSource::UltralyticsMetadata, None),
197            "ultralytics metadata"
198        );
199    }
200}