symcode/acute32/
trace.rs

1use bit_vec::BitVec;
2use visioncortex::BinaryImage;
3use stats::ShapeStats;
4use std::cmp::Ordering;
5
6pub trait Trace {
7    fn bits(&self) -> &BitVec;
8
9    /// the default implementation is to XOR the two bit strings and count the number of 1s
10    fn diff(&self, other: &Self) -> usize {
11        let (mut self_clone, mut other_clone) = (self.bits().clone(), other.bits().clone());
12        self_clone.difference(&other.bits());
13        other_clone.difference(&self.bits());
14        self_clone.or(&other_clone);
15        self_clone.into_iter().filter(|bit| *bit).count()
16    }
17
18
19    fn from_image(image: &BinaryImage, tolerance: f64) -> Self;
20}
21
22#[derive(Debug)]
23pub struct GlyphTrace {
24    pub bits: BitVec,
25}
26
27impl Trace for GlyphTrace {
28    fn bits(&self) -> &BitVec {
29        &self.bits
30    }
31
32    fn from_image(image: &BinaryImage, tolerance: f64) -> Self {
33        let mut layer_traces = vec![];
34        // Encode each small layer
35        // layer_traces = Self::subdivide_and_encode(image, tolerance);
36        // Encode the big layer
37        layer_traces.push(LayerTrace::from_image(image, tolerance));
38        Self::from_layer_traces(layer_traces)
39    }
40}
41
42impl GlyphTrace {
43    pub fn from_layer_traces(layer_traces: Vec<LayerTrace>) -> Self {
44        let total_length = layer_traces.len() * LayerTrace::LENGTH;
45        Self {
46            bits: BitVec::from_fn(total_length, |i| {layer_traces[i/LayerTrace::LENGTH].bits.get(i%LayerTrace::LENGTH).unwrap()}),
47        }
48    }
49}
50
51#[derive(Debug)]
52pub struct LayerTrace {
53    pub bits: BitVec,
54}
55
56impl Trace for LayerTrace {
57    fn bits(&self) -> &BitVec {
58        &self.bits
59    }
60
61    #[allow(unused_assignments)]
62    fn from_image(image: &BinaryImage, tolerance: f64) -> Self {
63        let stats = ShapeStats::from_image(image, tolerance);
64        if stats.is_empty() {
65            return Self::default();
66        }
67
68        let mut bits = BitVec::from_elem(Self::LENGTH, true); // start with all 1's
69
70        let mut bit_offset = 0;
71        macro_rules! set_bits {
72            ($fun:ident) => {
73                match stats.$fun() {
74                    Ordering::Less => bits.set(bit_offset, false),
75                    Ordering::Greater => bits.set(bit_offset+1, false),
76                    Ordering::Equal => {},
77                }
78                bit_offset += 2;
79            }
80        }
81
82        set_bits!(vertical_comparison);
83        set_bits!(horizontal_comparison);
84        set_bits!(diagonal_comparison);
85        set_bits!(a_b_comparison);
86        set_bits!(a_c_comparison);
87        set_bits!(a_d_comparison);
88        set_bits!(b_c_comparison);
89        set_bits!(b_d_comparison);
90        set_bits!(c_d_comparison);
91        set_bits!(ef_gh_comparison);
92
93        Self {
94            bits
95        }
96    }
97}
98
99impl Default for LayerTrace {
100    fn default() -> Self {
101        Self {
102            bits: BitVec::from_elem(Self::LENGTH, false),
103        }
104    }
105}
106
107// Partition the symbol image into 2x2 = 4 big blocks
108// Denote top-left, top-right, bottom-left, bottom-right weights by a,b,c,d respectively
109// Partition the symbol image into 4x4 = 16 small blocks
110// Denote top two, bottom two, left two, right two weights by e,f,g,h respectively
111impl LayerTrace {
112    /// 2 bits each for a+b <> c+d, a+c <> b+d, a+d <> b+c, a <> b, c <> d, a <> c, b <> d, a <> d, b <> c, e+f <> g+h
113    const LENGTH: usize = ShapeStats::NUM_COMPARISONS << 1;
114}
115
116#[cfg(test)]
117mod tests {
118
119    use super::*;
120
121    const T: bool = true;
122    const F: bool = false;
123
124    #[test]
125    fn trace_diff() {
126        let a = &mut LayerTrace::default();
127        let b = &mut LayerTrace::default();
128        assert_eq!(a.diff(b), 0);
129
130        a.bits.set(0, true);
131        assert_eq!(a.diff(b), 1);
132        b.bits.set(0, true);
133        assert_eq!(a.diff(b), 0);
134
135        a.bits.set(2, true);
136        a.bits.set(5, true);
137        b.bits.set(4, true);
138        assert_eq!(a.diff(b), 3);
139
140        b.bits.set(5, true);
141        assert_eq!(a.diff(b), 2);
142    }
143
144    #[test]
145    fn layer_trace_from_image() {
146        // Should be 10 01 01 01 11 11 10 10 11
147        let encoding = &LayerTrace::from_image(
148            &BinaryImage::from_string(
149              &("-*\n".to_owned() +
150                "--")
151            ),
152            0.0
153        );
154        assert!(encoding.bits.eq_vec(&[
155            T,F,F,T,F,T,F,T,T,T,T,T,T,F,T,F,T,T
156        ]));
157
158        // Should be 11 11 10 10 10 11 11 01 01
159        let encoding = &LayerTrace::from_image(
160            &BinaryImage::from_string(
161              &("*-\n".to_owned() +
162                "-*")
163            ),
164            0.0
165        );
166        assert!(encoding.bits.eq_vec(&[
167            T,T,T,T,T,F,T,F,T,F,T,T,T,T,F,T,F,T
168        ]));
169    }
170
171    #[test]
172    fn layer_trace_empty_image() {
173        // Should be 00 00 00
174        let encoding = &LayerTrace::from_image(
175            &BinaryImage::from_string(
176              &("--\n".to_owned() +
177                "--")
178            ),
179            0.0
180        );
181        println!("{:?}", encoding.bits);
182        assert!(!encoding.bits.any());
183    }
184
185    #[test]
186    fn glyph_trace_empty() {
187        let encoding = &GlyphTrace::from_image(
188            &BinaryImage::from_string(
189              &("----\n".to_owned() +
190                "----\n" +
191                "----\n" +
192                "----\n"
193              )
194            ),
195            0.0
196        );
197        assert!(!encoding.bits.any());
198    }
199
200    #[test]
201    fn glyph_trace_typical() {
202        // Should be 01 01 10 11 11 01 11 01 01
203        let encoding = &GlyphTrace::from_image(
204            &BinaryImage::from_string(
205              &("*---\n".to_owned() +
206                "--*-\n" +
207                "*--*\n" +
208                "--*-\n"
209              )
210            ),
211            0.0
212        );
213
214        assert!(encoding.bits.eq_vec(&[
215            F,T,F,T,T,F,T,T,T,T,F,T,T,T,F,T,F,T
216        ]));
217    }
218}
219
220mod stats {
221    use std::cmp::Ordering;
222
223    use visioncortex::{BinaryImage, Sampler};
224
225    #[derive(Debug)]
226    pub struct ShapeStats {
227        // a
228        top_left: usize,
229        // b
230        top_right: usize,
231        // c
232        bot_left: usize,
233        // d
234        bot_right: usize,
235        // e
236        top: usize,
237        // f
238        bottom: usize,
239        // g
240        left: usize,
241        // h
242        right: usize,
243        tolerance: f64,
244    }
245
246    impl ShapeStats {
247        pub fn from_image(image: &BinaryImage, tolerance: f64) -> Self {
248            // Upscale so that the image can be divided into 4x4 = 16 blocks
249            let horiz_q1 = image.width;
250            let vert_q1 = image.height;
251            let horiz_mid = image.width << 1;
252            let vert_mid = image.height << 1;
253            let horiz_q3 = horiz_mid + horiz_q1;
254            let vert_q3 = vert_mid + vert_q1;
255
256            let image = &Sampler::resample_image(image, image.width*4, image.height*4);
257            let sampler = Sampler::new(image);
258
259            let top_left = sampler.sample(0, 0, horiz_mid, vert_mid);
260            let top_right = sampler.sample(horiz_mid, 0, sampler.image.width, vert_mid);
261            let bot_left = sampler.sample(0, vert_mid, horiz_mid, sampler.image.height);
262            let bot_right = sampler.sample(horiz_mid,vert_mid, sampler.image.width, sampler.image.height);
263           
264            let top = sampler.sample(horiz_q1, 0, horiz_q3, vert_q1);
265            let bottom = sampler.sample(horiz_q1, vert_q3, horiz_q3, sampler.image.height);
266            let left = sampler.sample(0, vert_q1, horiz_q1, vert_q3);
267            let right = sampler.sample(horiz_q3, vert_q1, sampler.image.width, vert_q3);
268
269            Self {
270                top_left,
271                top_right,
272                bot_left,
273                bot_right,
274                top,
275                bottom,
276                left,
277                right,
278                tolerance,
279            }
280        }
281
282        pub fn is_empty(&self) -> bool {
283            self.top_left + self.top_right + self.bot_left + self.bot_right == 0
284        }
285
286        pub const NUM_COMPARISONS: usize = 10;
287
288        /// Returns an Ordering based on top vs bottom
289        pub fn vertical_comparison(&self) -> Ordering {
290            Self::approximate_compare(self.top_left + self.top_right, self.bot_left + self.bot_right, self.tolerance)
291        }
292
293        /// Returns an Ordering based on left vs right
294        pub fn horizontal_comparison(&self) -> Ordering {
295            Self::approximate_compare(self.top_left + self.bot_left, self.top_right + self.bot_right, self.tolerance)
296        }
297
298        /// Returns an Ordering based on backslash vs slash
299        pub fn diagonal_comparison(&self) -> Ordering {
300            Self::approximate_compare(self.top_left + self.bot_right, self.top_right + self.bot_left, self.tolerance)
301        }
302
303        pub fn a_b_comparison(&self) -> Ordering {
304            Self::approximate_compare(self.top_left, self.top_right, self.tolerance)
305        }
306
307        pub fn c_d_comparison(&self) -> Ordering {
308            Self::approximate_compare(self.bot_left, self.bot_right, self.tolerance)
309        }
310
311        pub fn a_c_comparison(&self) -> Ordering {
312            Self::approximate_compare(self.top_left, self.bot_left, self.tolerance)
313        }
314
315        pub fn b_d_comparison(&self) -> Ordering {
316            Self::approximate_compare(self.top_right, self.bot_right, self.tolerance)
317        }
318
319        pub fn a_d_comparison(&self) -> Ordering {
320            Self::approximate_compare(self.top_left, self.bot_right, self.tolerance)
321        }
322
323        pub fn b_c_comparison(&self) -> Ordering {
324            Self::approximate_compare(self.top_right, self.bot_left, self.tolerance)
325        }
326
327        pub fn ef_gh_comparison(&self) -> Ordering {
328            Self::approximate_compare(self.top + self.bottom, self.left + self.right, self.tolerance)
329        }
330
331        /// The higher the tolerance, the easier it is for a,b to be considered equal
332        fn approximate_compare(a: usize, b: usize, tolerance: f64) -> Ordering {
333            if a == b {
334                return Ordering::Equal;
335            }
336            let determinant = std::cmp::min(a,b) as f64 / std::cmp::max(a,b) as f64;
337            if determinant > (1.0-tolerance) {
338                return Ordering::Equal;
339            }
340            if a > b {
341                Ordering::Greater
342            } else {
343                Ordering::Less
344            }
345        }
346    }
347}