projective_grid/topological/
trace.rs1use serde::{Deserialize, Serialize};
9use thiserror::Error;
10
11use crate::detect::{
12 detect_grid_all, DetectionParams, DetectionRequest, Evidence, TopologicalParams,
13};
14use crate::feature::OrientedFeature;
15use crate::lattice::LatticeKind;
16
17#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
19#[non_exhaustive]
20pub struct TopologicalCornerTrace {
21 pub index: usize,
23 pub source_index: usize,
25 pub position: [f32; 2],
27 pub axis_angles_rad: [f32; 2],
29 pub axis_sigmas_rad: [Option<f32>; 2],
31 pub usable: bool,
33}
34
35#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
37#[non_exhaustive]
38pub struct TopologicalLabelTrace {
39 pub u: i32,
41 pub v: i32,
43 pub source_index: usize,
45}
46
47impl TopologicalLabelTrace {
48 pub fn new(u: i32, v: i32, source_index: usize) -> Self {
50 Self { u, v, source_index }
51 }
52}
53
54#[derive(Clone, Debug, Serialize, Deserialize)]
56#[non_exhaustive]
57pub struct TopologicalComponentTrace {
58 pub index: usize,
60 pub labels: Vec<TopologicalLabelTrace>,
62}
63
64#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
66#[non_exhaustive]
67pub struct TopologicalTraceDiagnostics {
68 pub corners_in: usize,
70 pub corners_used: usize,
72 pub components: usize,
74 pub labels: usize,
76}
77
78#[derive(Clone, Debug, Serialize, Deserialize)]
80#[non_exhaustive]
81pub struct TopologicalTrace {
82 pub params: TopologicalParams,
84 pub corners: Vec<TopologicalCornerTrace>,
86 pub components: Vec<TopologicalComponentTrace>,
88 pub diagnostics: TopologicalTraceDiagnostics,
90}
91
92#[derive(Clone, Debug, Error, PartialEq, Eq)]
94#[non_exhaustive]
95pub enum TopologicalTraceError {
96 #[error("not enough usable corners ({usable}) for Delaunay triangulation")]
98 NotEnoughCorners {
99 usable: usize,
101 },
102 #[error("topological detection produced no labelled components")]
104 NoComponents,
105}
106
107pub fn build_grid_topological_trace(
109 features: &[OrientedFeature<2>],
110 params: TopologicalParams,
111) -> Result<TopologicalTrace, TopologicalTraceError> {
112 let corners: Vec<TopologicalCornerTrace> = features
113 .iter()
114 .enumerate()
115 .map(|(index, feature)| {
116 let usable = feature.axes.iter().any(|axis| {
117 axis.sigma_rad
118 .map(|s| s < params.max_axis_sigma_rad)
119 .unwrap_or(true)
120 });
121 TopologicalCornerTrace {
122 index,
123 source_index: feature.point.source_index,
124 position: [feature.point.position.x, feature.point.position.y],
125 axis_angles_rad: [feature.axes[0].angle_rad, feature.axes[1].angle_rad],
126 axis_sigmas_rad: [feature.axes[0].sigma_rad, feature.axes[1].sigma_rad],
127 usable,
128 }
129 })
130 .collect();
131 let corners_used = corners.iter().filter(|c| c.usable).count();
132 if corners_used < 3 {
133 return Err(TopologicalTraceError::NotEnoughCorners {
134 usable: corners_used,
135 });
136 }
137
138 let validate = crate::detect::ValidateParams::default()
139 .with_line_tol_rel(f32::INFINITY)
140 .with_local_h_tol_rel(f32::INFINITY)
141 .with_edge_length_band_rel(f32::INFINITY);
142 let request = DetectionRequest::new(
143 LatticeKind::Square,
144 Evidence::Oriented2(features),
145 None,
146 DetectionParams::default()
147 .with_topological(params)
148 .with_validate(validate)
149 .with_max_residual_px(f32::INFINITY),
150 );
151 let report = detect_grid_all(request).map_err(|_| TopologicalTraceError::NoComponents)?;
152 if report.solutions.is_empty() {
153 return Err(TopologicalTraceError::NoComponents);
154 }
155
156 let components: Vec<TopologicalComponentTrace> = report
157 .solutions
158 .iter()
159 .enumerate()
160 .map(|(index, solution)| {
161 let mut labels: Vec<TopologicalLabelTrace> = solution
162 .grid
163 .entries
164 .iter()
165 .map(|entry| TopologicalLabelTrace {
166 u: entry.coord.u,
167 v: entry.coord.v,
168 source_index: entry.source_index,
169 })
170 .collect();
171 labels.sort_by_key(|label| (label.v, label.u, label.source_index));
172 TopologicalComponentTrace { index, labels }
173 })
174 .collect();
175 let labels = components
176 .iter()
177 .map(|component| component.labels.len())
178 .sum();
179 let diagnostics = TopologicalTraceDiagnostics {
180 corners_in: features.len(),
181 corners_used,
182 components: components.len(),
183 labels,
184 };
185 Ok(TopologicalTrace {
186 params,
187 corners,
188 components,
189 diagnostics,
190 })
191}