1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
//! # 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
//!
//! ```rust
//! use edfplus::{EdfReader, EdfWriter, SignalParam, Result};
//! # use std::fs;
//!
//! fn main() -> Result<()> {
//! # // Create a test file first
//! # let mut writer = EdfWriter::create("test_data.edf")?;
//! # writer.set_patient_info("P001", "M", "01-JAN-1990", "Test Patient")?;
//! # let signal = SignalParam {
//! # label: "EEG".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".to_string(),
//! # transducer: "AgAgCl".to_string(),
//! # };
//! # writer.add_signal(signal)?;
//! # let samples = vec![10.0; 256];
//! # for _ in 0..10 { writer.write_samples(&[samples.clone()])?; }
//! # writer.finalize()?;
//!
//! // 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());
//! }
//!
//! # // Cleanup
//! # drop(reader);
//! # fs::remove_file("test_data.edf").ok();
//! Ok(())
//! }
//! ```
//!
//! ### Creating an EDF+ file
//!
//! ```rust
//! use edfplus::{EdfWriter, SignalParam, Result};
//! # use std::fs;
//!
//! 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()?;
//!
//! # // Cleanup
//! # fs::remove_file("test_output.edf").ok();
//! 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
//!
//! ```rust
//! // Write 5 seconds of data (5 records)
//! # use edfplus::{EdfWriter, SignalParam, Result};
//! # fn main() -> Result<()> {
//! let mut writer = EdfWriter::create("annotations.edf")?;
//! # let signal = SignalParam {
//! # label: "EEG".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: "".to_string(),
//! # transducer: "".to_string(),
//! # };
//! # writer.add_signal(signal)?;
//!
//! // ✅ 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])?;
//! }
//! # std::fs::remove_file("annotations.edf").ok();
//! # Ok(())
//! # }
//! ```
//!
//! ## 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:
//!
//! ```rust
//! 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
//! ```
// 新增
// For internal doctest support
// Re-export main types for convenience
pub use ;
pub use ;
pub use EdfReader;
pub use EdfWriter; // 新增
// Important constants
pub const EDFLIB_TIME_DIMENSION: i64 = 10_000_000; // 100 nanoseconds unit
pub const EDFLIB_MAXSIGNALS: usize = 4096;
pub const EDFLIB_MAX_ANNOTATION_LEN: usize = 512;
/// Library version
///
/// Returns the current version of the edfplus library.
///
/// # Examples
///
/// ```rust
/// use edfplus;
///
/// let version = edfplus::version();
/// assert!(!version.is_empty());
/// assert!(version.contains('.'));
/// println!("EDF+ library version: {}", version);
/// ```