1use box_image_pyramid::PyramidParams;
2use chess_corners_core::{
3 CenterOfMassConfig, ChessParams, ForstnerConfig, RadonDetectorParams, RadonPeakConfig,
4 RefinerKind, SaddlePointConfig,
5};
6use serde::{Deserialize, Serialize};
7
8use crate::multiscale::CoarseToFineParams;
9use crate::upscale::UpscaleConfig;
10
11#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "snake_case")]
20#[non_exhaustive]
21pub enum DetectorMode {
22 #[default]
23 Canonical,
24 Broad,
25 Radon,
26}
27
28#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
29#[serde(rename_all = "snake_case")]
30#[non_exhaustive]
31pub enum DescriptorMode {
32 #[default]
33 FollowDetector,
34 Canonical,
35 Broad,
36}
37
38#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
39#[serde(rename_all = "snake_case")]
40#[non_exhaustive]
41pub enum ThresholdMode {
42 #[default]
43 Relative,
44 Absolute,
45}
46
47#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "snake_case")]
49#[non_exhaustive]
50pub enum RefinementMethod {
51 #[default]
52 CenterOfMass,
53 Forstner,
54 SaddlePoint,
55 RadonPeak,
56}
57
58#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
59#[serde(default)]
60#[non_exhaustive]
61pub struct RefinerConfig {
62 pub kind: RefinementMethod,
63 pub center_of_mass: CenterOfMassConfig,
64 pub forstner: ForstnerConfig,
65 pub saddle_point: SaddlePointConfig,
66 pub radon_peak: RadonPeakConfig,
67}
68
69impl RefinerConfig {
70 #[allow(clippy::too_many_arguments)]
72 pub fn build(
73 kind: RefinementMethod,
74 center_of_mass: CenterOfMassConfig,
75 forstner: ForstnerConfig,
76 saddle_point: SaddlePointConfig,
77 radon_peak: RadonPeakConfig,
78 ) -> Self {
79 Self {
80 kind,
81 center_of_mass,
82 forstner,
83 saddle_point,
84 radon_peak,
85 }
86 }
87
88 pub fn center_of_mass() -> Self {
92 Self {
93 kind: RefinementMethod::CenterOfMass,
94 ..Self::default()
95 }
96 }
97
98 pub fn forstner() -> Self {
102 Self {
103 kind: RefinementMethod::Forstner,
104 ..Self::default()
105 }
106 }
107
108 pub fn saddle_point() -> Self {
112 Self {
113 kind: RefinementMethod::SaddlePoint,
114 ..Self::default()
115 }
116 }
117
118 pub fn radon_peak() -> Self {
122 Self {
123 kind: RefinementMethod::RadonPeak,
124 ..Self::default()
125 }
126 }
127
128 pub fn to_refiner_kind(&self) -> RefinerKind {
132 match self.kind {
133 RefinementMethod::CenterOfMass => RefinerKind::CenterOfMass(self.center_of_mass),
134 RefinementMethod::Forstner => RefinerKind::Forstner(self.forstner),
135 RefinementMethod::SaddlePoint => RefinerKind::SaddlePoint(self.saddle_point),
136 RefinementMethod::RadonPeak => RefinerKind::RadonPeak(self.radon_peak),
137 }
138 }
139}
140
141#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
142#[serde(default)]
143#[non_exhaustive]
144pub struct ChessConfig {
145 pub detector_mode: DetectorMode,
146 pub descriptor_mode: DescriptorMode,
147 pub threshold_mode: ThresholdMode,
148 pub threshold_value: f32,
149 pub nms_radius: u32,
150 pub min_cluster_size: u32,
151 pub refiner: RefinerConfig,
152 pub pyramid_levels: u8,
153 pub pyramid_min_size: usize,
154 pub refinement_radius: u32,
155 pub merge_radius: f32,
156 pub upscale: UpscaleConfig,
158 pub radon_detector: RadonDetectorParams,
162}
163
164impl Default for ChessConfig {
165 fn default() -> Self {
166 Self {
167 detector_mode: DetectorMode::default(),
168 descriptor_mode: DescriptorMode::default(),
169 threshold_mode: ThresholdMode::Absolute,
174 threshold_value: 0.0,
175 nms_radius: 2,
176 min_cluster_size: 2,
177 refiner: RefinerConfig::default(),
178 pyramid_levels: 1,
179 pyramid_min_size: 128,
180 refinement_radius: 3,
181 merge_radius: 3.0,
182 upscale: UpscaleConfig::default(),
183 radon_detector: RadonDetectorParams::default(),
184 }
185 }
186}
187
188impl ChessConfig {
189 pub fn single_scale() -> Self {
193 Self::default()
194 }
195
196 pub fn multiscale() -> Self {
200 Self {
201 pyramid_levels: 3,
202 pyramid_min_size: 128,
203 ..Self::default()
204 }
205 }
206
207 pub fn radon() -> Self {
215 Self {
216 detector_mode: DetectorMode::Radon,
217 pyramid_levels: 1,
218 ..Self::default()
219 }
220 }
221
222 pub fn to_chess_params(&self) -> ChessParams {
227 let mut params = ChessParams::default();
228 params.use_radius10 = matches!(self.detector_mode, DetectorMode::Broad);
229 params.descriptor_use_radius10 = match self.descriptor_mode {
230 DescriptorMode::FollowDetector => None,
231 DescriptorMode::Canonical => Some(false),
232 DescriptorMode::Broad => Some(true),
233 };
234 match self.threshold_mode {
235 ThresholdMode::Relative => {
236 params.threshold_rel = self.threshold_value;
237 params.threshold_abs = None;
238 }
239 ThresholdMode::Absolute => {
240 params.threshold_abs = Some(self.threshold_value);
241 }
242 }
243 params.nms_radius = self.nms_radius;
244 params.min_cluster_size = self.min_cluster_size;
245 params.refiner = self.refiner.to_refiner_kind();
246 params
247 }
248
249 pub fn to_coarse_to_fine_params(&self) -> CoarseToFineParams {
253 let mut cfg = CoarseToFineParams::default();
254 let mut pyramid = PyramidParams::default();
255 pyramid.num_levels = self.pyramid_levels;
256 pyramid.min_size = self.pyramid_min_size;
257 cfg.pyramid = pyramid;
258 cfg.refinement_radius = self.refinement_radius;
259 cfg.merge_radius = self.merge_radius;
260 cfg
261 }
262}
263
264#[cfg(test)]
265mod tests {
266 use super::*;
267
268 #[test]
269 fn default_config_accepts_any_positive_response() {
270 let cfg = ChessConfig::default();
271 let params = cfg.to_chess_params();
272 let cf = cfg.to_coarse_to_fine_params();
273
274 assert!(!params.use_radius10);
275 assert_eq!(params.descriptor_use_radius10, None);
276 assert_eq!(cfg.threshold_mode, ThresholdMode::Absolute);
278 assert_eq!(cfg.threshold_value, 0.0);
279 assert_eq!(params.threshold_abs, Some(0.0));
280 assert_eq!(params.nms_radius, 2);
281 assert_eq!(params.min_cluster_size, 2);
282 assert_eq!(
283 params.refiner,
284 RefinerKind::CenterOfMass(CenterOfMassConfig::default())
285 );
286 assert_eq!(cf.pyramid.num_levels, 1);
287 assert_eq!(cf.pyramid.min_size, 128);
288 assert_eq!(cf.refinement_radius, 3);
289 assert_eq!(cf.merge_radius, 3.0);
290 }
291
292 #[test]
293 fn absolute_threshold_maps_to_internal_params() {
294 let cfg = ChessConfig {
295 threshold_mode: ThresholdMode::Absolute,
296 threshold_value: 7.5,
297 ..ChessConfig::default()
298 };
299
300 let params = cfg.to_chess_params();
301 assert_eq!(params.threshold_abs, Some(7.5));
302 assert_eq!(params.threshold_rel, 0.2);
303 }
304
305 #[test]
306 fn ring_and_refiner_modes_map_to_internal_params() {
307 let cfg = ChessConfig {
308 detector_mode: DetectorMode::Broad,
309 descriptor_mode: DescriptorMode::Canonical,
310 refiner: RefinerConfig {
311 kind: RefinementMethod::Forstner,
312 forstner: ForstnerConfig {
313 max_offset: 2.0,
314 ..ForstnerConfig::default()
315 },
316 ..RefinerConfig::default()
317 },
318 ..ChessConfig::default()
319 };
320
321 let params = cfg.to_chess_params();
322 assert!(params.use_radius10);
323 assert_eq!(params.descriptor_use_radius10, Some(false));
324 assert_eq!(
325 params.refiner,
326 RefinerKind::Forstner(ForstnerConfig {
327 max_offset: 2.0,
328 ..ForstnerConfig::default()
329 })
330 );
331 }
332
333 #[test]
334 fn multiscale_preset_has_expected_defaults() {
335 let cfg = ChessConfig::multiscale();
336 assert_eq!(cfg.pyramid_levels, 3);
337 assert_eq!(cfg.pyramid_min_size, 128);
338 assert_eq!(cfg.refinement_radius, 3);
339 assert_eq!(cfg.merge_radius, 3.0);
340 }
341}