1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
//! Ergonomic ChESS / Radon corner-detector facade over
//! `chess-corners-core`.
//!
//! # Overview
//!
//! This crate is the high-level entry point for two chessboard-corner
//! detectors that share the same output surface:
//!
//! - **ChESS** (Chess-board Extraction by Subtraction and Summation)
//! — a dense ring-difference response with NMS and a pluggable
//! subpixel refiner. This is the default path and the fastest preset
//! in the repository's clean-image benchmark.
//! - **Radon** — a whole-image Duda-Frese accumulator that scores
//! corners by summing ray intensities through each pixel. It is useful
//! when the ChESS ring does not produce enough seeds, especially in
//! the small-cell, blur, and low-contrast fixtures covered by the
//! tests.
//!
//! The [`Detector`] struct ties together the active strategy, the
//! orientation fit, and the multiscale / upscale scratch buffers
//! behind a single `detect` call. It returns subpixel
//! [`CornerDescriptor`] values in full-resolution input coordinates.
//! In most applications you construct a [`DetectorConfig`] (typically
//! via [`DetectorConfig::chess`], [`DetectorConfig::chess_multiscale`],
//! [`DetectorConfig::radon`], or [`DetectorConfig::radon_multiscale`]),
//! optionally tweak its fields, build a [`Detector`], and call
//! [`Detector::detect`].
//!
//! Building a [`Detector`] once and calling [`Detector::detect`] in a
//! loop reuses the pyramid, response, and upscale scratch buffers
//! across frames — no per-frame allocation.
//!
//! # Quick start
//!
//! ## Using `image` (default)
//!
//! The default feature set includes integration with the `image`
//! crate. This example reads from disk and is marked `no_run`:
//!
//! ```no_run
//! use chess_corners::{ChessRefiner, Detector, DetectorConfig};
//! use image::ImageReader;
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! let img = ImageReader::open("board.png")?
//! .decode()?
//! .to_luma8();
//!
//! let cfg = DetectorConfig::chess_multiscale()
//! .with_threshold(120.0)
//! .with_chess(|c| c.refiner = ChessRefiner::forstner());
//!
//! let mut detector = Detector::new(cfg)?;
//! let corners = detector.detect(&img)?;
//! println!("found {} corners", corners.len());
//!
//! for c in &corners {
//! // `axes` is `None` when the orientation fit was skipped
//! // (see `DetectorConfig::without_orientation`).
//! if let Some(axes) = c.axes {
//! println!(
//! "corner at ({:.2}, {:.2}), response {:.1}, axes [{:.2}, {:.2}] rad",
//! c.x, c.y, c.response, axes[0].angle, axes[1].angle,
//! );
//! }
//! }
//! # Ok(()) }
//! ```
//!
//! ## Raw grayscale buffer
//!
//! If you already have an 8-bit grayscale buffer, call
//! [`Detector::detect_u8`]:
//!
//! ```
//! use chess_corners::{Detector, DetectorConfig};
//!
//! // 8×8 black/white checkerboard of 16-pixel squares (128×128).
//! let mut img = vec![0u8; 128 * 128];
//! for y in 0..128 {
//! for x in 0..128 {
//! if ((x / 16) + (y / 16)) % 2 == 0 {
//! img[y * 128 + x] = 255;
//! }
//! }
//! }
//!
//! let cfg = DetectorConfig::chess();
//! let mut detector = Detector::new(cfg)?;
//! let corners = detector.detect_u8(&img, 128, 128)?;
//! assert!(!corners.is_empty());
//! # Ok::<(), chess_corners::ChessError>(())
//! ```
//!
//! ## Radon strategy
//!
//! Switch to the whole-image Radon detector when ChESS misses corners on
//! the images you care about. The strategy lives inside
//! [`DetectorConfig::strategy`]; pick a Radon preset to get sensible
//! defaults:
//!
//! ```
//! use chess_corners::{Detector, DetectorConfig};
//!
//! let mut img = vec![0u8; 128 * 128];
//! for y in 0..128 {
//! for x in 0..128 {
//! if ((x / 16) + (y / 16)) % 2 == 0 {
//! img[y * 128 + x] = 255;
//! }
//! }
//! }
//!
//! let cfg = DetectorConfig::radon();
//! let mut detector = Detector::new(cfg)?;
//! let corners = detector.detect_u8(&img, 128, 128)?;
//! assert!(!corners.is_empty());
//! # Ok::<(), chess_corners::ChessError>(())
//! ```
//!
//! ## ML refiner (feature `ml-refiner`)
//!
//! Pick the ML pipeline by selecting `ChessRefiner::Ml` inside the
//! ChESS strategy. The example is marked `no_run` because loading the
//! embedded ONNX model on first use is not appropriate for a doctest:
//!
//! ```no_run
//! # #[cfg(feature = "ml-refiner")]
//! # {
//! use chess_corners::{ChessRefiner, Detector, DetectorConfig};
//! use image::GrayImage;
//!
//! let cfg = DetectorConfig::chess()
//! .with_chess(|c| c.refiner = ChessRefiner::Ml);
//!
//! let img: GrayImage = image::open("board.png").unwrap().to_luma8();
//! let mut detector = Detector::new(cfg).unwrap();
//! let corners = detector.detect(&img).unwrap();
//! # let _ = corners;
//! # }
//! ```
//!
//! The ML refiner runs a small ONNX model on normalized intensity
//! patches (uint8 / 255.0) centered at each candidate. The model
//! predicts `[dx, dy, conf_logit]`, but the confidence output is
//! currently ignored; the offsets are applied directly. Current
//! accuracy benchmarks are synthetic; real-world accuracy still needs
//! validation. Per-refiner cost is measured in Part VIII §7.6 of the
//! book. The ML path is slower than the hand-coded refiners and should
//! be chosen only after measuring that its behavior helps your data.
//!
//! ## Python and JavaScript bindings
//!
//! The workspace also ships bindings that wrap this facade:
//!
//! - `crates/chess-corners-py` (PyO3 / maturin) exposes a
//! `chess_corners.Detector` class whose `detect(image)` method
//! accepts a 2D `uint8` NumPy array and returns a `Detections`
//! structure-of-arrays object with named fields: `.xy` (`(N, 2)`
//! float32), `.response` (`(N,)` float32), `.angles` (`(N, 2)`
//! float32, or `None` when orientation is disabled), and `.sigmas`
//! (`(N, 2)` float32, or `None` when orientation is disabled). See
//! its README for usage and configuration details.
//! - `crates/chess-corners-wasm` (wasm-bindgen / wasm-pack) exposes
//! the same surface to JavaScript / TypeScript via the
//! `@vitavision/chess-corners` npm package.
//!
//! # Configuration
//!
//! [`DetectorConfig`] is strategy-typed: the [`DetectorConfig::strategy`]
//! field is a [`DetectionStrategy`] enum carrying either a
//! [`ChessConfig`] (detector ring, refiner) or a [`RadonConfig`]
//! (whole-image Duda-Frese parameters). Acceptance is a single
//! [`threshold`](DetectorConfig::threshold) number — read as an absolute
//! response floor by ChESS and as a fraction of the per-frame maximum by
//! Radon. [`MultiscaleConfig`] and [`UpscaleConfig`] live at the top level
//! and apply to both strategies. The detector translates this into
//! lower-level parameter structs internally. To drive those stages
//! yourself, lower a config with [`DetectorConfig::chess_params`] or
//! [`DetectorConfig::radon_detector_params`] and call the stage
//! functions re-exported from `chess-corners-core`.
//!
//! Intermediate response maps and Radon heatmaps for debugging and
//! visualization live in the opt-in [`diagnostics`] module, which
//! carries a weaker stability promise than the facade root and is not
//! needed by typical consumers. For deeper internals (ring offsets, SAT
//! views, scalar reference paths) depend on `chess-corners-core`
//! directly.
//!
//! # Features
//!
//! - `image` *(default)* – enables [`Detector::detect`] and
//! `image::GrayImage` integration.
//! - `rayon` – parallelizes response computation and multiscale
//! refinement over image rows. Combine with `par_pyramid` to
//! parallelize pyramid downsampling as well.
//! - `ml-refiner` – enables the ML-backed refiner entry points via the
//! `chess-corners-ml` crate and embedded ONNX model.
//! - `simd` – enables portable-SIMD accelerated inner loops for the
//! response kernel (requires a nightly compiler). Combine with
//! `par_pyramid` to SIMD-accelerate pyramid downsampling.
//! - `par_pyramid` – opt-in gate for SIMD/`rayon` acceleration inside
//! the pyramid builder.
//! - `tracing` – emits structured spans for multiscale detection,
//! suitable for use with `tracing-subscriber` or JSON tracing from
//! the CLI.
//! - `cli` – builds the `chess-corners` binary shipped with this
//! crate; it is not required when using the library as a
//! dependency.
//!
//! The library API is stable across feature combinations; features
//! only affect performance and observability, not numerical results.
//!
//! # Minimum supported Rust version
//!
//! The default (stable) build requires Rust **1.91** or newer, as
//! declared by `rust-version` in `Cargo.toml`. The optional `simd`
//! feature uses `portable_simd` and therefore requires a **nightly**
//! toolchain; every other feature builds on stable.
//!
//! # References
//!
//! - Bennett, Lasenby. *ChESS: A Fast and Accurate Chessboard Corner
//! Detector*. CVIU 2014.
//! - Duda, Frese. *Accurate Detection and Localization of Checkerboard
//! Corners for Calibration*. BMVC 2018.
// The crate root surfaces the stable facade: the detector, its
// configuration and result types, errors, and the config-lowering and
// pipeline-stage primitives for callers that compose the stages
// themselves. Diagnostic outputs are reachable via [`diagnostics`];
// deeper internals (ring offsets, SAT views, scalar reference paths)
// via a direct `chess-corners-core` dependency.
pub use crate;
pub use crateChessError;
/// Optional pre-pipeline integer bilinear upscaling stage. These are the
/// raw stage primitives behind [`UpscaleConfig`]; [`Detector`] applies
/// the stage automatically, so they are only needed when composing the
/// pipeline by hand.
pub use crate;
pub use ;
// Primary detector entry point.
pub use crateDetector;