use super::error::TargetValidationError;
use crate::marker::codebook::CODEBOOK_N;
#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct RingGeometry {
pub outer_radius_mm: f32,
pub inner_radius_mm: f32,
}
impl RingGeometry {
pub(crate) fn validate(&self) -> Result<(), TargetValidationError> {
if !self.outer_radius_mm.is_finite() || self.outer_radius_mm <= 0.0 {
return Err(TargetValidationError::InvalidOuterRadius {
marker_outer_radius_mm: self.outer_radius_mm,
});
}
if !self.inner_radius_mm.is_finite() || self.inner_radius_mm <= 0.0 {
return Err(TargetValidationError::InvalidInnerRadius {
marker_inner_radius_mm: self.inner_radius_mm,
});
}
if self.inner_radius_mm >= self.outer_radius_mm {
return Err(TargetValidationError::InnerRadiusNotSmallerThanOuter {
marker_inner_radius_mm: self.inner_radius_mm,
marker_outer_radius_mm: self.outer_radius_mm,
});
}
Ok(())
}
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
pub struct CodedRingSpec {
pub ring_width_mm: f32,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub id_assignment: Option<Vec<usize>>,
}
#[derive(Debug, Clone, PartialEq, serde::Serialize, serde::Deserialize)]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum MarkerCoding {
Coded16(CodedRingSpec),
Plain,
}
impl MarkerCoding {
pub fn is_coded(&self) -> bool {
matches!(self, Self::Coded16(_))
}
pub(crate) fn ring_half_width_mm(&self) -> f32 {
match self {
Self::Coded16(spec) => 0.5 * spec.ring_width_mm,
Self::Plain => 0.0,
}
}
pub(crate) fn outer_draw_radius_mm(&self, ring: &RingGeometry) -> f32 {
ring.outer_radius_mm + self.ring_half_width_mm()
}
pub(crate) fn code_band_bounds_mm(&self, ring: &RingGeometry) -> Option<(f32, f32)> {
match self {
Self::Coded16(_) => {
let half = self.ring_half_width_mm();
Some((ring.inner_radius_mm + half, ring.outer_radius_mm - half))
}
Self::Plain => None,
}
}
pub(crate) fn validate(
&self,
ring: &RingGeometry,
n_cells: usize,
) -> Result<(), TargetValidationError> {
match self {
Self::Coded16(spec) => {
if !spec.ring_width_mm.is_finite() || spec.ring_width_mm <= 0.0 {
return Err(TargetValidationError::InvalidRingWidth {
marker_ring_width_mm: spec.ring_width_mm,
});
}
let half = self.ring_half_width_mm();
let inner_ring_outer_edge_mm = ring.inner_radius_mm + half;
let outer_ring_inner_edge_mm = ring.outer_radius_mm - half;
if inner_ring_outer_edge_mm >= outer_ring_inner_edge_mm {
return Err(TargetValidationError::NonPositiveCodeBandGap {
inner_ring_outer_edge_mm,
outer_ring_inner_edge_mm,
});
}
if n_cells > CODEBOOK_N {
return Err(TargetValidationError::CodebookCapacityExceeded {
n_cells,
codebook_len: CODEBOOK_N,
});
}
if let Some(assignment) = &spec.id_assignment {
if assignment.len() != n_cells {
return Err(TargetValidationError::IdAssignmentLength {
expected: n_cells,
got: assignment.len(),
});
}
let mut seen = std::collections::HashSet::new();
for (position, &id) in assignment.iter().enumerate() {
if id >= CODEBOOK_N {
return Err(TargetValidationError::IdAssignmentOutOfRange {
id,
position,
codebook_len: CODEBOOK_N,
});
}
if !seen.insert(id) {
return Err(TargetValidationError::IdAssignmentDuplicate {
id,
position,
});
}
}
}
Ok(())
}
Self::Plain => Ok(()),
}
}
}