1use chrono::{Datelike, NaiveDate, NaiveTime};
2use sha2::{Digest, Sha256};
3use std::io::{BufRead, Seek, SeekFrom};
4use std::str::FromStr;
5
6use crate::EDFSpecifications;
7use crate::error::edf_error::EDFError;
8use crate::headers::patient::PatientId;
9use crate::headers::recording::RecordingId;
10use crate::headers::signal_header::SignalHeader;
11use crate::record::Record;
12use crate::utils::is_printable_ascii;
13
14#[derive(Debug, Default, Clone, PartialEq)]
15pub struct EDFHeader {
16 pub(crate) version: String,
17 pub(crate) patient_id: PatientId,
18 pub(crate) recording_id: RecordingId,
19 start_date: NaiveDate,
20 pub(crate) start_time: NaiveTime,
21 pub(crate) header_bytes: usize,
22 pub(crate) specification: EDFSpecifications,
23 pub(crate) is_continuous: bool,
24 pub(crate) record_count: Option<usize>,
25 pub(crate) record_duration: f64,
26 pub(crate) signal_count: usize,
27 pub(crate) signals: Vec<SignalHeader>,
28 pub(crate) updated_signals: Option<Vec<SignalHeader>>,
29
30 initial_record_size: usize,
31 initial_record_hash: String,
32
33 #[allow(dead_code)]
34 reserved: String,
35}
36
37impl EDFHeader {
38 pub fn new() -> Self {
39 Self {
40 version: "0".to_string(),
41 updated_signals: None,
42 ..Default::default()
43 }
44 }
45
46 pub fn with_version(&mut self, version: String) -> &mut Self {
47 self.version = version;
48 self
49 }
50
51 pub fn with_patient_id(&mut self, patient_id: PatientId) -> &mut Self {
52 self.patient_id = patient_id;
53 self
54 }
55
56 pub fn with_recording_id(&mut self, recording_id: RecordingId) -> &mut Self {
57 self.recording_id = recording_id;
58 self
59 }
60
61 pub fn with_start_date(&mut self, start_date: NaiveDate) -> &mut Self {
62 self.start_date = start_date;
63 self
65 }
66
67 pub fn with_start_time(&mut self, start_time: NaiveTime) -> &mut Self {
68 self.start_time = start_time;
69 self
70 }
71
72 pub fn with_specification(&mut self, specification: EDFSpecifications) -> &mut Self {
73 self.specification = specification;
74 self.is_continuous = self.specification == EDFSpecifications::EDF || self.is_continuous;
75 self
76 }
77
78 pub fn with_is_continuous(&mut self, is_continuous: bool) -> &mut Self {
79 self.is_continuous = is_continuous;
80 self
81 }
82
83 pub fn with_record_count(&mut self, record_count: usize) -> &mut Self {
84 self.record_count = Some(record_count);
85 self
86 }
87
88 pub fn with_record_duration(&mut self, record_duration: f64) -> &mut Self {
89 self.record_duration = record_duration;
90 self
91 }
92
93 pub fn get_version(&self) -> &String {
94 &self.version
95 }
96
97 pub fn get_patient_id(&self) -> &PatientId {
98 &self.patient_id
99 }
100
101 pub fn get_recording_id(&self) -> &RecordingId {
102 &self.recording_id
103 }
104
105 pub fn get_start_date(&self) -> NaiveDate {
106 self.start_date
107 }
109
110 pub fn get_start_time(&self) -> NaiveTime {
111 self.start_time
112 }
113
114 pub fn get_header_bytes(&self) -> usize {
115 self.header_bytes
116 }
117
118 pub fn get_specification(&self) -> EDFSpecifications {
119 self.specification.clone()
120 }
121
122 pub fn is_continuous(&self) -> bool {
123 self.is_continuous
124 }
125
126 pub fn get_record_count(&self) -> Option<usize> {
127 self.record_count
128 }
129
130 pub fn get_record_duration(&self) -> f64 {
131 self.record_duration
132 }
133
134 pub fn get_signals(&self) -> &Vec<SignalHeader> {
135 self.updated_signals.as_ref().unwrap_or(&self.signals)
136 }
137
138 pub fn calculate_header_bytes(&self) -> usize {
139 let signal_count = self.signals.len();
140 let fixed_size = 8 + 80 + 80 + 8 + 8 + 8 + 44 + 8 + 8 + 4;
141 let signal_size = 16 + 80 + 8 + 8 + 8 + 8 + 8 + 80 + 8 + 32;
142 fixed_size + signal_count * signal_size
143 }
144
145 pub fn data_record_bytes(&self) -> usize {
146 self.signals.iter().map(|s| s.samples_count * 2).sum()
147 }
148
149 pub fn get_signal_sample_frequency(&self, signal_index: usize) -> Option<f64> {
150 self.signals
151 .get(signal_index)
152 .map(|s| s.samples_count as f64 / self.record_duration)
153 }
154
155 pub(crate) fn get_initial_record_bytes(&self) -> usize {
158 if self.initial_record_size == 0 {
159 return self.data_record_bytes();
160 }
161 self.initial_record_size
162 }
163
164 pub(crate) fn update_initial_record_bytes(&mut self) {
168 self.initial_record_size = self.data_record_bytes();
169 }
170
171 pub(crate) fn get_initial_header_sha256(&self) -> &String {
174 &self.initial_record_hash
175 }
176
177 pub(crate) fn update_initial_header_sha256(&mut self) -> Result<(), EDFError> {
181 Ok(self.initial_record_hash = self.get_sha256()?)
182 }
183
184 pub fn create_record(&self) -> Record {
185 Record::new(self.updated_signals.as_ref().unwrap_or(&self.signals))
186 }
187
188 pub(crate) fn modify_signals(&mut self) -> &mut Vec<SignalHeader> {
189 if self.updated_signals.is_none() {
190 self.updated_signals = Some(self.signals.clone());
191 }
192 self.updated_signals.as_mut().unwrap()
193 }
194
195 pub fn serialize(&self) -> Result<String, EDFError> {
196 let version = pad_string(&self.version, 8)?;
197 let user_id = pad_string(&self.patient_id.serialize(&self.specification)?, 80)?;
198 let recording_id = pad_string(&self.recording_id.serialize(&self.specification)?, 80)?;
199 let start_date = pad_string(&Self::serialize_old_start_date(&self.start_date), 8)?;
200 let start_time = pad_string(&self.start_time.format("%H.%M.%S").to_string(), 8)?;
201 let reserved = pad_string(
202 match self.specification {
203 EDFSpecifications::EDF => "",
204 EDFSpecifications::EDFPlus if self.is_continuous => "EDF+C",
205 EDFSpecifications::EDFPlus => "EDF+D",
206 },
207 44,
208 )?;
209 let record_count = pad_string(
210 &self
211 .record_count
212 .map(|c| c as i64)
213 .unwrap_or(-1)
214 .to_string(),
215 8,
216 )?;
217 let record_duration = pad_string(&self.record_duration.to_string(), 8)?;
218 let signal_count = pad_string(&self.signals.len().to_string(), 4)?;
219
220 let mut header = format!(
222 "{}{}{}{}{}{}{}{}{}",
223 version,
224 user_id,
225 recording_id,
226 start_date,
227 start_time,
228 reserved,
230 record_count,
231 record_duration,
232 signal_count
233 );
234
235 let signals = self.signals.clone();
236
237 if self.specification == EDFSpecifications::EDFPlus
239 && !signals.iter().any(|s| s.is_annotation())
240 {
241 return Err(EDFError::MissingAnnotations);
242 }
243
244 for signal in &signals {
246 header += &pad_string(&signal.label, 16)?;
247 }
248
249 for signal in &signals {
251 header += &pad_string(&signal.transducer, 80)?;
252 }
253
254 for signal in &signals {
256 header += &pad_string(&signal.physical_dimension, 8)?;
257 }
258
259 for signal in &signals {
261 header += &pad_string(&signal.physical_minimum.to_string(), 8)?;
262 }
263
264 for signal in &signals {
266 header += &pad_string(&signal.physical_maximum.to_string(), 8)?;
267 }
268
269 for signal in &signals {
271 header += &pad_string(&signal.digital_minimum.to_string(), 8)?;
272 }
273
274 for signal in &signals {
276 header += &pad_string(&signal.digital_maximum.to_string(), 8)?;
277 }
278
279 for signal in &signals {
281 header += &pad_string(&signal.prefilter, 80)?;
282 }
283
284 for signal in &signals {
286 header += &pad_string(&signal.samples_count.to_string(), 8)?;
287 }
288
289 for signal in &signals {
291 header += &pad_string(&signal.reserved, 32)?;
292 }
293
294 let header_bytes = header.len() + 8;
296 header.insert_str(184, &pad_string(&header_bytes.to_string(), 8)?);
297
298 if !is_printable_ascii(&header) {
300 return Err(EDFError::InvalidASCII);
301 }
302
303 Ok(header)
304 }
305
306 pub fn deserialize<R: BufRead + Seek>(reader: &mut R) -> Result<Self, EDFError> {
307 reader
309 .seek(SeekFrom::Start(192))
310 .map_err(EDFError::FileReadError)?;
311 let reserved = read_ascii(reader, 44)?;
312
313 let is_continuous_edfplus = reserved.starts_with("EDF+C");
315 let is_discontinuous_edfplus = reserved.starts_with("EDF+D");
316 let is_pro = is_continuous_edfplus || is_discontinuous_edfplus;
317 let specification = if is_pro {
318 EDFSpecifications::EDFPlus
319 } else {
320 EDFSpecifications::EDF
321 };
322
323 let is_continuous = is_continuous_edfplus || !is_pro;
325
326 reader
328 .seek(SeekFrom::Start(0))
329 .map_err(EDFError::FileReadError)?;
330 let version = read_ascii(reader, 8)?.trim_ascii_end().to_string();
331 let patient_id = PatientId::deserialize(
332 read_ascii(reader, 80)?.trim_ascii_end().to_string(),
333 &specification,
334 )?;
335 let recording_id = RecordingId::deserialize(
336 read_ascii(reader, 80)?.trim_ascii_end().to_string(),
337 &specification,
338 )?;
339 let start_date = Self::parse_old_start_date(&read_ascii(reader, 8)?)?;
340 let start_time = NaiveTime::parse_from_str(&read_ascii(reader, 8)?, "%H.%M.%S")
341 .map_err(|_| EDFError::InvalidStartTime)?;
342 let header_bytes = usize::from_str(&read_ascii(reader, 8)?.trim_ascii_end())
343 .map_err(|_| EDFError::InvalidHeaderSize)?;
344
345 reader
347 .seek(SeekFrom::Start(236))
348 .map_err(EDFError::FileReadError)?;
349
350 let record_count = usize::from_str(&read_ascii(reader, 8)?.trim_ascii_end()).ok();
351 let record_duration = f64::from_str(&read_ascii(reader, 8)?.trim_ascii_end())
352 .map_err(|_| EDFError::InvalidRecordDuration)?; let signal_count = usize::from_str(&read_ascii(reader, 4)?.trim_ascii_end())
354 .map_err(|_| EDFError::InvalidSignalCount)?;
355
356 let mut signals = vec![SignalHeader::default(); signal_count];
357
358 for signal in &mut signals {
360 signal.label = read_ascii(reader, 16)?.trim_ascii_end().to_string();
361 }
362
363 for signal in &mut signals {
365 signal.transducer = read_ascii(reader, 80)?.trim_ascii_end().to_string();
366 }
367
368 for signal in &mut signals {
370 signal.physical_dimension = read_ascii(reader, 8)?.trim_ascii_end().to_string();
371 }
372
373 for signal in &mut signals {
375 signal.physical_minimum = f64::from_str(&read_ascii(reader, 8)?.trim_ascii_end())
376 .map_err(|_| EDFError::InvalidPhysicalRange)?;
377 }
378
379 for signal in &mut signals {
381 signal.physical_maximum = f64::from_str(&read_ascii(reader, 8)?.trim_ascii_end())
382 .map_err(|_| EDFError::InvalidPhysicalRange)?;
383 }
384
385 for signal in &mut signals {
387 signal.digital_minimum = i32::from_str(&read_ascii(reader, 8)?.trim_ascii_end())
388 .map_err(|_| EDFError::InvalidPhysicalRange)?;
389 }
390
391 for signal in &mut signals {
393 signal.digital_maximum = i32::from_str(&read_ascii(reader, 8)?.trim_ascii_end())
394 .map_err(|_| EDFError::InvalidPhysicalRange)?;
395 }
396
397 for signal in &mut signals {
399 signal.prefilter = read_ascii(reader, 80)?.trim_ascii_end().to_string();
400 }
401
402 for signal in &mut signals {
404 signal.samples_count = usize::from_str(&read_ascii(reader, 8)?.trim_ascii_end())
405 .map_err(|_| EDFError::InvalidSamplesCount)?;
406 }
407
408 for signal in &mut signals {
410 signal.reserved = read_ascii(reader, 32)?.trim_ascii_end().to_string();
411 }
412
413 let mut header = Self {
414 version,
415 patient_id,
416 recording_id,
417 start_date,
418 start_time,
419 header_bytes,
420 reserved,
421 specification,
422 is_continuous,
423 record_count,
424 record_duration,
425 signal_count,
426 signals,
427 initial_record_size: 0,
428 initial_record_hash: String::new(),
429 updated_signals: None,
430 };
431
432 header.initial_record_hash = header.get_sha256()?;
434 header.update_initial_record_bytes();
435
436 Ok(header)
437 }
438
439 pub fn get_sha256(&self) -> Result<String, EDFError> {
441 let serialized = self.serialize()?;
442 let mut hasher = Sha256::new();
443 hasher.update(serialized.as_bytes());
444 let result = hasher.finalize();
445 Ok(format!("{:x}", result))
446 }
447
448 pub fn is_recording(&self) -> bool {
449 self.record_count.is_none()
450 }
451
452 pub fn start_date(&self) -> NaiveDate {
457 self.recording_id.startdate.unwrap_or(self.start_date)
458 }
459
460 pub fn parse_old_start_date(date: &str) -> Result<NaiveDate, EDFError> {
463 let parts = date.split('.').collect::<Vec<_>>();
464 let year;
465
466 if parts.len() != 3 {
468 return Err(EDFError::InvalidStartDate);
469 }
470
471 if parts[2] == "yy" {
473 year = "2100".to_string();
474 } else if let Ok(year_num) = u8::from_str(parts[2]) {
475 if year_num < 85 {
476 year = format!("20{:0>2}", year_num);
477 } else if year_num < 100 {
478 year = format!("19{:0>2}", year_num);
479 } else {
480 return Err(EDFError::InvalidStartDate);
481 }
482 } else {
483 return Err(EDFError::InvalidStartDate);
484 }
485
486 let parsed_year = format!("{}.{}.{}", parts[0], parts[1], year);
488 NaiveDate::parse_from_str(&parsed_year, "%d.%m.%Y").map_err(|_| EDFError::InvalidStartDate)
489 }
490
491 pub fn serialize_old_start_date(date: &NaiveDate) -> String {
494 let year = if date.year() >= 2085 || date.year() <= 1984 {
495 "yy".to_string()
496 } else {
497 format!("{:0>2}", (date.year() % 100))
498 };
499
500 format!("{:0>2}.{:0>2}.{}", date.day(), date.month(), year)
502 }
503}
504
505pub fn read_ascii<'a, R: BufRead>(reader: &'a mut R, count: usize) -> Result<String, EDFError> {
506 let mut buf = vec![0; count];
507 reader
508 .read_exact(&mut buf)
509 .map_err(EDFError::FileReadError)?;
510
511 Ok(buf.iter().map(|c| *c as char).collect())
512}
513
514fn pad_string(value: &str, size: usize) -> Result<String, EDFError> {
515 if value.len() > size {
516 return Err(EDFError::FieldSizeExceeded);
517 }
518 let padding = " ".repeat(size - value.len());
519
520 Ok(format!("{}{}", value, padding))
521}
522
523#[cfg(test)]
524mod tests {
525 use super::*;
526 use crate::headers::patient::PatientId;
527 use crate::headers::patient::Sex;
528 use crate::headers::recording::RecordingId;
529
530 use chrono::{NaiveDate, NaiveTime};
531 use std::io::BufReader;
532 use std::io::Cursor;
533
534 #[test]
535 fn serialize() {
536 let test_header = "0 MCH-0234567 F 16-SEP-1987 Haagse_Harry Startdate 16-SEP-1987 PSG-1234/1987 NN Telemetry03 16.09.8720.35.001024 EDF+C 2880 30 3 EEG Fpz-Cz Temp rectal EDF Annotations AgAgCl cup electrodes Rectal thermistor uV degC -440 34.4 -1 510 40.2 1 -2048 -2048 -32768 2047 2047 32767 HP:0.1Hz LP:75Hz N:50Hz LP:0.1Hz (first order) 15000 3 320 Reserved for EEG signal Reserved for Body temperature ".to_string();
537 let cursor = Cursor::new(test_header.as_bytes());
538 let mut reader = BufReader::new(cursor);
539 let value = EDFHeader::deserialize(&mut reader);
540 assert!(value.is_ok());
541 let value = value.unwrap();
542 let serialized = value.serialize();
543 assert!(serialized.is_ok());
544 assert_eq!(serialized.unwrap(), test_header);
545 }
546
547 #[test]
548 fn deserialize() {
549 let test_header = "0 MCH-0234567 F 16-SEP-1987 Haagse_Harry Startdate 16-SEP-1987 PSG-1234/1987 NN Telemetry03 16.09.8720.35.001024 EDF+C 2880 30 3 EEG Fpz-Cz Temp rectal EDF Annotations AgAgCl cup electrodes Rectal thermistor uV degC -440 34.4 -1 510 40.2 1 -2048 -2048 -32768 2047 2047 32767 HP:0.1Hz LP:75Hz N:50Hz LP:0.1Hz (first order) 15000 3 320 Reserved for EEG signal Reserved for Body temperature ".to_string();
550 let cursor = Cursor::new(test_header.as_bytes());
551 let mut reader = BufReader::new(cursor);
552 let value = EDFHeader::deserialize(&mut reader);
553 let mut expected = EDFHeader {
554 version: "0".to_string(),
555 patient_id: PatientId {
556 code: Some("MCH-0234567".to_string()),
557 sex: Some(Sex::Female),
558 date: Some(NaiveDate::from_ymd_opt(1987, 09, 16).unwrap()),
559 name: Some("Haagse Harry".to_string()),
560 additional: Vec::new(),
561 },
562 recording_id: RecordingId {
563 startdate: Some(NaiveDate::from_ymd_opt(1987, 09, 16).unwrap()),
564 admin_code: Some("PSG-1234/1987".to_string()),
565 technician: Some("NN".to_string()),
566 equipment: Some("Telemetry03".to_string()),
567 additional: Vec::new(),
568 },
569 start_date: NaiveDate::from_ymd_opt(1987, 09, 16).unwrap(),
570 start_time: NaiveTime::from_hms_opt(20, 35, 00).unwrap(),
571 header_bytes: 1024,
572 specification: EDFSpecifications::EDFPlus,
573 is_continuous: true,
574 record_count: Some(2880),
575 record_duration: 30.0,
576 signal_count: 3,
577 signals: vec![
578 SignalHeader {
579 label: "EEG Fpz-Cz".to_string(),
580 transducer: "AgAgCl cup electrodes".to_string(),
581 physical_dimension: "uV".to_string(),
582 physical_minimum: -440.0,
583 physical_maximum: 510.0,
584 digital_minimum: -2048,
585 digital_maximum: 2047,
586 prefilter: "HP:0.1Hz LP:75Hz N:50Hz".to_string(),
587 samples_count: 15000,
588 reserved: "Reserved for EEG signal".to_string(),
589 },
590 SignalHeader {
591 label: "Temp rectal".to_string(),
592 transducer: "Rectal thermistor".to_string(),
593 physical_dimension: "degC".to_string(),
594 physical_minimum: 34.4,
595 physical_maximum: 40.2,
596 digital_minimum: -2048,
597 digital_maximum: 2047,
598 prefilter: "LP:0.1Hz (first order)".to_string(),
599 samples_count: 3,
600 reserved: "Reserved for Body temperature".to_string(),
601 },
602 SignalHeader {
603 label: "EDF Annotations".to_string(),
604 transducer: "".to_string(),
605 physical_dimension: "".to_string(),
606 physical_minimum: -1.0,
607 physical_maximum: 1.0,
608 digital_minimum: -32768,
609 digital_maximum: 32767,
610 prefilter: "".to_string(),
611 samples_count: 320,
612 reserved: "".to_string(),
613 },
614 ],
615 reserved: "EDF+C ".to_string(),
616 initial_record_size: 30646,
617 updated_signals: None,
618 initial_record_hash: String::new(),
619 };
620 assert!(expected.update_initial_header_sha256().is_ok());
621 assert!(value.is_ok());
622 let value = value.unwrap();
623 assert_eq!(value, expected);
624 assert_eq!(value.serialize().unwrap(), test_header);
625 }
626}