Skip to main content

audio_lab/signal/data/
real_data.rs

1use ndarray::prelude::*;
2
3#[cfg(feature = "serde")]
4use super::check_channel_labels;
5use super::{DataError, XAxis};
6
7#[derive(Clone, Debug)]
8#[cfg_attr(
9    feature = "serde",
10    derive(serde::Serialize, serde::Deserialize),
11    serde(try_from = "RealDataFields")
12)]
13pub struct RealData {
14    x_axis: XAxis,
15    y_data: Array2<f64>,
16    comment: Option<String>,
17    channel_labels: Vec<Option<String>>,
18}
19
20/// Deserialized fields, checked by `TryFrom` before they become a `RealData`.
21#[cfg(feature = "serde")]
22#[derive(serde::Deserialize)]
23struct RealDataFields {
24    x_axis: XAxis,
25    y_data: Array2<f64>,
26    comment: Option<String>,
27    channel_labels: Vec<Option<String>>,
28}
29
30#[cfg(feature = "serde")]
31impl TryFrom<RealDataFields> for RealData {
32    type Error = DataError;
33
34    fn try_from(fields: RealDataFields) -> Result<Self, Self::Error> {
35        if fields.y_data.ncols() != fields.x_axis.len() {
36            return Err(DataError::NotMatching);
37        }
38        check_channel_labels(&fields.channel_labels, fields.y_data.nrows())?;
39        // Struct literal on purpose: a new field fails to compile here instead
40        // of being silently dropped when reading.
41        Ok(Self {
42            x_axis: fields.x_axis,
43            y_data: fields.y_data,
44            comment: fields.comment,
45            channel_labels: fields.channel_labels,
46        })
47    }
48}
49
50impl RealData {
51    pub fn new(x_data: Array1<f64>, y_data: Array2<f64>) -> Result<Self, DataError> {
52        if y_data.ncols() != x_data.len() {
53            return Err(DataError::NotMatching);
54        }
55        Self::from_axis(XAxis::explicit(x_data)?, y_data)
56    }
57
58    /// Create data from an existing axis. Only the length is checked, since
59    /// an [`XAxis`] is always valid.
60    pub fn from_axis(x_axis: XAxis, y_data: Array2<f64>) -> Result<Self, DataError> {
61        if y_data.ncols() != x_axis.len() {
62            return Err(DataError::NotMatching);
63        }
64        let num_channels = y_data.nrows();
65        Ok(Self {
66            x_axis,
67            y_data,
68            comment: None,
69            channel_labels: vec![None; num_channels],
70        })
71    }
72
73    pub fn zeros(num_channels: usize, num_data_points: usize) -> Self {
74        Self {
75            x_axis: XAxis::indices(num_data_points),
76            y_data: Array2::zeros((num_channels, num_data_points)),
77            comment: None,
78            channel_labels: vec![None; num_channels],
79        }
80    }
81
82    pub fn from_x_data(x_data: Array1<f64>, num_channels: usize) -> Result<Self, DataError> {
83        let x_axis = XAxis::explicit(x_data)?;
84        Ok(Self {
85            y_data: Array2::zeros((num_channels, x_axis.len())),
86            x_axis,
87            comment: None,
88            channel_labels: vec![None; num_channels],
89        })
90    }
91
92    pub fn from_y_data(y_data: Array2<f64>) -> Self {
93        let num_channels = y_data.nrows();
94        Self {
95            x_axis: XAxis::indices(y_data.ncols()),
96            y_data,
97            comment: None,
98            channel_labels: vec![None; num_channels],
99        }
100    }
101
102    pub fn comment(&self) -> Option<&str> {
103        self.comment.as_deref()
104    }
105
106    pub fn set_comment(&mut self, comment: Option<&str>) {
107        self.comment = comment.map(Into::into);
108    }
109
110    pub fn channel_label(&self, channel: usize) -> Option<&str> {
111        self.channel_labels.get(channel).and_then(|s| s.as_deref())
112    }
113
114    pub fn set_channel_label(&mut self, channel: usize, label: Option<&str>) {
115        if let Some(slot) = self.channel_labels.get_mut(channel) {
116            *slot = label.map(Into::into);
117        }
118    }
119
120    /// All channel labels, one per channel.
121    #[cfg(feature = "serde")]
122    pub(crate) fn channel_labels(&self) -> &[Option<String>] {
123        &self.channel_labels
124    }
125
126    /// Replace all channel labels. There must be one per channel.
127    #[cfg(feature = "serde")]
128    pub(crate) fn set_channel_labels(
129        &mut self,
130        channel_labels: Vec<Option<String>>,
131    ) -> Result<(), DataError> {
132        check_channel_labels(&channel_labels, self.num_channels())?;
133        self.channel_labels = channel_labels;
134        Ok(())
135    }
136
137    pub fn num_channels(&self) -> usize {
138        self.y_data.nrows()
139    }
140
141    pub fn num_data_points(&self) -> usize {
142        self.y_data.ncols()
143    }
144
145    /// The x axis. Prefer this over [`x_data`](Self::x_data) for single
146    /// values ([`XAxis::get`]) or iteration, because it never allocates.
147    pub fn x_axis(&self) -> &XAxis {
148        &self.x_axis
149    }
150
151    /// The x values as an array. Borrowed for an explicit axis, computed
152    /// (one allocation) for a uniform one.
153    pub fn x_data(&self) -> CowArray<'_, f64, Ix1> {
154        self.x_axis.to_cow_array()
155    }
156
157    /// Replace the x axis. It must have one value per data point.
158    pub fn set_x_axis(&mut self, x_axis: XAxis) -> Result<(), DataError> {
159        if x_axis.len() != self.num_data_points() {
160            return Err(DataError::NotMatching);
161        }
162        self.x_axis = x_axis;
163        Ok(())
164    }
165
166    pub fn y_data(&self) -> ArrayView2<'_, f64> {
167        self.y_data.view()
168    }
169
170    pub fn y_data_mut(&mut self) -> ArrayViewMut2<'_, f64> {
171        self.y_data.view_mut()
172    }
173
174    /// Consume the data and return the owned y values without copying.
175    pub fn into_y_data(self) -> Array2<f64> {
176        self.y_data
177    }
178
179    pub fn channel(&self, ch: usize) -> ArrayView1<'_, f64> {
180        self.y_data.row(ch)
181    }
182
183    pub fn channel_mut(&mut self, ch: usize) -> ArrayViewMut1<'_, f64> {
184        self.y_data.row_mut(ch)
185    }
186
187    pub fn channel_iter(&self) -> ndarray::iter::AxisIter<'_, f64, Ix1> {
188        self.y_data.outer_iter()
189    }
190
191    pub fn channel_iter_mut(&mut self) -> ndarray::iter::AxisIterMut<'_, f64, Ix1> {
192        self.y_data.outer_iter_mut()
193    }
194
195    pub fn iter(&self) -> ndarray::iter::Iter<'_, f64, Ix2> {
196        self.y_data.iter()
197    }
198
199    pub fn iter_mut(&mut self) -> ndarray::iter::IterMut<'_, f64, Ix2> {
200        self.y_data.iter_mut()
201    }
202}
203
204impl std::fmt::Display for RealData {
205    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
206        let base_info = format!(
207            "Real data with {} channels and {} data points.",
208            self.num_channels(),
209            self.num_data_points()
210        );
211
212        if let Some(comment) = self.comment() {
213            write!(f, "{}\nComment: {:?}", base_info, comment)
214        } else {
215            write!(f, "{}", base_info)
216        }
217    }
218}
219
220impl PartialEq for RealData {
221    fn eq(&self, other: &Self) -> bool {
222        self.x_axis == other.x_axis && self.y_data == other.y_data
223    }
224}
225
226impl approx::AbsDiffEq for RealData {
227    type Epsilon = f64;
228
229    fn default_epsilon() -> Self::Epsilon {
230        f64::EPSILON
231    }
232
233    fn abs_diff_eq(&self, other: &Self, epsilon: Self::Epsilon) -> bool {
234        if !self.x_axis.abs_diff_eq(&other.x_axis, epsilon) {
235            return false;
236        }
237
238        // Compare y_data
239        if !self.y_data().abs_diff_eq(&other.y_data(), epsilon) {
240            return false;
241        }
242
243        true
244    }
245}
246
247// Immutable IntoIterator
248impl<'a> IntoIterator for &'a RealData {
249    type Item = &'a f64;
250    type IntoIter = ndarray::iter::Iter<'a, f64, ndarray::Ix2>;
251
252    fn into_iter(self) -> Self::IntoIter {
253        self.iter()
254    }
255}
256
257// Mutable IntoIterator
258impl<'a> IntoIterator for &'a mut RealData {
259    type Item = &'a mut f64;
260    type IntoIter = ndarray::iter::IterMut<'a, f64, ndarray::Ix2>;
261
262    fn into_iter(self) -> Self::IntoIter {
263        self.iter_mut()
264    }
265}
266
267// Owned IntoIterator
268impl IntoIterator for RealData {
269    type Item = f64;
270    type IntoIter = ndarray::iter::IntoIter<f64, Ix2>;
271
272    fn into_iter(self) -> Self::IntoIter {
273        self.y_data.into_iter()
274    }
275}
276
277#[cfg(test)]
278mod tests {
279    use super::*;
280
281    #[test]
282    fn from_axis_checks_length_and_keeps_axis() {
283        let axis = XAxis::uniform(0.0, 0.5, 3).unwrap();
284        let data = RealData::from_axis(axis.clone(), array![[1.0, 2.0, 3.0]]).unwrap();
285        assert_eq!(data.x_axis(), &axis);
286        assert_eq!(data.x_data(), arr1(&[0.0, 0.5, 1.0]));
287
288        let error = RealData::from_axis(axis, array![[1.0, 2.0]]).err();
289        assert!(matches!(error, Some(DataError::NotMatching)));
290    }
291
292    #[test]
293    fn set_x_axis_replaces_axis_of_matching_length() {
294        let mut data = RealData::from_y_data(array![[1.0, 2.0, 3.0]]);
295        assert_eq!(data.x_axis().as_uniform(), Some((0.0, 1.0)));
296
297        data.set_x_axis(XAxis::explicit(array![0.0, 1.0, 5.0]).unwrap())
298            .unwrap();
299        assert_eq!(data.x_data(), arr1(&[0.0, 1.0, 5.0]));
300
301        let error = data.set_x_axis(XAxis::indices(4)).err();
302        assert!(matches!(error, Some(DataError::NotMatching)));
303        assert_eq!(data.x_data(), arr1(&[0.0, 1.0, 5.0]));
304    }
305
306    #[test]
307    fn time_data_errors() {
308        let error = RealData::new(
309            array![0.0, 1.0, 2.0, 5.0],
310            array![[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
311        )
312        .err();
313        assert!(matches!(error, Some(DataError::NotMatching)));
314
315        let error = RealData::new(
316            array![0.0, 0.0, 0.0],
317            array![[0.0, 0.0, 0.0], [0.0, 0.0, 0.0]],
318        )
319        .err();
320        assert!(matches!(error, Some(DataError::NotIncreasing)));
321
322        let error = RealData::from_x_data(array![0.0, 0.0, 0.0], 3).err();
323        assert!(matches!(error, Some(DataError::NotIncreasing)));
324    }
325
326    #[test]
327    fn time_data() -> Result<(), DataError> {
328        let data = RealData::zeros(2, 4);
329        assert_eq!(data.x_data(), arr1(&[0.0, 1.0, 2.0, 3.0]));
330        assert_eq!(data.y_data(), arr2(&[[0.0; 4]; 2]),);
331
332        let data = RealData::from_x_data(arr1(&[0.0, 1.0, 2.0, 3.0]), 2).unwrap();
333        assert_eq!(data.x_data(), arr1(&[0.0, 1.0, 2.0, 3.0]));
334        assert_eq!(data.y_data(), arr2(&[[0.0; 4]; 2]),);
335
336        let mut i = 0;
337        let mut data = RealData::new(
338            Array1::from_iter((0..10).map(|v| v as f64 / 10.0)),
339            Array2::from_shape_fn((2, 10), |_| {
340                let value = i as f64;
341                i += 1;
342                value
343            }),
344        )?;
345
346        assert_eq!(data.comment(), None);
347
348        data.set_comment(Some("This is just test data"));
349
350        assert_eq!(data.comment(), Some("This is just test data"));
351
352        assert_eq!(data.num_channels(), 2);
353        assert_eq!(data.num_data_points(), 10);
354
355        assert_eq!(
356            data.x_data(),
357            arr1(&[0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9])
358        );
359
360        assert_eq!(
361            data.y_data(),
362            arr2(&[
363                [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
364                [10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0]
365            ])
366        );
367
368        assert_eq!(
369            data.y_data_mut(),
370            arr2(&[
371                [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0],
372                [10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0]
373            ])
374        );
375
376        assert_eq!(
377            data.channel(0),
378            arr1(&[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
379        );
380        assert_eq!(
381            data.channel(1),
382            arr1(&[10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0])
383        );
384
385        assert_eq!(
386            data.channel_mut(0),
387            arr1(&[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
388        );
389        assert_eq!(
390            data.channel_mut(1),
391            arr1(&[10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0])
392        );
393
394        for (i, ch) in data.channel_iter().enumerate() {
395            match i {
396                0 => assert_eq!(
397                    ch,
398                    arr1(&[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
399                ),
400                1 => assert_eq!(
401                    ch,
402                    arr1(&[10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0])
403                ),
404                _ => panic!("Should only have two channels"),
405            }
406        }
407
408        for (i, ch) in data.channel_iter_mut().enumerate() {
409            match i {
410                0 => assert_eq!(
411                    ch,
412                    arr1(&[0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0])
413                ),
414                1 => assert_eq!(
415                    ch,
416                    arr1(&[10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0, 17.0, 18.0, 19.0])
417                ),
418                _ => panic!("Should only have two channels"),
419            }
420        }
421
422        Ok(())
423    }
424}