chess_corners_core/params.rs
1//! Low-level ChESS detection parameters.
2//!
3//! [`ChessParams`] is the parameter bundle consumed by the ChESS
4//! response and detection stages. It is re-exported from the crate
5//! root as part of the low-level contract: the `chess-corners` facade
6//! lowers its `DetectorConfig` onto it, and callers driving the
7//! response and detection stages directly construct it themselves.
8
9use crate::detect::chess::ring::RingOffsets;
10use crate::orientation::OrientationMethod;
11use crate::refine::RefinerKind;
12use serde::{Deserialize, Serialize};
13
14/// Tunable parameters for the ChESS response computation and corner detection.
15#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
16#[serde(default)]
17#[non_exhaustive]
18pub struct ChessParams {
19 /// Use the larger r=10 ring instead of the canonical r=5.
20 pub use_radius10: bool,
21 /// Absolute response floor: a corner is kept when its raw ChESS
22 /// response exceeds this value (strict `>`). `0.0` accepts every
23 /// strictly-positive response — the paper's contract.
24 pub threshold: f32,
25 /// Non-maximum suppression radius (in pixels).
26 pub nms_radius: u32,
27 /// Minimum count of positive-response neighbors in NMS window
28 /// to accept a corner (rejects isolated noise).
29 pub min_cluster_size: u32,
30 /// Subpixel refinement backend and its configuration. Defaults to
31 /// center-of-mass on the response map.
32 pub refiner: RefinerKind,
33 /// Orientation-fit method used to estimate the two grid axes at
34 /// each detected corner, or `None` to skip the fit entirely (every
35 /// descriptor then carries `axes: None`). Default
36 /// `Some(`[`OrientationMethod::RingFit`]`)` fits the parametric
37 /// two-axis model with robust seeding and calibrated per-axis
38 /// uncertainties.
39 #[serde(default = "default_orientation_method")]
40 pub orientation_method: Option<OrientationMethod>,
41}
42
43#[inline]
44fn default_orientation_method() -> Option<OrientationMethod> {
45 Some(OrientationMethod::default())
46}
47
48impl Default for ChessParams {
49 fn default() -> Self {
50 Self {
51 use_radius10: false,
52 // Paper's contract: accept every strictly-positive ChESS
53 // response. `threshold = 0.0` combined with the strict
54 // comparison in `detect_corners_from_response` gives
55 // "R > 0 ⇒ corner". The facade raises this to a denoise
56 // floor for real images.
57 threshold: 0.0,
58 nms_radius: Self::DEFAULT_NMS_RADIUS,
59 min_cluster_size: Self::DEFAULT_MIN_CLUSTER_SIZE,
60 refiner: RefinerKind::default(),
61 orientation_method: Some(OrientationMethod::default()),
62 }
63 }
64}
65
66impl ChessParams {
67 /// Default non-maximum-suppression half-radius, in pixels. Single
68 /// source of truth shared with the `chess-corners` facade's
69 /// `DetectionParams` default.
70 pub const DEFAULT_NMS_RADIUS: u32 = 2;
71 /// Default minimum count of positive-response neighbours required to
72 /// accept a cluster. Single source of truth shared with the
73 /// `chess-corners` facade's `DetectionParams` default.
74 pub const DEFAULT_MIN_CLUSTER_SIZE: u32 = 2;
75
76 /// Ring radius in pixels selected by [`Self::use_radius10`]: `10`
77 /// when set, `5` otherwise.
78 #[inline]
79 pub fn ring_radius(&self) -> u32 {
80 if self.use_radius10 {
81 10
82 } else {
83 5
84 }
85 }
86
87 #[inline]
88 pub(crate) fn ring(&self) -> RingOffsets {
89 RingOffsets::from_radius(self.ring_radius())
90 }
91}