CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-pyedflib

Python library for reading and writing EDF+/BDF+ files used for storing biomedical signal data

Pending
Overview
Eval results
Files

low-level-interface.mddocs/

Low-Level Interface

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.

Capabilities

Low-Level Reader Class

Direct interface to the underlying C-based EDF reader with minimal Python overhead.

class CyEdfReader:
    def __init__(self, file_name: str, annotations_mode: int = READ_ANNOTATIONS, 
                 check_file_size: int = CHECK_FILE_SIZE):
        """
        Initialize low-level EDF reader.
        
        Parameters:
        - file_name: str, path to EDF/BDF file
        - annotations_mode: int, annotation reading mode
        - check_file_size: int, file size checking mode
        """

    def open(self, file_name: str, annotations_mode: int = READ_ANNOTATIONS, 
             check_file_size: int = CHECK_FILE_SIZE) -> bool:
        """
        Open EDF file for reading.
        
        Parameters:
        - file_name: str, path to file
        - annotations_mode: int, annotation mode
        - check_file_size: int, file size check mode
        
        Returns:
        bool: True if successful
        """

    def _close(self):
        """Close file and release resources."""

    def read_digital_signal(self, signalnum: int, start: int, n: int, sigbuf: np.ndarray):
        """
        Read digital signal data directly into buffer.
        
        Parameters:
        - signalnum: int, signal number
        - start: int, starting sample
        - n: int, number of samples
        - sigbuf: numpy.ndarray, output buffer (pre-allocated)
        """

    def readsignal(self, signalnum: int, start: int, n: int, sigbuf: np.ndarray):
        """
        Read physical signal data directly into buffer.
        
        Parameters:
        - signalnum: int, signal number
        - start: int, starting sample
        - n: int, number of samples
        - sigbuf: numpy.ndarray, output buffer (pre-allocated)
        """

    def read_annotation(self) -> List[List[str]]:
        """
        Read annotations as list of string lists.
        
        Returns:
        List[List[str]]: Annotation data
        """

    def load_datarecord(self, db: np.ndarray, n: int = 1):
        """
        Load data records directly.
        
        Parameters:
        - db: numpy.ndarray, data buffer
        - n: int, number of records to load
        """

CyEdfReader Properties

Direct access to file metadata through properties (read-only).

# File properties
@property
def file_name(self) -> str: ...
@property  
def handle(self) -> int: ...
@property
def datarecords_in_file(self) -> int: ...
@property
def signals_in_file(self) -> int: ...
@property
def file_duration(self) -> int: ...
@property
def filetype(self) -> int: ...
@property
def datarecord_duration(self) -> int: ...
@property
def annotations_in_file(self) -> int: ...

# Patient properties
@property
def patient(self) -> str: ...
@property
def patientcode(self) -> str: ...
@property
def sex(self) -> int: ...
@property
def gender(self) -> int: ...
@property
def birthdate(self) -> str: ...
@property
def patientname(self) -> str: ...
@property
def patient_additional(self) -> str: ...

# Recording properties  
@property
def recording(self) -> str: ...
@property
def startdate_year(self) -> int: ...
@property
def startdate_month(self) -> int: ...
@property
def startdate_day(self) -> int: ...
@property
def starttime_hour(self) -> int: ...
@property
def starttime_minute(self) -> int: ...
@property
def starttime_second(self) -> int: ...
@property
def starttime_subsecond(self) -> int: ...
@property
def admincode(self) -> str: ...
@property
def technician(self) -> str: ...
@property
def equipment(self) -> str: ...
@property
def recording_additional(self) -> str: ...

CyEdfReader Signal Methods

Access signal-specific information using method calls with signal index.

def signal_label(self, signalnum: int) -> str:
    """Get signal label."""

def samples_in_file(self, signalnum: int) -> int:
    """Get total samples in file for signal."""

def samples_in_datarecord(self, signalnum: int) -> int:
    """Get samples per data record for signal."""

def physical_dimension(self, signalnum: int) -> str:
    """Get physical dimension (units) for signal."""

def physical_max(self, signalnum: int) -> float:
    """Get physical maximum value for signal."""

def physical_min(self, signalnum: int) -> float:
    """Get physical minimum value for signal."""

def digital_max(self, signalnum: int) -> int:
    """Get digital maximum value for signal."""

def digital_min(self, signalnum: int) -> int:
    """Get digital minimum value for signal."""

def prefilter(self, signalnum: int) -> str:
    """Get prefilter description for signal."""

def transducer(self, signalnum: int) -> str:
    """Get transducer description for signal."""

def samplefrequency(self, signalnum: int) -> float:
    """Get sampling frequency for signal."""

def smp_per_record(self, signalnum: int) -> int:
    """Get samples per record for signal."""

Usage example:

import pyedflib
import numpy as np

