1use crate::region::{BoundingBox, Region};
2
3pub fn merge_regions(regions: &mut Vec<Region>, max_gap: u32) {
8 if max_gap == 0 || regions.len() < 2 {
9 return;
10 }
11
12 let mut merged = true;
13 while merged {
14 merged = false;
15 let mut i = 0;
16 while i < regions.len() {
17 let mut j = i + 1;
18 while j < regions.len() {
19 if bbox_gap(®ions[i].bounding_box, ®ions[j].bounding_box) <= max_gap {
20 let removed = regions.remove(j);
22 let target = &mut regions[i];
23 let new_bbox = bbox_union(&target.bounding_box, &removed.bounding_box);
24 let total_px = target.pixel_count + removed.pixel_count;
25 target.avg_delta = (target.avg_delta * target.pixel_count as f64
26 + removed.avg_delta * removed.pixel_count as f64)
27 / total_px as f64;
28 target.max_delta = target.max_delta.max(removed.max_delta);
29 target.pixel_count = total_px;
30 target.bounding_box = new_bbox;
31 target.component_ids.extend(removed.component_ids);
32 merged = true;
33 } else {
35 j += 1;
36 }
37 }
38 i += 1;
39 }
40 }
41
42 regions.sort_by(|a, b| b.pixel_count.cmp(&a.pixel_count));
44 for (i, region) in regions.iter_mut().enumerate() {
45 region.id = (i + 1) as u32;
46 }
47}
48
49fn bbox_gap(a: &BoundingBox, b: &BoundingBox) -> u32 {
52 let a_right = a.x + a.width;
53 let a_bottom = a.y + a.height;
54 let b_right = b.x + b.width;
55 let b_bottom = b.y + b.height;
56
57 let gap_x = if a_right <= b.x {
58 b.x - a_right
59 } else if b_right <= a.x {
60 a.x - b_right
61 } else {
62 0
63 };
64
65 let gap_y = if a_bottom <= b.y {
66 b.y - a_bottom
67 } else if b_bottom <= a.y {
68 a.y - b_bottom
69 } else {
70 0
71 };
72
73 gap_x.max(gap_y)
75}
76
77fn bbox_union(a: &BoundingBox, b: &BoundingBox) -> BoundingBox {
79 let x = a.x.min(b.x);
80 let y = a.y.min(b.y);
81 let right = (a.x + a.width).max(b.x + b.width);
82 let bottom = (a.y + a.height).max(b.y + b.height);
83 BoundingBox {
84 x,
85 y,
86 width: right - x,
87 height: bottom - y,
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94
95 fn make_region(id: u32, x: u32, y: u32, w: u32, h: u32, px: u32) -> Region {
96 Region {
97 id,
98 bounding_box: BoundingBox {
99 x,
100 y,
101 width: w,
102 height: h,
103 },
104 pixel_count: px,
105 avg_delta: 0.5,
106 max_delta: 0.8,
107 label: String::new(),
108 component_ids: vec![id],
109 }
110 }
111
112 #[test]
113 fn no_merge_when_disabled() {
114 let mut regions = vec![
115 make_region(1, 0, 0, 10, 10, 100),
116 make_region(2, 15, 0, 10, 10, 50),
117 ];
118 merge_regions(&mut regions, 0);
119 assert_eq!(regions.len(), 2);
120 }
121
122 #[test]
123 fn nearby_regions_merge() {
124 let mut regions = vec![
125 make_region(1, 0, 0, 10, 10, 100),
126 make_region(2, 15, 0, 10, 10, 50),
127 ];
128 merge_regions(&mut regions, 10);
129 assert_eq!(regions.len(), 1);
130 assert_eq!(regions[0].bounding_box.x, 0);
131 assert_eq!(regions[0].bounding_box.width, 25);
132 assert_eq!(regions[0].pixel_count, 150);
133 }
134
135 #[test]
136 fn distant_regions_stay_separate() {
137 let mut regions = vec![
138 make_region(1, 0, 0, 10, 10, 100),
139 make_region(2, 100, 100, 10, 10, 50),
140 ];
141 merge_regions(&mut regions, 20);
142 assert_eq!(regions.len(), 2);
143 }
144
145 #[test]
146 fn overlapping_regions_merge() {
147 let mut regions = vec![
148 make_region(1, 0, 0, 20, 20, 200),
149 make_region(2, 10, 10, 20, 20, 100),
150 ];
151 merge_regions(&mut regions, 1);
152 assert_eq!(regions.len(), 1);
153 assert_eq!(regions[0].bounding_box.x, 0);
154 assert_eq!(regions[0].bounding_box.y, 0);
155 assert_eq!(regions[0].bounding_box.width, 30);
156 assert_eq!(regions[0].bounding_box.height, 30);
157 }
158
159 #[test]
160 fn chain_merge() {
161 let mut regions = vec![
164 make_region(1, 0, 0, 10, 10, 50),
165 make_region(2, 15, 0, 10, 10, 50),
166 make_region(3, 30, 0, 10, 10, 50),
167 ];
168 merge_regions(&mut regions, 10);
169 assert_eq!(regions.len(), 1);
170 assert_eq!(regions[0].pixel_count, 150);
171 }
172}