chess_corners_core/lib.rs
1#![cfg_attr(not(feature = "std"), no_std)]
2#![cfg_attr(feature = "simd", feature(portable_simd))]
3//! Core primitives for computing ChESS responses and extracting subpixel corners.
4//!
5//! # Overview
6//!
7//! This crate exposes two main building blocks:
8//!
9//! - [`response`] – dense ChESS response computation on 8‑bit grayscale images.
10//! - [`detect`] + [`refine`] – thresholding, non‑maximum suppression (NMS),
11//! and pluggable subpixel refinement (center-of-mass, Förstner, saddle-point).
12//!
13//! The response is based on a 16‑sample ring (see [`ring`]) and is intended for
14//! chessboard‑like corner detection, as described in the ChESS paper
15//! (“Chess‑board Extraction by Subtraction and Summation”).
16//!
17//! # Features
18//!
19//! - `std` *(default)* – enables use of the Rust standard library. When
20//! disabled, the crate is `no_std` + `alloc`.
21//! - `rayon` – parallelizes the dense response computation over image rows
22//! using the `rayon` crate. This does not change numerical results, only
23//! performance on multi‑core machines.
24//! - `simd` – enables a SIMD‑accelerated inner loop for the response
25//! computation, based on `portable_simd`. This feature currently requires a
26//! nightly compiler and is intended as a performance optimization; the
27//! scalar path remains the reference implementation.
28//! - `tracing` – emits structured spans around response and detector functions
29//! using the [`tracing`](https://docs.rs/tracing) ecosystem, useful for
30//! profiling and diagnostics.
31//!
32//! Feature combinations:
33//!
34//! - no features / `std` only – single‑threaded scalar implementation.
35//! - `rayon` – same scalar math, but rows are processed in parallel.
36//! - `simd` – single‑threaded, but the inner ring computation is vectorized.
37//! - `rayon + simd` – rows are processed in parallel *and* each row uses the
38//! SIMD‑accelerated inner loop.
39//!
40//! The detector in [`detect`] is independent of `rayon`/`simd`, and `tracing`
41//! only adds observability; none of these features change the numerical
42//! results, only performance and instrumentation.
43//!
44//! The ChESS idea is proposed in the papaer Bennett, Lasenby, *ChESS: A Fast and
45//! Accurate Chessboard Corner Detector*, CVIU 2014
46
47pub mod descriptor;
48pub mod detect;
49pub mod imageview;
50pub mod refine;
51pub mod response;
52pub mod ring;
53
54use crate::ring::RingOffsets;
55
56pub use crate::descriptor::CornerDescriptor;
57pub use crate::refine::{
58 CenterOfMassConfig, CenterOfMassRefiner, CornerRefiner, ForstnerConfig, ForstnerRefiner,
59 RefineContext, RefineResult, RefineStatus, Refiner, RefinerKind, SaddlePointConfig,
60 SaddlePointRefiner,
61};
62pub use imageview::ImageView;
63/// Tunable parameters for the ChESS response computation and corner detection.
64#[derive(Clone, Debug)]
65#[non_exhaustive]
66pub struct ChessParams {
67 /// Use the larger r=10 ring instead of the canonical r=5.
68 pub use_radius10: bool,
69 /// Optional override for descriptor sampling ring (r=5 vs r=10). Falls back
70 /// to `use_radius10` when `None`.
71 pub descriptor_use_radius10: Option<bool>,
72 /// Relative threshold as a fraction of max response (e.g. 0.2 = 20%).
73 pub threshold_rel: f32,
74 /// Absolute threshold override; if `Some`, this is used instead of `threshold_rel`.
75 pub threshold_abs: Option<f32>,
76 /// Non-maximum suppression radius (in pixels).
77 pub nms_radius: u32,
78 /// Minimum count of positive-response neighbors in NMS window
79 /// to accept a corner (rejects isolated noise).
80 pub min_cluster_size: u32,
81 /// Subpixel refinement backend and its configuration. Defaults to the legacy
82 /// center-of-mass refiner on the response map.
83 pub refiner: RefinerKind,
84}
85
86impl Default for ChessParams {
87 fn default() -> Self {
88 Self {
89 use_radius10: false,
90 descriptor_use_radius10: None,
91 threshold_rel: 0.2,
92 threshold_abs: None,
93 nms_radius: 2,
94 min_cluster_size: 2,
95 refiner: RefinerKind::default(),
96 }
97 }
98}
99
100impl ChessParams {
101 #[inline]
102 pub fn ring_radius(&self) -> u32 {
103 if self.use_radius10 {
104 10
105 } else {
106 5
107 }
108 }
109
110 #[inline]
111 pub fn descriptor_ring_radius(&self) -> u32 {
112 match self.descriptor_use_radius10 {
113 Some(true) => 10,
114 Some(false) => 5,
115 None => self.ring_radius(),
116 }
117 }
118
119 #[inline]
120 pub fn ring(&self) -> RingOffsets {
121 RingOffsets::from_radius(self.ring_radius())
122 }
123
124 #[inline]
125 pub fn descriptor_ring(&self) -> RingOffsets {
126 RingOffsets::from_radius(self.descriptor_ring_radius())
127 }
128}
129
130/// Dense response map in row-major layout.
131#[derive(Clone, Debug)]
132pub struct ResponseMap {
133 pub(crate) w: usize,
134 pub(crate) h: usize,
135 pub(crate) data: Vec<f32>,
136}
137
138impl ResponseMap {
139 /// Create a new response map. `data` must have exactly `w * h` elements.
140 ///
141 /// # Panics
142 ///
143 /// Panics if `data.len() != w * h`.
144 pub fn new(w: usize, h: usize, data: Vec<f32>) -> Self {
145 assert_eq!(data.len(), w * h, "ResponseMap data length mismatch");
146 Self { w, h, data }
147 }
148
149 /// Width of the response map.
150 #[inline]
151 pub fn width(&self) -> usize {
152 self.w
153 }
154
155 /// Height of the response map.
156 #[inline]
157 pub fn height(&self) -> usize {
158 self.h
159 }
160
161 /// Raw response data in row-major order.
162 #[inline]
163 pub fn data(&self) -> &[f32] {
164 &self.data
165 }
166
167 /// Mutable access to the raw response data.
168 #[inline]
169 pub fn data_mut(&mut self) -> &mut [f32] {
170 &mut self.data
171 }
172
173 #[inline]
174 /// Response value at an integer coordinate.
175 pub fn at(&self, x: usize, y: usize) -> f32 {
176 self.data[y * self.w + x]
177 }
178}