Struct EdfReader

Source
pub struct EdfReader { /* private fields */ }
Expand description

EDF+ file reader for reading European Data Format Plus files

The EdfReader provides methods to open and read EDF+ files, which are commonly used for storing biosignal recordings like EEG, ECG, EMG, etc.

§Examples

§Basic usage

use edfplus::EdfReader;
 
// Open an EDF+ file
let mut reader = EdfReader::open("recording.edf")?;
 
// Get header information
let header = reader.header();
println!("Duration: {:.1} seconds", header.file_duration as f64 / 10_000_000.0);
println!("Signals: {}", header.signals.len());
 
// Read physical samples from first signal
let samples = reader.read_physical_samples(0, 256)?;
println!("Read {} samples", samples.len());
 

§Processing all signals

use edfplus::EdfReader;
 
let mut reader = EdfReader::open("multi_signal.edf")?;
let signal_count = reader.header().signals.len();
 
// Process each signal
for i in 0..signal_count {
    let signal_label = reader.header().signals[i].label.clone();
    let signal_dimension = reader.header().signals[i].physical_dimension.clone();
    let samples_per_second = reader.header().signals[i].samples_per_record as usize;
     
    println!("Processing signal {}: {}", i, signal_label);
     
    // Read one second of data (assuming 256 Hz sampling rate)
    let physical_values = reader.read_physical_samples(i, samples_per_second)?;
     
    // Calculate basic statistics
    let mean = physical_values.iter().sum::<f64>() / physical_values.len() as f64;
    let max = physical_values.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
    let min = physical_values.iter().fold(f64::INFINITY, |a, &b| a.min(b));
     
    println!("  Mean: {:.2} {}", mean, signal_dimension);
    println!("  Range: {:.2} to {:.2} {}", min, max, signal_dimension);
}
 

Implementations§

Source§

impl EdfReader

Source

pub fn open<P: AsRef<Path>>(path: P) -> Result<Self>

Opens an EDF+ file for reading

This method opens the specified file, validates it as a proper EDF+ file, and parses the header information. Only EDF+ format is supported.

§Arguments
  • path - Path to the EDF+ file to open
§Returns

Returns a Result<EdfReader, EdfError>. On success, contains an EdfReader instance ready for reading data. On failure, contains an error describing what went wrong.

§Errors
  • EdfError::FileNotFound - File doesn’t exist or can’t be opened
  • EdfError::UnsupportedFileType - File is not EDF+ format
  • EdfError::InvalidHeader - File header is corrupted or invalid
  • EdfError::InvalidSignalCount - Invalid number of signals
§Examples
use edfplus::EdfReader;
 
// Open a file successfully
match EdfReader::open("recording.edf") {
    Ok(reader) => {
        println!("File opened successfully!");
        println!("Duration: {:.1} seconds", 
            reader.header().file_duration as f64 / 10_000_000.0);
    }
    Err(e) => eprintln!("Failed to open file: {}", e),
}
 
// Handle different error types
match EdfReader::open("nonexistent.edf") {
    Ok(_) => println!("Unexpected success"),
    Err(edfplus::EdfError::FileNotFound(msg)) => {
        println!("File not found: {}", msg);
    }
    Err(e) => println!("Other error: {}", e),
}
 
Source

pub fn header(&self) -> &EdfHeader

Gets a reference to the file header information

The header contains all metadata about the recording including:

  • Patient information (name, code, birth date, etc.)
  • Recording information (start time, duration, equipment, etc.)
  • Signal parameters (labels, sampling rates, physical ranges, etc.)
  • File format details
§Examples
use edfplus::EdfReader;
 
let reader = EdfReader::open("recording.edf")?;
let header = reader.header();
 
// Display basic file information
println!("Patient: {}", header.patient_name);
println!("Recording duration: {:.2} seconds", 
    header.file_duration as f64 / 10_000_000.0);
println!("Number of signals: {}", header.signals.len());
 
// Display signal information
for (i, signal) in header.signals.iter().enumerate() {
    println!("Signal {}: {} ({})", 
        i, signal.label, signal.physical_dimension);
    println!("  Sample rate: {} Hz", signal.samples_per_record);
    println!("  Range: {} to {} {}", 
        signal.physical_min, signal.physical_max, signal.physical_dimension);
}
 
Source

pub fn annotations(&self) -> &[Annotation]

Gets a reference to the list of annotations in the file

Annotations represent events, markers, and metadata that occurred during the recording. Common examples include sleep stages, seizures, artifacts, stimuli, and user-defined events.

§Examples
use edfplus::{EdfReader, EdfWriter, SignalParam, Annotation};
 
 
let reader = EdfReader::open("test_annotations.edf").unwrap();
let annotations = reader.annotations();
 
println!("Found {} annotations", annotations.len());
 
