0
# Low-Level Interface
1
2
Direct access to underlying C library functions for maximum performance and fine-grained control over EDF/BDF file operations and data handling. These functions provide the foundation for the high-level classes and enable custom implementations with minimal overhead.
3
4
## Capabilities
5
6
### Low-Level Reader Class
7
8
Direct interface to the underlying C-based EDF reader with minimal Python overhead.
9
10
```python { .api }
11
class CyEdfReader:
12
def __init__(self, file_name: str, annotations_mode: int = READ_ANNOTATIONS,
13
check_file_size: int = CHECK_FILE_SIZE):
14
"""
15
Initialize low-level EDF reader.
16
17
Parameters:
18
- file_name: str, path to EDF/BDF file
19
- annotations_mode: int, annotation reading mode
20
- check_file_size: int, file size checking mode
21
"""
22
23
def open(self, file_name: str, annotations_mode: int = READ_ANNOTATIONS,
24
check_file_size: int = CHECK_FILE_SIZE) -> bool:
25
"""
26
Open EDF file for reading.
27
28
Parameters:
29
- file_name: str, path to file
30
- annotations_mode: int, annotation mode
31
- check_file_size: int, file size check mode
32
33
Returns:
34
bool: True if successful
35
"""
36
37
def _close(self):
38
"""Close file and release resources."""
39
40
def read_digital_signal(self, signalnum: int, start: int, n: int, sigbuf: np.ndarray):
41
"""
42
Read digital signal data directly into buffer.
43
44
Parameters:
45
- signalnum: int, signal number
46
- start: int, starting sample
47
- n: int, number of samples
48
- sigbuf: numpy.ndarray, output buffer (pre-allocated)
49
"""
50
51
def readsignal(self, signalnum: int, start: int, n: int, sigbuf: np.ndarray):
52
"""
53
Read physical signal data directly into buffer.
54
55
Parameters:
56
- signalnum: int, signal number
57
- start: int, starting sample
58
- n: int, number of samples
59
- sigbuf: numpy.ndarray, output buffer (pre-allocated)
60
"""
61
62
def read_annotation(self) -> List[List[str]]:
63
"""
64
Read annotations as list of string lists.
65
66
Returns:
67
List[List[str]]: Annotation data
68
"""
69
70
def load_datarecord(self, db: np.ndarray, n: int = 1):
71
"""
72
Load data records directly.
73
74
Parameters:
75
- db: numpy.ndarray, data buffer
76
- n: int, number of records to load
77
"""
78
```
79
80
### CyEdfReader Properties
81
82
Direct access to file metadata through properties (read-only).
83
84
```python { .api }
85
# File properties
86
@property
87
def file_name(self) -> str: ...
88
@property
89
def handle(self) -> int: ...
90
@property
91
def datarecords_in_file(self) -> int: ...
92
@property
93
def signals_in_file(self) -> int: ...
94
@property
95
def file_duration(self) -> int: ...
96
@property
97
def filetype(self) -> int: ...
98
@property
99
def datarecord_duration(self) -> int: ...
100
@property
101
def annotations_in_file(self) -> int: ...
102
103
# Patient properties
104
@property
105
def patient(self) -> str: ...
106
@property
107
def patientcode(self) -> str: ...
108
@property
109
def sex(self) -> int: ...
110
@property
111
def gender(self) -> int: ...
112
@property
113
def birthdate(self) -> str: ...
114
@property
115
def patientname(self) -> str: ...
116
@property
117
def patient_additional(self) -> str: ...
118
119
# Recording properties
120
@property
121
def recording(self) -> str: ...
122
@property
123
def startdate_year(self) -> int: ...
124
@property
125
def startdate_month(self) -> int: ...
126
@property
127
def startdate_day(self) -> int: ...
128
@property
129
def starttime_hour(self) -> int: ...
130
@property
131
def starttime_minute(self) -> int: ...
132
@property
133
def starttime_second(self) -> int: ...
134
@property
135
def starttime_subsecond(self) -> int: ...
136
@property
137
def admincode(self) -> str: ...
138
@property
139
def technician(self) -> str: ...
140
@property
141
def equipment(self) -> str: ...
142
@property
143
def recording_additional(self) -> str: ...
144
```
145
146
### CyEdfReader Signal Methods
147
148
Access signal-specific information using method calls with signal index.
149
150
```python { .api }
151
def signal_label(self, signalnum: int) -> str:
152
"""Get signal label."""
153
154
def samples_in_file(self, signalnum: int) -> int:
155
"""Get total samples in file for signal."""
156
157
def samples_in_datarecord(self, signalnum: int) -> int:
158
"""Get samples per data record for signal."""
159
160
def physical_dimension(self, signalnum: int) -> str:
161
"""Get physical dimension (units) for signal."""
162
163
def physical_max(self, signalnum: int) -> float:
164
"""Get physical maximum value for signal."""
165
166
def physical_min(self, signalnum: int) -> float:
167
"""Get physical minimum value for signal."""
168
169
def digital_max(self, signalnum: int) -> int:
170
"""Get digital maximum value for signal."""
171
172
def digital_min(self, signalnum: int) -> int:
173
"""Get digital minimum value for signal."""
174
175
def prefilter(self, signalnum: int) -> str:
176
"""Get prefilter description for signal."""
177
178
def transducer(self, signalnum: int) -> str:
179
"""Get transducer description for signal."""
180
181
def samplefrequency(self, signalnum: int) -> float:
182
"""Get sampling frequency for signal."""
183
184
def smp_per_record(self, signalnum: int) -> int:
185
"""Get samples per record for signal."""
186
```
187
188
Usage example:
189
190
```python
191
import pyedflib
192
import numpy as np
193
194
# Open file with low-level reader
195
reader = pyedflib.CyEdfReader('data.edf')
196
197
# Get file info
198
print(f"File: {reader.file_name}")
199
print(f"Signals: {reader.signals_in_file}")
200
print(f"Duration: {reader.file_duration} seconds")
201
print(f"Patient: {reader.patientname}")
202
203
# Read signal data directly into pre-allocated buffer
204
signal_samples = reader.samples_in_file(0)
205
buffer = np.zeros(signal_samples, dtype=np.float64)
206
reader.readsignal(0, 0, signal_samples, buffer)
207
208
# Get signal properties
209
label = reader.signal_label(0)
210
sfreq = reader.samplefrequency(0)
211
phys_min = reader.physical_min(0)
212
phys_max = reader.physical_max(0)
213
214
print(f"Signal 0: {label}, {sfreq} Hz, range [{phys_min}, {phys_max}]")
215
216
reader._close()
217
```
218
219
### File Operations
220
221
Low-level file management functions for writing operations.
222
223
```python { .api }
224
def lib_version() -> str:
225
"""
226
Get EDFlib version.
227
228
Returns:
229
str: Library version string
230
"""
231
232
def open_file_writeonly(path: str, filetype: int, number_of_signals: int) -> int:
233
"""
234
Open file for writing.
235
236
Parameters:
237
- path: str, file path
238
- filetype: int, file type constant
239
- number_of_signals: int, number of signal channels
240
241
Returns:
242
int: File handle (>0 on success, <0 on error)
243
"""
244
245
def close_file(handle: int) -> int:
246
"""
247
Close file by handle.
248
249
Parameters:
250
- handle: int, file handle from open_file_writeonly
251
252
Returns:
253
int: 0 on success, error code on failure
254
"""
255
256
def get_number_of_open_files() -> int:
257
"""
258
Get number of currently open files.
259
260
Returns:
261
int: Number of open files
262
"""
263
264
def get_handle(file_number: int) -> int:
265
"""
266
Get handle for file number.
267
268
Parameters:
269
- file_number: int, file number
270
271
Returns:
272
int: File handle
273
"""
274
275
def is_file_used(path: str) -> bool:
276
"""
277
Check if file is currently in use.
278
279
Parameters:
280
- path: str, file path
281
282
Returns:
283
bool: True if file is in use
284
"""
285
```
286
287
### Data Reading Functions
288
289
Low-level functions for reading signal data with direct buffer access.
290
291
```python { .api }
292
def read_int_samples(handle: int, edfsignal: int, n: int, buf: np.ndarray) -> int:
293
"""
294
Read integer samples directly into buffer.
295
296
Parameters:
297
- handle: int, file handle
298
- edfsignal: int, signal number
299
- n: int, number of samples to read
300
- buf: numpy.ndarray, pre-allocated buffer
301
302
Returns:
303
int: Number of samples read
304
"""
305
306
def read_physical_samples(handle: int, edfsignal: int, n: int, buf: np.ndarray) -> int:
307
"""
308
Read physical samples directly into buffer.
309
310
Parameters:
311
- handle: int, file handle
312
- edfsignal: int, signal number
313
- n: int, number of samples to read
314
- buf: numpy.ndarray, pre-allocated buffer
315
316
Returns:
317
int: Number of samples read
318
"""
319
320
def get_annotation(handle: int, n: int, edf_annotation: EdfAnnotation) -> int:
321
"""
322
Get annotation by index.
323
324
Parameters:
325
- handle: int, file handle
326
- n: int, annotation index
327
- edf_annotation: EdfAnnotation, annotation object to populate
328
329
Returns:
330
int: 0 on success, error code on failure
331
"""
332
```
333
334
### Data Writing Functions
335
336
Low-level functions for writing signal data with different data types and block modes.
337
338
```python { .api }
339
def write_digital_samples(handle: int, buf: np.ndarray) -> int:
340
"""
341
Write digital samples to file.
342
343
Parameters:
344
- handle: int, file handle
345
- buf: numpy.ndarray, digital sample data
346
347
Returns:
348
int: Number of samples written
349
"""
350
351
def write_digital_short_samples(handle: int, buf: np.ndarray) -> int:
352
"""
353
Write digital short samples to file.
354
355
Parameters:
356
- handle: int, file handle
357
- buf: numpy.ndarray, digital short sample data
358
359
Returns:
360
int: Number of samples written
361
"""
362
363
def write_physical_samples(handle: int, buf: np.ndarray) -> int:
364
"""
365
Write physical samples to file.
366
367
Parameters:
368
- handle: int, file handle
369
- buf: numpy.ndarray, physical sample data
370
371
Returns:
372
int: Number of samples written
373
"""
374
375
def blockwrite_digital_samples(handle: int, buf: np.ndarray) -> int:
376
"""
377
Block write digital samples.
378
379
Parameters:
380
- handle: int, file handle
381
- buf: numpy.ndarray, digital sample data
382
383
Returns:
384
int: Number of samples written
385
"""
386
387
def blockwrite_digital_short_samples(handle: int, buf: np.ndarray) -> int:
388
"""
389
Block write digital short samples.
390
391
Parameters:
392
- handle: int, file handle
393
- buf: numpy.ndarray, digital short sample data
394
395
Returns:
396
int: Number of samples written
397
"""
398
399
def blockwrite_physical_samples(handle: int, buf: np.ndarray) -> int:
400
"""
401
Block write physical samples.
402
403
Parameters:
404
- handle: int, file handle
405
- buf: numpy.ndarray, physical sample data
406
407
Returns:
408
int: Number of samples written
409
"""
410
```
411
412
### File Header Setting Functions
413
414
Low-level functions for configuring file-level header information.
415
416
```python { .api }
417
def set_patientcode(handle: int, patientcode: Union[str, bytes]) -> int:
418
"""Set patient identification code."""
419
420
def set_patientname(handle: int, name: Union[str, bytes]) -> int:
421
"""Set patient name."""
422
423
def set_patient_additional(handle: int, patient_additional: Union[str, bytes]) -> int:
424
"""Set additional patient information."""
425
426
def set_admincode(handle: int, admincode: Union[str, bytes]) -> int:
427
"""Set administration code."""
428
429
def set_technician(handle: int, technician: Union[str, bytes]) -> int:
430
"""Set technician name."""
431
432
def set_equipment(handle: int, equipment: Union[str, bytes]) -> int:
433
"""Set equipment description."""
434
435
def set_recording_additional(handle: int, recording_additional: Union[str, bytes]) -> int:
436
"""Set additional recording information."""
437
438
def set_birthdate(handle: int, birthdate_year: int, birthdate_month: int,
439
birthdate_day: int) -> int:
440
"""
441
Set patient birthdate.
442
443
Parameters:
444
- handle: int, file handle
445
- birthdate_year: int, birth year
446
- birthdate_month: int, birth month (1-12)
447
- birthdate_day: int, birth day (1-31)
448
449
Returns:
450
int: 0 on success, error code on failure
451
"""
452
453
def set_sex(handle: int, sex: Optional[int]) -> int:
454
"""
455
Set patient sex.
456
457
Parameters:
458
- handle: int, file handle
459
- sex: int or None, sex code (0=female, 1=male)
460
461
Returns:
462
int: 0 on success, error code on failure
463
"""
464
465
def set_startdatetime(handle: int, startdate_year: int, startdate_month: int,
466
startdate_day: int, starttime_hour: int, starttime_minute: int,
467
starttime_second: int) -> int:
468
"""
469
Set recording start date and time.
470
471
Parameters:
472
- handle: int, file handle
473
- startdate_year: int, start year
474
- startdate_month: int, start month (1-12)
475
- startdate_day: int, start day (1-31)
476
- starttime_hour: int, start hour (0-23)
477
- starttime_minute: int, start minute (0-59)
478
- starttime_second: int, start second (0-59)
479
480
Returns:
481
int: 0 on success, error code on failure
482
"""
483
484
def set_starttime_subsecond(handle: int, subsecond: int) -> int:
485
"""
486
Set subsecond precision for start time.
487
488
Parameters:
489
- handle: int, file handle
490
- subsecond: int, subsecond value
491
492
Returns:
493
int: 0 on success, error code on failure
494
"""
495
496
def set_datarecord_duration(handle: int, duration: Union[int, float]) -> int:
497
"""
498
Set data record duration.
499
500
Parameters:
501
- handle: int, file handle
502
- duration: int or float, record duration in seconds
503
504
Returns:
505
int: 0 on success, error code on failure
506
"""
507
508
def set_number_of_annotation_signals(handle: int, annot_signals: int) -> int:
509
"""
510
Set number of annotation signals.
511
512
Parameters:
513
- handle: int, file handle
514
- annot_signals: int, annotation signal count
515
516
Returns:
517
int: 0 on success, error code on failure
518
"""
519
```
520
521
### Signal Header Setting Functions
522
523
Low-level functions for configuring signal-specific header information.
524
525
```python { .api }
526
def set_label(handle: int, edfsignal: int, label: Union[str, bytes]) -> int:
527
"""
528
Set signal label.
529
530
Parameters:
531
- handle: int, file handle
532
- edfsignal: int, signal number
533
- label: str or bytes, signal label
534
535
Returns:
536
int: 0 on success, error code on failure
537
"""
538
539
def set_physical_dimension(handle: int, edfsignal: int, phys_dim: Union[str, bytes]) -> int:
540
"""Set physical dimension (units) for signal."""
541
542
def set_physical_maximum(handle: int, edfsignal: int, phys_max: float) -> int:
543
"""Set physical maximum value for signal."""
544
545
def set_physical_minimum(handle: int, edfsignal: int, phys_min: float) -> int:
546
"""Set physical minimum value for signal."""
547
548
def set_digital_maximum(handle: int, edfsignal: int, dig_max: int) -> int:
549
"""Set digital maximum value for signal."""
550
551
def set_digital_minimum(handle: int, edfsignal: int, dig_min: int) -> int:
552
"""Set digital minimum value for signal."""
553
554
def set_samples_per_record(handle: int, edfsignal: int, smp_per_record: int) -> int:
555
"""
556
Set samples per record for signal.
557
558
Parameters:
559
- handle: int, file handle
560
- edfsignal: int, signal number
561
- smp_per_record: int, samples per data record
562
563
Returns:
564
int: 0 on success, error code on failure
565
"""
566
567
def set_transducer(handle: int, edfsignal: int, transducer: Union[str, bytes]) -> int:
568
"""Set transducer description for signal."""
569
570
def set_prefilter(handle: int, edfsignal: int, prefilter: Union[str, bytes]) -> int:
571
"""Set prefilter description for signal."""
572
```
573
574
### Navigation Functions
575
576
Low-level functions for positioning within signal data streams.
577
578
```python { .api }
579
def tell(handle: int, edfsignal: int) -> int:
580
"""
581
Get current read position in signal.
582
583
Parameters:
584
- handle: int, file handle
585
- edfsignal: int, signal number
586
587
Returns:
588
int: Current sample position
589
"""
590
591
def seek(handle: int, edfsignal: int, offset: int, whence: int) -> int:
592
"""
593
Seek to position in signal.
594
595
Parameters:
596
- handle: int, file handle
597
- edfsignal: int, signal number
598
- offset: int, offset in samples
599
- whence: int, seek mode (0=absolute, 1=relative, 2=from end)
600
601
Returns:
602
int: New position or error code
603
"""
604
605
def rewind(handle: int, edfsignal: int):
606
"""
607
Rewind signal to beginning.
608
609
Parameters:
610
- handle: int, file handle
611
- edfsignal: int, signal number
612
"""
613
```
614
615
### Annotation Functions
616
617
Low-level functions for writing annotations with different text encodings.
618
619
```python { .api }
620
def write_annotation_latin1(handle: int, onset: int, duration: int,
621
description: Union[str, bytes]) -> int:
622
"""
623
Write annotation with Latin-1 encoding.
624
625
Parameters:
626
- handle: int, file handle
627
- onset: int, onset time in 100ns units
628
- duration: int, duration in 100ns units
629
- description: str or bytes, annotation text
630
631
Returns:
632
int: 0 on success, error code on failure
633
"""
634
635
def write_annotation_utf8(handle: int, onset: int, duration: int,
636
description: Union[str, bytes]) -> int:
637
"""
638
Write annotation with UTF-8 encoding.
639
640
Parameters:
641
- handle: int, file handle
642
- onset: int, onset time in 100ns units
643
- duration: int, duration in 100ns units
644
- description: str or bytes, annotation text
645
646
Returns:
647
int: 0 on success, error code on failure
648
"""
649
```
650
651
### Annotation Class
652
653
Low-level annotation data structure.
654
655
```python { .api }
656
class EdfAnnotation:
657
"""
658
EDF annotation data structure.
659
660
Attributes:
661
- onset: int, annotation onset time
662
- duration: int, annotation duration
663
- annotation: str, annotation text
664
"""
665
666
onset: int
667
duration: int
668
annotation: str
669
```
670
671
### Error Handling
672
673
Error code dictionaries for interpreting function return values.
674
675
```python { .api }
676
# Error code mappings
677
open_errors: Dict[int, str] # File opening error codes
678
write_errors: Dict[int, str] # Writing error codes
679
```
680
681
## Complete Low-Level Writing Example
682
683
```python
684
import pyedflib
685
import numpy as np
686
from datetime import datetime
687
688
# Generate sample data
689
n_channels = 2
690
n_samples = 1000
691
sample_rate = 256
692
data = np.random.randn(n_channels, n_samples) * 100 # microvolts
693
694
# Open file for writing
695
handle = pyedflib.open_file_writeonly('lowlevel.edf', pyedflib.FILETYPE_EDFPLUS, n_channels)
696
if handle < 0:
697
raise RuntimeError(f"Failed to open file: {pyedflib.open_errors.get(handle, 'Unknown error')}")
698
699
try:
700
# Set file header
701
pyedflib.set_patientname(handle, b"Test Patient")
702
pyedflib.set_patientcode(handle, b"TP001")
703
pyedflib.set_technician(handle, b"Dr. Test")
704
pyedflib.set_equipment(handle, b"Test System")
705
706
# Set start date/time
707
now = datetime.now()
708
pyedflib.set_startdatetime(handle, now.year, now.month, now.day,
709
now.hour, now.minute, now.second)
710
711
# Set data record duration (1 second)
712
pyedflib.set_datarecord_duration(handle, 1.0)
713
714
# Configure each signal
715
for ch in range(n_channels):
716
pyedflib.set_label(handle, ch, f"CH{ch+1}".encode())
717
pyedflib.set_physical_dimension(handle, ch, b"uV")
718
pyedflib.set_physical_maximum(handle, ch, 500.0)
719
pyedflib.set_physical_minimum(handle, ch, -500.0)
720
pyedflib.set_digital_maximum(handle, ch, 32767)
721
pyedflib.set_digital_minimum(handle, ch, -32768)
722
pyedflib.set_samples_per_record(handle, ch, sample_rate)
723
pyedflib.set_transducer(handle, ch, b"Electrode")
724
pyedflib.set_prefilter(handle, ch, b"HP:0.1Hz")
725
726
# Write data using block write
727
data_interleaved = data.T.flatten() # Interleave channels
728
samples_written = pyedflib.blockwrite_physical_samples(handle, data_interleaved)
729
print(f"Wrote {samples_written} samples")
730
731
# Add annotations
732
onset_100ns = int(2.5 * 1e7) # 2.5 seconds in 100ns units
733
duration_100ns = 0 # Point annotation
734
pyedflib.write_annotation_utf8(handle, onset_100ns, duration_100ns, b"Stimulus")
735
736
finally:
737
# Always close the file
738
result = pyedflib.close_file(handle)
739
if result != 0:
740
print(f"Warning: Error closing file: {pyedflib.write_errors.get(result, 'Unknown error')}")
741
742
print("Low-level EDF file created successfully!")
743
```