use std::collections::{HashMap, HashSet};
use crate::DetectedMarker;
fn sort_by_confidence(markers: Vec<DetectedMarker>) -> Vec<DetectedMarker> {
let mut markers = markers;
markers.sort_by(|a, b| b.confidence.partial_cmp(&a.confidence).unwrap());
markers
}
fn dedup_by_proximity(markers: Vec<DetectedMarker>, radius: f64) -> Vec<DetectedMarker> {
let mut keep = vec![true; markers.len()];
let r2 = radius * radius;
for i in 0..markers.len() {
if !keep[i] {
continue;
}
for j in (i + 1)..markers.len() {
if !keep[j] {
continue;
}
let dx = markers[i].center[0] - markers[j].center[0];
let dy = markers[i].center[1] - markers[j].center[1];
if dx * dx + dy * dy < r2 {
keep[j] = false;
}
}
}
markers
.into_iter()
.enumerate()
.filter_map(|(index, marker)| keep[index].then_some(marker))
.collect()
}
fn dedup_by_id_core(markers: Vec<DetectedMarker>) -> Vec<DetectedMarker> {
let mut best_idx: HashMap<usize, usize> = HashMap::new();
for (index, marker) in markers.iter().enumerate() {
if let Some(id) = marker.id {
match best_idx.get(&id) {
Some(&prev) if markers[prev].confidence >= marker.confidence => {}
_ => {
best_idx.insert(id, index);
}
}
}
}
let keep_set: HashSet<usize> = best_idx.values().copied().collect();
markers
.into_iter()
.enumerate()
.filter_map(|(index, marker)| {
let keep = marker.id.is_none() || keep_set.contains(&index);
keep.then_some(marker)
})
.collect()
}
pub fn dedup_markers(markers: Vec<DetectedMarker>, radius: f64) -> Vec<DetectedMarker> {
let markers = sort_by_confidence(markers);
dedup_by_proximity(markers, radius)
}
pub fn dedup_by_id(markers: &mut Vec<DetectedMarker>) {
let input = std::mem::take(markers);
*markers = dedup_by_id_core(input);
}
fn outer_radius(m: &DetectedMarker) -> f64 {
m.ellipse_outer
.as_ref()
.map(|e| e.mean_axis())
.unwrap_or(0.0)
}
fn size_consistency_scores(markers: &[DetectedMarker], k_neighbors: usize) -> Vec<f64> {
let n = markers.len();
let k = k_neighbors.min(n.saturating_sub(1));
let radii: Vec<f64> = markers.iter().map(outer_radius).collect();
let mut scores = vec![1.0f64; n];
if k == 0 || n <= 1 {
return scores;
}
for i in 0..n {
let ci = markers[i].center;
let mut dists: Vec<(f64, usize)> = (0..n)
.filter(|&j| j != i)
.map(|j| {
let cj = markers[j].center;
let dx = ci[0] - cj[0];
let dy = ci[1] - cj[1];
(dx * dx + dy * dy, j)
})
.collect();
dists.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap());
let neighbor_radii: Vec<f64> = dists[..k.min(dists.len())]
.iter()
.map(|(_, j)| radii[*j])
.collect();
if neighbor_radii.len() < 3 {
continue;
}
let mut sorted_nr = neighbor_radii.clone();
sorted_nr.sort_by(|a, b| a.partial_cmp(b).unwrap());
let mid = sorted_nr.len() / 2;
let median_r = if sorted_nr.len().is_multiple_of(2) {
(sorted_nr[mid - 1] + sorted_nr[mid]) * 0.5
} else {
sorted_nr[mid]
};
if median_r <= 0.0 {
continue;
}
let r_i = radii[i];
scores[i] = (1.0 - (r_i - median_r).abs() / median_r).max(0.0);
}
scores
}
pub(crate) fn merge_multiscale_markers(
all_markers: Vec<DetectedMarker>,
dedup_radius: f64,
k_neighbors: usize,
) -> Vec<DetectedMarker> {
if all_markers.is_empty() {
return all_markers;
}
let n = all_markers.len();
let scores = size_consistency_scores(&all_markers, k_neighbors);
let mut order: Vec<usize> = (0..n).collect();
order.sort_by(|&i, &j| {
scores[j]
.partial_cmp(&scores[i])
.unwrap_or(std::cmp::Ordering::Equal)
.then_with(|| {
all_markers[j]
.confidence
.partial_cmp(&all_markers[i].confidence)
.unwrap_or(std::cmp::Ordering::Equal)
})
});
let r2 = dedup_radius * dedup_radius;
let mut keep = vec![true; n];
for pos in 0..order.len() {
let i = order[pos];
if !keep[i] {
continue;
}
let ci = all_markers[i].center;
for &j in &order[pos + 1..] {
if !keep[j] {
continue;
}
let cj = all_markers[j].center;
let dx = ci[0] - cj[0];
let dy = ci[1] - cj[1];
if dx * dx + dy * dy < r2 {
keep[j] = false;
}
}
}
let surviving: Vec<DetectedMarker> = all_markers
.into_iter()
.enumerate()
.filter_map(|(i, m)| keep[i].then_some(m))
.collect();
dedup_by_id_core(surviving)
}