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#[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 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 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 #[cfg(feature = "serde")]
122 pub(crate) fn channel_labels(&self) -> &[Option<String>] {
123 &self.channel_labels
124 }
125
126 #[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 pub fn x_axis(&self) -> &XAxis {
148 &self.x_axis
149 }
150
151 pub fn x_data(&self) -> CowArray<'_, f64, Ix1> {
154 self.x_axis.to_cow_array()
155 }
156
157 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 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 if !self.y_data().abs_diff_eq(&other.y_data(), epsilon) {
240 return false;
241 }
242
243 true
244 }
245}
246
247impl<'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
257impl<'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
267impl 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}