use edfplus::{EdfReader, EdfWriter, SignalParam};
use std::fs;
use std::path::Path;
fn cleanup_test_file(filename: &str) {
if Path::new(filename).exists() {
fs::remove_file(filename).ok();
}
}
fn create_test_eeg_signal() -> SignalParam {
SignalParam {
label: "EEG Fp1".to_string(),
samples_in_file: 0,
physical_max: 200.0,
physical_min: -200.0,
digital_max: 32767,
digital_min: -32768,
samples_per_record: 256,
physical_dimension: "uV".to_string(),
prefilter: "HP:0.1Hz LP:70Hz".to_string(),
transducer: "AgAgCl electrodes".to_string(),
}
}
fn create_test_ecg_signal() -> SignalParam {
SignalParam {
label: "ECG Lead II".to_string(),
samples_in_file: 0,
physical_max: 5.0,
physical_min: -5.0,
digital_max: 32767,
digital_min: -32768,
samples_per_record: 256,
physical_dimension: "mV".to_string(),
prefilter: "HP:0.1Hz LP:100Hz".to_string(),
transducer: "Chest electrodes".to_string(),
}
}
#[test]
fn test_basic_write_read_cycle() {
let filename = "test_basic_cycle.edf";
{
let mut writer = EdfWriter::create(filename).unwrap();
writer.set_patient_info("P001", "M", "01-JAN-1990", "Test Patient").unwrap();
let signal = create_test_eeg_signal();
writer.add_signal(signal).unwrap();
for second in 0..5 {
let mut samples = Vec::new();
for i in 0..256 {
let t = (second * 256 + i) as f64 / 256.0;
let value = 50.0 * (2.0 * std::f64::consts::PI * 10.0 * t).sin() +
5.0 * (2.0 * std::f64::consts::PI * 50.0 * t).sin();
samples.push(value);
}
writer.write_samples(&[samples]).unwrap();
}
writer.finalize().unwrap();
}
{
let mut reader = EdfReader::open(filename).unwrap();
let header = reader.header();
println!("Patient name in header: '{}'", header.patient_name);
assert!(header.patient_name.contains("Test Patient") || header.patient_name == "Test");
assert_eq!(header.signals.len(), 1);
assert_eq!(header.signals[0].label, "EEG Fp1");
assert_eq!(header.signals[0].physical_dimension, "uV");
assert_eq!(header.signals[0].samples_per_record, 256);
let samples = reader.read_physical_samples(0, 256).unwrap();
assert_eq!(samples.len(), 256);
let max_val = samples.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let min_val = samples.iter().fold(f64::INFINITY, |a, &b| a.min(b));
assert!(max_val < 200.0 && max_val > -200.0);
assert!(min_val > -200.0 && min_val < 200.0);
println!("Basic cycle test: Read {} samples, range: {:.2} to {:.2} µV",
samples.len(), min_val, max_val);
}
cleanup_test_file(filename);
}
#[test]
fn test_multi_channel_recording() {
let filename = "test_multi_channel.edf";
{
let mut writer = EdfWriter::create(filename).unwrap();
writer.set_patient_info("MC001", "F", "15-MAR-1985", "Multi Channel Test").unwrap();
for i in 0..4 {
let mut signal = create_test_eeg_signal();
signal.label = format!("EEG C{}", i + 1);
writer.add_signal(signal).unwrap();
}
writer.add_signal(create_test_ecg_signal()).unwrap();
for second in 0..10 {
let mut all_samples = Vec::new();
for ch in 0..4 {
let mut channel_samples = Vec::new();
for sample in 0..256 {
let t = (second * 256 + sample) as f64 / 256.0;
let freq = 8.0 + ch as f64 * 2.0; let value = 30.0 * (2.0 * std::f64::consts::PI * freq * t).sin() +
10.0 * (2.0 * std::f64::consts::PI * 50.0 * t).sin(); channel_samples.push(value);
}
all_samples.push(channel_samples);
}
let mut ecg_samples = Vec::new();
for sample in 0..256 {
let t = (second * 256 + sample) as f64 / 256.0;
let value = 2.0 * (2.0 * std::f64::consts::PI * 1.0 * t).sin(); ecg_samples.push(value);
}
all_samples.push(ecg_samples);
writer.write_samples(&all_samples).unwrap();
}
writer.finalize().unwrap();
}
{
let mut reader = EdfReader::open(filename).unwrap();
assert_eq!(reader.header().signals.len(), 5);
assert_eq!(reader.header().signals[0].label, "EEG C1");
assert_eq!(reader.header().signals[1].label, "EEG C2");
assert_eq!(reader.header().signals[2].label, "EEG C3");
assert_eq!(reader.header().signals[3].label, "EEG C4");
assert_eq!(reader.header().signals[4].label, "ECG Lead II");
for signal_idx in 0..5 {
let samples = reader.read_physical_samples(signal_idx, 256).unwrap();
assert_eq!(samples.len(), 256);
let signal_label = reader.header().signals[signal_idx].label.clone();
let dimension = reader.header().signals[signal_idx].physical_dimension.clone();
let mean = samples.iter().sum::<f64>() / samples.len() as f64;
let max_val = samples.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b));
let min_val = samples.iter().fold(f64::INFINITY, |a, &b| a.min(b));
println!("Channel {}: {} - Mean: {:.2} {}, Range: {:.2} to {:.2} {}",
signal_idx, signal_label, mean, dimension, min_val, max_val, dimension);
if signal_label.starts_with("EEG") {
assert!(max_val <= 200.0 && min_val >= -200.0);
} else if signal_label.starts_with("ECG") {
assert!(max_val <= 5.0 && min_val >= -5.0);
}
}
}
cleanup_test_file(filename);
}
#[test]
fn test_different_sampling_rates() {
let filename = "test_different_rates.edf";
{
let mut writer = EdfWriter::create(filename).unwrap();
writer.set_patient_info("SR001", "X", "X", "Sampling Rate Test").unwrap();
let mut high_freq_signal = create_test_eeg_signal();
high_freq_signal.label = "EEG High Freq".to_string();
high_freq_signal.samples_per_record = 512;
writer.add_signal(high_freq_signal).unwrap();
let mut standard_signal = create_test_eeg_signal();
standard_signal.label = "EEG Standard".to_string();
standard_signal.samples_per_record = 256;
writer.add_signal(standard_signal).unwrap();
let low_freq_signal = SignalParam {
label: "Temperature".to_string(),
samples_in_file: 0,
physical_max: 40.0,
physical_min: 30.0,
digital_max: 32767,
digital_min: -32768,
samples_per_record: 1,
physical_dimension: "degC".to_string(),
prefilter: "None".to_string(),
transducer: "Thermistor".to_string(),
};
writer.add_signal(low_freq_signal).unwrap();
for second in 0..5 {
let mut high_freq_samples = Vec::new();
for i in 0..512 {
let t = (second * 512 + i) as f64 / 512.0;
let value = 40.0 * (2.0 * std::f64::consts::PI * 20.0 * t).sin();
high_freq_samples.push(value);
}
let mut standard_samples = Vec::new();
for i in 0..256 {
let t = (second * 256 + i) as f64 / 256.0;
let value = 30.0 * (2.0 * std::f64::consts::PI * 10.0 * t).sin();
standard_samples.push(value);
}
let temp_value = 36.5 + 0.5 * (second as f64 / 5.0 * 2.0 * std::f64::consts::PI).sin();
let low_freq_samples = vec![temp_value];
writer.write_samples(&[high_freq_samples, standard_samples, low_freq_samples]).unwrap();
}
writer.finalize().unwrap();
}
{
let mut reader = EdfReader::open(filename).unwrap();
let header = reader.header();
assert_eq!(header.signals.len(), 3);
assert_eq!(header.signals[0].samples_per_record, 512);
assert_eq!(header.signals[1].samples_per_record, 256);
assert_eq!(header.signals[2].samples_per_record, 1);
let high_freq_data = reader.read_physical_samples(0, 512).unwrap(); let standard_data = reader.read_physical_samples(1, 256).unwrap(); let temp_data = reader.read_physical_samples(2, 1).unwrap();
assert_eq!(high_freq_data.len(), 512);
assert_eq!(standard_data.len(), 256);
assert_eq!(temp_data.len(), 1);
assert!(temp_data[0] >= 30.0 && temp_data[0] <= 40.0);
println!("Different sampling rates test:");
println!(" High freq (512Hz): {} samples, max: {:.2}",
high_freq_data.len(), high_freq_data.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)));
println!(" Standard (256Hz): {} samples, max: {:.2}",
standard_data.len(), standard_data.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)));
println!(" Temperature (1Hz): {} samples, value: {:.2}°C",
temp_data.len(), temp_data[0]);
}
cleanup_test_file(filename);
}
#[test]
fn test_seek_and_random_access() {
let filename = "test_seek.edf";
{
let mut writer = EdfWriter::create(filename).unwrap();
writer.set_patient_info("SEEK001", "X", "X", "Seek Test").unwrap();
let signal = create_test_eeg_signal();
writer.add_signal(signal).unwrap();
for second in 0..60 {
let mut samples = Vec::new();
for i in 0..256 {
let t = (second * 256 + i) as f64 / 256.0;
let freq = 10.0 + (second / 10) as f64 * 5.0;
let value = 50.0 * (2.0 * std::f64::consts::PI * freq * t).sin();
samples.push(value);
}
writer.write_samples(&[samples]).unwrap();
}
writer.finalize().unwrap();
}
{
let mut reader = EdfReader::open(filename).unwrap();
reader.rewind(0).unwrap();
let start_samples = reader.read_physical_samples(0, 256).unwrap();
assert_eq!(start_samples.len(), 256);
let target_position = 30 * 256; reader.seek(0, target_position).unwrap();
let middle_samples = reader.read_physical_samples(0, 256).unwrap();
assert_eq!(middle_samples.len(), 256);
let current_pos = reader.tell(0).unwrap();
assert_eq!(current_pos, target_position + 256);
let near_end_position = 58 * 256; reader.seek(0, near_end_position).unwrap();
let end_samples = reader.read_physical_samples(0, 256).unwrap();
assert_eq!(end_samples.len(), 256);
let final_position = 59 * 256; reader.seek(0, final_position).unwrap();
let final_samples = reader.read_physical_samples(0, 512).unwrap(); assert_eq!(final_samples.len(), 256);
println!("Seek test completed:");
println!(" Start samples: {}, max: {:.2}", start_samples.len(),
start_samples.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)));
println!(" Middle samples: {}, max: {:.2}", middle_samples.len(),
middle_samples.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)));
println!(" End samples: {}, max: {:.2}", end_samples.len(),
end_samples.iter().fold(f64::NEG_INFINITY, |a, &b| a.max(b)));
println!(" Final samples: {}", final_samples.len());
}
cleanup_test_file(filename);
}
#[test]
fn test_digital_vs_physical_values() {
let filename = "test_digital_physical.edf";
{
let mut writer = EdfWriter::create(filename).unwrap();
writer.set_patient_info("DIG001", "X", "X", "Digital Test").unwrap();
let signal = create_test_eeg_signal();
writer.add_signal(signal).unwrap();
let known_values = vec![
0.0, 100.0, -100.0, 50.0, -50.0, ];
let mut samples = Vec::new();
for i in 0..256 {
let value = known_values[i % known_values.len()];
samples.push(value);
}
writer.write_samples(&[samples]).unwrap();
writer.finalize().unwrap();
}
{
let mut reader = EdfReader::open(filename).unwrap();
let physical_samples = reader.read_physical_samples(0, 256).unwrap();
assert_eq!(physical_samples.len(), 256);
reader.rewind(0).unwrap();
let digital_samples = reader.read_digital_samples(0, 256).unwrap();
println!("Digital samples length: {}, Physical samples length: {}",
digital_samples.len(), physical_samples.len());
if digital_samples.len() == physical_samples.len() {
let known_values = vec![0.0, 100.0, -100.0, 50.0, -50.0];
for (i, &expected_physical) in known_values.iter().enumerate() {
let actual_physical = physical_samples[i];
let digital_value = digital_samples[i];
let tolerance = 0.01;
assert!((actual_physical - expected_physical).abs() < tolerance,
"Physical value mismatch at index {}: expected {}, got {}",
i, expected_physical, actual_physical);
println!("Index {}: Physical {:.3}, Digital {}", i, actual_physical, digital_value);
}
let signal = &reader.header().signals[0];
for &digital_val in &digital_samples {
assert!(digital_val >= signal.digital_min && digital_val <= signal.digital_max,
"Digital value {} out of range [{}, {}]",
digital_val, signal.digital_min, signal.digital_max);
}
} else {
println!("Skipping digital/physical comparison due to length mismatch");
let known_values = vec![0.0, 100.0, -100.0, 50.0, -50.0];
for (i, &expected_physical) in known_values.iter().enumerate() {
let actual_physical = physical_samples[i];
let tolerance = 0.01;
assert!((actual_physical - expected_physical).abs() < tolerance,
"Physical value mismatch at index {}: expected {}, got {}",
i, expected_physical, actual_physical);
}
}
}
cleanup_test_file(filename);
}