Crate edfplus

Source
Expand description

§EDF+ Library for Rust

A pure Rust library for reading and writing EDF+ (European Data Format Plus) files. This library focuses specifically on EDF+ format and provides a safe, efficient API.

§Quick Start

§Reading an EDF+ file

use edfplus::{EdfReader, EdfWriter, SignalParam, Result};

fn main() -> Result<()> {
 
    // Open an EDF+ file
    let mut reader = EdfReader::open("test_data.edf")?;
     
    // Get file information
    let header = reader.header();
    println!("Number of signals: {}", header.signals.len());
    println!("File duration: {:.2} seconds", 
        header.file_duration as f64 / 10_000_000.0);
     
    // Read first 1000 samples from signal 0
    if !header.signals.is_empty() {
        let samples = reader.read_physical_samples(0, 1000)?;
        println!("Read {} samples", samples.len());
    }
     
    Ok(())
}

§Creating an EDF+ file

use edfplus::{EdfWriter, SignalParam, Result};

fn main() -> Result<()> {
    // Create a writer
    let mut writer = EdfWriter::create("test_output.edf")?;
     
    // Set patient information
    writer.set_patient_info("P001", "M", "01-JAN-1990", "Patient Name")?;
     
    // Define signal parameters
    let signal = SignalParam {
        label: "EEG Fp1".to_string(),
        samples_in_file: 0,  // Will be calculated automatically
        physical_max: 200.0,
        physical_min: -200.0,
        digital_max: 32767,
        digital_min: -32768,
        samples_per_record: 256,  // Sample rate
        physical_dimension: "uV".to_string(),
        prefilter: "HP:0.1Hz LP:70Hz".to_string(),
        transducer: "AgAgCl cup electrodes".to_string(),
    };
     
    // Add the signal
    writer.add_signal(signal)?;
     
    // Generate and write data
    let mut samples = Vec::new();
    for i in 0..256 {
        let t = i as f64 / 256.0;
        let value = 50.0 * (2.0 * std::f64::consts::PI * 10.0 * t).sin();
        samples.push(value);
    }
     
    writer.write_samples(&[samples])?;
    writer.finalize()?;
     
    Ok(())
}

§Adds an annotation/event to the EDF+ file

⚠️ CRITICAL TIMING CONSTRAINT

Annotations are only saved when their onset time falls within future data records. Once a data record is written with write_samples(), no new annotations can be added to that time period.

Timing Rules:

  • Add annotations BEFORE writing the data records that cover their time range
  • Annotations with onset_seconds in already-written time periods will be silently lost
  • This is due to the sequential write architecture - no backtracking is possible
§Arguments
  • onset_seconds - Time when the event occurred (seconds since recording start)
  • duration_seconds - Duration of the event in seconds (None for instantaneous events)
  • description - UTF-8 text describing the event (max 40 chars effective)
§Description Length Limit

Warning: Annotation descriptions are subject to EDF+ format constraints:

  • Maximum effective length is 40 characters in the final TAL (Time-stamped Annotations Lists) data
  • Longer descriptions will be automatically truncated during file writing
  • UTF-8 multi-byte characters may be truncated at byte boundaries, potentially corrupting the text
  • This limit is enforced by the EDF+ standard and matches edflib behavior
// Write 5 seconds of data (5 records)
let mut writer = EdfWriter::create("annotations.edf")?;
 
// ✅ Good - within file duration [0.0, 5.0)
writer.add_annotation(2.5, None, "Valid event")?;
writer.add_annotation(4.999, None, "Last moment")?;
 
// ❌ Lost - outside file duration
writer.add_annotation(5.0, None, "Will be discarded")?;
writer.add_annotation(6.0, None, "Also discarded")?;
 
for i in 0..5 {
    let samples = vec![0.0; 256];
    writer.write_samples(&[samples])?;
}

§Working with Signal Data

§Physical vs Digital Values

EDF+ stores data as 16-bit integers but represents real-world measurements. The library automatically handles conversion between digital and physical values:

use edfplus::SignalParam;

let signal = SignalParam {
    label: "Test Signal".to_string(),
    samples_in_file: 1000,
    physical_max: 100.0,   // +100 µV
    physical_min: -100.0,  // -100 µV  
    digital_max: 32767,    // +32767 (16-bit max)
    digital_min: -32768,   // -32768 (16-bit min)
    samples_per_record: 256,
    physical_dimension: "uV".to_string(),
    prefilter: "".to_string(),
    transducer: "".to_string(),
};

// Convert digital to physical
let digital_value = 16384;  // Half of max digital value
let physical_value = signal.to_physical(digital_value);
assert!((physical_value - 50.0).abs() < 0.1); // Should be ~50 µV

// Convert physical to digital  
let physical_input = 25.0;  // 25 µV
let digital_output = signal.to_digital(physical_input);
assert!((digital_output - 8192).abs() <= 1); // Should be ~8192

Re-exports§

pub use error::EdfError;
pub use error::Result;
pub use types::EdfHeader;
pub use types::SignalParam;
pub use types::Annotation;
pub use reader::EdfReader;
pub use writer::EdfWriter;

Modules§

error
reader
types
utils
writer

Constants§

EDFLIB_MAXSIGNALS
EDFLIB_MAX_ANNOTATION_LEN
EDFLIB_TIME_DIMENSION

Functions§

version
Library version