Skip to main content

agent_image_diff/
denoise.rs

1use crate::cluster;
2
3/// Remove small noise clusters from a diff mask before dilation.
4///
5/// Runs a quick connected-component pass on the raw diff mask and zeros out
6/// any component with fewer than `min_size` pixels. This prevents tiny
7/// rendering-noise specks from being dilated and bridging real change regions.
8pub fn denoise(mask: &mut Vec<bool>, width: u32, height: u32, min_size: u32) {
9    if min_size <= 1 {
10        return;
11    }
12
13    // Label raw connected components (8-connectivity to match default)
14    let labels = cluster::label_components(mask, width, height, 8);
15
16    // Count pixels per component
17    let max_label = labels.iter().copied().max().unwrap_or(0) as usize;
18    let mut counts = vec![0u32; max_label + 1];
19    for &label in &labels {
20        if label > 0 {
21            counts[label as usize] += 1;
22        }
23    }
24
25    // Zero out mask entries for small components
26    for (i, &label) in labels.iter().enumerate() {
27        if label > 0 && counts[label as usize] < min_size {
28            mask[i] = false;
29        }
30    }
31}
32
33#[cfg(test)]
34mod tests {
35    use super::*;
36
37    #[test]
38    fn removes_small_clusters() {
39        // 5x5 grid: one 4-pixel cluster and one 1-pixel speck
40        #[rustfmt::skip]
41        let mut mask = vec![
42            true,  true,  false, false, false,
43            true,  true,  false, false, false,
44            false, false, false, false, false,
45            false, false, false, false, true,
46            false, false, false, false, false,
47        ];
48        denoise(&mut mask, 5, 5, 3);
49        // The 4-pixel cluster survives, the 1-pixel speck is removed
50        assert!(mask[0]); // part of big cluster
51        assert!(!mask[19]); // the speck is gone
52    }
53
54    #[test]
55    fn noop_when_disabled() {
56        let mut mask = vec![true, false, true, false];
57        let original = mask.clone();
58        denoise(&mut mask, 2, 2, 1);
59        assert_eq!(mask, original);
60    }
61}