for (i, annotation) in annotations.iter().enumerate() {
    let onset_seconds = annotation.onset as f64 / 10_000_000.0;
    let duration_seconds = if annotation.duration >= 0 {
        annotation.duration as f64 / 10_000_000.0
    } else {
        0.0  // Instantaneous event
    };
     
    println!("Annotation {}: {} at {:.2}s (duration: {:.2}s)",
        i, annotation.description, onset_seconds, duration_seconds);
}
 
Source

pub fn read_physical_samples( &mut self, signal: usize, count: usize, ) -> Result<Vec<f64>>

Reads physical value samples from the specified signal

Physical values are the real-world measurements (e.g., microvolts for EEG, millivolts for ECG) as opposed to the raw digital values stored in the file. The conversion from digital to physical values is performed automatically using the signal’s calibration parameters.

§Arguments
  • signal - Zero-based index of the signal to read from
  • count - Number of samples to read
§Returns

Vector of physical values in the signal’s physical dimension (e.g., µV, mV).

§Errors
  • EdfError::InvalidSignalIndex - Signal index is out of bounds
  • EdfError::FileReadError - I/O error reading from file
§Examples
use edfplus::EdfReader;
 
let mut reader = EdfReader::open("eeg_recording.edf")?;
 
// Read 1 second of EEG data (assuming 256 Hz)
let samples = reader.read_physical_samples(0, 256)?;
 
// Get header after reading samples
let header = reader.header();
 
// Calculate basic statistics
let mean = samples.iter().sum::<f64>() / samples.len() as f64;
let max_value = samples.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let min_value = samples.iter().fold(f64::INFINITY, |a, &b| a.min(b));
 
println!("Signal: {}", header.signals[0].label);
println!("Mean: {:.2} {}", mean, header.signals[0].physical_dimension);
println!("Range: {:.2} to {:.2} {}", 
    min_value, max_value, header.signals[0].physical_dimension);
 
§Processing multiple signals
use edfplus::EdfReader;
 
let mut reader = EdfReader::open("multi_channel.edf")?;
let signal_count = reader.header().signals.len();
 
// Read data from all signals  
for signal_idx in 0..signal_count {
    let signal_label = reader.header().signals[signal_idx].label.clone();
    let signal_dimension = reader.header().signals[signal_idx].physical_dimension.clone();
    let samples_per_record = reader.header().signals[signal_idx].samples_per_record as usize;
     
    // Read one record worth of data (safe amount)
    let samples = reader.read_physical_samples(signal_idx, samples_per_record)?;
     
    println!("Signal {}: {} samples from {}", 
        signal_label, samples.len(), signal_dimension);
         
    // Find peak-to-peak amplitude
    let max = samples.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
    let min = samples.iter().fold(f64::INFINITY, |a, &b| a.min(b));
    println!("  Amplitude: {:.2} {}", max - min, signal_dimension);
}
 
Source

pub fn read_digital_samples( &mut self, signal: usize, count: usize, ) -> Result<Vec<i32>>

Reads digital value samples from the specified signal

Digital values are the raw integer values stored in the EDF+ file, before conversion to physical units. These are typically 16-bit signed integers representing the ADC output.

Most users should use read_physical_samples() instead, which automatically converts to real-world units.

§Arguments
  • signal - Zero-based index of the signal to read from
  • count - Number of samples to read
§Returns

Vector of digital values as signed 32-bit integers.

§Errors
  • EdfError::InvalidSignalIndex - Signal index is out of bounds
  • EdfError::FileReadError - I/O error reading from file
§Examples
use edfplus::EdfReader;
 
let mut reader = EdfReader::open("recording.edf")?;
 
// Read raw digital values
let digital_samples = reader.read_digital_samples(0, 100)?;
 
// Get header after reading
let header = reader.header();
let signal = &header.signals[0];
 
// Manual conversion to physical values
let physical_samples: Vec<f64> = digital_samples
    .iter()
    .map(|&d| signal.to_physical(d))
    .collect();
 
println!("Digital range: {} to {}", 
    digital_samples.iter().min().unwrap(),
    digital_samples.iter().max().unwrap());
 
§Checking digital value ranges
use edfplus::EdfReader;
 
let mut reader = EdfReader::open("test.edf")?;
let signal_count = reader.header().signals.len();
 
for i in 0..signal_count {
    let signal_label = reader.header().signals[i].label.clone();
    let digital_min = reader.header().signals[i].digital_min;
    let digital_max = reader.header().signals[i].digital_max;
     
    let samples = reader.read_digital_samples(i, 10)?;
     
    let min_val = *samples.iter().min().unwrap();
    let max_val = *samples.iter().max().unwrap();
     
    println!("Signal {}: digital range {} to {} (expected: {} to {})",
        signal_label, min_val, max_val, digital_min, digital_max);
         
    // Check for clipping
    if min_val <= digital_min || max_val >= digital_max {
        println!("  Warning: Signal may be clipped!");
    }
}
 
Source

pub fn seek(&mut self, signal: usize, position: i64) -> Result<i64>

Sets the sample position for the specified signal

