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;
55use serde::{Deserialize, Serialize};
56
57pub use crate::descriptor::CornerDescriptor;
58pub use crate::refine::{
59 CenterOfMassConfig, CenterOfMassRefiner, CornerRefiner, ForstnerConfig, ForstnerRefiner,
60 RefineContext, RefineResult, RefineStatus, Refiner, RefinerKind, SaddlePointConfig,
61 SaddlePointRefiner,
62};
63pub use imageview::ImageView;
64/// Tunable parameters for the ChESS response computation and corner detection.
65#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
66#[serde(default)]
67#[non_exhaustive]
68pub struct ChessParams {
69 /// Use the larger r=10 ring instead of the canonical r=5.
70 pub use_radius10: bool,
71 /// Optional override for descriptor sampling ring (r=5 vs r=10). Falls back
72 /// to `use_radius10` when `None`.
73 pub descriptor_use_radius10: Option<bool>,
74 /// Relative threshold as a fraction of max response (e.g. 0.2 = 20%).
75 pub threshold_rel: f32,
76 /// Absolute threshold override; if `Some`, this is used instead of `threshold_rel`.
77 pub threshold_abs: Option<f32>,
78 /// Non-maximum suppression radius (in pixels).
79 pub nms_radius: u32,
80 /// Minimum count of positive-response neighbors in NMS window
81 /// to accept a corner (rejects isolated noise).
82 pub min_cluster_size: u32,
83 /// Subpixel refinement backend and its configuration. Defaults to the legacy
84 /// center-of-mass refiner on the response map.
85 pub refiner: RefinerKind,
86}
87
88impl Default for ChessParams {
89 fn default() -> Self {
90 Self {
91 use_radius10: false,
92 descriptor_use_radius10: None,
93 threshold_rel: 0.2,
94 threshold_abs: None,
95 nms_radius: 2,
96 min_cluster_size: 2,
97 refiner: RefinerKind::default(),
98 }
99 }
100}
101
102impl ChessParams {
103 #[inline]
104 pub fn ring_radius(&self) -> u32 {
105 if self.use_radius10 {
106 10
107 } else {
108 5
109 }
110 }
111
112 #[inline]
113 pub fn descriptor_ring_radius(&self) -> u32 {
114 match self.descriptor_use_radius10 {
115 Some(true) => 10,
116 Some(false) => 5,
117 None => self.ring_radius(),
118 }
119 }
120
121 #[inline]
122 pub fn ring(&self) -> RingOffsets {
123 RingOffsets::from_radius(self.ring_radius())
124 }
125
126 #[inline]
127 pub fn descriptor_ring(&self) -> RingOffsets {
128 RingOffsets::from_radius(self.descriptor_ring_radius())
129 }
130}
131
132/// Dense response map in row-major layout.
133#[derive(Clone, Debug)]
134pub struct ResponseMap {
135 pub(crate) w: usize,
136 pub(crate) h: usize,
137 pub(crate) data: Vec<f32>,
138}
139
140impl ResponseMap {
141 /// Create a new response map. `data` must have exactly `w * h` elements.
142 ///
143 /// # Panics
144 ///
145 /// Panics if `data.len() != w * h`.
146 pub fn new(w: usize, h: usize, data: Vec<f32>) -> Self {
147 assert_eq!(data.len(), w * h, "ResponseMap data length mismatch");
148 Self { w, h, data }
149 }
150
151 /// Width of the response map.
152 #[inline]
153 pub fn width(&self) -> usize {
154 self.w
155 }
156
157 /// Height of the response map.
158 #[inline]
159 pub fn height(&self) -> usize {
160 self.h
161 }
162
163 /// Raw response data in row-major order.
164 #[inline]
165 pub fn data(&self) -> &[f32] {
166 &self.data
167 }
168
169 /// Mutable access to the raw response data.
170 #[inline]
171 pub fn data_mut(&mut self) -> &mut [f32] {
172 &mut self.data
173 }
174
175 #[inline]
176 /// Response value at an integer coordinate.
177 pub fn at(&self, x: usize, y: usize) -> f32 {
178 self.data[y * self.w + x]
179 }
180}