use crate::detector::DetectedMarker;
use crate::proposal::Proposal;
use std::cmp::Ordering;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, serde::Serialize, serde::Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DetectionFrame {
#[default]
Image,
Working,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
pub struct DetectionResult {
pub detected_markers: Vec<DetectedMarker>,
pub center_frame: DetectionFrame,
pub homography_frame: DetectionFrame,
pub image_size: [u32; 2],
#[serde(skip_serializing_if = "Option::is_none")]
pub homography: Option<[[f64; 3]; 3]>,
#[serde(skip_serializing_if = "Option::is_none")]
pub ransac: Option<crate::homography::RansacStats>,
#[serde(skip_serializing_if = "Option::is_none")]
pub self_undistort: Option<crate::pixelmap::SelfUndistortResult>,
}
impl DetectionResult {
pub fn empty(width: u32, height: u32) -> Self {
Self {
detected_markers: Vec::new(),
center_frame: DetectionFrame::Image,
homography_frame: DetectionFrame::Image,
image_size: [width, height],
homography: None,
ransac: None,
self_undistort: None,
}
}
pub fn seed_proposals(&self, max_seeds: Option<usize>) -> Vec<Proposal> {
let mut candidates: Vec<SeedCandidate> = self
.detected_markers
.iter()
.enumerate()
.filter_map(|(source_index, marker)| {
let x = marker.center[0] as f32;
let y = marker.center[1] as f32;
if !(x.is_finite() && y.is_finite()) {
return None;
}
let score = if marker.confidence.is_finite() {
marker.confidence
} else {
f32::NEG_INFINITY
};
Some(SeedCandidate {
proposal: Proposal { x, y, score },
marker_id: marker.id,
source_index,
})
})
.collect();
candidates.sort_by(compare_seed_candidate);
let max = max_seeds.unwrap_or(candidates.len());
candidates.truncate(max.min(candidates.len()));
candidates.into_iter().map(|c| c.proposal).collect()
}
}
fn compare_seed_candidate(a: &SeedCandidate, b: &SeedCandidate) -> Ordering {
b.proposal
.score
.total_cmp(&a.proposal.score)
.then_with(|| b.marker_id.is_some().cmp(&a.marker_id.is_some()))
.then_with(|| match (a.marker_id, b.marker_id) {
(Some(aid), Some(bid)) => aid.cmp(&bid),
_ => Ordering::Equal,
})
.then_with(|| a.proposal.x.total_cmp(&b.proposal.x))
.then_with(|| a.proposal.y.total_cmp(&b.proposal.y))
.then_with(|| a.source_index.cmp(&b.source_index))
}
#[derive(Clone)]
struct SeedCandidate {
proposal: Proposal,
marker_id: Option<usize>,
source_index: usize,
}
#[cfg(test)]
mod tests {
use super::*;
fn marker(id: Option<usize>, confidence: f32, center: [f64; 2]) -> DetectedMarker {
DetectedMarker {
id,
confidence,
center,
..DetectedMarker::default()
}
}
#[test]
fn seed_proposals_are_confidence_ordered_and_truncated() {
let result = DetectionResult {
detected_markers: vec![
marker(Some(11), 0.1, [10.0, 10.0]),
marker(Some(12), 0.8, [20.0, 20.0]),
marker(Some(13), 0.6, [30.0, 30.0]),
],
..DetectionResult::default()
};
let proposals = result.seed_proposals(Some(2));
assert_eq!(proposals.len(), 2);
assert_eq!(proposals[0].score, 0.8);
assert_eq!(proposals[1].score, 0.6);
}
#[test]
fn seed_proposals_tie_break_is_deterministic() {
let markers = vec![
marker(Some(7), 0.8, [20.0, 10.0]),
marker(None, 0.8, [1.0, 1.0]),
marker(Some(3), 0.8, [8.0, 8.0]),
marker(Some(3), 0.8, [4.0, 9.0]),
marker(Some(2), 0.95, [100.0, 100.0]),
];
let permuted = vec![
markers[1].clone(),
markers[3].clone(),
markers[4].clone(),
markers[0].clone(),
markers[2].clone(),
];
let a = DetectionResult {
detected_markers: markers,
..DetectionResult::default()
};
let b = DetectionResult {
detected_markers: permuted,
..DetectionResult::default()
};
let pa = a.seed_proposals(None);
let pb = b.seed_proposals(None);
assert_eq!(pa.len(), 5);
let pa_xy_score: Vec<(f32, f32, f32)> = pa.iter().map(|p| (p.x, p.y, p.score)).collect();
let pb_xy_score: Vec<(f32, f32, f32)> = pb.iter().map(|p| (p.x, p.y, p.score)).collect();
assert_eq!(pa_xy_score, pb_xy_score);
let ordered_centers: Vec<[f32; 2]> = pa.iter().map(|p| [p.x, p.y]).collect();
assert_eq!(
ordered_centers,
vec![
[100.0, 100.0], [4.0, 9.0], [8.0, 8.0],
[20.0, 10.0],
[1.0, 1.0], ]
);
}
#[test]
fn seed_proposals_skip_non_finite_centers_and_demote_non_finite_confidence() {
let result = DetectionResult {
detected_markers: vec![
marker(Some(1), 0.7, [10.0, 10.0]),
marker(Some(2), f32::NAN, [11.0, 11.0]),
marker(Some(3), 0.9, [f64::NAN, 12.0]),
],
..DetectionResult::default()
};
let proposals = result.seed_proposals(None);
assert_eq!(proposals.len(), 2);
assert_eq!(proposals[0].score, 0.7);
assert!(proposals[1].score.is_infinite() && proposals[1].score.is_sign_negative());
}
}