Skip to main content

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}