# Open file with low-level reader
reader = pyedflib.CyEdfReader('data.edf')

# Get file info
print(f"File: {reader.file_name}")
print(f"Signals: {reader.signals_in_file}")
print(f"Duration: {reader.file_duration} seconds")
print(f"Patient: {reader.patientname}")

# Read signal data directly into pre-allocated buffer
signal_samples = reader.samples_in_file(0)
buffer = np.zeros(signal_samples, dtype=np.float64)
reader.readsignal(0, 0, signal_samples, buffer)

# Get signal properties
label = reader.signal_label(0)
sfreq = reader.samplefrequency(0)
phys_min = reader.physical_min(0)
phys_max = reader.physical_max(0)

print(f"Signal 0: {label}, {sfreq} Hz, range [{phys_min}, {phys_max}]")

reader._close()

File Operations

Low-level file management functions for writing operations.

def lib_version() -> str:
    """
    Get EDFlib version.
    
    Returns:
    str: Library version string
    """

def open_file_writeonly(path: str, filetype: int, number_of_signals: int) -> int:
    """
    Open file for writing.
    
    Parameters:
    - path: str, file path
    - filetype: int, file type constant
    - number_of_signals: int, number of signal channels
    
    Returns:
    int: File handle (>0 on success, <0 on error)
    """

def close_file(handle: int) -> int:
    """
    Close file by handle.
    
    Parameters:
    - handle: int, file handle from open_file_writeonly
    
    Returns:
    int: 0 on success, error code on failure
    """

def get_number_of_open_files() -> int:
    """
    Get number of currently open files.
    
    Returns:
    int: Number of open files
    """

def get_handle(file_number: int) -> int:
    """
    Get handle for file number.
    
    Parameters:
    - file_number: int, file number
    
    Returns:
    int: File handle
    """

def is_file_used(path: str) -> bool:
    """
    Check if file is currently in use.
    
    Parameters:
    - path: str, file path
    
    Returns:
    bool: True if file is in use
    """

Data Reading Functions

Low-level functions for reading signal data with direct buffer access.

def read_int_samples(handle: int, edfsignal: int, n: int, buf: np.ndarray) -> int:
    """
    Read integer samples directly into buffer.
    
    Parameters:
    - handle: int, file handle
    - edfsignal: int, signal number
    - n: int, number of samples to read
    - buf: numpy.ndarray, pre-allocated buffer
    
    Returns:
    int: Number of samples read
    """

def read_physical_samples(handle: int, edfsignal: int, n: int, buf: np.ndarray) -> int:
    """
    Read physical samples directly into buffer.
    
    Parameters:
    - handle: int, file handle
    - edfsignal: int, signal number
    - n: int, number of samples to read
    - buf: numpy.ndarray, pre-allocated buffer
    
    Returns:
    int: Number of samples read
    """

def get_annotation(handle: int, n: int, edf_annotation: EdfAnnotation) -> int:
    """
    Get annotation by index.
    
    Parameters:
    - handle: int, file handle
    - n: int, annotation index
    - edf_annotation: EdfAnnotation, annotation object to populate
    
    Returns:
    int: 0 on success, error code on failure
    """

Data Writing Functions

Low-level functions for writing signal data with different data types and block modes.

def write_digital_samples(handle: int, buf: np.ndarray) -> int:
    """
    Write digital samples to file.
    
    Parameters:
    - handle: int, file handle
    - buf: numpy.ndarray, digital sample data
    
    Returns:
    int: Number of samples written
    """

def write_digital_short_samples(handle: int, buf: np.ndarray) -> int:
    """
    Write digital short samples to file.
    
    Parameters:
    - handle: int, file handle
    - buf: numpy.ndarray, digital short sample data
    
    Returns:
    int: Number of samples written
    """

def write_physical_samples(handle: int, buf: np.ndarray) -> int:
    """
    Write physical samples to file.
    
    Parameters:
    - handle: int, file handle
    - buf: numpy.ndarray, physical sample data
    
    Returns:
    int: Number of samples written
    """

def blockwrite_digital_samples(handle: int, buf: np.ndarray) -> int:
    """
    Block write digital samples.
    
    Parameters:
    - handle: int, file handle
    - buf: numpy.ndarray, digital sample data
    
    Returns:
    int: Number of samples written
    """

def blockwrite_digital_short_samples(handle: int, buf: np.ndarray) -> int:
    """
    Block write digital short samples.
    
    Parameters:
    - handle: int, file handle
    - buf: numpy.ndarray, digital short sample data
    
    Returns:
    int: Number of samples written
    """

def blockwrite_physical_samples(handle: int, buf: np.ndarray) -> int:
    """
    Block write physical samples.
    
    Parameters:
    - handle: int, file handle
    - buf: numpy.ndarray, physical sample data
    
    Returns:
    int: Number of samples written
    """

