use serde::{Deserialize, Serialize};
use thiserror::Error;
use crate::detect::{
detect_grid_all, DetectionParams, DetectionRequest, Evidence, TopologicalParams,
};
use crate::feature::OrientedFeature;
use crate::lattice::LatticeKind;
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TopologicalCornerTrace {
pub index: usize,
pub source_index: usize,
pub position: [f32; 2],
pub axis_angles_rad: [f32; 2],
pub axis_sigmas_rad: [Option<f32>; 2],
pub usable: bool,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TopologicalLabelTrace {
pub u: i32,
pub v: i32,
pub source_index: usize,
}
impl TopologicalLabelTrace {
pub fn new(u: i32, v: i32, source_index: usize) -> Self {
Self { u, v, source_index }
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TopologicalComponentTrace {
pub index: usize,
pub labels: Vec<TopologicalLabelTrace>,
}
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TopologicalTraceDiagnostics {
pub corners_in: usize,
pub corners_used: usize,
pub components: usize,
pub labels: usize,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TopologicalTrace {
pub params: TopologicalParams,
pub corners: Vec<TopologicalCornerTrace>,
pub components: Vec<TopologicalComponentTrace>,
pub diagnostics: TopologicalTraceDiagnostics,
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum TopologicalTraceError {
#[error("not enough usable corners ({usable}) for Delaunay triangulation")]
NotEnoughCorners {
usable: usize,
},
#[error("topological detection produced no labelled components")]
NoComponents,
}
pub fn build_grid_topological_trace(
features: &[OrientedFeature<2>],
params: TopologicalParams,
) -> Result<TopologicalTrace, TopologicalTraceError> {
let corners: Vec<TopologicalCornerTrace> = features
.iter()
.enumerate()
.map(|(index, feature)| {
let usable = feature.axes.iter().any(|axis| {
axis.sigma_rad
.map(|s| s < params.max_axis_sigma_rad)
.unwrap_or(true)
});
TopologicalCornerTrace {
index,
source_index: feature.point.source_index,
position: [feature.point.position.x, feature.point.position.y],
axis_angles_rad: [feature.axes[0].angle_rad, feature.axes[1].angle_rad],
axis_sigmas_rad: [feature.axes[0].sigma_rad, feature.axes[1].sigma_rad],
usable,
}
})
.collect();
let corners_used = corners.iter().filter(|c| c.usable).count();
if corners_used < 3 {
return Err(TopologicalTraceError::NotEnoughCorners {
usable: corners_used,
});
}
let validate = crate::detect::ValidateParams::default()
.with_line_tol_rel(f32::INFINITY)
.with_local_h_tol_rel(f32::INFINITY)
.with_edge_length_band_rel(f32::INFINITY);
let request = DetectionRequest::new(
LatticeKind::Square,
Evidence::Oriented2(features),
None,
DetectionParams::default()
.with_topological(params)
.with_validate(validate)
.with_max_residual_px(f32::INFINITY),
);
let report = detect_grid_all(request).map_err(|_| TopologicalTraceError::NoComponents)?;
if report.solutions.is_empty() {
return Err(TopologicalTraceError::NoComponents);
}
let components: Vec<TopologicalComponentTrace> = report
.solutions
.iter()
.enumerate()
.map(|(index, solution)| {
let mut labels: Vec<TopologicalLabelTrace> = solution
.grid
.entries
.iter()
.map(|entry| TopologicalLabelTrace {
u: entry.coord.u,
v: entry.coord.v,
source_index: entry.source_index,
})
.collect();
labels.sort_by_key(|label| (label.v, label.u, label.source_index));
TopologicalComponentTrace { index, labels }
})
.collect();
let labels = components
.iter()
.map(|component| component.labels.len())
.sum();
let diagnostics = TopologicalTraceDiagnostics {
corners_in: features.len(),
corners_used,
components: components.len(),
labels,
};
Ok(TopologicalTrace {
params,
corners,
components,
diagnostics,
})
}