use edfplus::{EdfReader, EdfWriter, SignalParam};
use std::fs;
use std::path::Path;
use std::thread;
use std::time::Duration;
fn cleanup_test_file(filename: &str) {
if Path::new(filename).exists() {
fs::remove_file(filename).ok();
}
}
fn create_streaming_signal() -> SignalParam {
SignalParam {
label: "Stream Signal".to_string(),
samples_in_file: 0,
physical_max: 100.0,
physical_min: -100.0,
digital_max: 32767,
digital_min: -32768,
samples_per_record: 256,
physical_dimension: "uV".to_string(),
prefilter: "HP:0.1Hz LP:40Hz".to_string(),
transducer: "Streaming electrodes".to_string(),
}
}
#[test]
fn test_streaming_write_incremental_read() {
let filename = "test_streaming.edf";
{
let mut writer = EdfWriter::create(filename).unwrap();
writer.set_patient_info("STREAM001", "X", "X", "Streaming Test").unwrap();
let signal = create_streaming_signal();
writer.add_signal(signal).unwrap();
for chunk in 0..30 {
let mut samples = Vec::new();
for i in 0..256 {
let t = (chunk * 256 + i) as f64 / 256.0;
let base_freq = 10.0 + (chunk as f64 * 0.5); let amplitude = 50.0 * (1.0 + 0.1 * (chunk as f64 * 0.2).sin()); let value = amplitude * (2.0 * std::f64::consts::PI * base_freq * t).sin();
samples.push(value);
}
writer.write_samples(&[samples]).unwrap();
if chunk % 10 == 0 {
println!("Streamed {} seconds of data", chunk + 1);
}
}
writer.finalize().unwrap();
}
{
let mut reader = EdfReader::open(filename).unwrap();
let header = reader.header();
println!("Streaming read test:");
println!(" Total duration: {:.1}s", header.file_duration as f64 / 10_000_000.0);
let chunk_size = 512; let mut total_samples_read = 0;
let mut chunk_count = 0;
loop {
let samples = reader.read_physical_samples(0, chunk_size).unwrap();
if samples.is_empty() {
break;
}
total_samples_read += samples.len();
chunk_count += 1;
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));
if chunk_count % 5 == 0 {
let time_s = total_samples_read as f64 / 256.0;
println!(" Chunk {}: {:.1}s, {} samples, range: {:.1} to {:.1}, mean: {:.2}",
chunk_count, time_s, samples.len(), min_val, max_val, mean);
}
thread::sleep(Duration::from_millis(1));
}
println!(" Total chunks read: {}", chunk_count);
println!(" Total samples read: {}", total_samples_read);
assert_eq!(total_samples_read, 30 * 256); }
cleanup_test_file(filename);
}
#[test]
fn test_random_access_streaming() {
let filename = "test_random_streaming.edf";
{
let mut writer = EdfWriter::create(filename).unwrap();
writer.set_patient_info("RANDOM001", "X", "X", "Random Access Test").unwrap();
let signal = create_streaming_signal();
writer.add_signal(signal).unwrap();
for second in 0..300 {
let mut samples = Vec::new();
for i in 0..256 {
let t = (second * 256 + i) as f64 / 256.0;
let minute = second / 60;
let freq = 5.0 + minute as f64 * 2.0; let phase_shift = minute as f64 * std::f64::consts::PI / 4.0;
let value = 40.0 * (2.0 * std::f64::consts::PI * freq * t + phase_shift).sin();
samples.push(value);
}
writer.write_samples(&[samples]).unwrap();
}
writer.finalize().unwrap();
}
{
let mut reader = EdfReader::open(filename).unwrap();
println!("Random access streaming test:");
let test_positions = vec![
(0, "Start"), (60, "1 minute"), (150, "2.5 minutes"), (240, "4 minutes"), (290, "Near end"), ];
for (target_second, description) in test_positions {
let target_sample = target_second * 256;
reader.seek(0, target_sample).unwrap();
let samples = reader.read_physical_samples(0, 256).unwrap();
assert_eq!(samples.len(), 256);
let current_pos = reader.tell(0).unwrap();
let actual_second = (current_pos - 256) / 256;
let mut freq_powers = vec![0.0; 20]; for freq_idx in 0..20 {
let freq = freq_idx as f64;
let mut power = 0.0;
for (i, &sample) in samples.iter().enumerate() {
let t = i as f64 / 256.0;
power += sample * (2.0 * std::f64::consts::PI * freq * t).cos();
}
freq_powers[freq_idx] = power.abs();
}
let dominant_freq = freq_powers.iter()
.enumerate()
.max_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
.unwrap().0 as f64;
println!(" {}: sample {}, dominant freq: {:.1}Hz, expected ~{:.1}Hz",
description, actual_second, dominant_freq,
5.0 + (target_second / 60) as f64 * 2.0);
}
reader.rewind(0).unwrap();
let sequential_start = reader.read_physical_samples(0, 256).unwrap();
reader.seek(0, 0).unwrap();
let random_start = reader.read_physical_samples(0, 256).unwrap();
assert_eq!(sequential_start.len(), random_start.len());
for (i, (&seq, &rand)) in sequential_start.iter().zip(random_start.iter()).enumerate() {
let diff = (seq - rand).abs();
assert!(diff < 1e-10, "Mismatch at sample {}: sequential={}, random={}", i, seq, rand);
}
println!(" Sequential vs random access: consistent ✓");
}
cleanup_test_file(filename);
}
#[test]
fn test_large_file_handling() {
let filename = "test_large_file.edf";
{
let mut writer = EdfWriter::create(filename).unwrap();
writer.set_patient_info("LARGE001", "X", "X", "Large File Test").unwrap();
for i in 0..8 {
let mut signal = create_streaming_signal();
signal.label = format!("EEG_{}", i + 1);
writer.add_signal(signal).unwrap();
}
println!("Creating large file with 8 channels for 10 minutes...");
for second in 0..600 {
let mut all_samples = Vec::new();
for channel in 0..8 {
let mut channel_samples = Vec::new();
for sample in 0..256 {
let t = (second * 256 + sample) as f64 / 256.0;
let freq = 8.0 + channel as f64; let amplitude = 30.0 + channel as f64 * 5.0; let value = amplitude * (2.0 * std::f64::consts::PI * freq * t).sin();
channel_samples.push(value);
}
all_samples.push(channel_samples);
}
writer.write_samples(&all_samples).unwrap();
if second % 60 == 0 {
println!(" Written {} minutes", second / 60);
}
}
writer.finalize().unwrap();
}
{
let start_time = std::time::Instant::now();
let mut reader = EdfReader::open(filename).unwrap();
let open_time = start_time.elapsed();
let header = reader.header();
println!("Large file test results:");
println!(" File open time: {:.2}ms", open_time.as_millis());
println!(" Channels: {}", header.signals.len());
println!(" Duration: {:.1} minutes", header.file_duration as f64 / 10_000_000.0 / 60.0);
let seek_start = std::time::Instant::now();
let middle_position = 300 * 256; reader.seek(0, middle_position).unwrap();
let seek_time = seek_start.elapsed();
println!(" Seek to middle: {:.2}ms", seek_time.as_millis());
let read_start = std::time::Instant::now();
let mut total_samples = 0;
for channel in 0..8 {
let samples = reader.read_physical_samples(channel, 2560).unwrap(); total_samples += samples.len();
}
let read_time = read_start.elapsed();
println!(" Bulk read (8 channels × 10s): {:.2}ms for {} samples",
read_time.as_millis(), total_samples);
println!(" Read rate: {:.1} samples/ms", total_samples as f64 / read_time.as_millis() as f64);
reader.rewind(0).unwrap();
let first_samples = reader.read_physical_samples(0, 256).unwrap();
assert_eq!(first_samples.len(), 256);
let final_position = 599 * 256; reader.seek(0, final_position).unwrap();
let final_samples = reader.read_physical_samples(0, 256).unwrap();
assert_eq!(final_samples.len(), 256);
println!(" Data integrity: ✓");
}
cleanup_test_file(filename);
}
#[test]
fn test_concurrent_read_access() {
let filename = "test_concurrent.edf";
{
let mut writer = EdfWriter::create(filename).unwrap();
writer.set_patient_info("CONC001", "X", "X", "Concurrent Test").unwrap();
let signal = create_streaming_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 value = 30.0 * (2.0 * std::f64::consts::PI * 10.0 * t).sin() +
(second as f64).sin() * 10.0; samples.push(value);
}
writer.write_samples(&[samples]).unwrap();
}
writer.finalize().unwrap();
}
{
use std::sync::{Arc, Mutex};
use std::thread;
let results = Arc::new(Mutex::new(Vec::new()));
let mut handles = Vec::new();
println!("Testing concurrent file access with 4 readers...");
for reader_id in 0..4 {
let results_clone = Arc::clone(&results);
let filename_clone = filename.to_string();
let handle = thread::spawn(move || {
let mut reader = EdfReader::open(&filename_clone).unwrap();
let mut reader_results = Vec::new();
let start_second = reader_id * 15; let start_sample = start_second * 256;
reader.seek(0, start_sample).unwrap();
for _ in 0..15 {
let samples = reader.read_physical_samples(0, 256).unwrap();
if !samples.is_empty() {
let mean = samples.iter().sum::<f64>() / samples.len() as f64;
reader_results.push(mean);
}
}
let mut results_lock = results_clone.lock().unwrap();
results_lock.push((reader_id, reader_results));
});
handles.push(handle);
}
for handle in handles {
handle.join().unwrap();
}
let results_lock = results.lock().unwrap();
assert_eq!(results_lock.len(), 4);
for (reader_id, reader_results) in results_lock.iter() {
assert_eq!(reader_results.len(), 15);
println!(" Reader {}: read {} segments, first mean: {:.3}",
reader_id, reader_results.len(), reader_results[0]);
}
println!(" Concurrent access: ✓");
}
cleanup_test_file(filename);
}
#[test]
fn test_streaming_with_annotations() {
let filename = "test_streaming_annotations.edf";
{
let mut writer = EdfWriter::create(filename).unwrap();
writer.set_patient_info("STREAM_ANN001", "X", "X", "Streaming with Annotations").unwrap();
let signal = create_streaming_signal();
writer.add_signal(signal).unwrap();
writer.add_annotation(5.0, None, "Event 1").unwrap();
writer.add_annotation(15.5, Some(2.0), "Long Event").unwrap();
writer.add_annotation(25.2, None, "Event 2").unwrap();
writer.add_annotation(35.7, Some(1.5), "Another Long Event").unwrap();
writer.add_annotation(45.1, None, "Final Event").unwrap();
for second in 0..50 {
let mut samples = Vec::new();
for i in 0..256 {
let t = (second * 256 + i) as f64 / 256.0;
let value = 25.0 * (2.0 * std::f64::consts::PI * 12.0 * t).sin();
samples.push(value);
}
writer.write_samples(&[samples]).unwrap();
}
writer.finalize().unwrap();
}
{
let mut reader = EdfReader::open(filename).unwrap();
let annotations = reader.annotations().to_vec();
println!("Streaming with annotations test:");
println!(" Found {} annotations", annotations.len());
let window_size = 256 * 5; let mut current_position = 0i64;
let mut window_count = 0;
loop {
reader.seek(0, current_position).unwrap();
let samples = reader.read_physical_samples(0, window_size).unwrap();
if samples.is_empty() {
break;
}
let window_start_time = current_position as f64 / 256.0;
let window_end_time = window_start_time + samples.len() as f64 / 256.0;
let window_annotations: Vec<_> = annotations.iter()
.filter(|ann| {
let ann_time = ann.onset as f64 / 10_000_000.0;
ann_time >= window_start_time && ann_time < window_end_time
})
.collect();
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!(" Window {}: {:.1}-{:.1}s, {} samples, range: {:.1} to {:.1}",
window_count, window_start_time, window_end_time,
samples.len(), min_val, max_val);
for annotation in window_annotations {
let ann_time = annotation.onset as f64 / 10_000_000.0;
let relative_time = ann_time - window_start_time;
println!(" Annotation at +{:.1}s: {}", relative_time, annotation.description);
}
current_position += samples.len() as i64;
window_count += 1;
}
println!(" Processed {} windows", window_count);
let expected_count = 5;
assert_eq!(annotations.len(), expected_count);
}
cleanup_test_file(filename);
}