File Header Setting Functions

Low-level functions for configuring file-level header information.

def set_patientcode(handle: int, patientcode: Union[str, bytes]) -> int:
    """Set patient identification code."""

def set_patientname(handle: int, name: Union[str, bytes]) -> int:
    """Set patient name."""

def set_patient_additional(handle: int, patient_additional: Union[str, bytes]) -> int:
    """Set additional patient information."""

def set_admincode(handle: int, admincode: Union[str, bytes]) -> int:
    """Set administration code."""

def set_technician(handle: int, technician: Union[str, bytes]) -> int:
    """Set technician name."""

def set_equipment(handle: int, equipment: Union[str, bytes]) -> int:
    """Set equipment description."""

def set_recording_additional(handle: int, recording_additional: Union[str, bytes]) -> int:
    """Set additional recording information."""

def set_birthdate(handle: int, birthdate_year: int, birthdate_month: int, 
                 birthdate_day: int) -> int:
    """
    Set patient birthdate.
    
    Parameters:
    - handle: int, file handle
    - birthdate_year: int, birth year
    - birthdate_month: int, birth month (1-12)
    - birthdate_day: int, birth day (1-31)
    
    Returns:
    int: 0 on success, error code on failure
    """

def set_sex(handle: int, sex: Optional[int]) -> int:
    """
    Set patient sex.
    
    Parameters:
    - handle: int, file handle
    - sex: int or None, sex code (0=female, 1=male)
    
    Returns:
    int: 0 on success, error code on failure
    """

def set_startdatetime(handle: int, startdate_year: int, startdate_month: int, 
                     startdate_day: int, starttime_hour: int, starttime_minute: int, 
                     starttime_second: int) -> int:
    """
    Set recording start date and time.
    
    Parameters:
    - handle: int, file handle
    - startdate_year: int, start year
    - startdate_month: int, start month (1-12)
    - startdate_day: int, start day (1-31)
    - starttime_hour: int, start hour (0-23)
    - starttime_minute: int, start minute (0-59)
    - starttime_second: int, start second (0-59)
    
    Returns:
    int: 0 on success, error code on failure
    """

def set_starttime_subsecond(handle: int, subsecond: int) -> int:
    """
    Set subsecond precision for start time.
    
    Parameters:
    - handle: int, file handle
    - subsecond: int, subsecond value
    
    Returns:
    int: 0 on success, error code on failure
    """

def set_datarecord_duration(handle: int, duration: Union[int, float]) -> int:
    """
    Set data record duration.
    
    Parameters:
    - handle: int, file handle
    - duration: int or float, record duration in seconds
    
    Returns:
    int: 0 on success, error code on failure
    """

def set_number_of_annotation_signals(handle: int, annot_signals: int) -> int:
    """
    Set number of annotation signals.
    
    Parameters:
    - handle: int, file handle
    - annot_signals: int, annotation signal count
    
    Returns:
    int: 0 on success, error code on failure
    """

Signal Header Setting Functions

Low-level functions for configuring signal-specific header information.

def set_label(handle: int, edfsignal: int, label: Union[str, bytes]) -> int:
    """
    Set signal label.
    
    Parameters:
    - handle: int, file handle
    - edfsignal: int, signal number
    - label: str or bytes, signal label
    
    Returns:
    int: 0 on success, error code on failure
    """

def set_physical_dimension(handle: int, edfsignal: int, phys_dim: Union[str, bytes]) -> int:
    """Set physical dimension (units) for signal."""

def set_physical_maximum(handle: int, edfsignal: int, phys_max: float) -> int:
    """Set physical maximum value for signal."""

def set_physical_minimum(handle: int, edfsignal: int, phys_min: float) -> int:
    """Set physical minimum value for signal."""

def set_digital_maximum(handle: int, edfsignal: int, dig_max: int) -> int:
    """Set digital maximum value for signal."""

def set_digital_minimum(handle: int, edfsignal: int, dig_min: int) -> int:
    """Set digital minimum value for signal."""

def set_samples_per_record(handle: int, edfsignal: int, smp_per_record: int) -> int:
    """
    Set samples per record for signal.
    
    Parameters:
    - handle: int, file handle
    - edfsignal: int, signal number
    - smp_per_record: int, samples per data record
    
    Returns:
    int: 0 on success, error code on failure
    """

def set_transducer(handle: int, edfsignal: int, transducer: Union[str, bytes]) -> int:
    """Set transducer description for signal."""

def set_prefilter(handle: int, edfsignal: int, prefilter: Union[str, bytes]) -> int:
    """Set prefilter description for signal."""

Navigation Functions

Low-level functions for positioning within signal data streams.

