1use rayon::prelude::*;
35
36#[derive(Clone, Copy, Debug, PartialEq)]
38pub struct Candidate {
39 pub x: f32,
41 pub y: f32,
43 pub sigma: f32,
45 pub response: f32,
47}
48
49#[derive(Clone, Copy, Debug)]
51pub struct DetectParams {
52 pub peak_threshold: f32,
54 pub edge_threshold: f32,
56 pub refinement_iterations: usize,
58 pub reject_singular: bool,
60 pub sigma0: f32,
62 pub k: f32,
64}
65
66#[allow(clippy::needless_range_loop)]
67fn solve3(mut a: [[f32; 3]; 3], mut b: [f32; 3]) -> Option<[f32; 3]> {
69 for col in 0..3 {
70 let mut piv = col;
71 for r in col + 1..3 {
72 if a[r][col].abs() > a[piv][col].abs() {
73 piv = r;
74 }
75 }
76 if a[piv][col].abs() < 1e-10 {
77 return None;
78 }
79 a.swap(col, piv);
80 b.swap(col, piv);
81 for r in col + 1..3 {
82 let f = a[r][col] / a[col][col];
83 for c in col..3 {
84 a[r][c] -= f * a[col][c];
85 }
86 b[r] -= f * b[col];
87 }
88 }
89 let mut x = [0f32; 3];
90 for r in (0..3).rev() {
91 let mut s = b[r];
92 for c in r + 1..3 {
93 s -= a[r][c] * x[c];
94 }
95 x[r] = s / a[r][r];
96 }
97 Some(x)
98}
99
100#[allow(clippy::needless_range_loop)]
103pub fn detect_level(dm: &[f32], d0: &[f32], dp: &[f32], w: usize, h: usize, j: usize, p: &DetectParams) -> Vec<Candidate> {
104 if w < 3 || h < 3 {
105 return Vec::new();
106 }
107 let pre = 0.8 * p.peak_threshold;
108 let e = p.edge_threshold;
109 let edge_ratio = (e + 1.0) * (e + 1.0) / e;
110 (1..h - 1)
111 .into_par_iter()
112 .flat_map_iter(|r| {
113 let mut out = Vec::new();
114 let row = &d0[r * w..(r + 1) * w];
115 for c in 1..w - 1 {
116 let v = row[c];
117 if v.abs() <= pre {
118 continue;
119 }
120 if !is_extremum(dm, d0, dp, w, r, c, v) {
121 continue;
122 }
123 let i = r * w + c;
124 let fxx = d0[i - 1] + d0[i + 1] - 2.0 * v;
126 let fyy = d0[i - w] + d0[i + w] - 2.0 * v;
127 let fxy = 0.25 * (d0[i + w + 1] + d0[i - w - 1] - d0[i + w - 1] - d0[i - w + 1]);
128 let det = fxx * fyy - fxy * fxy;
129 let tr = fxx + fyy;
130 if det <= 0.0 || tr * tr > edge_ratio * det {
131 continue;
132 }
133 if let Some(cand) = refine(dm, d0, dp, w, h, r, c, j, p) {
134 out.push(cand);
135 }
136 }
137 out
138 })
139 .collect()
140}
141
142#[inline]
143fn is_extremum(dm: &[f32], d0: &[f32], dp: &[f32], w: usize, r: usize, c: usize, v: f32) -> bool {
144 let i = r * w + c;
145 let (l, rt) = (d0[i - 1], d0[i + 1]);
146 let is_max = if v > l.max(rt) {
147 true
148 } else if v < l.min(rt) {
149 false
150 } else {
151 return false;
152 };
153 let others = |img: &[f32], skip_lr: bool| -> bool {
155 for dy in [-1isize, 0, 1] {
156 let base = (i as isize + dy * w as isize) as usize;
157 for dx in [-1isize, 0, 1] {
158 if skip_lr && dy == 0 {
159 continue;
160 }
161 let n = img[(base as isize + dx) as usize];
162 if is_max {
163 if v < n {
164 return false;
165 }
166 } else if v > n {
167 return false;
168 }
169 }
170 }
171 true
172 };
173 others(d0, true) && others(dm, false) && others(dp, false)
174}
175
176#[allow(clippy::too_many_arguments)]
177fn refine(dm: &[f32], d0: &[f32], dp: &[f32], w: usize, h: usize, r0: usize, c0: usize, j: usize, p: &DetectParams) -> Option<Candidate> {
178 let (mut r, mut c) = (r0, c0);
179 let iters = p.refinement_iterations.max(1);
180 for it in 0..iters {
181 let i = r * w + c;
182 let v = d0[i];
183 let fx = 0.5 * (d0[i + 1] - d0[i - 1]);
184 let fy = 0.5 * (d0[i + w] - d0[i - w]);
185 let fs = 0.5 * (dp[i] - dm[i]);
186 let fxx = d0[i - 1] + d0[i + 1] - 2.0 * v;
187 let fyy = d0[i - w] + d0[i + w] - 2.0 * v;
188 let fss = dp[i] + dm[i] - 2.0 * v;
189 let fxy = 0.25 * (d0[i + w + 1] + d0[i - w - 1] - d0[i + w - 1] - d0[i - w + 1]);
190 let fxs = 0.25 * (dp[i + 1] + dm[i - 1] - dp[i - 1] - dm[i + 1]);
191 let fys = 0.25 * (dp[i + w] + dm[i - w] - dp[i - w] - dm[i + w]);
192 let hm = [[fxx, fxy, fxs], [fxy, fyy, fys], [fxs, fys, fss]];
193 let g = [fx, fy, fs];
194 let sol = solve3(hm, [-fx, -fy, -fs]);
195 let (delta, resp) = match sol {
196 None => {
197 if p.reject_singular {
198 return None;
199 }
200 ([0f32; 3], v)
201 }
202 Some(d) => {
203 if it + 1 < iters {
205 let mut moved = false;
206 if d[0] > 0.6 && c + 2 < w {
207 c += 1;
208 moved = true;
209 } else if d[0] < -0.6 && c > 1 {
210 c -= 1;
211 moved = true;
212 }
213 if d[1] > 0.6 && r + 2 < h {
214 r += 1;
215 moved = true;
216 } else if d[1] < -0.6 && r > 1 {
217 r -= 1;
218 moved = true;
219 }
220 if moved {
221 continue;
222 }
223 }
224 let resp = v + 0.5 * (g[0] * d[0] + g[1] * d[1] + g[2] * d[2]);
225 if resp.abs() <= p.peak_threshold || d[0].abs() >= 1.0 || d[1].abs() >= 1.0 || d[2].abs() >= 1.0 {
226 return None;
227 }
228 (d, resp)
229 }
230 };
231 return Some(Candidate {
232 x: c as f32 + 0.5 + delta[0],
233 y: r as f32 + 0.5 + delta[1],
234 sigma: p.sigma0 * p.k.powf(j as f32 + delta[2]),
235 response: resp,
236 });
237 }
238 None
239}
240
241pub fn dog(a: &[f32], b: &[f32], out: &mut [f32]) {
243 out.par_chunks_mut(4096).zip(a.par_chunks(4096).zip(b.par_chunks(4096))).for_each(|(o, (hi, lo))| {
244 for ((o, x), y) in o.iter_mut().zip(hi).zip(lo) {
245 *o = x - y;
246 }
247 });
248}
249
250#[cfg(test)]
251mod tests {
252 use super::*;
253
254 #[test]
255 fn solve3_basic() {
256 let a = [[2.0, 1.0, 0.0], [1.0, 3.0, 1.0], [0.0, 1.0, 4.0]];
257 let x = [1.0f32, -2.0, 0.5];
258 let b = [2.0 * 1.0 - 2.0, 1.0 - 6.0 + 0.5, -2.0 + 2.0];
259 let s = solve3(a, b).unwrap();
260 for i in 0..3 {
261 assert!((s[i] - x[i]).abs() < 1e-5);
262 }
263 assert!(solve3([[0.0; 3]; 3], [1.0; 3]).is_none());
264 }
265}