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;
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}