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::{Samples, 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, EDFSpecifications::EDFPlus)).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        Samples::Values16Bit((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;
109use edf_rs::record::Samples;
110
111pub fn main() -> Result<(), Box<dyn std::error::Error>> {
112    // Load the EDF+ file from any path
113    let mut edf = EDFFile::open("recording.edf")?;
114
115    // Print the debug output for the parsed EDF+ file header
116    println!("{:#?}", edf.header);
117
118    // Read all data-records and print the stored annotations
119    let record_count = edf.header.get_record_count().unwrap_or(0);
120    for i in 0..record_count {
121        let Some(record) = edf.read_record()? else {
122            continue;
123        };
124
125        // Print the annotations (including the time keeping annotation).
126        // For regular EDF files, these annotations will always be empty
127        // as they are only included in the EDF+ specification
128        println!("Annotations in data-record {}", i);
129        println!("{:#?}", record.annotations);
130
131        // Do something with the signals. The order of the signals
132        // is the same as the signals in the header
133        let max_signal_values: Vec<i32> = record.raw_signal_samples.into_iter()
134            .map(|samples| match samples {
135                Samples::Values16Bit(samples) => samples.into_iter().max().unwrap_or(0) as i32,
136                Samples::Values24Bit(samples) => samples.into_iter().max().unwrap_or(0),
137            })
138            .collect();
139        println!("Max values: {:?}", max_signal_values)
140    }
141
142    Ok(())
143}
144```
145
146Further examples will be added in the future
147*/
148
149pub mod error;
150pub mod file;
151pub mod headers;
152pub mod record;
153pub mod save;
154mod tests;
155pub mod utils;
156
157#[derive(Debug, Default, Clone, PartialEq)]
158pub enum EDFSpecifications {
159    /// The original EDF specification from 1992. See the official specifications [here](https://www.edfplus.info/specs/edf.html).
160    EDF,
161
162    #[default]
163    /// The extended EDF specification from 2003. See the official specifications [here](https://www.edfplus.info/specs/edfplus.html).
164    EDFPlus,
165
166    /// The 24 bit version of the EDF format. See the official BioSemi Data Format specifications [here](https://www.biosemi.com/faq/file_format.htm).
167    BDF,
168
169    /// The 24 bit version of the EDF+ format. See the specifications [here](https://www.teuniz.net/edfbrowser/bdfplus%20format%20description.html).
170    BDFPlus,
171}