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
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
//! Ergonomic ChESS detector facade over `chess-corners-core`.
//!
//! # Overview
//!
//! This crate is the high-level entry point for the ChESS
//! (Chess-board Extraction by Subtraction and Summation) corner
//! detector. It exposes:
//!
//! - single-scale detection on raw grayscale buffers via
//! [`find_chess_corners`],
//! - optional `image::GrayImage` helpers (see
//! `find_chess_corners_image`) when the `image` feature is
//! enabled,
//! - a flat user-facing [`ChessConfig`] with explicit modes for
//! thresholding, ring selection, and multiscale tuning.
//!
//! The detector returns subpixel [`CornerDescriptor`] values in
//! full-resolution image coordinates. In most applications you
//! construct a [`ChessConfig`], optionally tweak its fields, and call
//! [`find_chess_corners`] or `find_chess_corners_image`.
//!
//! # Quick start
//!
//! ## Using `image` (default)
//!
//! The default feature set includes integration with the `image`
//! crate:
//!
//! ```no_run
//! use chess_corners::{ChessConfig, RefinementMethod, find_chess_corners_image};
//! use image::io::Reader as ImageReader;
//!
//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
//! // Load a grayscale chessboard image.
//! let img = ImageReader::open("board.png")?
//! .decode()?
//! .to_luma8();
//!
//! // Start from the recommended coarse-to-fine preset.
//! let mut cfg = ChessConfig::multiscale();
//! cfg.threshold_value = 0.15;
//! cfg.refiner.kind = RefinementMethod::Forstner;
//!
//! let corners = find_chess_corners_image(&img, &cfg)?;
//! println!("found {} corners", corners.len());
//!
//! for c in &corners {
//! println!(
//! "corner at ({:.2}, {:.2}), response {:.1}, axes [{:.2}, {:.2}] rad",
//! c.x, c.y, c.response, c.axes[0].angle, c.axes[1].angle,
//! );
//! }
//! # Ok(()) }
//! ```
//!
//! ## Raw grayscale buffer
//!
//! If you already have an 8-bit grayscale buffer, you can call the
//! detector directly without depending on `image`:
//!
//! ```no_run
//! use chess_corners::{ChessConfig, find_chess_corners_u8};
//!
//! # fn detect(img: &[u8], width: u32, height: u32) -> Result<(), chess_corners::ChessError> {
//! // Single-scale convenience configuration.
//! let cfg = ChessConfig::single_scale();
//!
//! let corners = find_chess_corners_u8(img, width, height, &cfg)?;
//! println!("found {} corners", corners.len());
//! # let _ = corners;
//! # Ok(()) }
//! ```
//!
//! ## ML refiner (feature `ml-refiner`)
//!
//! ```no_run
//! # #[cfg(feature = "ml-refiner")]
//! # {
//! use chess_corners::{ChessConfig, find_chess_corners_image_with_ml};
//! use image::GrayImage;
//!
//! let img = GrayImage::new(1, 1);
//! let cfg = ChessConfig::single_scale();
//!
//! let corners = find_chess_corners_image_with_ml(&img, &cfg).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
//! benchmarks are synthetic; real-world accuracy still needs
//! validation. It is also slower (about 23.5 ms vs 0.6 ms for 77
//! corners on `testimages/mid.png`).
//!
//! ## Python bindings
//!
//! The workspace includes a PyO3-based Python extension crate at
//! `crates/chess-corners-py`. It exposes `chess_corners.find_chess_corners`,
//! which accepts a 2D `uint8` NumPy array and returns a float32 `(N, 9)` array
//! with columns `[x, y, response, contrast, fit_rms, axis0_angle,
//! axis0_sigma, axis1_angle, axis1_sigma]`. See
//! `crates/chess-corners-py/README.md` for usage and configuration details.
//!
//! For tight processing loops you can also reuse pyramid storage
//! explicitly via [`find_chess_corners_buff`] and the internal
//! `pyramid` module; this avoids reallocating intermediate pyramid
//! levels across frames. Most users should stick to
//! [`find_chess_corners`] / `find_chess_corners_image` unless they
//! need fine-grained control over allocations.
//!
//! # Configuration
//!
//! [`ChessConfig`] is intentionally flat. It exposes detector ring,
//! descriptor ring, threshold mode/value, NMS controls, refiner
//! choice, and multiscale settings directly. The detector translates
//! that high-level config into lower-level [`ChessParams`] and
//! [`CoarseToFineParams`] internally.
//!
//! If you need raw response maps or more control, the most useful
//! low-level primitives are re-exported here:
//! [`chess_response_u8`], [`chess_response_u8_patch`], [`Roi`],
//! [`detect_corners_from_response_with_refiner`], [`Corner`], and
//! [`corners_to_descriptors_with_method`]. For deeper internals (ring offsets,
//! SAT views, scalar reference paths) depend on `chess-corners-core`
//! directly.
//!
//! # Features
//!
//! - `image` *(default)* – enables `find_chess_corners_image` 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.
//!
//! The ChESS idea was proposed in the papaer Bennett, Lasenby, *ChESS: A Fast and
//! Accurate Chessboard Corner Detector*, CVIU 2014
// Re-export a focused subset of core types for convenience. The facade also
// surfaces the most useful low-level primitives (response, detect,
// describe) below so callers composing custom pipelines don't need a
// separate `chess-corners-core` dependency. Deeper internals (ring offsets,
// SAT views, scalar reference paths) remain reachable only via a direct
// `chess-corners-core` dep.
pub use crate;
pub use crateChessError;
pub use crate;
pub use ;
// Low-level building blocks for callers composing custom pipelines:
// response → detect → describe. Surfaced from core's submodules.
pub use ;
pub use ;
pub use ;
// High-level helpers on `image::GrayImage`.
pub use find_chess_corners_image_with_ml;
pub use ;
// Multiscale/coarse-to-fine API types.
pub use crate;
pub use crate;
pub use ;
// Radon-detector convenience entry points.
pub use crateradon_heatmap_image;
pub use crateradon_heatmap_u8;
/// Detect chessboard corners from a raw grayscale image buffer.
///
/// The `img` slice must be `width * height` bytes in row-major order.
/// If `cfg.upscale` is enabled, the image is upscaled internally and
/// output corner coordinates are rescaled back to the original input
/// pixel frame.
///
/// # Errors
///
/// Returns [`ChessError::DimensionMismatch`] if `img.len() != width * height`.
/// Returns [`ChessError::Upscale`] if the upscale configuration is invalid.
/// Detect corners from a raw grayscale buffer with an explicit refiner choice.
///
/// # Errors
///
/// Returns [`ChessError::DimensionMismatch`] if `img.len() != width * height`.
/// Returns [`ChessError::Upscale`] if the upscale configuration is invalid.
/// Detect corners from a raw grayscale buffer using the ML refiner pipeline.
///
/// # Errors
///
/// Returns [`ChessError::DimensionMismatch`] if `img.len() != width * height`.
/// Returns [`ChessError::Upscale`] if the upscale configuration is invalid.
/// Thread the optional upscaling stage around the detection closure.
/// Allocates a single-use `UpscaleBuffers`; callers with their own
/// buffer reuse pattern should drive the pipeline directly via the
/// `multiscale` module plus `upscale_bilinear_u8`.