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
impl EdfReader
Sourcepub fn open<P: AsRef<Path>>(path: P) -> Result<Self>
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 openedEdfError::UnsupportedFileType
- File is not EDF+ formatEdfError::InvalidHeader
- File header is corrupted or invalidEdfError::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),
}
Sourcepub fn header(&self) -> &EdfHeader
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);
}
Sourcepub fn annotations(&self) -> &[Annotation]
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);
}
Sourcepub fn read_physical_samples(
&mut self,
signal: usize,
count: usize,
) -> Result<Vec<f64>>
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 fromcount
- 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 boundsEdfError::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);
}
Sourcepub fn read_digital_samples(
&mut self,
signal: usize,
count: usize,
) -> Result<Vec<i32>>
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 fromcount
- Number of samples to read
§Returns
Vector of digital values as signed 32-bit integers.
§Errors
EdfError::InvalidSignalIndex
- Signal index is out of boundsEdfError::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!");
}
}
Sourcepub fn seek(&mut self, signal: usize, position: i64) -> Result<i64>
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 signalposition
- 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
Sourcepub fn tell(&self, signal: usize) -> Result<i64>
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);
}
Sourcepub fn rewind(&mut self, signal: usize) -> Result<()>
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);