1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use crate::classifier::Sample;
use crate::dtw::gdtw;
use crate::{
    point::{Point, ONE_POINT, ZERO_POINT},
    rect::Rect,
    stroke::Stroke,
};
use serde::{Deserialize, Serialize};
use std::f64::consts::PI;

#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StrokeSample {
    strokes: Vec<Stroke>,
}

impl StrokeSample {
    pub fn new(mut strokes: Vec<Stroke>) -> Option<Self> {
        strokes = strokes.into_iter().filter(|s| !s.is_empty()).collect();

        if strokes.is_empty() {
            return None;
        }

        strokes.truncate(10);
        for stroke in strokes.iter_mut() {
            stroke.dedup();
            stroke.smooth();
            stroke.aspect_refit(Rect::new(ZERO_POINT, ONE_POINT));
            stroke.redistribute(10);
            stroke.dedup();
            stroke.dominant(2.0 * PI * 15.0 / 360.0)
        }

        Some(StrokeSample { strokes })
    }

    pub(crate) fn is_empty(&self) -> bool {
        self.strokes.is_empty()
    }
}

impl Sample<StrokeSample> for StrokeSample {
    fn distance(a: StrokeSample, b: StrokeSample) -> f64 {
        gdtw(
            Point::manhattan_distance,
            Stroke::concat(a.strokes),
            Stroke::concat(b.strokes),
        )
    }
}

#[cfg(test)]
mod tests {
    use crate::{point::Point, Stroke, StrokeSample};

    #[test]
    fn test_sample() {
        let points = vec![
            Point { x: 166.0, y: 80.0 },
            Point { x: 156.0, y: 104.0 },
            Point { x: 82.0, y: 182.0 },
            Point { x: 48.0, y: 194.0 },
            Point { x: 28.0, y: 127.0 },
            Point { x: 39.0, y: 115.0 },
            Point { x: 59.0, y: 106.0 },
            Point { x: 120.0, y: 106.0 },
            Point { x: 135.0, y: 115.0 },
            Point { x: 149.0, y: 129.0 },
            Point { x: 160.0, y: 145.0 },
            Point { x: 207.0, y: 200.0 },
        ];

        let expected_points = vec![
            Point {
                x: 0.7569169960474308,
                y: 0.1442687747035573,
            },
            Point {
                x: 0.40477099163285507,
                y: 0.5540160467382995,
            },
            Point {
                x: 0.1626658042136413,
                y: 0.6500724952223573,
            },
            Point {
                x: 4.308955299830499e-3,
                y: 0.4971489109610397,
            },
            Point {
                x: 0.14894547060976254,
                y: 0.3286712800588731,
            },
            Point {
                x: 0.41775887969016,
                y: 0.32246108567927273,
            },
            Point {
                x: 0.651671871693856,
                y: 0.44047560390735446,
            },
            Point {
                x: 0.9999999999999999,
                y: 0.8557312252964425,
            },
        ];

        let sample = StrokeSample::new(vec![Stroke::new(points)]).unwrap();
        assert_eq!(sample.strokes.len(), 1);

        for (i, &point) in sample.strokes[0].clone().points().enumerate() {
            assert!(Point::approx_eq(point, expected_points[i]));
        }
    }

    #[test]
    fn test_bad_samples() {
        let strokes = Vec::new();
        assert!(StrokeSample::new(strokes.clone()).is_none());

        let strokes = vec![Stroke::new(vec![])];
        assert!(StrokeSample::new(strokes.clone()).is_none());

        let strokes = vec![Stroke::new(vec![Point { x: 0.5, y: 0.5 }])];
        assert_eq!(StrokeSample::new(strokes.clone()).unwrap().strokes, strokes);
    }
}