Skip to main content

edf_rs/
record.rs

1use std::collections::HashMap;
2
3use crate::error::edf_error::EDFError;
4use crate::headers::annotation_list::AnnotationList;
5use crate::headers::edf_header::EDFHeader;
6use crate::headers::signal_header::SignalHeader;
7use crate::save::{SaveInstruction, SaveValue};
8
9#[derive(Debug, Default, Clone, PartialEq)]
10struct RecordLayout {
11    signal_map: HashMap<usize, SignalType>,
12    annotation_samples_count: Vec<usize>,
13}
14
15#[derive(Debug, Clone, PartialEq)]
16enum SignalType {
17    Samples(usize),
18    Annotation(usize),
19}
20
21#[derive(Debug, Clone, PartialEq)]
22pub struct Record {
23    layout: RecordLayout,
24    pub(crate) default_offset: f64,
25    pub raw_signal_samples: Vec<Vec<i16>>,
26    pub annotations: Vec<Vec<AnnotationList>>,
27}
28
29impl Record {
30    pub fn new(signal_headers: &Vec<SignalHeader>) -> Self {
31        let mut raw_signal_samples = Vec::new();
32        let mut annotations = Vec::new();
33        let mut annotation_samples_count = Vec::new();
34        let mut signal_map = HashMap::new();
35        for (i, signal) in signal_headers.iter().enumerate() {
36            if signal.is_annotation() {
37                signal_map.insert(i, SignalType::Annotation(annotations.len()));
38                annotation_samples_count.push(signal.samples_count);
39                annotations.push(Vec::new());
40            } else {
41                signal_map.insert(i, SignalType::Samples(raw_signal_samples.len()));
42                raw_signal_samples.push(vec![0; signal.samples_count]);
43            }
44        }
45
46        Self {
47            layout: RecordLayout {
48                signal_map,
49                annotation_samples_count,
50            },
51            default_offset: 0.0,
52            raw_signal_samples,
53            annotations,
54        }
55    }
56
57    pub fn patch_record(&mut self, instructions: &Vec<SaveInstruction>) -> Result<(), EDFError> {
58        if instructions.is_empty() {
59            return Ok(());
60        }
61
62        // Process each instruction
63        let mut signal_idx = instructions[0].index();
64        let mut instruction_idx = 0;
65        loop {
66            let Some(tr) = instructions.get(instruction_idx) else {
67                break;
68            };
69
70            match tr {
71                SaveInstruction::Remove(idx) if *idx == signal_idx => {
72                    instruction_idx += 1;
73                    self.remove_signal(*idx)?;
74                }
75                SaveInstruction::Insert(idx, SaveValue::Signal(value)) if *idx == signal_idx => {
76                    instruction_idx += 1;
77                    if value.is_annotation() {
78                        self.insert_annotation(*idx, value.samples_count)?;
79                    } else {
80                        self.insert_signal_samples(*idx, value.samples_count)?;
81                    }
82                }
83                SaveInstruction::Update(idx, SaveValue::Signal(value)) if *idx == signal_idx => {
84                    signal_idx += 1;
85                    instruction_idx += 1;
86                    self.update_samples_count(*idx, value.samples_count)?;
87                }
88                _ => {
89                    signal_idx += 1;
90                }
91            }
92        }
93
94        Ok(())
95    }
96
97    pub fn insert_signal_samples(
98        &mut self,
99        signal_index: usize,
100        samples_count: usize,
101    ) -> Result<(), EDFError> {
102        // Get count of signal indices which are samples and lower than the target index
103        let insert_idx = (0..signal_index)
104            .filter(|i| {
105                self.layout
106                    .signal_map
107                    .get(&i)
108                    .is_some_and(|s| matches!(s, SignalType::Samples(idx) if *idx < signal_index))
109            })
110            .count();
111
112        // Increase the global signal index pointers in the signal map as well as the sample signal index pointers
113        self.apply_index_change_samples(signal_index, insert_idx, 1);
114        self.layout
115            .signal_map
116            .insert(signal_index, SignalType::Samples(insert_idx));
117
118        // Insert the new annotation signal values
119        self.raw_signal_samples
120            .insert(insert_idx, vec![0; samples_count]);
121
122        Ok(())
123    }
124
125    pub fn insert_annotation(
126        &mut self,
127        signal_index: usize,
128        samples_count: usize,
129    ) -> Result<(), EDFError> {
130        // Get count of signal indices which are annotations and lower than the target index
131        let insert_idx = (0..signal_index)
132            .filter(|i| {
133                self.layout.signal_map.get(&i).is_some_and(
134                    |s| matches!(s, SignalType::Annotation(idx) if *idx < signal_index),
135                )
136            })
137            .count();
138
139        // Increase the global signal index pointers in the signal map as well as the annotation signal index pointers
140        self.apply_index_change_annotation(signal_index, insert_idx, 1);
141        self.layout
142            .signal_map
143            .insert(signal_index, SignalType::Annotation(insert_idx));
144
145        // Insert the new annotation signal values
146        self.layout
147            .annotation_samples_count
148            .insert(insert_idx, samples_count);
149        self.annotations.insert(insert_idx, Vec::new());
150
151        Ok(())
152    }
153
154    pub fn remove_signal(&mut self, signal_index: usize) -> Result<(), EDFError> {
155        match self.layout.signal_map.remove(&signal_index) {
156            Some(SignalType::Samples(idx)) => {
157                self.raw_signal_samples.remove(idx);
158                self.apply_index_change_samples(signal_index, idx, -1);
159            }
160            Some(SignalType::Annotation(idx)) => {
161                self.layout.annotation_samples_count.remove(idx);
162                self.annotations.remove(idx);
163                self.apply_index_change_annotation(signal_index, idx, -1);
164            }
165            _ => return Err(EDFError::ItemNotFound),
166        }
167
168        Ok(())
169    }
170
171    pub fn update_samples_count(
172        &mut self,
173        signal_index: usize,
174        samples_count: usize,
175    ) -> Result<(), EDFError> {
176        match self.layout.signal_map.get(&signal_index) {
177            Some(SignalType::Samples(idx)) => {
178                if let Some(count) = self.raw_signal_samples.get_mut(*idx) {
179                    count.resize(samples_count, 0);
180                } else {
181                    return Err(EDFError::ItemNotFound);
182                }
183            }
184            Some(SignalType::Annotation(idx)) => {
185                if let Some(count) = self.layout.annotation_samples_count.get_mut(*idx) {
186                    *count = samples_count;
187                } else {
188                    return Err(EDFError::ItemNotFound);
189                }
190            }
191            _ => return Err(EDFError::ItemNotFound),
192        }
193
194        Ok(())
195    }
196
197    pub fn set_annotation(
198        &mut self,
199        signal_index: usize,
200        annotations: Vec<AnnotationList>,
201    ) -> Result<(), EDFError> {
202        let Some(SignalType::Annotation(idx)) = self.layout.signal_map.get(&signal_index) else {
203            return Err(EDFError::ItemNotFound);
204        };
205
206        let Some(old_annotations) = self.annotations.get_mut(*idx) else {
207            return Err(EDFError::ItemNotFound);
208        };
209
210        *old_annotations = annotations;
211
212        Ok(())
213    }
214
215    pub fn set_samples(&mut self, signal_index: usize, samples: Vec<i16>) -> Result<(), EDFError> {
216        let Some(SignalType::Samples(idx)) = self.layout.signal_map.get(&signal_index) else {
217            return Err(EDFError::ItemNotFound);
218        };
219
220        let Some(old_samples) = self.raw_signal_samples.get_mut(*idx) else {
221            return Err(EDFError::ItemNotFound);
222        };
223
224        if old_samples.len() != samples.len() {
225            return Err(EDFError::InvalidSamplesCount);
226        }
227
228        *old_samples = samples;
229
230        Ok(())
231    }
232
233    pub fn get_digital_samples(&self, signal: &SignalHeader) -> Vec<Vec<i32>> {
234        self.raw_signal_samples.iter().map(|signals| {
235            signals.iter().map(|sample| {
236                (*sample as i32).clamp(signal.digital_minimum, signal.digital_maximum)
237            }).collect()
238        }).collect()
239    }
240
241    pub fn get_physical_samples(&self, signal: &SignalHeader) -> Vec<Vec<f64>> {
242        let range = (signal.physical_maximum - signal.physical_minimum) / (signal.digital_maximum - signal.digital_minimum) as f64;
243        let offset = signal.physical_maximum / range - signal.digital_maximum as f64;
244
245        self.raw_signal_samples.iter().map(|signals| {
246            signals.iter().map(|sample| {
247                let digital = *sample as f64;
248                let physical = range * (offset + digital);
249                physical.clamp(signal.physical_minimum, signal.physical_maximum)
250            }).collect()
251        }).collect()
252    }
253
254    fn apply_index_change_annotation(
255        &mut self,
256        signal_index: usize,
257        target_index: usize,
258        direction: i8,
259    ) {
260        let mut new = HashMap::new();
261        for (k, v) in self.layout.signal_map.drain() {
262            let new_global_index =
263                (k as i64 + direction as i64 * (k >= signal_index) as i64) as usize;
264            let value = if let SignalType::Annotation(idx) = v
265                && idx >= target_index
266            {
267                SignalType::Annotation((idx as i64 + direction as i64) as usize)
268            } else {
269                v
270            };
271            new.insert(new_global_index, value);
272        }
273        self.layout.signal_map = new;
274    }
275
276    fn apply_index_change_samples(
277        &mut self,
278        signal_index: usize,
279        target_index: usize,
280        direction: i8,
281    ) {
282        let mut new = HashMap::new();
283        for (k, v) in self.layout.signal_map.drain() {
284            let new_global_index =
285                (k as i64 + direction as i64 * (k >= signal_index) as i64) as usize;
286            let value = if let SignalType::Samples(idx) = v
287                && idx >= target_index
288            {
289                SignalType::Samples((idx as i64 + direction as i64) as usize)
290            } else {
291                v
292            };
293            new.insert(new_global_index, value);
294        }
295        self.layout.signal_map = new;
296    }
297
298    /// Returns the onset of the current record relative to the start of the recording of the EDF+ file.
299    /// This only returns useful information for EDF+ files. Regular EDF files will always return the
300    /// index of the data-record multiplied by the data-record duration as records are missing the time keeping context.
301    /// If there were to be multiple signals labeled `EDF Annotations`, the first one will be used to check for the
302    /// Time-keeping-list entry
303    pub fn get_start_offset(&self) -> f64 {
304        self.annotations
305            .first()
306            .map(|tals| tals.iter().find(|a| a.is_time_keeping()).map(|a| a.onset))
307            .flatten()
308            .unwrap_or(self.default_offset)
309    }
310
311    pub fn serialize(&self) -> Result<Vec<u8>, EDFError> {
312        let mut result_buffer = vec![];
313
314        for signal_idx in 0..self.layout.signal_map.len() {
315            match self.layout.signal_map.get(&signal_idx) {
316                Some(SignalType::Annotation(idx)) => {
317                    if let Some(annotation) = self.annotations.get(*idx)
318                        && let Some(sample_count) = self.layout.annotation_samples_count.get(*idx)
319                    {
320                        let tals = annotation
321                            .iter()
322                            .map(|a| a.serialize())
323                            .collect::<Vec<_>>()
324                            .join("");
325                        let mut tal_bytes = tals.as_bytes().to_vec();
326                        tal_bytes.extend(vec![0; 2 * sample_count - tal_bytes.len()]);
327                        result_buffer.extend(tal_bytes);
328                    }
329                }
330                Some(SignalType::Samples(idx)) => {
331                    if let Some(signal) = self.raw_signal_samples.get(*idx) {
332                        result_buffer.extend(
333                            &signal
334                                .into_iter()
335                                .map(|s| s.to_le_bytes())
336                                .flatten()
337                                .collect::<Vec<_>>(),
338                        );
339                    }
340                }
341                _ => {
342                    panic!("Invalid record signal mapping index. This should not be possible")
343                }
344            }
345        }
346
347        Ok(result_buffer)
348    }
349
350    pub fn matches_signals(&self, signal_headers: &Vec<SignalHeader>) -> bool {
351        // Validate the signal count of the record matches the provided signal header count
352        let actual_count = self.annotations.len() + self.raw_signal_samples.len();
353        if actual_count != signal_headers.len()
354            || actual_count != self.layout.signal_map.len()
355            || actual_count
356                != self
357                    .layout
358                    .signal_map
359                    .keys()
360                    .max()
361                    .map(|k| *k + 1)
362                    .unwrap_or(0)
363        {
364            return false;
365        }
366
367        // Validate the sample count of every signal in the record matches the provided signal header
368        for i in 0..actual_count {
369            match self.layout.signal_map.get(&i) {
370                Some(SignalType::Samples(idx)) => {
371                    if !self
372                        .raw_signal_samples
373                        .get(*idx)
374                        .is_some_and(|s| s.len() == signal_headers[i].samples_count)
375                    {
376                        return false;
377                    }
378                }
379                Some(SignalType::Annotation(idx)) => {
380                    if !self
381                        .layout
382                        .annotation_samples_count
383                        .get(*idx)
384                        .is_some_and(|c| *c == signal_headers[i].samples_count)
385                    {
386                        return false;
387                    }
388                }
389                _ => return false,
390            }
391        }
392
393        true
394    }
395}
396
397#[derive(Debug, Default, Clone, PartialEq)]
398pub struct RelativeRecordData {
399    pub offset: f64,
400    pub raw_signal_samples: Vec<i16>,
401}
402
403impl RelativeRecordData {
404    pub fn new(offset: f64) -> Self {
405        Self {
406            offset,
407            raw_signal_samples: Vec::new(),
408        }
409    }
410
411    pub fn get_digital_samples(&self, signal: &SignalHeader) -> Vec<i32> {
412        self.raw_signal_samples.iter().map(|sample| {
413            (*sample as i32).clamp(signal.digital_minimum, signal.digital_maximum)
414        }).collect()
415    }
416
417    pub fn get_physical_samples(&self, signal: &SignalHeader) -> Vec<f64> {
418        let range = (signal.physical_maximum - signal.physical_minimum) / (signal.digital_maximum - signal.digital_minimum) as f64;
419        let offset = signal.physical_maximum / range - signal.digital_maximum as f64;
420
421        self.raw_signal_samples.iter().map(|sample| {
422            let digital = *sample as f64;
423            let physical = range * (offset + digital);
424            physical.clamp(signal.physical_minimum, signal.physical_maximum)
425        }).collect()
426    }
427}
428
429#[derive(Debug, Default, Clone, PartialEq)]
430pub struct SpanningRecord {
431    pub raw_signal_samples: Vec<Vec<RelativeRecordData>>,
432    pub annotations: Vec<Vec<AnnotationList>>,
433}
434
435impl SpanningRecord {
436    pub fn new(header: &EDFHeader) -> Self {
437        let signal_count = header.signals.iter().filter(|s| !s.is_annotation()).count();
438        Self {
439            raw_signal_samples: vec![Vec::new(); signal_count],
440            annotations: Vec::new(),
441        }
442    }
443
444    pub fn is_spanning_wait(&self) -> bool {
445        self.raw_signal_samples
446            .iter()
447            .all(|sp| sp.last().is_some_and(|data| data.raw_signal_samples.is_empty()))
448    }
449
450    pub fn remove_last_spanning_wait(&mut self) -> bool {
451        if self.is_spanning_wait() {
452            for signal in &mut self.raw_signal_samples {
453                signal.remove(signal.len() - 1);
454            }
455
456            return true;
457        }
458        return false;
459    }
460
461    pub fn insert_spanning_wait(&mut self, offset: f64) {
462        self.remove_last_spanning_wait();
463
464        // Check if the last spanning entry is the same offset
465        if self
466            .raw_signal_samples
467            .first()
468            .map(|s| s.last())
469            .flatten()
470            .is_some_and(|s| s.offset == offset)
471        {
472            return;
473        }
474
475        // In case it is a new offset, insert the new spanning values
476        for signal in &mut self.raw_signal_samples {
477            signal.push(RelativeRecordData::new(offset));
478        }
479    }
480
481    pub fn finish(&mut self) {
482        self.remove_last_spanning_wait();
483        // TODO: This should probably also go through all annotations and remove all
484        // Time-keeping entries.
485    }
486
487    pub fn extend_samples(&mut self, signal_index: usize, samples: Vec<i16>) {
488        if let Some(signal) = self.raw_signal_samples.get_mut(signal_index) {
489            if let Some(data) = signal.last_mut() {
490                data.raw_signal_samples.extend(samples);
491            }
492        }
493    }
494}