use box_image_pyramid::PyramidParams;
use chess_corners_core::{
CenterOfMassConfig, ChessParams, ForstnerConfig, OrientationMethod, RadonDetectorParams,
RadonPeakConfig, RefinerKind, SaddlePointConfig,
};
use serde::{Deserialize, Serialize};
use crate::multiscale::CoarseToFineParams;
use crate::upscale::UpscaleConfig;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum DetectorMode {
#[default]
Canonical,
Broad,
Radon,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum DescriptorMode {
#[default]
FollowDetector,
Canonical,
Broad,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum ThresholdMode {
#[default]
Relative,
Absolute,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum RefinementMethod {
#[default]
CenterOfMass,
Forstner,
SaddlePoint,
RadonPeak,
}
#[derive(Clone, Debug, Default, PartialEq, Serialize, Deserialize)]
#[serde(default)]
#[non_exhaustive]
pub struct RefinerConfig {
pub kind: RefinementMethod,
pub center_of_mass: CenterOfMassConfig,
pub forstner: ForstnerConfig,
pub saddle_point: SaddlePointConfig,
pub radon_peak: RadonPeakConfig,
}
impl RefinerConfig {
#[allow(clippy::too_many_arguments)]
pub fn build(
kind: RefinementMethod,
center_of_mass: CenterOfMassConfig,
forstner: ForstnerConfig,
saddle_point: SaddlePointConfig,
radon_peak: RadonPeakConfig,
) -> Self {
Self {
kind,
center_of_mass,
forstner,
saddle_point,
radon_peak,
}
}
pub fn center_of_mass() -> Self {
Self {
kind: RefinementMethod::CenterOfMass,
..Self::default()
}
}
pub fn forstner() -> Self {
Self {
kind: RefinementMethod::Forstner,
..Self::default()
}
}
pub fn saddle_point() -> Self {
Self {
kind: RefinementMethod::SaddlePoint,
..Self::default()
}
}
pub fn radon_peak() -> Self {
Self {
kind: RefinementMethod::RadonPeak,
..Self::default()
}
}
pub fn to_refiner_kind(&self) -> RefinerKind {
match self.kind {
RefinementMethod::CenterOfMass => RefinerKind::CenterOfMass(self.center_of_mass),
RefinementMethod::Forstner => RefinerKind::Forstner(self.forstner),
RefinementMethod::SaddlePoint => RefinerKind::SaddlePoint(self.saddle_point),
RefinementMethod::RadonPeak => RefinerKind::RadonPeak(self.radon_peak),
}
}
}
#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default)]
#[non_exhaustive]
pub struct ChessConfig {
pub detector_mode: DetectorMode,
pub descriptor_mode: DescriptorMode,
pub threshold_mode: ThresholdMode,
pub threshold_value: f32,
pub nms_radius: u32,
pub min_cluster_size: u32,
pub refiner: RefinerConfig,
pub pyramid_levels: u8,
pub pyramid_min_size: usize,
pub refinement_radius: u32,
pub merge_radius: f32,
pub upscale: UpscaleConfig,
pub radon_detector: RadonDetectorParams,
#[serde(default)]
pub orientation_method: OrientationMethod,
}
impl Default for ChessConfig {
fn default() -> Self {
Self {
detector_mode: DetectorMode::default(),
descriptor_mode: DescriptorMode::default(),
threshold_mode: ThresholdMode::Absolute,
threshold_value: 0.0,
nms_radius: 2,
min_cluster_size: 2,
refiner: RefinerConfig::default(),
pyramid_levels: 1,
pyramid_min_size: 128,
refinement_radius: 3,
merge_radius: 3.0,
upscale: UpscaleConfig::default(),
radon_detector: RadonDetectorParams::default(),
orientation_method: OrientationMethod::default(),
}
}
}
impl ChessConfig {
pub fn single_scale() -> Self {
Self::default()
}
pub fn multiscale() -> Self {
Self {
pyramid_levels: 3,
pyramid_min_size: 128,
..Self::default()
}
}
pub fn radon() -> Self {
Self {
detector_mode: DetectorMode::Radon,
pyramid_levels: 1,
..Self::default()
}
}
pub fn to_chess_params(&self) -> ChessParams {
let mut params = ChessParams::default();
params.use_radius10 = matches!(self.detector_mode, DetectorMode::Broad);
params.descriptor_use_radius10 = match self.descriptor_mode {
DescriptorMode::FollowDetector => None,
DescriptorMode::Canonical => Some(false),
DescriptorMode::Broad => Some(true),
};
match self.threshold_mode {
ThresholdMode::Relative => {
params.threshold_rel = self.threshold_value;
params.threshold_abs = None;
}
ThresholdMode::Absolute => {
params.threshold_abs = Some(self.threshold_value);
}
}
params.nms_radius = self.nms_radius;
params.min_cluster_size = self.min_cluster_size;
params.refiner = self.refiner.to_refiner_kind();
params.orientation_method = self.orientation_method;
params
}
pub fn to_coarse_to_fine_params(&self) -> CoarseToFineParams {
let mut cfg = CoarseToFineParams::default();
let mut pyramid = PyramidParams::default();
pyramid.num_levels = self.pyramid_levels;
pyramid.min_size = self.pyramid_min_size;
cfg.pyramid = pyramid;
cfg.refinement_radius = self.refinement_radius;
cfg.merge_radius = self.merge_radius;
cfg
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_config_accepts_any_positive_response() {
let cfg = ChessConfig::default();
let params = cfg.to_chess_params();
let cf = cfg.to_coarse_to_fine_params();
assert!(!params.use_radius10);
assert_eq!(params.descriptor_use_radius10, None);
assert_eq!(cfg.threshold_mode, ThresholdMode::Absolute);
assert_eq!(cfg.threshold_value, 0.0);
assert_eq!(params.threshold_abs, Some(0.0));
assert_eq!(params.nms_radius, 2);
assert_eq!(params.min_cluster_size, 2);
assert_eq!(
params.refiner,
RefinerKind::CenterOfMass(CenterOfMassConfig::default())
);
assert_eq!(cf.pyramid.num_levels, 1);
assert_eq!(cf.pyramid.min_size, 128);
assert_eq!(cf.refinement_radius, 3);
assert_eq!(cf.merge_radius, 3.0);
}
#[test]
fn absolute_threshold_maps_to_internal_params() {
let cfg = ChessConfig {
threshold_mode: ThresholdMode::Absolute,
threshold_value: 7.5,
..ChessConfig::default()
};
let params = cfg.to_chess_params();
assert_eq!(params.threshold_abs, Some(7.5));
assert_eq!(params.threshold_rel, 0.2);
}
#[test]
fn ring_and_refiner_modes_map_to_internal_params() {
let cfg = ChessConfig {
detector_mode: DetectorMode::Broad,
descriptor_mode: DescriptorMode::Canonical,
refiner: RefinerConfig {
kind: RefinementMethod::Forstner,
forstner: ForstnerConfig {
max_offset: 2.0,
..ForstnerConfig::default()
},
..RefinerConfig::default()
},
..ChessConfig::default()
};
let params = cfg.to_chess_params();
assert!(params.use_radius10);
assert_eq!(params.descriptor_use_radius10, Some(false));
assert_eq!(
params.refiner,
RefinerKind::Forstner(ForstnerConfig {
max_offset: 2.0,
..ForstnerConfig::default()
})
);
}
#[test]
fn multiscale_preset_has_expected_defaults() {
let cfg = ChessConfig::multiscale();
assert_eq!(cfg.pyramid_levels, 3);
assert_eq!(cfg.pyramid_min_size, 128);
assert_eq!(cfg.refinement_radius, 3);
assert_eq!(cfg.merge_radius, 3.0);
}
}