use nalgebra::Point2;
use crate::cluster::angular_dist_pi;
use crate::feature::{LocalAxis, OrientedFeature};
use crate::shared::grow::{Admit, LabelledNeighbour, SquareAttachPolicy};
#[derive(Clone, Copy, Debug)]
pub(super) struct PositionsTolerances {
pub soft_axis_tol_rad: f32,
pub edge_length_tol: f32,
pub cell_size: f32,
}
pub(super) struct PositionsAttachPolicy<'a> {
features: &'a [OrientedFeature<2>],
positions: &'a [Point2<f32>],
local_pitch: &'a [f32],
tol: PositionsTolerances,
}
impl<'a> PositionsAttachPolicy<'a> {
pub(super) fn new(
features: &'a [OrientedFeature<2>],
positions: &'a [Point2<f32>],
local_pitch: &'a [f32],
tol: PositionsTolerances,
) -> Self {
Self {
features,
positions,
local_pitch,
tol,
}
}
fn axes(&self, idx: usize) -> [LocalAxis; 2] {
self.features[idx].axes
}
}
impl SquareAttachPolicy for PositionsAttachPolicy<'_> {
fn is_eligible(&self, _idx: usize) -> bool {
true
}
fn required_label_at(&self, _i: i32, _j: i32) -> Option<u8> {
None
}
fn label_of(&self, _idx: usize) -> Option<u8> {
None
}
fn accept_candidate(
&self,
idx: usize,
_at: (i32, i32),
_prediction: Point2<f32>,
neighbours: &[LabelledNeighbour],
) -> Admit {
let cand = self.axes(idx);
let tol = self.tol.soft_axis_tol_rad;
let _aligned = neighbours
.iter()
.any(|n| axis_pairs_align(cand, self.features[n.idx].axes, tol));
Admit::Accept
}
fn edge_ok(
&self,
candidate_idx: usize,
neighbour_idx: usize,
_at_candidate: (i32, i32),
_at_neighbour: (i32, i32),
) -> bool {
let to = self.positions[candidate_idx];
let from = self.positions[neighbour_idx];
let len = (to - from).norm();
let local = 0.5 * (self.local_pitch[candidate_idx] + self.local_pitch[neighbour_idx]);
let expected = if local > 1e-3 {
local
} else {
self.tol.cell_size
};
let ratio = len / expected;
let low = 1.0 - self.tol.edge_length_tol;
let high = 1.0 + self.tol.edge_length_tol;
ratio >= low && ratio <= high
}
}
fn axis_pairs_align(a: [LocalAxis; 2], b: [LocalAxis; 2], tol: f32) -> bool {
let d = |x: f32, y: f32| angular_dist_pi(x, y);
let direct =
d(a[0].angle_rad, b[0].angle_rad) <= tol && d(a[1].angle_rad, b[1].angle_rad) <= tol;
let swapped =
d(a[0].angle_rad, b[1].angle_rad) <= tol && d(a[1].angle_rad, b[0].angle_rad) <= tol;
direct || swapped
}