chess_corners_core/detect/chess/
response.rs1use super::ring::RingOffsets;
3use crate::{ChessParams, ResponseMap};
4
5#[cfg(feature = "rayon")]
6use rayon::prelude::*;
7
8#[cfg(feature = "simd")]
9use core::simd::Simd;
10
11#[cfg(feature = "simd")]
12use std::simd::prelude::{SimdInt, SimdUint};
13
14#[cfg(feature = "simd")]
15const LANES: usize = 16;
16
17#[cfg(feature = "simd")]
18type U8s = Simd<u8, LANES>;
19
20#[cfg(feature = "simd")]
21type I16s = Simd<i16, LANES>;
22
23#[cfg(feature = "simd")]
24type I32s = Simd<i32, LANES>;
25
26#[cfg(feature = "tracing")]
27use tracing::instrument;
28
29#[derive(Clone, Copy, Debug)]
37pub struct Roi {
38 x0: usize,
39 y0: usize,
40 x1: usize,
41 y1: usize,
42}
43
44impl Roi {
45 pub fn new(x0: usize, y0: usize, x1: usize, y1: usize) -> Option<Self> {
47 if x0 < x1 && y0 < y1 {
48 Some(Self { x0, y0, x1, y1 })
49 } else {
50 None
51 }
52 }
53
54 #[inline]
55 pub fn x0(&self) -> usize {
56 self.x0
57 }
58 #[inline]
59 pub fn y0(&self) -> usize {
60 self.y0
61 }
62 #[inline]
63 pub fn x1(&self) -> usize {
64 self.x1
65 }
66 #[inline]
67 pub fn y1(&self) -> usize {
68 self.y1
69 }
70}
71
72#[inline]
73fn ring_from_params(params: &ChessParams) -> (RingOffsets, &'static [(i32, i32); 16]) {
74 let ring = params.ring();
75 (ring, ring.offsets())
76}
77
78#[cfg_attr(
151 feature = "tracing",
152 instrument(level = "info", skip(img, params), fields(w, h))
153)]
154pub fn chess_response_u8(img: &[u8], w: usize, h: usize, params: &ChessParams) -> ResponseMap {
155 #[cfg(feature = "rayon")]
157 {
158 compute_response_parallel(img, w, h, params)
159 }
160 #[cfg(not(feature = "rayon"))]
161 {
162 compute_response_sequential(img, w, h, params)
163 }
164}
165
166pub fn chess_response_u8_scalar(
169 img: &[u8],
170 w: usize,
171 h: usize,
172 params: &ChessParams,
173) -> ResponseMap {
174 compute_response_sequential_scalar(img, w, h, params)
175}
176
177#[cfg_attr(
190 feature = "tracing",
191 instrument(
192 level = "debug",
193 skip(img, params),
194 fields(img_w, img_h, roi_w = roi.x1 - roi.x0, roi_h = roi.y1 - roi.y0)
195 )
196)]
197pub fn chess_response_u8_patch(
198 img: &[u8],
199 img_w: usize,
200 img_h: usize,
201 params: &ChessParams,
202 roi: Roi,
203) -> ResponseMap {
204 let Roi { x0, y0, x1, y1 } = roi;
205
206 let x0 = x0.min(img_w);
208 let y0 = y0.min(img_h);
209 let x1 = x1.min(img_w);
210 let y1 = y1.min(img_h);
211
212 if x1 <= x0 || y1 <= y0 {
213 return ResponseMap {
214 w: 0,
215 h: 0,
216 data: Vec::new(),
217 };
218 }
219
220 let patch_w = x1 - x0;
221 let patch_h = y1 - y0;
222 let mut data = vec![0.0f32; patch_w * patch_h];
223
224 let (ring_kind, ring) = ring_from_params(params);
225 let r = ring_kind.radius() as i32;
226
227 let gx0 = r as usize;
229 let gy0 = r as usize;
230 let gx1 = img_w - r as usize;
231 let gy1 = img_h - r as usize;
232
233 for py in 0..patch_h {
234 let gy = y0 + py;
235 if gy < gy0 || gy >= gy1 {
236 continue;
237 }
238
239 let row_gx0 = x0.max(gx0);
241 let row_gx1 = x1.min(gx1);
242 if row_gx0 >= row_gx1 {
243 continue;
244 }
245
246 let row = &mut data[py * patch_w..(py + 1) * patch_w];
247 let rel_start = row_gx0 - x0;
248 let rel_end = row_gx1 - x0;
249 let dst_row = &mut row[rel_start..rel_end];
250
251 #[cfg(feature = "simd")]
252 {
253 compute_row_range_simd(img, img_w, gy as i32, ring, dst_row, row_gx0, row_gx1);
254 }
255
256 #[cfg(not(feature = "simd"))]
257 {
258 compute_row_range_scalar(img, img_w, gy as i32, ring, dst_row, row_gx0, row_gx1);
259 }
260 }
261
262 ResponseMap {
263 w: patch_w,
264 h: patch_h,
265 data,
266 }
267}
268
269#[cfg(not(feature = "rayon"))]
270fn compute_response_sequential(
271 img: &[u8],
272 w: usize,
273 h: usize,
274 params: &ChessParams,
275) -> ResponseMap {
276 let (ring_kind, ring) = ring_from_params(params);
277 let r = ring_kind.radius() as i32;
278
279 let mut data = vec![0.0f32; w * h];
280
281 let x0 = r as usize;
283 let y0 = r as usize;
284 let x1 = w - r as usize;
285 let y1 = h - r as usize;
286
287 for y in y0..y1 {
288 let row = &mut data[y * w..(y + 1) * w];
289 let dst_row = &mut row[x0..x1];
290
291 #[cfg(feature = "simd")]
292 {
293 compute_row_range_simd(img, w, y as i32, ring, dst_row, x0, x1);
294 }
295
296 #[cfg(not(feature = "simd"))]
297 {
298 compute_row_range_scalar(img, w, y as i32, ring, dst_row, x0, x1);
299 }
300 }
301
302 ResponseMap { w, h, data }
303}
304
305fn compute_response_sequential_scalar(
306 img: &[u8],
307 w: usize,
308 h: usize,
309 params: &ChessParams,
310) -> ResponseMap {
311 let (ring_kind, ring) = ring_from_params(params);
312 let r = ring_kind.radius() as i32;
313
314 let mut data = vec![0.0f32; w * h];
315
316 let x0 = r as usize;
318 let y0 = r as usize;
319 let x1 = w - r as usize;
320 let y1 = h - r as usize;
321
322 for y in y0..y1 {
323 let row = &mut data[y * w..(y + 1) * w];
324 let dst_row = &mut row[x0..x1];
325 compute_row_range_scalar(img, w, y as i32, ring, dst_row, x0, x1);
326 }
327
328 ResponseMap { w, h, data }
329}
330
331#[cfg(feature = "rayon")]
332fn compute_response_parallel(img: &[u8], w: usize, h: usize, params: &ChessParams) -> ResponseMap {
333 let (ring_kind, ring) = ring_from_params(params);
334 let r = ring_kind.radius() as i32;
335 let mut data = vec![0.0f32; w * h];
336
337 let x0 = r as usize;
339 let y0 = r as usize;
340 let x1 = w - r as usize;
341 let y1 = h - r as usize;
342
343 data.par_chunks_mut(w).enumerate().for_each(|(y, row)| {
346 let y_i = y as i32;
347 if y_i < y0 as i32 || y_i >= y1 as i32 {
348 return;
349 }
350
351 let dst_row = &mut row[x0..x1];
352
353 #[cfg(feature = "simd")]
354 {
355 compute_row_range_simd(img, w, y_i, ring, dst_row, x0, x1);
356 }
357
358 #[cfg(not(feature = "simd"))]
359 {
360 compute_row_range_scalar(img, w, y_i, ring, dst_row, x0, x1);
361 }
362 });
363
364 ResponseMap { w, h, data }
365}
366
367#[cfg(not(feature = "rayon"))]
372#[cfg_attr(not(feature = "rayon"), allow(dead_code))]
373fn compute_response_parallel(img: &[u8], w: usize, h: usize, params: &ChessParams) -> ResponseMap {
374 compute_response_sequential(img, w, h, params)
375}
376
377#[inline(always)]
391fn chess_response_at_u8(img: &[u8], w: usize, x: i32, y: i32, ring: &[(i32, i32); 16]) -> f32 {
392 let mut s = [0i32; 16];
394 for k in 0..16 {
395 let (dx, dy) = ring[k];
396 let xx = (x + dx) as usize;
397 let yy = (y + dy) as usize;
398 s[k] = img[yy * w + xx] as i32;
399 }
400
401 let mut sr = 0i32;
403 for k in 0..4 {
404 let a = s[k] + s[k + 8];
405 let b = s[k + 4] + s[k + 12];
406 sr += (a - b).abs();
407 }
408
409 let mut dr = 0i32;
411 for k in 0..8 {
412 dr += (s[k] - s[k + 8]).abs();
413 }
414
415 let sum_ring: i32 = s.iter().sum();
417 let mu_n = sum_ring as f32 / 16.0;
418
419 let c = img[(y as usize) * w + (x as usize)] as f32;
421 let n = img[((y - 1) as usize) * w + (x as usize)] as f32;
422 let s0 = img[((y + 1) as usize) * w + (x as usize)] as f32;
423 let e = img[(y as usize) * w + ((x + 1) as usize)] as f32;
424 let w0 = img[(y as usize) * w + ((x - 1) as usize)] as f32;
425 let mu_l = (c + n + s0 + e + w0) / 5.0;
426
427 let mr = (mu_n - mu_l).abs();
428
429 (sr as f32) - (dr as f32) - 16.0 * mr
430}
431
432fn compute_row_range_scalar(
433 img: &[u8],
434 w: usize,
435 y: i32,
436 ring: &[(i32, i32); 16],
437 dst_row: &mut [f32],
438 x_start: usize,
439 x_end: usize,
440) {
441 for (offset, x) in (x_start..x_end).enumerate() {
442 dst_row[offset] = chess_response_at_u8(img, w, x as i32, y, ring);
443 }
444}
445
446#[cfg(feature = "simd")]
447fn compute_row_range_simd(
448 img: &[u8],
449 w: usize,
450 y: i32,
451 ring: &[(i32, i32); 16],
452 dst_row: &mut [f32],
453 x_start: usize,
454 x_end: usize,
455) {
456 let y_usize = y as usize;
457
458 let mut ring_bases: [isize; 16] = [0; 16];
460 for k in 0..16 {
461 let (dx, dy) = ring[k];
462 let yy = (y + dy) as usize;
463 ring_bases[k] = (yy * w) as isize + dx as isize;
464 }
465
466 let mut x = x_start;
467
468 while x + LANES <= x_end {
469 let mut s: [I16s; 16] = [I16s::splat(0); 16];
471
472 for k in 0..16 {
473 let base = (ring_bases[k] + x as isize) as usize;
474
475 let v_u8 = U8s::from_slice(&img[base..base + LANES]);
477 s[k] = v_u8.cast::<i16>();
478 }
479
480 let mut sum_ring_v = I32s::splat(0);
482 for &v in &s {
483 sum_ring_v += v.cast::<i32>();
484 }
485
486 let mut sr_v = I32s::splat(0);
488 for k in 0..4 {
489 let a = s[k].cast::<i32>() + s[k + 8].cast::<i32>();
490 let b = s[k + 4].cast::<i32>() + s[k + 12].cast::<i32>();
491 sr_v += (a - b).abs();
492 }
493
494 let mut dr_v = I32s::splat(0);
496 for k in 0..8 {
497 let a = s[k].cast::<i32>();
498 let b = s[k + 8].cast::<i32>();
499 dr_v += (a - b).abs();
500 }
501
502 let sr_arr = sr_v.cast::<f32>().to_array();
504 let dr_arr = dr_v.cast::<f32>().to_array();
505 let sum_ring_arr = sum_ring_v.cast::<f32>().to_array();
506
507 for lane in 0..LANES {
509 let xx = x + lane;
510 let px = xx - x_start;
511
512 let c = img[y_usize * w + xx] as f32;
514 let n = img[(y_usize - 1) * w + xx] as f32;
515 let s0 = img[(y_usize + 1) * w + xx] as f32;
516 let e = img[y_usize * w + (xx + 1)] as f32;
517 let w0 = img[y_usize * w + (xx - 1)] as f32;
518
519 let mu_n = sum_ring_arr[lane] / 16.0;
520 let mu_l = (c + n + s0 + e + w0) / 5.0;
521 let mr = (mu_n - mu_l).abs();
522
523 dst_row[px] = sr_arr[lane] - dr_arr[lane] - 16.0 * mr;
524 }
525
526 x += LANES;
527 }
528
529 while x < x_end {
531 let px = x - x_start;
532 let resp = chess_response_at_u8(img, w, x as i32, y, ring);
533 dst_row[px] = resp;
534 x += 1;
535 }
536}