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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
use crate::*;

/// Image histogram
#[derive(Debug, Clone, PartialEq)]
pub struct Histogram {
    total: usize,
    bins: Box<[usize]>,
}

impl std::ops::Index<usize> for Histogram {
    type Output = usize;

    fn index(&self, i: usize) -> &usize {
        &self.bins[i]
    }
}

impl std::ops::IndexMut<usize> for Histogram {
    fn index_mut(&mut self, i: usize) -> &mut usize {
        &mut self.bins[i]
    }
}

impl AsRef<[usize]> for Histogram {
    fn as_ref(&self) -> &[usize] {
        self.bins.as_ref()
    }
}

impl Histogram {
    /// Create a new histogram with the given number of bins
    pub fn new(nbins: usize) -> Histogram {
        Histogram {
            total: 0,
            bins: vec![0; nbins].into_boxed_slice(),
        }
    }

    /// Join data from multiple histograms
    pub fn join(h: impl AsRef<[Histogram]>) -> Histogram {
        let h = h.as_ref();
        let mut hist = Histogram::new(h[0].len());

        for i in h {
            hist.total += i.total;
            for (index, value) in i.bins() {
                hist[index] += value
            }
        }

        hist
    }

    /// Add a value to the histogram
    pub fn add_value<T: Type>(&mut self, value: T) {
        let x = value.to_norm() * (self.bins.len() - 1) as f64;
        self.incr_bin(x.round() as usize)
    }

    /// Increment a bin without adding a value
    pub fn incr_bin(&mut self, index: usize) {
        self.bins[index] += 1;
        self.total += 1;
    }

    /// Get value of a specific bin
    pub fn bin(&self, index: usize) -> usize {
        self.bins[index]
    }

    /// Returns an iterator over all bins
    pub fn bins(&self) -> impl '_ + Iterator<Item = (usize, usize)> {
        self.bins.iter().enumerate().map(|(a, b)| (a, *b))
    }

    /// Get the number of bins
    pub fn len(&self) -> usize {
        self.bins.len()
    }

    /// Returns true when there are zero bins
    pub fn is_empty(&self) -> bool {
        self.len() == 0
    }

    /// Get the bin index of the minimum value. There may be other bins with the same value, which
    /// would not be reported by this function
    pub fn min_index(&self) -> usize {
        let mut min = usize::MAX;
        let mut index = 0;
        for (i, n) in self.bins.iter().enumerate() {
            if *n < min {
                min = *n;
                index = i;
            }
        }

        index
    }

    /// Get the bin index of the maximum value. There may be other bins with the same value, which
    /// would not be reported by this function
    pub fn max_index(&self) -> usize {
        let mut max = 0;
        let mut index = 0;
        for (i, n) in self.bins.iter().enumerate() {
            if *n > max {
                max = *n;
                index = i;
            }
        }
        index
    }

    /// Count the number of bins with the given value
    pub fn count(&self, v: usize) -> usize {
        self.bins.iter().map(|bin| (*bin == v) as usize).sum()
    }

    /// Get distribution of values
    pub fn distribution(&self) -> Vec<f64> {
        let total: f64 = self.bins().map(|(_, x)| x as f64).sum();
        self.bins().map(|(_, x)| x as f64 / total).collect()
    }

    /// Get sum of all values
    pub fn sum(&self) -> usize {
        self.total
    }
}

#[cfg(test)]
mod tests {
    use crate::*;

    #[test]
    fn test_histogram_basic() {
        let image = Image::<f32, Rgb>::new((100, 100));
        let hist = image.histogram(255);

        for h in hist {
            assert!(h.bins[0] == 100 * 100);
            assert!(h.min_index() == 1);
            assert!(h.max_index() == 0);
            assert!(h.distribution()[0] == 1.0);
            assert!(h.distribution().into_iter().skip(1).sum::<f64>() == 0.0);
        }
    }
}