visioncortex/shape/
image_operations.rs

1pub use bit_vec::BitVec;
2
3use crate::{BinaryImage, Shape};
4
5impl BinaryImage {
6    pub fn operation(
7        &self,
8        other: &BinaryImage,
9        operator: impl FnMut((&mut u8, &u8)),
10    ) -> BinaryImage {
11        assert_eq!(self.width, other.width);
12        assert_eq!(self.height, other.height);
13        let mut i = self.pixels.to_bytes();
14        let u = other.pixels.to_bytes();
15        i.iter_mut().zip(u.iter()).for_each(operator);
16        BinaryImage {
17            pixels: BitVec::from_bytes(&i),
18            width: self.width,
19            height: self.height,
20        }
21    }
22
23    pub fn negative(&self) -> BinaryImage {
24        let i = self.pixels.to_bytes();
25        use std::ops::Not;
26        let ii = i.iter().map(|x| x.not()).collect::<Vec<u8>>();
27        BinaryImage {
28            pixels: BitVec::from_bytes(&ii.as_slice()),
29            width: self.width,
30            height: self.height,
31        }
32    }
33
34    pub fn diff(&self, other: &BinaryImage) -> BinaryImage {
35        self.operation(other, |(x1, x2)| *x1 ^= *x2)
36    }
37
38    pub fn union(&self, other: &BinaryImage) -> BinaryImage {
39        self.operation(other, |(x1, x2)| *x1 |= *x2)
40    }
41
42    pub fn intersect(&self, other: &BinaryImage) -> BinaryImage {
43        self.operation(other, |(x1, x2)| *x1 &= *x2)
44    }
45
46    pub fn clustered_diff(&self, other: &BinaryImage) -> u32 {
47        self.diff(other).significance(self.area(), std::u32::MAX)
48    }
49
50    /// early return if diff >= threshold, so maximum return value is equal to threshold
51    pub fn significance(&self, area: u64, threshold: u32) -> u32 {
52        let clusters = self.to_clusters(false);
53        let mut diff: u64 = 0;
54        let scale = 4 * 128 * 128;
55        let divisor = area * self.width as u64;
56        let threshold_u64 = threshold as u64 * divisor;
57        for cluster in clusters.iter() {
58            let size = cluster.size() as u64;
59            let cluster_image = cluster.to_binary_image();
60            let boundary = Shape::image_boundary_list(&cluster_image);
61            let skeleton = cluster_image.to_skeleton();
62            diff += scale * size *
63                skeleton.stat.mean as u64 *
64                skeleton.stat.count as u64 /
65                boundary.len() as u64;
66            if diff >= threshold_u64 {
67                break;
68            }
69        }
70        (diff / divisor) as u32
71    }
72
73    pub fn diff_and_count(&self, other: &BinaryImage) -> usize {
74        assert_eq!(self.width, other.width);
75        assert_eq!(self.height, other.height);
76        let mut i = self.pixels.to_bytes();
77        let u = other.pixels.to_bytes();
78        i.iter_mut().zip(u.iter()).for_each(|(x1, x2)| *x1 ^= *x2);
79        while i.len() % 4 != 0 {
80            i.push(0);
81        }
82        let mut count = 0;
83        for ii in (0..i.len()).step_by(4) {
84            count += Self::popcount(u32::from_be_bytes([i[ii], i[ii + 1], i[ii + 2], i[ii + 3]]))
85                as usize;
86        }
87        count
88    }
89
90    #[inline(always)]
91    pub fn popcount(i: u32) -> u32 {
92        i.count_ones()
93    }
94
95    /// expand a binary image using a circular brush
96    pub fn stroke(&self, s: u32) -> BinaryImage {
97        let mut new_image = BinaryImage::new_w_h(self.width + s as usize, self.height + s as usize);
98        let ox = (s as usize) >> 1;
99        let oy = (s as usize) >> 1;
100        let ss = (s >> 1) as i32;
101        for y in 0..self.height {
102            for x in 0..self.width {
103                let v = self.get_pixel(x, y);
104                if v {
105                    for yy in -ss..ss {
106                        for xx in -ss..ss {
107                            if (((xx * xx + yy * yy) as f64).sqrt() as i32) < ss {
108                                new_image.set_pixel(
109                                    (x as i32 + xx + ox as i32) as usize,
110                                    (y as i32 + yy + oy as i32) as usize,
111                                    true,
112                                );
113                            }
114                        }
115                    }
116                }
117            }
118        }
119        new_image
120    }
121}
122
123#[cfg(test)]
124mod tests {
125    use super::*;
126
127    #[test]
128    fn image_diff() {
129        let mut a = BinaryImage::new_w_h(2, 2);
130        a.set_pixel(0, 0, true);
131        let mut b = BinaryImage::new_w_h(2, 2);
132        b.set_pixel(1, 1, true);
133        assert_eq!(a.diff_and_count(&b), 2);
134
135        let mut a = BinaryImage::new_w_h(3, 3);
136        a.set_pixel(1, 1, true);
137        let mut b = BinaryImage::new_w_h(3, 3);
138        b.set_pixel(1, 1, true);
139        assert_eq!(a.diff_and_count(&b), 0);
140
141        let mut a = BinaryImage::new_w_h(3, 3);
142        a.set_pixel(0, 0, true);
143        a.set_pixel(1, 1, true);
144        let mut b = BinaryImage::new_w_h(3, 3);
145        b.set_pixel(1, 1, true);
146        b.set_pixel(2, 2, true);
147        assert_eq!(a.diff_and_count(&b), 2);
148    }
149
150    #[test]
151    fn negative_image() {
152        assert_eq!(
153            BinaryImage::from_string(&(
154                "*-*\n".to_owned() +
155                "-*-\n" +
156                "*-*\n"
157            ))
158            .negative()
159            .to_string(),
160            BinaryImage::from_string(&(
161                "-*-\n".to_owned() +
162                "*-*\n" +
163                "-*-\n"
164            )).to_string()
165        );
166    }
167}