use box_image_pyramid::PyramidParams;
use chess_corners_core::{ChessParams, OrientationMethod, RadonDetectorParams, RefinerKind};
use serde::{Deserialize, Serialize};
use crate::multiscale::CoarseToFineParams;
use crate::upscale::UpscaleConfig;
use super::{ChessConfig, ChessRefiner, ChessRing, MultiscaleConfig, RadonConfig};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
#[non_exhaustive]
pub struct DetectionParams {
pub nms_radius: u32,
pub min_cluster_size: u32,
}
impl Default for DetectionParams {
fn default() -> Self {
Self {
nms_radius: ChessParams::DEFAULT_NMS_RADIUS,
min_cluster_size: ChessParams::DEFAULT_MIN_CLUSTER_SIZE,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum DetectionStrategy {
Chess(ChessConfig),
Radon(RadonConfig),
}
impl Default for DetectionStrategy {
fn default() -> Self {
DetectionStrategy::Chess(ChessConfig::default())
}
}
#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
#[serde(default)]
#[non_exhaustive]
pub struct DetectorConfig {
pub strategy: DetectionStrategy,
pub threshold: f32,
pub detection: DetectionParams,
pub multiscale: MultiscaleConfig,
pub upscale: UpscaleConfig,
pub orientation_method: Option<OrientationMethod>,
pub merge_radius: f32,
}
impl Default for DetectorConfig {
fn default() -> Self {
Self::chess()
}
}
impl DetectorConfig {
pub fn chess() -> Self {
Self {
strategy: DetectionStrategy::Chess(ChessConfig::default()),
threshold: 30.0,
detection: DetectionParams::default(),
multiscale: MultiscaleConfig::SingleScale,
upscale: UpscaleConfig::Disabled,
orientation_method: Some(OrientationMethod::default()),
merge_radius: 3.0,
}
}
pub fn chess_multiscale() -> Self {
Self {
multiscale: MultiscaleConfig::pyramid_default(),
..Self::chess()
}
}
pub fn radon() -> Self {
Self {
strategy: DetectionStrategy::Radon(RadonConfig::default()),
threshold: RadonDetectorParams::DEFAULT_THRESHOLD_REL,
detection: DetectionParams {
nms_radius: RadonDetectorParams::DEFAULT_NMS_RADIUS,
min_cluster_size: RadonDetectorParams::DEFAULT_MIN_CLUSTER_SIZE,
},
multiscale: MultiscaleConfig::SingleScale,
..Self::chess()
}
}
pub fn radon_multiscale() -> Self {
Self {
multiscale: MultiscaleConfig::pyramid_default(),
..Self::radon()
}
}
pub fn with_chess<F: FnOnce(&mut ChessConfig)>(mut self, f: F) -> Self {
let mut chess = match self.strategy {
DetectionStrategy::Chess(c) => c,
DetectionStrategy::Radon(_) => ChessConfig::default(),
};
f(&mut chess);
self.strategy = DetectionStrategy::Chess(chess);
self
}
pub fn with_radon<F: FnOnce(&mut RadonConfig)>(mut self, f: F) -> Self {
let mut radon = match self.strategy {
DetectionStrategy::Radon(r) => r,
DetectionStrategy::Chess(_) => RadonConfig::default(),
};
f(&mut radon);
self.strategy = DetectionStrategy::Radon(radon);
self
}
pub fn with_threshold(mut self, threshold: f32) -> Self {
self.threshold = threshold;
self
}
pub fn with_multiscale(mut self, multiscale: MultiscaleConfig) -> Self {
self.multiscale = multiscale;
self
}
pub fn with_upscale(mut self, upscale: UpscaleConfig) -> Self {
self.upscale = upscale;
self
}
pub fn with_orientation_method(mut self, method: OrientationMethod) -> Self {
self.orientation_method = Some(method);
self
}
pub fn without_orientation(mut self) -> Self {
self.orientation_method = None;
self
}
pub fn with_merge_radius(mut self, radius: f32) -> Self {
self.merge_radius = radius;
self
}
pub fn with_detection<F: FnOnce(&mut DetectionParams)>(mut self, f: F) -> Self {
f(&mut self.detection);
self
}
pub fn chess_params(&self) -> ChessParams {
let mut params = ChessParams::default();
params.nms_radius = self.detection.nms_radius;
params.min_cluster_size = self.detection.min_cluster_size;
if let DetectionStrategy::Chess(chess) = &self.strategy {
params.use_radius10 = matches!(chess.ring, ChessRing::Broad);
match chess.refiner {
ChessRefiner::CenterOfMass(cfg) => params.refiner = RefinerKind::CenterOfMass(cfg),
ChessRefiner::Forstner(cfg) => params.refiner = RefinerKind::Forstner(cfg),
ChessRefiner::SaddlePoint(cfg) => params.refiner = RefinerKind::SaddlePoint(cfg),
#[cfg(feature = "ml-refiner")]
ChessRefiner::Ml => {}
}
}
params.threshold = self.threshold;
params.orientation_method = self.orientation_method;
params
}
pub fn radon_detector_params(&self) -> RadonDetectorParams {
let mut params = RadonDetectorParams::default();
params.nms_radius = self.detection.nms_radius;
params.min_cluster_size = self.detection.min_cluster_size;
if let DetectionStrategy::Radon(radon) = &self.strategy {
params.ray_radius = radon.ray_radius;
params.image_upsample = radon.image_upsample;
params.response_blur_radius = radon.response_blur_radius;
params.peak_fit = radon.peak_fit;
}
params.threshold_rel = self.threshold;
params
}
pub(crate) fn coarse_to_fine_params(&self) -> Option<CoarseToFineParams> {
let MultiscaleConfig::Pyramid {
levels,
min_size,
refinement_radius,
} = self.multiscale
else {
return None;
};
let mut cfg = CoarseToFineParams::default();
let mut pyramid = PyramidParams::default();
pyramid.num_levels = levels;
pyramid.min_size = min_size;
cfg.pyramid = pyramid;
cfg.refinement_radius = refinement_radius;
cfg.merge_radius = self.merge_radius;
Some(cfg)
}
}