hrv_algos/preprocessing/
noise.rs

1//! This module provides functionality for adding random noise to input data to hide quantization effects.
2
3use rand::distributions::{Distribution, Uniform};
4
5/// Trait to add random noise to an iterator of f64 values to hide quantization effects.
6///
7/// # Arguments
8///
9/// * `self` - An iterator of f64 values representing the input data.
10/// * `quantization` - An optional f64 value representing the scale of the noise to be added.
11///                    If not provided, a default value of 1.0 is used.
12///
13/// # Returns
14///
15/// A `DitheringIter` iterator that yields f64 values with added noise.
16///
17/// # Examples
18///
19/// ```rust
20/// use hrv_algos::preprocessing::noise::ApplyDithering;
21/// let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
22/// let dithered: Vec<f64> = data.clone().into_iter()
23///     .apply_dithering(Some(0.1))
24///     .collect();
25/// data.iter().zip(dithered.iter()).for_each(|(p, np)| {assert!((p-np).abs() <= 0.1)});
26/// ```
27pub trait ApplyDithering: Iterator<Item = f64> {
28    fn apply_dithering(self, quantization: Option<f64>) -> DitheringIter<Self>
29    where
30        Self: Sized;
31}
32
33impl<I> ApplyDithering for I
34where
35    I: Iterator<Item = f64>,
36{
37    fn apply_dithering(self, quantization: Option<f64>) -> DitheringIter<Self> {
38        let limit = quantization.unwrap_or(1.0) / 2.0;
39        DitheringIter {
40            iter: self,
41            dist: Uniform::new(-limit, limit),
42        }
43    }
44}
45
46/// Dithering iterator struct that adds random noise to input data.
47pub struct DitheringIter<I: Iterator<Item = f64>> {
48    iter: I,
49    dist: Uniform<f64>,
50}
51
52impl<I: Iterator<Item = f64>> Iterator for DitheringIter<I> {
53    type Item = f64;
54
55    fn next(&mut self) -> Option<Self::Item> {
56        self.iter.next().map(|value| {
57            // Generate two uniform random numbers and average them for triangular distribution
58            let mut rng = rand::thread_rng();
59            let u1 = self.dist.sample(&mut rng);
60            let u2 = self.dist.sample(&mut rng);
61            value + (u1 + u2) / 2.0
62        })
63    }
64}
65
66#[cfg(test)]
67mod tests {
68    use super::*;
69
70    #[test]
71    fn test_dithering_iter() {
72        let data = vec![1.0, 2.0, 3.0];
73        let gt = data.clone();
74        let dithered: Vec<f64> = data.into_iter().apply_dithering(Some(0.1)).collect();
75        assert_eq!(dithered.len(), 3);
76        // Values should be within ±0.5 of original
77        for (orig, dith) in gt.iter().zip(dithered.iter()) {
78            assert!((orig - dith).abs() <= 0.05);
79        }
80    }
81    #[test]
82    fn test_dithering_iter_default() {
83        let data = vec![1.0, 2.0, 3.0];
84        let gt = data.clone();
85        let dithered: Vec<f64> = data.into_iter().apply_dithering(None).collect();
86        assert_eq!(dithered.len(), 3);
87        // Values should be within ±0.5 of original
88        for (orig, dith) in gt.iter().zip(dithered.iter()) {
89            assert!((orig - dith).abs() <= 0.5);
90        }
91    }
92}