use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use thiserror::Error;
use crate::detect::{validate_request, DetectionParams, DetectionRequest, Evidence};
use crate::feature::OrientedFeature;
use crate::lattice::{GridDimensions, LatticeKind};
use crate::shared::recovery_schedule::SquareAxisProvenance;
use crate::topological::classify::EdgeClass;
use crate::topological::square_detector::{
detect_square_oriented2_all_observed, SquarePipelineTrace,
};
use crate::topological::TopologicalParams;
#[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, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
#[non_exhaustive]
pub enum TopologicalEdgeClass {
Grid,
Diagonal,
Spurious,
}
impl From<EdgeClass> for TopologicalEdgeClass {
fn from(value: EdgeClass) -> Self {
match value {
EdgeClass::Grid => Self::Grid,
EdgeClass::Diagonal => Self::Diagonal,
EdgeClass::Spurious => Self::Spurious,
}
}
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TopologicalEdgeTrace {
pub start: usize,
pub end: usize,
pub class: TopologicalEdgeClass,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TopologicalTriangleTrace {
pub vertices: [usize; 3],
pub edge_classes: [TopologicalEdgeClass; 3],
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TopologicalQuadTrace {
pub vertices: [usize; 4],
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TopologicalLabelTrace {
pub u: i32,
pub v: i32,
pub feature_index: usize,
pub source_index: usize,
pub residual_px: Option<f32>,
}
#[derive(Clone, Copy, Debug, Serialize, Deserialize)]
pub struct TopologicalFitTrace {
pub count: usize,
pub mean_px: f32,
pub max_px: f32,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TopologicalComponentTrace {
pub index: usize,
pub labels: Vec<TopologicalLabelTrace>,
pub fit: Option<TopologicalFitTrace>,
}
#[derive(Clone, Copy, Debug, Default, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TopologicalTraceDiagnostics {
pub corners_in: usize,
pub corners_used: usize,
pub triangles: usize,
pub raw_quads: usize,
pub topology_quads: usize,
pub geometry_quads: usize,
pub scale_quads: usize,
pub walk_components: usize,
pub merged_components: usize,
pub final_components: usize,
pub final_labels: usize,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct TopologicalTrace {
pub schema_version: u32,
pub params: TopologicalParams,
pub corners: Vec<TopologicalCornerTrace>,
pub edges: Vec<TopologicalEdgeTrace>,
pub triangles: Vec<TopologicalTriangleTrace>,
pub raw_quads: Vec<TopologicalQuadTrace>,
pub topology_quads: Vec<TopologicalQuadTrace>,
pub geometry_quads: Vec<TopologicalQuadTrace>,
pub scale_quads: Vec<TopologicalQuadTrace>,
pub walk_components: Vec<TopologicalComponentTrace>,
pub merged_components: Vec<TopologicalComponentTrace>,
pub final_components: Vec<TopologicalComponentTrace>,
pub diagnostics: TopologicalTraceDiagnostics,
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[non_exhaustive]
pub enum TopologicalTraceError {
#[error("topological detection failed: {message}")]
DetectionFailed {
message: String,
},
}
pub fn build_grid_topological_trace(
features: &[OrientedFeature<2>],
dimensions: Option<GridDimensions>,
params: DetectionParams,
) -> Result<TopologicalTrace, TopologicalTraceError> {
let request = DetectionRequest::new(LatticeKind::Square, Evidence::Oriented2(features))
.with_params(params.clone());
let request = match dimensions {
Some(dimensions) => request.with_dimensions(dimensions),
None => request,
};
validate_request(&request).map_err(|error| TopologicalTraceError::DetectionFailed {
message: error.to_string(),
})?;
let topological_params = params.tuning().topological;
let mut raw = SquarePipelineTrace::default();
let solutions = detect_square_oriented2_all_observed(
features,
dimensions,
¶ms,
SquareAxisProvenance::FullyMeasured,
Some(&mut raw),
)
.map_err(|error| TopologicalTraceError::DetectionFailed {
message: error.to_string(),
})?;
let corners = features
.iter()
.enumerate()
.map(|(index, feature)| 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: raw.usable[index],
})
.collect();
let edges: Vec<TopologicalEdgeTrace> = raw
.edges
.iter()
.map(|&(start, end, class)| TopologicalEdgeTrace {
start,
end,
class: class.into(),
})
.collect();
let triangles = raw
.triangles
.iter()
.enumerate()
.map(|(index, &vertices)| TopologicalTriangleTrace {
vertices,
edge_classes: [
edges[3 * index].class,
edges[3 * index + 1].class,
edges[3 * index + 2].class,
],
})
.collect();
let raw_quads = quads(&raw.raw_quads);
let topology_quads = quads(&raw.topology_quads);
let geometry_quads = quads(&raw.geometry_quads);
let scale_quads = quads(&raw.scale_quads);
let walk_components = components(&raw.walk_components, features);
let merged_components = components(&raw.merged_components, features);
let feature_index_by_source: HashMap<usize, usize> = features
.iter()
.enumerate()
.map(|(index, feature)| (feature.point.source_index, index))
.collect();
let final_components: Vec<TopologicalComponentTrace> = solutions
.iter()
.enumerate()
.map(|(index, solution)| TopologicalComponentTrace {
index,
labels: solution
.detection
.grid()
.entries()
.iter()
.map(|entry| TopologicalLabelTrace {
u: entry.coord.u,
v: entry.coord.v,
feature_index: feature_index_by_source[&entry.source_index],
source_index: entry.source_index,
residual_px: entry.residual_px,
})
.collect(),
fit: Some(TopologicalFitTrace {
count: solution.detection.fit().residuals.count,
mean_px: solution.detection.fit().residuals.mean_px,
max_px: solution.detection.fit().residuals.max_px,
}),
})
.collect();
let diagnostics = TopologicalTraceDiagnostics {
corners_in: features.len(),
corners_used: raw.usable.iter().filter(|&&usable| usable).count(),
triangles: raw.triangles.len(),
raw_quads: raw.raw_quads.len(),
topology_quads: raw.topology_quads.len(),
geometry_quads: raw.geometry_quads.len(),
scale_quads: raw.scale_quads.len(),
walk_components: raw.walk_components.len(),
merged_components: raw.merged_components.len(),
final_components: final_components.len(),
final_labels: final_components
.iter()
.map(|component| component.labels.len())
.sum(),
};
Ok(TopologicalTrace {
schema_version: 1,
params: topological_params,
corners,
edges,
triangles,
raw_quads,
topology_quads,
geometry_quads,
scale_quads,
walk_components,
merged_components,
final_components,
diagnostics,
})
}
fn quads(items: &[[usize; 4]]) -> Vec<TopologicalQuadTrace> {
items
.iter()
.copied()
.map(|vertices| TopologicalQuadTrace { vertices })
.collect()
}
fn components(
items: &[Vec<(crate::Coord, usize)>],
features: &[OrientedFeature<2>],
) -> Vec<TopologicalComponentTrace> {
items
.iter()
.enumerate()
.map(|(index, labels)| TopologicalComponentTrace {
index,
labels: labels
.iter()
.map(|&(coord, feature_index)| TopologicalLabelTrace {
u: coord.u,
v: coord.v,
feature_index,
source_index: features[feature_index].point.source_index,
residual_px: None,
})
.collect(),
fit: None,
})
.collect()
}