# chess-corners
Ergonomic chessboard corner detector on top of `chess-corners-core`.
This crate is the public Rust API:
- strategy-typed `DetectorConfig` (`DetectionStrategy::Chess(ChessConfig)`
/ `DetectionStrategy::Radon(RadonConfig)`) with a single numeric
`threshold` and per-ChESS-detector refiner selection (`ChessRefiner`)
- top-level `MultiscaleConfig` (`SingleScale | Pyramid { ... }`) and
`UpscaleConfig` (`Disabled | Fixed(factor)`), honoured by both
detectors symmetrically
- single-scale and coarse-to-fine multiscale detection through a single
`Detector` struct that reuses pyramid and scratch buffers across
frames
- optional `image::GrayImage` helpers
- optional CLI binary and ML-backed refinement pipeline
`chess-corners-core` and `box-image-pyramid` remain available as
lower-level sharp tools, but `chess-corners` is the intended
compatibility boundary.
## Quick start
```rust
use chess_corners::{Detector, DetectorConfig};
use image::ImageReader;
fn main() -> Result<(), Box<dyn std::error::Error>> {
let img = ImageReader::open("board.png")?.decode()?.to_luma8();
// ChESS reads `threshold` as an absolute floor on the raw response
// (default 30); raise it to suppress more textured-region noise.
let cfg = DetectorConfig::chess_multiscale()
.with_threshold(60.0);
let mut detector = Detector::new(cfg)?;
let corners = detector.detect(&img)?;
println!("found {} corners", corners.len());
Ok(())
}
```
`Detector` owns the pyramid and upscale scratch buffers, so calling
`detector.detect(&img)` repeatedly on successive frames does not
re-allocate.
## Presets
| `DetectorConfig::chess()` | ChESS | Single-scale |
| `DetectorConfig::chess_multiscale()` | ChESS | 3-level pyramid |
| `DetectorConfig::radon()` | Radon | Single-scale |
| `DetectorConfig::radon_multiscale()` | Radon | 3-level pyramid |
Use `chess()` and `chess_multiscale()` for single-scale and multiscale
ChESS detection respectively.
## Public config shape
`DetectorConfig` groups detector-specific tuning under a typed
`DetectionStrategy` enum and shares cross-cutting fields at the top
level:
```rust
use chess_corners::{
ChessRefiner, ChessRing, DetectorConfig,
MultiscaleConfig, UpscaleConfig,
};
let cfg = DetectorConfig::chess()
.with_threshold(60.0) // ChESS: absolute floor on the raw response (default 30)
.with_merge_radius(3.0)
.with_multiscale(MultiscaleConfig::pyramid_default())
.with_upscale(UpscaleConfig::Fixed(2))
.with_chess(|c| {
c.ring = ChessRing::Broad;
c.refiner = ChessRefiner::default();
})
.with_detection(|d| {
d.nms_radius = 2;
d.min_cluster_size = 2;
});
// Or switch to the Radon strategy:
is the built-in 3-point Gaussian peak fit, selected via
`RadonConfig.peak_fit` (`PeakFitMode::Parabolic` or `Gaussian`).
## CLI config shape
The CLI uses the same `DetectorConfig` schema, combined with
application fields such as `image`, `output_json`, `output_png`,
`log_level`, and `ml`. It reads PNG, JPEG, TIFF, BMP, WebP and GIF
input images.
See:
- `config/chess_algorithm_config_example.json` for the pure
`DetectorConfig` shape (round-trips through the Rust and Python
APIs).
- `config/chess_cli_config_example.json` for a complete CLI runner
input (algorithm config + envelope).
## ML refiner
Enable the `ml-refiner` feature, then pick the `Ml` variant on the
ChESS strategy's refiner:
```rust
# #[cfg(feature = "ml-refiner")]
# {
use chess_corners::{ChessRefiner, Detector, DetectorConfig};
use image::GrayImage;
let img = GrayImage::new(1, 1);
let cfg = DetectorConfig::chess().with_chess(|c| c.refiner = ChessRefiner::Ml);
let mut detector = Detector::new(cfg).unwrap();
let _ = detector.detect(&img).unwrap();
# }
```
The ML path is slower than the classic refiners in the shipped
benchmark and falls back to the classic CenterOfMass refiner at coarse
pyramid levels.
## Examples
- Single-scale: `cargo run -p chess-corners --example single_scale_image -- testimages/mid.png`
- Multiscale: `cargo run -p chess-corners --example multiscale_image -- testimages/large.png`
## Feature flags
- `image` (default): `image::GrayImage` integration, no image-format codecs
- `rayon`: parallel response/refinement
- `simd`: optional high-performance path — portable `std::simd` in the core response path (nightly only)
- `par_pyramid`: SIMD/`rayon` in pyramid construction
- `tracing`: structured spans
- `ml-refiner`: ONNX-backed ML refinement
- `cli`: build the `chess-corners` binary
`image` only wires up in-memory buffer types (`GrayImage`,
`ImageBuffer`, `Luma`) — it enables no image-format codec, so this
crate never decodes or encodes a file itself. If your code calls
`image::open` or `ImageReader::decode`, declare the formats you need
on your own `image` dependency:
```toml
image = { version = "0.25", features = ["png", "jpeg"] }
```
The stable scalar/autovectorized build is the supported, portable
baseline — correct and fast enough for typical use. It requires Rust
**1.91** or newer (`rust-version` in `Cargo.toml`). The `simd` feature
uses `std::simd` and is the only feature that needs a nightly
toolchain; every other feature builds on stable.