#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
pub enum CircleRefinementMethod {
None,
#[default]
ProjectiveCenter,
}
impl CircleRefinementMethod {
pub fn uses_projective_center(self) -> bool {
matches!(self, Self::ProjectiveCenter)
}
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
#[serde(default)]
pub struct MarkerScalePrior {
pub diameter_min_px: f32,
pub diameter_max_px: f32,
}
impl MarkerScalePrior {
const MIN_DIAMETER_FLOOR_PX: f32 = 4.0;
pub fn new(diameter_min_px: f32, diameter_max_px: f32) -> Self {
let mut out = Self {
diameter_min_px,
diameter_max_px,
};
out.normalize_in_place();
out
}
pub fn from_nominal_diameter_px(diameter_px: f32) -> Self {
Self::new(diameter_px, diameter_px)
}
pub fn diameter_range_px(self) -> [f32; 2] {
let n = self.normalized();
[n.diameter_min_px, n.diameter_max_px]
}
pub fn nominal_diameter_px(self) -> f32 {
let [d_min, d_max] = self.diameter_range_px();
0.5 * (d_min + d_max)
}
pub fn nominal_outer_radius_px(self) -> f32 {
self.nominal_diameter_px() * 0.5
}
pub fn normalized(self) -> Self {
let mut out = self;
out.normalize_in_place();
out
}
fn normalize_in_place(&mut self) {
let defaults = MarkerScalePrior::default();
let mut d_min = if self.diameter_min_px.is_finite() {
self.diameter_min_px
} else {
defaults.diameter_min_px
};
let mut d_max = if self.diameter_max_px.is_finite() {
self.diameter_max_px
} else {
defaults.diameter_max_px
};
if d_min > d_max {
std::mem::swap(&mut d_min, &mut d_max);
}
d_min = d_min.max(Self::MIN_DIAMETER_FLOOR_PX);
d_max = d_max.max(d_min);
self.diameter_min_px = d_min;
self.diameter_max_px = d_max;
}
}
impl Default for MarkerScalePrior {
fn default() -> Self {
Self {
diameter_min_px: 14.0,
diameter_max_px: 66.0,
}
}
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
pub struct ScaleTier {
pub prior: MarkerScalePrior,
}
impl ScaleTier {
pub fn new(diameter_min_px: f32, diameter_max_px: f32) -> Self {
Self {
prior: MarkerScalePrior::new(diameter_min_px, diameter_max_px),
}
}
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct ScaleTiers(Vec<ScaleTier>);
impl ScaleTiers {
pub fn new(tiers: Vec<ScaleTier>) -> Self {
Self(tiers)
}
pub fn four_tier_wide() -> Self {
Self(vec![
ScaleTier::new(8.0, 24.0),
ScaleTier::new(20.0, 60.0),
ScaleTier::new(50.0, 130.0),
ScaleTier::new(110.0, 220.0),
])
}
pub fn two_tier_standard() -> Self {
Self(vec![
ScaleTier::new(14.0, 42.0),
ScaleTier::new(36.0, 100.0),
])
}
pub fn single(prior: MarkerScalePrior) -> Self {
Self(vec![ScaleTier { prior }])
}
pub fn from_detected_radii(probe_radii: &[f32]) -> Self {
let mut sorted: Vec<f32> = probe_radii
.iter()
.copied()
.filter(|r| r.is_finite() && *r > 0.0)
.collect();
if sorted.is_empty() {
return Self::single(MarkerScalePrior::default());
}
sorted.sort_by(|a, b| a.total_cmp(b));
const PROBE_TO_OUTER: f32 = 1.0 / 0.8;
let mut tiers = Vec::new();
let mut cluster_min = sorted[0];
let mut cluster_max = sorted[0];
for &r in &sorted[1..] {
if r / cluster_min <= 3.0 {
cluster_max = r;
} else {
let r_outer_min = cluster_min * PROBE_TO_OUTER;
let r_outer_max = cluster_max * PROBE_TO_OUTER;
let d_min = (2.0 * r_outer_min * 0.70).max(4.0);
let d_max = (2.0 * r_outer_max * 1.35).max(d_min);
tiers.push(ScaleTier::new(d_min, d_max));
cluster_min = r;
cluster_max = r;
}
}
let r_outer_min = cluster_min * PROBE_TO_OUTER;
let r_outer_max = cluster_max * PROBE_TO_OUTER;
let d_min = (2.0 * r_outer_min * 0.70).max(4.0);
let d_max = (2.0 * r_outer_max * 1.35).max(d_min);
tiers.push(ScaleTier::new(d_min, d_max));
Self(tiers)
}
pub fn tiers(&self) -> &[ScaleTier] {
&self.0
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ProposalDownscale {
Auto,
#[default]
Off,
Factor(u32),
}
impl ProposalDownscale {
pub fn resolve(&self, marker_scale: MarkerScalePrior) -> u32 {
match self {
Self::Auto => {
let d_min = marker_scale.diameter_range_px()[0];
(d_min / 14.0).floor().clamp(1.0, 4.0) as u32
}
Self::Off => 1,
Self::Factor(f) => (*f).clamp(1, 4),
}
}
}