1use anyhow::Context;
4use serde::Deserialize;
5use std::path::Path;
6
7#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
9#[serde(rename_all = "lowercase")]
10pub enum ImageInterpolation {
11 Nearest,
12 #[default]
13 Linear,
14 Cubic,
15 Area,
16 Lanczos4,
17}
18
19#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)]
21#[serde(rename_all = "lowercase")]
22pub enum ImageColorMode {
23 #[default]
24 Grayscale,
25 Rgb,
26}
27
28#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
32#[serde(untagged)]
33pub enum PaddingColor {
34 Gray(u8),
35 Rgb([u8; 3]),
36}
37
38impl Default for PaddingColor {
39 fn default() -> Self {
40 PaddingColor::Rgb([114, 114, 114])
41 }
42}
43
44impl PaddingColor {
45 pub fn as_gray(&self) -> u8 {
47 match self {
48 PaddingColor::Gray(v) => *v,
49 PaddingColor::Rgb([r, _, _]) => *r,
50 }
51 }
52
53 pub fn as_rgb(&self) -> [u8; 3] {
55 match self {
56 PaddingColor::Gray(v) => [*v, *v, *v],
57 PaddingColor::Rgb(c) => *c,
58 }
59 }
60}
61
62#[derive(Debug, Clone, Deserialize)]
64pub struct PlateConfig {
65 pub max_plate_slots: usize,
67 pub alphabet: String,
69 pub pad_char: char,
71 pub img_height: u32,
73 pub img_width: u32,
75 #[serde(default)]
77 pub keep_aspect_ratio: bool,
78 #[serde(default)]
80 pub interpolation: ImageInterpolation,
81 #[serde(default)]
83 pub image_color_mode: ImageColorMode,
84 #[serde(default)]
86 pub padding_color: PaddingColor,
87 #[serde(default)]
89 pub plate_regions: Option<Vec<String>>,
90}
91
92impl PlateConfig {
93 pub fn from_yaml(path: impl AsRef<Path>) -> anyhow::Result<Self> {
95 let text = std::fs::read_to_string(path.as_ref())
96 .with_context(|| format!("Cannot read config: {}", path.as_ref().display()))?;
97 let cfg: PlateConfig = serde_yml::from_str(&text)
98 .with_context(|| format!("Cannot parse config: {}", path.as_ref().display()))?;
99 Ok(cfg)
100 }
101
102 pub fn num_channels(&self) -> u32 {
104 match self.image_color_mode {
105 ImageColorMode::Rgb => 3,
106 ImageColorMode::Grayscale => 1,
107 }
108 }
109
110 pub fn has_region_recognition(&self) -> bool {
112 self.plate_regions
113 .as_ref()
114 .map_or(false, |v| !v.is_empty())
115 }
116
117 pub fn pad_idx(&self) -> usize {
119 self.alphabet
120 .chars()
121 .position(|c| c == self.pad_char)
122 .unwrap_or(0)
123 }
124}