Skip to main content

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}