1#[derive(Debug, Clone, Copy, PartialEq)]
7pub struct PixelBox {
8 pub x: f64,
9 pub y: f64,
10 pub w: f64,
11 pub h: f64,
12}
13
14pub fn iou(a: &PixelBox, b: &PixelBox) -> f64 {
17 let ix = a.x.max(b.x);
18 let iy = a.y.max(b.y);
19 let ix2 = (a.x + a.w).min(b.x + b.w);
20 let iy2 = (a.y + a.h).min(b.y + b.h);
21 let iw = (ix2 - ix).max(0.0);
22 let ih = (iy2 - iy).max(0.0);
23 let inter = iw * ih;
24 let union = a.w * a.h + b.w * b.h - inter;
25 if union <= 0.0 {
26 0.0
27 } else {
28 inter / union
29 }
30}
31
32pub fn greedy_match(
37 regions: &[PixelBox],
38 faces: &[(i64, PixelBox)],
39 threshold: f64,
40) -> Vec<Option<i64>> {
41 let mut pairs: Vec<(f64, usize, i64, usize)> = Vec::new();
43 for (ri, r) in regions.iter().enumerate() {
44 for (fi, (fid, fb)) in faces.iter().enumerate() {
45 let s = iou(r, fb);
46 if s >= threshold {
47 pairs.push((s, ri, *fid, fi));
48 }
49 }
50 }
51 pairs.sort_by(|a, b| {
53 b.0.partial_cmp(&a.0)
54 .unwrap_or(std::cmp::Ordering::Equal)
55 .then(a.1.cmp(&b.1))
56 .then(a.2.cmp(&b.2))
57 });
58 let mut out = vec![None; regions.len()];
59 let mut used_face = vec![false; faces.len()];
60 for (_s, ri, fid, fi) in pairs {
61 if out[ri].is_none() && !used_face[fi] {
62 out[ri] = Some(fid);
63 used_face[fi] = true;
64 }
65 }
66 out
67}
68
69pub const DEFAULT_IOU_THRESHOLD: f64 = 0.5;
72
73#[cfg(test)]
74mod tests {
75 use super::*;
76
77 #[test]
78 fn iou_of_identical_boxes_is_one() {
79 let a = PixelBox {
80 x: 10.0,
81 y: 10.0,
82 w: 100.0,
83 h: 100.0,
84 };
85 assert!((iou(&a, &a) - 1.0).abs() < 1e-6);
86 }
87
88 #[test]
89 fn iou_of_disjoint_boxes_is_zero() {
90 let a = PixelBox {
91 x: 0.0,
92 y: 0.0,
93 w: 10.0,
94 h: 10.0,
95 };
96 let b = PixelBox {
97 x: 100.0,
98 y: 100.0,
99 w: 10.0,
100 h: 10.0,
101 };
102 assert_eq!(iou(&a, &b), 0.0);
103 }
104
105 #[test]
106 fn iou_of_half_overlap() {
107 let a = PixelBox {
109 x: 0.0,
110 y: 0.0,
111 w: 10.0,
112 h: 10.0,
113 };
114 let b = PixelBox {
115 x: 0.0,
116 y: 5.0,
117 w: 10.0,
118 h: 10.0,
119 };
120 assert!((iou(&a, &b) - (50.0 / 150.0)).abs() < 1e-6);
121 }
122
123 #[test]
124 fn greedy_matches_best_first_one_to_one() {
125 let regions = vec![
126 PixelBox {
127 x: 0.0,
128 y: 0.0,
129 w: 10.0,
130 h: 10.0,
131 },
132 PixelBox {
133 x: 100.0,
134 y: 100.0,
135 w: 10.0,
136 h: 10.0,
137 },
138 ];
139 let faces = vec![
140 (
141 7i64,
142 PixelBox {
143 x: 1.0,
144 y: 1.0,
145 w: 10.0,
146 h: 10.0,
147 },
148 ),
149 (
150 9i64,
151 PixelBox {
152 x: 101.0,
153 y: 101.0,
154 w: 10.0,
155 h: 10.0,
156 },
157 ),
158 ];
159 let m = greedy_match(®ions, &faces, 0.3);
160 assert_eq!(m, vec![Some(7), Some(9)]);
161 }
162
163 #[test]
164 fn below_threshold_is_unmatched() {
165 let regions = vec![PixelBox {
166 x: 0.0,
167 y: 0.0,
168 w: 10.0,
169 h: 10.0,
170 }];
171 let faces = vec![(
172 7i64,
173 PixelBox {
174 x: 50.0,
175 y: 50.0,
176 w: 10.0,
177 h: 10.0,
178 },
179 )];
180 assert_eq!(greedy_match(®ions, &faces, 0.5), vec![None]);
181 }
182}