chess_corners_core/refine/
mod.rs1use crate::imageview::ImageView;
16use crate::ResponseMap;
17use serde::{Deserialize, Serialize};
18
19pub mod center_of_mass;
20pub mod forstner;
21pub mod radon_peak;
22pub mod saddle_point;
23
24pub use center_of_mass::{CenterOfMassConfig, CenterOfMassRefiner};
25pub use forstner::{ForstnerConfig, ForstnerRefiner};
26pub use radon_peak::{RadonPeakConfig, RadonPeakRefiner};
27pub use saddle_point::{SaddlePointConfig, SaddlePointRefiner};
28
29#[derive(Copy, Clone, Debug, PartialEq, Eq)]
31#[non_exhaustive]
32pub enum RefineStatus {
33 Accepted,
34 Rejected,
35 OutOfBounds,
36 IllConditioned,
37}
38
39#[derive(Copy, Clone, Debug)]
41#[non_exhaustive]
42pub struct RefineResult {
43 pub x: f32,
45 pub y: f32,
47 pub score: f32,
48 pub status: RefineStatus,
49}
50
51impl RefineResult {
52 #[inline]
53 pub fn accepted(xy: [f32; 2], score: f32) -> Self {
54 Self {
55 x: xy[0],
56 y: xy[1],
57 score,
58 status: RefineStatus::Accepted,
59 }
60 }
61}
62
63#[derive(Copy, Clone, Debug, Default)]
65#[non_exhaustive]
66pub struct RefineContext<'a> {
67 pub image: Option<ImageView<'a>>,
68 pub response: Option<&'a ResponseMap>,
69}
70
71impl<'a> RefineContext<'a> {
72 #[inline]
74 pub fn new(image: Option<ImageView<'a>>, response: Option<&'a ResponseMap>) -> Self {
75 Self { image, response }
76 }
77}
78
79pub trait CornerRefiner {
81 fn radius(&self) -> i32;
83 fn refine(&mut self, seed_xy: [f32; 2], ctx: RefineContext<'_>) -> RefineResult;
84}
85
86#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
88#[non_exhaustive]
89pub enum RefinerKind {
90 CenterOfMass(CenterOfMassConfig),
91 Forstner(ForstnerConfig),
92 SaddlePoint(SaddlePointConfig),
93 RadonPeak(RadonPeakConfig),
94}
95
96impl Default for RefinerKind {
97 fn default() -> Self {
98 Self::CenterOfMass(CenterOfMassConfig::default())
99 }
100}
101
102#[derive(Debug)]
104#[non_exhaustive]
105pub enum Refiner {
106 CenterOfMass(CenterOfMassRefiner),
107 Forstner(ForstnerRefiner),
108 SaddlePoint(SaddlePointRefiner),
109 RadonPeak(RadonPeakRefiner),
110}
111
112impl Refiner {
113 pub fn from_kind(kind: RefinerKind) -> Self {
114 match kind {
115 RefinerKind::CenterOfMass(cfg) => Refiner::CenterOfMass(CenterOfMassRefiner::new(cfg)),
116 RefinerKind::Forstner(cfg) => Refiner::Forstner(ForstnerRefiner::new(cfg)),
117 RefinerKind::SaddlePoint(cfg) => Refiner::SaddlePoint(SaddlePointRefiner::new(cfg)),
118 RefinerKind::RadonPeak(cfg) => Refiner::RadonPeak(RadonPeakRefiner::new(cfg)),
119 }
120 }
121}
122
123impl CornerRefiner for Refiner {
124 #[inline]
125 fn radius(&self) -> i32 {
126 match self {
127 Refiner::CenterOfMass(r) => r.radius(),
128 Refiner::Forstner(r) => r.radius(),
129 Refiner::SaddlePoint(r) => r.radius(),
130 Refiner::RadonPeak(r) => r.radius(),
131 }
132 }
133
134 #[inline]
135 fn refine(&mut self, seed_xy: [f32; 2], ctx: RefineContext<'_>) -> RefineResult {
136 match self {
137 Refiner::CenterOfMass(r) => r.refine(seed_xy, ctx),
138 Refiner::Forstner(r) => r.refine(seed_xy, ctx),
139 Refiner::SaddlePoint(r) => r.refine(seed_xy, ctx),
140 Refiner::RadonPeak(r) => r.refine(seed_xy, ctx),
141 }
142 }
143}
144
145#[cfg(test)]
146pub(crate) mod test_fixtures {
147 pub(crate) fn synthetic_checkerboard(
150 size: usize,
151 offset: (f32, f32),
152 dark: u8,
153 bright: u8,
154 ) -> Vec<u8> {
155 let mut img = vec![0u8; size * size];
156 let ox = offset.0;
157 let oy = offset.1;
158 for y in 0..size {
159 for x in 0..size {
160 let xf = x as f32 - ox;
161 let yf = y as f32 - oy;
162 let dark_quad = (xf >= 0.0 && yf >= 0.0) || (xf < 0.0 && yf < 0.0);
163 img[y * size + x] = if dark_quad { dark } else { bright };
164 }
165 }
166 let mut blurred = img.clone();
167 for y in 1..(size - 1) {
168 for x in 1..(size - 1) {
169 let mut acc = 0u32;
170 for ky in -1..=1 {
171 for kx in -1..=1 {
172 acc +=
173 img[(y as i32 + ky) as usize * size + (x as i32 + kx) as usize] as u32;
174 }
175 }
176 blurred[y * size + x] = (acc / 9) as u8;
177 }
178 }
179 blurred
180 }
181}