def tell(handle: int, edfsignal: int) -> int:
    """
    Get current read position in signal.
    
    Parameters:
    - handle: int, file handle
    - edfsignal: int, signal number
    
    Returns:
    int: Current sample position
    """

def seek(handle: int, edfsignal: int, offset: int, whence: int) -> int:
    """
    Seek to position in signal.
    
    Parameters:
    - handle: int, file handle
    - edfsignal: int, signal number
    - offset: int, offset in samples
    - whence: int, seek mode (0=absolute, 1=relative, 2=from end)
    
    Returns:
    int: New position or error code
    """

def rewind(handle: int, edfsignal: int):
    """
    Rewind signal to beginning.
    
    Parameters:
    - handle: int, file handle
    - edfsignal: int, signal number
    """

Annotation Functions

Low-level functions for writing annotations with different text encodings.

def write_annotation_latin1(handle: int, onset: int, duration: int, 
                           description: Union[str, bytes]) -> int:
    """
    Write annotation with Latin-1 encoding.
    
    Parameters:
    - handle: int, file handle
    - onset: int, onset time in 100ns units
    - duration: int, duration in 100ns units
    - description: str or bytes, annotation text
    
    Returns:
    int: 0 on success, error code on failure
    """

def write_annotation_utf8(handle: int, onset: int, duration: int, 
                         description: Union[str, bytes]) -> int:
    """
    Write annotation with UTF-8 encoding.
    
    Parameters:
    - handle: int, file handle  
    - onset: int, onset time in 100ns units
    - duration: int, duration in 100ns units
    - description: str or bytes, annotation text
    
    Returns:
    int: 0 on success, error code on failure
    """

Annotation Class

Low-level annotation data structure.

class EdfAnnotation:
    """
    EDF annotation data structure.
    
    Attributes:
    - onset: int, annotation onset time
    - duration: int, annotation duration  
    - annotation: str, annotation text
    """
    
    onset: int
    duration: int
    annotation: str

Error Handling

Error code dictionaries for interpreting function return values.

# Error code mappings
open_errors: Dict[int, str]    # File opening error codes
write_errors: Dict[int, str]   # Writing error codes

Complete Low-Level Writing Example

import pyedflib
import numpy as np
from datetime import datetime

# Generate sample data
n_channels = 2
n_samples = 1000
sample_rate = 256
data = np.random.randn(n_channels, n_samples) * 100  # microvolts

# Open file for writing
handle = pyedflib.open_file_writeonly('lowlevel.edf', pyedflib.FILETYPE_EDFPLUS, n_channels)
if handle < 0:
    raise RuntimeError(f"Failed to open file: {pyedflib.open_errors.get(handle, 'Unknown error')}")

try:
    # Set file header
    pyedflib.set_patientname(handle, b"Test Patient")
    pyedflib.set_patientcode(handle, b"TP001")
    pyedflib.set_technician(handle, b"Dr. Test")
    pyedflib.set_equipment(handle, b"Test System")
    
    # Set start date/time
    now = datetime.now()
    pyedflib.set_startdatetime(handle, now.year, now.month, now.day, 
                              now.hour, now.minute, now.second)
    
    # Set data record duration (1 second)
    pyedflib.set_datarecord_duration(handle, 1.0)
    
    # Configure each signal
    for ch in range(n_channels):
        pyedflib.set_label(handle, ch, f"CH{ch+1}".encode())
        pyedflib.set_physical_dimension(handle, ch, b"uV")
        pyedflib.set_physical_maximum(handle, ch, 500.0)
        pyedflib.set_physical_minimum(handle, ch, -500.0)
        pyedflib.set_digital_maximum(handle, ch, 32767)
        pyedflib.set_digital_minimum(handle, ch, -32768)
        pyedflib.set_samples_per_record(handle, ch, sample_rate)
        pyedflib.set_transducer(handle, ch, b"Electrode")
        pyedflib.set_prefilter(handle, ch, b"HP:0.1Hz")
    
    # Write data using block write
    data_interleaved = data.T.flatten()  # Interleave channels
    samples_written = pyedflib.blockwrite_physical_samples(handle, data_interleaved)
    print(f"Wrote {samples_written} samples")
    
    # Add annotations
    onset_100ns = int(2.5 * 1e7)  # 2.5 seconds in 100ns units
    duration_100ns = 0  # Point annotation
    pyedflib.write_annotation_utf8(handle, onset_100ns, duration_100ns, b"Stimulus")
    
finally:
    # Always close the file
    result = pyedflib.close_file(handle)
    if result != 0:
        print(f"Warning: Error closing file: {pyedflib.write_errors.get(result, 'Unknown error')}")

print("Low-level EDF file created successfully!")

Install with Tessl CLI

npx tessl i tessl/pypi-pyedflib

docs

edf-reading.md

edf-writing.md

high-level-functions.md

index.md

low-level-interface.md

tile.json