Skip to main content

edf_rs/
lib.rs

1/*!
2`edf-rs` is a pure Rust library for reading and writing EDF/EDF+ files. It aims at providing a feature-rich and
3reliable way to work with `*.edf` files. This crate is optimized to work efficiently with very large EDF file sizes.
4It is based on the official specification [here](https://www.edfplus.info/). Currently the primary
5focus for this project is to power my other project [NoctiG Scorer](https://github.com/BitFlaker/noctig-scorer).
6
7**This library is an unofficial implementation. It is still in an early development stage which is not yet
8considered stable and will have breaking changes in the future.**
9
10# Examples
11To get started using this crate, follow the examples below. It will outline how to create and read a
12basic EDF+ file with some test data. To see all available fields and functions, take a look at the
13individual module documentations.
14
15## Create an EDF+ file
16
17The following example shows how to create a new EDF+ file and fill it with test data. This will
18generate a file called `recording.edf`. It will have 1 regular signal and 1 annotations signal.
19The regular signal will contain 100 samples with values from 0 to 99 in every data-record. The
20annotations signal will have a time-keeping annotation and an additional record-global annotation.
21
22```no_run
23use chrono::{NaiveDate, NaiveTime};
24
25use edf_rs::EDFSpecifications;
26use edf_rs::file::EDFFile;
27use edf_rs::record::Record;
28use edf_rs::headers::patient::{PatientId, Sex};
29use edf_rs::headers::recording::RecordingId;
30use edf_rs::headers::signal_header::SignalHeader;
31use edf_rs::headers::annotation_list::AnnotationList;
32
33pub fn main() -> Result<(), Box<dyn std::error::Error>> {
34    // Load the EDF+ file from any path
35    let mut edf = EDFFile::new("recording.edf")?;
36
37    // Configure the header of the EDF file
38    edf.header
39        .with_specification(EDFSpecifications::EDFPlus)
40        .with_is_continuous(true)
41        .with_patient_id(PatientId {
42            code: Some("PAT-CODE1".to_string()),
43            name: Some("PAT-NAME".to_string()),
44            date: Some(NaiveDate::from_ymd_opt(2001, 07, 11).unwrap()),
45            sex: Some(Sex::Male),
46            additional: Vec::new(),
47        })
48        .with_recording_id(RecordingId {
49            admin_code: Some("REC-CODE1".to_string()),
50            equipment: Some("EQUIPMENT".to_string()),
51            technician: Some("TECHNICIAN".to_string()),
52            startdate: Some(NaiveDate::from_ymd_opt(2026, 02, 13).unwrap()),
53            additional: Vec::new()
54        })
55        .with_start_date(NaiveDate::from_ymd_opt(2026, 02, 13).unwrap())
56        .with_start_time(NaiveTime::from_hms_opt(17, 30, 0).unwrap())
57        .with_record_duration(1.0);
58
59    // Create a regular signal
60    let mut signal = SignalHeader::new();
61    signal.with_label("Signal".to_string())
62        .with_transducer("AgAgCl cup electrodes".to_string())
63        .with_physical_dimension("uV".to_string())
64        .with_physical_range(-440.0, 510.0)
65        .with_digital_range(-2048, 2047)
66        .with_samples_count(100);
67
68    // Insert the regular and the annotation signals
69    edf.insert_signal(0, signal).unwrap();
70    edf.insert_signal(1, SignalHeader::new_annotation(80)).unwrap();
71
72    // Insert some data-records
73    edf.append_record(generate_record(&edf, 0)).unwrap();
74    edf.append_record(generate_record(&edf, 1)).unwrap();
75    edf.append_record(generate_record(&edf, 2)).unwrap();
76    edf.append_record(generate_record(&edf, 3)).unwrap();
77    edf.append_record(generate_record(&edf, 4)).unwrap();
78
79    // Save the file
80    edf.save().unwrap();
81
82    Ok(())
83}
84
85fn generate_record(edf: &EDFFile, index: usize) -> Record {
86    let mut record = edf.header.create_record();
87    record.raw_signal_samples = vec![
88        (0..100).collect()
89    ];
90    record.annotations = vec![vec![
91        AnnotationList::new(0.0 + index as f64, 0.0, vec![
92            "".to_string(),
93            format!("GlobalAnnotation {}", index)
94        ]).unwrap()
95    ]];
96
97    record
98}
99```
100
101## Read an EDF+ file
102
103The following example shows how to read an EDF+ file from the beginning to the end. It will read the EDF
104file created with the example [above](#create-an-edf-file). It will read all data-records from the file and
105print the header, all annotations and the maximum values of each signal in every data-record to the console.
106
107```no_run
108use edf_rs::file::EDFFile;
109
110pub fn main() -> Result<(), Box<dyn std::error::Error>> {
111    // Load the EDF+ file from any path
112    let mut edf = EDFFile::open("recording.edf")?;
113
114    // Print the debug output for the parsed EDF+ file header
115    println!("{:#?}", edf.header);
116
117    // Read all data-records and print the stored annotations
118    let record_count = edf.header.get_record_count().unwrap_or(0);
119    for i in 0..record_count {
120        let Some(record) = edf.read_record()? else {
121            continue;
122        };
123
124        // Print the annotations (including the time keeping annotation).
125        // For regular EDF files, these annotations will always be empty
126        // as they are only included in the EDF+ specification
127        println!("Annotations in data-record {}", i);
128        println!("{:#?}", record.annotations);
129
130        // Do something with the signals. The order of the signals
131        // is the same as the signals in the header
132        let max_signal_values: Vec<i16> = record.raw_signal_samples.into_iter()
133            .map(|samples| samples.into_iter().max().unwrap_or(0))
134            .collect();
135        println!("Max values: {:?}", max_signal_values)
136    }
137
138    Ok(())
139}
140```
141
142Further examples will be added in the future
143*/
144
145pub mod error;
146pub mod file;
147pub mod headers;
148pub mod record;
149pub mod save;
150mod tests;
151pub mod utils;
152
153#[derive(Debug, Default, Clone, PartialEq)]
154pub enum EDFSpecifications {
155    /// The original EDF specification from 1992. See the official specifications [here](https://www.edfplus.info/specs/edf.html).
156    EDF,
157
158    #[default]
159    /// The extended EDF specification from 2003. See the official specifications [here](https://www.edfplus.info/specs/edfplus.html).
160    EDFPlus,
161}