This method allows you to jump to any position within the signal’s data for non-sequential reading. Position is automatically clamped to valid range [0, total_samples_in_signal].

§Arguments
  • signal - Zero-based index of the signal
  • position - Sample position to seek to (0-based)
§Returns

Returns the actual position after clamping to valid range.

§Errors
  • EdfError::InvalidSignalIndex - Signal index is out of bounds
§Examples
use edfplus::EdfReader;
 
let mut reader = EdfReader::open("positioning.edf")?;
 
// Read from beginning
let start_samples = reader.read_physical_samples(0, 3)?;
 
// Jump to middle of the signal
let signal_length = reader.header().signals[0].samples_in_file;
let mid_position = signal_length / 2;
let actual_pos = reader.seek(0, mid_position)?;
assert_eq!(actual_pos, mid_position);
 
// Verify we can read from the new position
let mid_samples = reader.read_physical_samples(0, 3)?;
 
// Position should have advanced
assert_eq!(reader.tell(0)?, mid_position + 3);
 
§Position clamping and validation
use edfplus::EdfReader;
 
let mut reader = EdfReader::open("bounds_test.edf")?;
let signal_length = reader.header().signals[0].samples_in_file;
 
// Test position clamping
let actual_pos = reader.seek(0, -100)?;  // Negative position
assert_eq!(actual_pos, 0);  // Clamped to 0
 
let actual_pos = reader.seek(0, signal_length + 1000)?;  // Beyond end
assert_eq!(actual_pos, signal_length);  // Clamped to max
 
let actual_pos = reader.seek(0, 42)?;  // Valid position
assert_eq!(actual_pos, 42);  // Exact position
 
Source

pub fn tell(&self, signal: usize) -> Result<i64>

Gets the current sample position for the specified signal

This method returns the current reading position within the signal’s data. The position indicates which sample will be read next by read_physical_samples() or read_digital_samples().

§Arguments
  • signal - Zero-based index of the signal
§Returns

Current sample position (0-based) within the signal.

§Errors
  • EdfError::InvalidSignalIndex - Signal index is out of bounds
§Examples
use edfplus::EdfReader;
 
let mut reader = EdfReader::open("position_test.edf")?;
 
// Initially at position 0
assert_eq!(reader.tell(0)?, 0);
 
// Read some samples
reader.read_physical_samples(0, 10)?;
assert_eq!(reader.tell(0)?, 10);
 
// Seek to different position
reader.seek(0, 100)?;
assert_eq!(reader.tell(0)?, 100);
 
// Read more samples
reader.read_physical_samples(0, 5)?;
assert_eq!(reader.tell(0)?, 105);
 
§Working with multiple signals
use edfplus::EdfReader;
 
let mut reader = EdfReader::open("multi_pos.edf")?;
let signal_count = reader.header().signals.len();
 
// Each signal has independent position tracking
for i in 0..signal_count {
    assert_eq!(reader.tell(i)?, 0);
     
    // Read different amounts from each signal
    reader.read_physical_samples(i, (i + 1) * 10)?;
     
    // Each signal should be at different position
    assert_eq!(reader.tell(i)?, ((i + 1) * 10) as i64);
}
 
Source

pub fn rewind(&mut self, signal: usize) -> Result<()>

Resets the position of the specified signal to the beginning

This is equivalent to calling seek(signal, 0) but provides a more convenient and semantic interface for returning to the start of the signal.

§Arguments
  • signal - Zero-based index of the signal to rewind
§Errors
  • EdfError::InvalidSignalIndex - Signal index is out of bounds
§Examples
use edfplus::EdfReader;
 
let mut reader = EdfReader::open("rewind_test.edf")?;
 
// Read some data to advance position
reader.read_physical_samples(0, 50)?;
assert_eq!(reader.tell(0)?, 50);
 
// Rewind to beginning
reader.rewind(0)?;
assert_eq!(reader.tell(0)?, 0);
 
// We can read from beginning again
let samples_after_rewind = reader.read_physical_samples(0, 5)?;
assert_eq!(samples_after_rewind.len(), 5);
 
// Position should advance from 0 to 5
assert_eq!(reader.tell(0)?, 5);
 
§Complete positioning workflow
use edfplus::EdfReader;
 
let mut reader = EdfReader::open("workflow.edf")?;
 
// Start from beginning
assert_eq!(reader.tell(0)?, 0);
let start_data = reader.read_physical_samples(0, 3)?;
 
// Jump to middle
let signal_length = reader.header().signals[0].samples_in_file;
reader.seek(0, signal_length / 2)?;
let mid_data = reader.read_physical_samples(0, 3)?;
 
// Jump near end
reader.seek(0, signal_length - 10)?;
let end_data = reader.read_physical_samples(0, 3)?;
 
// Go back to beginning
reader.rewind(0)?;
let start_again = reader.read_physical_samples(0, 3)?;
 
// First and last reads from start should be identical
assert_eq!(start_data, start_again);
 

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.