Skip to content

API Reference

read_edf(source, *, physical=True, dtype=None, tzinfo=None, eager_load_samples=False)

Open and parse an EDF/EDF+ file, returning an :class:EDFFile.

The returned object contains parsed header metadata and wired :class:Signal instances. Sample data is loaded lazily on first access to signal.samples or signal.load(), unless eager_load_samples is True.

PARAMETER DESCRIPTION
source

Path to the EDF file (as str or :class:pathlib.Path), or an open binary file-like object exposing .read() and .seek().

TYPE: str | Path | BinaryIO

physical

When True (default), samples are scaled to physical values. When False, raw int16 digital values are returned.

TYPE: bool DEFAULT: True

dtype

Optional NumPy dtype to cast scaled physical values to (e.g. numpy.float32). Only valid when physical=True.

TYPE: dtype | type | None DEFAULT: None

tzinfo

Optional timezone info to attach to the parsed start time and start datetime. None (default) produces timezone-naive objects.

TYPE: tzinfo | None DEFAULT: None

eager_load_samples

When True, load all signal samples immediately rather than deferring to first access.

TYPE: bool DEFAULT: False

RETURNS DESCRIPTION
EDFFile

A fully-constructed :class:EDFFile with parsed headers, signal instances,

EDFFile

and (for EDF+ files) annotations.

RAISES DESCRIPTION
TypeError

If source is not a str, pathlib.Path, or binary file-like object; or if the file-like object lacks .read() or .seek(); or if tzinfo is not a datetime.tzinfo instance.

FileNotFoundError

If source is a path that does not exist.

PermissionError

If source is a path without read permission.

ValueError

If dtype is provided with physical=False, or if the file header is malformed or truncated.

Source code in src/edfplus/reader.py
def read_edf(
    source: str | pathlib.Path | BinaryIO,
    *,
    physical: bool = True,
    dtype: numpy.dtype | type | None = None,
    tzinfo: datetime.tzinfo | None = None,
    eager_load_samples: bool = False,
) -> EDFFile:
    """Open and parse an EDF/EDF+ file, returning an :class:`EDFFile`.

    The returned object contains parsed header metadata and wired
    :class:`Signal` instances.  Sample data is loaded lazily on first access
    to ``signal.samples`` or ``signal.load()``, unless ``eager_load_samples``
    is ``True``.

    Args:
        source: Path to the EDF file (as ``str`` or :class:`pathlib.Path`), or an
            open binary file-like object exposing ``.read()`` and ``.seek()``.
        physical: When ``True`` (default), samples are scaled to physical values.
            When ``False``, raw ``int16`` digital values are returned.
        dtype: Optional NumPy dtype to cast scaled physical values to (e.g.
            ``numpy.float32``).  Only valid when ``physical=True``.
        tzinfo: Optional timezone info to attach to the parsed start time and
            start datetime.  ``None`` (default) produces timezone-naive objects.
        eager_load_samples: When ``True``, load all signal samples immediately
            rather than deferring to first access.

    Returns:
        A fully-constructed :class:`EDFFile` with parsed headers, signal instances,
        and (for EDF+ files) annotations.

    Raises:
        TypeError: If ``source`` is not a ``str``, ``pathlib.Path``, or binary
            file-like object; or if the file-like object lacks ``.read()`` or
            ``.seek()``; or if ``tzinfo`` is not a ``datetime.tzinfo`` instance.
        FileNotFoundError: If ``source`` is a path that does not exist.
        PermissionError: If ``source`` is a path without read permission.
        ValueError: If ``dtype`` is provided with ``physical=False``, or if the
            file header is malformed or truncated.
    """
    # ------------------------------------------------------------------
    # Validate tzinfo
    # ------------------------------------------------------------------
    if tzinfo is not None and not isinstance(tzinfo, datetime.tzinfo):
        raise TypeError("tzinfo", type(tzinfo))

    # ------------------------------------------------------------------
    # Validate dtype + physical combination
    # ------------------------------------------------------------------
    if dtype is not None and not physical:
        raise ValueError("dtype cannot be combined with physical=False")

    # ------------------------------------------------------------------
    # Resolve dtype to numpy.dtype
    # ------------------------------------------------------------------
    resolved_dtype: numpy.dtype | None = None
    if dtype is not None:
        resolved_dtype = numpy.dtype(dtype) if not isinstance(dtype, numpy.dtype) else dtype

    # ------------------------------------------------------------------
    # Open or validate source
    # ------------------------------------------------------------------
    f: BinaryIO
    if isinstance(source, (str, pathlib.Path)):
        path = pathlib.Path(source)
        try:
            f = open(path, "rb")  # noqa: SIM115
        except FileNotFoundError:
            raise FileNotFoundError(str(path))
        except PermissionError:
            raise PermissionError(str(path))
    else:
        # Validate BinaryIO-like object
        if not hasattr(source, "read"):
            raise TypeError(".read() method is missing")
        if not hasattr(source, "seek"):
            raise TypeError(".seek() method is missing")
        f = source

    # ------------------------------------------------------------------
    # Determine file size
    # ------------------------------------------------------------------
    f.seek(0, 2)
    file_size = f.tell()
    f.seek(0)

    # ------------------------------------------------------------------
    # Read and parse global header (256 bytes)
    # ------------------------------------------------------------------
    global_data = f.read(256)
    if len(global_data) < 256:
        f.close()
        raise ValueError("truncated header")

    header = parse_global_header(global_data, file_size, tzinfo)

    # ------------------------------------------------------------------
    # Validate header_bytes == 256 + ns * 256
    # ------------------------------------------------------------------
    ns = header.num_signals
    expected_header_bytes = 256 + ns * 256
    if header.header_bytes != expected_header_bytes:
        f.close()
        raise ValueError("malformed header")

    # ------------------------------------------------------------------
    # Read and parse per-signal headers (ns * 256 bytes)
    # ------------------------------------------------------------------
    signal_data = f.read(ns * 256)
    if len(signal_data) < ns * 256:
        f.close()
        raise ValueError("truncated header")

    all_signals = parse_signal_headers(signal_data, ns, header.record_duration)

    # ------------------------------------------------------------------
    # Update EDFHeader with raw signal bytes (frozen dataclass workaround)
    # ------------------------------------------------------------------
    object.__setattr__(header, "_raw_signals", signal_data)

    # ------------------------------------------------------------------
    # Infer num_records if -1
    # ------------------------------------------------------------------
    num_records = header.num_records
    if num_records == -1:
        bytes_per_record = sum(sig.samples_per_record * 2 for sig in all_signals)
        if bytes_per_record > 0:
            num_records = (file_size - header.header_bytes) // bytes_per_record
        else:
            num_records = 0
        object.__setattr__(header, "num_records", num_records)

    # ------------------------------------------------------------------
    # Compute byte layout for per-signal reading
    # ------------------------------------------------------------------
    bytes_per_signal = [sig.samples_per_record * 2 for sig in all_signals]
    total_bytes_per_record = sum(bytes_per_signal)

    for i, sig in enumerate(all_signals):
        sig._byte_offset_in_record = sum(bytes_per_signal[:i])
        sig._bytes_per_record = total_bytes_per_record
        sig._edf_header = header
        sig._physical = physical
        sig._dtype = resolved_dtype

    # ------------------------------------------------------------------
    # Filter non-annotation signals
    # ------------------------------------------------------------------
    signals: list[Signal] = [sig for sig in all_signals if not sig.is_annotation]

    # ------------------------------------------------------------------
    # For EDF+ variants: read all annotation data and parse TALs
    # ------------------------------------------------------------------
    annotations: list[Annotation] = []
    record_onsets: list[float] = []

    if header.variant in ("EDF+C", "EDF+D"):
        # Find the annotation signal index and its byte position within a record
        annotation_idx: int | None = None
        for i, sig in enumerate(all_signals):
            if sig.is_annotation:
                annotation_idx = i
                break

        if annotation_idx is not None:
            ann_sig = all_signals[annotation_idx]
            ann_bytes_per_record = ann_sig.samples_per_record * 2

            # Offset of annotation signal within a single record
            ann_offset_in_record = sum(bytes_per_signal[:annotation_idx])

            # Read annotation bytes from every record
            for record_idx in range(num_records):
                record_start = header.header_bytes + record_idx * total_bytes_per_record
                f.seek(record_start + ann_offset_in_record)
                ann_data = f.read(ann_bytes_per_record)

                timekeeping_onset, record_annotations = parse_tals(ann_data, record_idx)
                annotations.extend(record_annotations)

                # Store record onsets for EDF+D gap handling
                if timekeeping_onset is not None:
                    record_onsets.append(timekeeping_onset)

            # Sort annotations by onset
            annotations.sort(key=lambda a: a.onset)

    # ------------------------------------------------------------------
    # Construct EDFFile
    # ------------------------------------------------------------------
    edf_file = EDFFile(
        header=header,
        signals=signals,
        annotations=annotations,
        _f=f,
        _all_signals=all_signals,
        _record_onsets=record_onsets,
    )

    # ------------------------------------------------------------------
    # Wire weakrefs on each non-annotation signal
    # ------------------------------------------------------------------
    edf_ref = weakref.ref(edf_file)
    for sig in signals:
        sig._edf_file = edf_ref

    # ------------------------------------------------------------------
    # Eager load if requested
    # ------------------------------------------------------------------
    if eager_load_samples:
        for sig in signals:
            sig._ensure_loaded()

    return edf_file

EDFFile(header, signals, annotations, _f, _all_signals=None, _record_onsets=None)

Top-level container returned by read_edf().

Holds the parsed header, all wired Signal instances (with lazy loading), and an Annotation list for EDF+ files. The underlying file handle is kept open for lazy signal loading. Always use the context-manager protocol or call close() when done.

Signals are populated immediately upon construction. Sample data is loaded lazily on first access to signal.samples or signal.load(), unless eager_load_samples=True was passed to read_edf().

PARAMETER DESCRIPTION
header

Parsed EDFHeader.

TYPE: EDFHeader

signals

List of non-annotation Signal instances (wired with weakrefs).

TYPE: list[Signal]

annotations

List of Annotation objects from the EDF Annotations channel; empty for plain EDF files.

TYPE: list[Annotation]

_f

Open binary file handle for lazy data record reading.

TYPE: BinaryIO

_all_signals

Full list of all Signal objects (including annotation channels) in file order, needed for byte offset calculation.

TYPE: list[Signal] | None DEFAULT: None

_record_onsets

Per-record time-keeping onsets for EDF+D files.

TYPE: list[float] | None DEFAULT: None

Source code in src/edfplus/_models.py
def __init__(
    self,
    header: EDFHeader,
    signals: list[Signal],
    annotations: list[Annotation],
    _f: BinaryIO,
    _all_signals: list[Signal] | None = None,
    _record_onsets: list[float] | None = None,
) -> None:
    self._header = header
    self._signals = signals
    self._annotations = annotations
    self._f = _f
    self._all_signals = _all_signals or []
    self._record_onsets = _record_onsets or []

header property

Parsed global EDFHeader.

signals property

Non-annotation Signal instances in file order (lazy-loading).

annotations property

Parsed Annotation list sorted by onset; empty for plain EDF files.

close()

Close the underlying file handle.

Safe to call multiple times.

Source code in src/edfplus/_models.py
def close(self) -> None:
    """Close the underlying file handle.

    Safe to call multiple times.
    """
    if self._f and not self._f.closed:
        self._f.close()

EDFHeader(version, local_patient_id, local_recording_id, num_records, record_duration, num_signals, header_bytes, variant, start_date, start_time, start_datetime, patient_code, patient_sex, patient_birthdate, patient_name, recording_startdate, recording_admin_code, recording_technician, recording_equipment, _raw_global, _raw_signals) dataclass

Parsed global header from a 256-byte EDF/EDF+ header block.

ATTRIBUTE DESCRIPTION
version

Version field, always "0" for a valid EDF file.

TYPE: str

local_patient_id

Raw 80-byte local patient identification field, stripped.

TYPE: str

local_recording_id

Raw 80-byte local recording identification field, stripped.

TYPE: str

num_records

Number of data records in the file (inferred from file size when the on-disk value is -1).

TYPE: int

record_duration

Duration of each data record in seconds.

TYPE: float

num_signals

Total number of signals (ns), including any EDF Annotations signal.

TYPE: int

header_bytes

Total header size in bytes (256 + ns * 256).

TYPE: int

variant

EDF variant detected from the reserved field. One of "EDF", "EDF+C" (continuous), or "EDF+D" (discontinuous).

TYPE: Literal['EDF', 'EDF+C', 'EDF+D']

start_date

Recording start date. Year is interpreted with the two-digit rule: yy >= 85 → 19xx, yy < 85 → 20xx.

TYPE: date

start_time

Recording start time. Timezone-naive unless a tzinfo was passed to read_edf().

TYPE: time

start_datetime

Combined start_date + start_time. Timezone-naive unless a tzinfo was passed to read_edf().

TYPE: datetime

patient_code

Hospital/patient code from the EDF+ patient identification subfield; None for plain EDF or when the subfield is "X".

TYPE: str | None

patient_sex

Sex subfield from the EDF+ patient identification; None for plain EDF or "X".

TYPE: str | None

patient_birthdate

Parsed birthdate from the EDF+ patient identification subfield; None for plain EDF or "X".

TYPE: date | None

patient_name

Patient name from the EDF+ patient identification subfield; None for plain EDF or "X".

TYPE: str | None

recording_startdate

Recording start date parsed from the EDF+ recording identification subfield; None for plain EDF or "X".

TYPE: date | None

recording_admin_code

Investigation/admin code from the EDF+ recording identification subfield; None for plain EDF or "X".

TYPE: str | None

recording_technician

Technician/investigator code from the EDF+ recording identification subfield; None for plain EDF or "X".

TYPE: str | None

recording_equipment

Equipment code from the EDF+ recording identification subfield; None for plain EDF or "X".

TYPE: str | None

_raw_global

Original 256 bytes of the global header, preserved for round-trip fidelity.

TYPE: bytes

_raw_signals

Original ns * 256 bytes of the per-signal header block, preserved for round-trip fidelity.

TYPE: bytes

Signal(label, transducer_type, physical_dimension, physical_min, physical_max, digital_min, digital_max, prefiltering, samples_per_record, sample_rate, is_annotation, index, _edf_file=None, _edf_header=None, _byte_offset_in_record=0, _bytes_per_record=0, _physical=True, _dtype=None) dataclass

A single EDF/EDF+ signal channel with header metadata and lazy sample loading.

All per-signal header fields are stored directly as dataclass fields. Sample data is loaded lazily: accessing the samples property auto-loads from disk on first access (if a parent EDFFile weakref is wired), or raises RuntimeError if the signal is not wired to a file.

ATTRIBUTE DESCRIPTION
label

Signal label, stripped of leading/trailing whitespace.

TYPE: str

transducer_type

Transducer type string.

TYPE: str

physical_dimension

Physical dimension (unit) string.

TYPE: str

physical_min

Physical minimum calibration value.

TYPE: float

physical_max

Physical maximum calibration value.

TYPE: float

digital_min

Minimum raw digital (int16) value.

TYPE: int

digital_max

Maximum raw digital (int16) value.

TYPE: int

prefiltering

Pre-filtering description string.

TYPE: str

samples_per_record

Number of samples in each data record for this signal.

TYPE: int

sample_rate

Sample rate in Hz (samples_per_record / record_duration).

TYPE: float

is_annotation

True when this signal is the EDF Annotations channel.

TYPE: bool

index

0-based position of this signal in the file's signal list.

TYPE: int

samples property

Loaded sample array (physical float64 by default, or raw int16 if unscaled).

Auto-loads from disk on first access if wired to an EDFFile.

load(start=None, stop=None, *, onset=None, duration=None)

Load and return a slice of the signal's samples.

Supports flexible cropping by sample index, seconds, datetime, or timedelta.

PARAMETER DESCRIPTION
start

Start position. int=sample index, float=seconds, datetime=absolute, timedelta=offset from recording start.

TYPE: int | float | datetime | timedelta | None DEFAULT: None

stop

Stop position (same type interpretation as start).

TYPE: int | float | datetime | timedelta | None DEFAULT: None

onset

Alias for start. Cannot be used together with start.

TYPE: int | float | datetime | timedelta | None DEFAULT: None

duration

Duration from start. float=seconds, timedelta=offset. Cannot be used together with stop.

TYPE: float | timedelta | None DEFAULT: None

RETURNS DESCRIPTION
ndarray

A NumPy array of the cropped samples.

RAISES DESCRIPTION
ValueError

If both start and onset are specified, or both stop and duration are specified.

Source code in src/edfplus/_models.py
def load(
    self,
    start: int | float | datetime.datetime | datetime.timedelta | None = None,
    stop: int | float | datetime.datetime | datetime.timedelta | None = None,
    *,
    onset: int | float | datetime.datetime | datetime.timedelta | None = None,
    duration: float | datetime.timedelta | None = None,
) -> numpy.ndarray:
    """Load and return a slice of the signal's samples.

    Supports flexible cropping by sample index, seconds, datetime, or timedelta.

    Args:
        start: Start position. int=sample index, float=seconds, datetime=absolute,
            timedelta=offset from recording start.
        stop: Stop position (same type interpretation as start).
        onset: Alias for start. Cannot be used together with start.
        duration: Duration from start. float=seconds, timedelta=offset. Cannot be
            used together with stop.

    Returns:
        A NumPy array of the cropped samples.

    Raises:
        ValueError: If both start and onset are specified, or both stop and
            duration are specified.
    """
    self._ensure_loaded()
    assert self._samples is not None
    total_samples = len(self._samples)
    start_idx, stop_idx = self._resolve_bounds(start, stop, onset, duration, total_samples)
    return self._samples[start_idx:stop_idx]

load_with_timestamps(start=None, stop=None, *, onset=None, duration=None, time_format='seconds')

Load samples and corresponding timestamps.

PARAMETER DESCRIPTION
start

Start position (same semantics as load()).

TYPE: int | float | datetime | timedelta | None DEFAULT: None

stop

Stop position (same semantics as load()).

TYPE: int | float | datetime | timedelta | None DEFAULT: None

onset

Alias for start.

TYPE: int | float | datetime | timedelta | None DEFAULT: None

duration

Duration from start.

TYPE: float | timedelta | None DEFAULT: None

time_format

"seconds" for float64 seconds-from-start timestamps, "datetime" for datetime64[us] absolute timestamps.

TYPE: Literal['seconds', 'datetime'] DEFAULT: 'seconds'

RETURNS DESCRIPTION
ndarray

A tuple (samples, timestamps) where both are NumPy arrays of equal

ndarray

length.

RAISES DESCRIPTION
ValueError

If both start and onset are specified, or both stop and duration are specified.

Source code in src/edfplus/_models.py
def load_with_timestamps(
    self,
    start: int | float | datetime.datetime | datetime.timedelta | None = None,
    stop: int | float | datetime.datetime | datetime.timedelta | None = None,
    *,
    onset: int | float | datetime.datetime | datetime.timedelta | None = None,
    duration: float | datetime.timedelta | None = None,
    time_format: Literal["seconds", "datetime"] = "seconds",
) -> tuple[numpy.ndarray, numpy.ndarray]:
    """Load samples and corresponding timestamps.

    Args:
        start: Start position (same semantics as ``load()``).
        stop: Stop position (same semantics as ``load()``).
        onset: Alias for start.
        duration: Duration from start.
        time_format: ``"seconds"`` for float64 seconds-from-start timestamps,
            ``"datetime"`` for datetime64[us] absolute timestamps.

    Returns:
        A tuple ``(samples, timestamps)`` where both are NumPy arrays of equal
        length.

    Raises:
        ValueError: If both start and onset are specified, or both stop and
            duration are specified.
    """
    self._ensure_loaded()
    assert self._samples is not None
    total_samples = len(self._samples)
    start_idx, stop_idx = self._resolve_bounds(start, stop, onset, duration, total_samples)
    samples = self._samples[start_idx:stop_idx]

    if time_format == "seconds":
        timestamps = numpy.arange(start_idx, stop_idx, dtype=numpy.float64) / self.sample_rate
    else:
        if self._edf_header is None:
            raise RuntimeError("Cannot compute datetime timestamps without an associated EDFHeader.")
        origin = numpy.datetime64(self._edf_header.start_datetime, "us")
        seconds = numpy.arange(start_idx, stop_idx, dtype=numpy.float64) / self.sample_rate
        delta = (seconds * 1_000_000).astype("timedelta64[us]")
        timestamps = origin + delta

    return samples, timestamps

physical_samples(dtype=None)

Return the loaded samples as scaled physical values.

PARAMETER DESCRIPTION
dtype

Optional NumPy float dtype to cast the output to. Defaults to float64.

TYPE: dtype | type | None DEFAULT: None

RETURNS DESCRIPTION
ndarray

A NumPy array of physical values.

RAISES DESCRIPTION
RuntimeError

If sample data has not been loaded yet.

Source code in src/edfplus/_models.py
def physical_samples(self, dtype: numpy.dtype | type | None = None) -> numpy.ndarray:
    """Return the loaded samples as scaled physical values.

    Args:
        dtype: Optional NumPy float dtype to cast the output to.  Defaults to
            ``float64``.

    Returns:
        A NumPy array of physical values.

    Raises:
        RuntimeError: If sample data has not been loaded yet.
    """
    arr = self.samples
    if dtype is not None:
        return arr.astype(dtype, copy=False)
    return arr

digital_samples()

Return the loaded samples as raw digital int16 values without scaling.

RETURNS DESCRIPTION
ndarray

A NumPy array with dtype int16.

RAISES DESCRIPTION
RuntimeError

If sample data has not been loaded yet.

Source code in src/edfplus/_models.py
def digital_samples(self) -> numpy.ndarray:
    """Return the loaded samples as raw digital int16 values without scaling.

    Returns:
        A NumPy array with dtype ``int16``.

    Raises:
        RuntimeError: If sample data has not been loaded yet.
    """
    arr = self.samples
    return arr.astype(numpy.int16, copy=False)

timestamps()

Return per-sample onset times in seconds from the recording start.

For sample index i, the onset is i / sample_rate. For EDF+D signals that contain NaN gap-filler values, the corresponding positions in the returned array are also NaN so the two arrays remain positionally aligned.

RETURNS DESCRIPTION
ndarray

A float64 NumPy array of length len(samples).

RAISES DESCRIPTION
RuntimeError

If sample data has not been loaded yet.

Source code in src/edfplus/_models.py
def timestamps(self) -> numpy.ndarray:
    """Return per-sample onset times in seconds from the recording start.

    For sample index ``i``, the onset is ``i / sample_rate``.  For EDF+D signals
    that contain NaN gap-filler values, the corresponding positions in the returned
    array are also ``NaN`` so the two arrays remain positionally aligned.

    Returns:
        A ``float64`` NumPy array of length ``len(samples)``.

    Raises:
        RuntimeError: If sample data has not been loaded yet.
    """
    arr = self.samples  # raises RuntimeError if not loaded
    n = len(arr)
    ts = numpy.arange(n, dtype=numpy.float64) / self.sample_rate
    nan_mask = numpy.isnan(arr)
    if nan_mask.any():
        ts[nan_mask] = numpy.nan
    return ts

datetimes()

Return per-sample absolute datetimes as numpy.datetime64 values.

Each element is EDFHeader.start_datetime + timedelta(seconds=onset_i) where onset_i comes from timestamps(). Gap samples (NaN onset) produce numpy.datetime64('NaT') entries.

RETURNS DESCRIPTION
ndarray

A NumPy array of datetime64[us] values of length len(samples).

RAISES DESCRIPTION
RuntimeError

If sample data has not been loaded yet.

Source code in src/edfplus/_models.py
def datetimes(self) -> numpy.ndarray:
    """Return per-sample absolute datetimes as ``numpy.datetime64`` values.

    Each element is ``EDFHeader.start_datetime + timedelta(seconds=onset_i)`` where
    ``onset_i`` comes from ``timestamps()``.  Gap samples (NaN onset) produce
    ``numpy.datetime64('NaT')`` entries.

    Returns:
        A NumPy array of ``datetime64[us]`` values of length ``len(samples)``.

    Raises:
        RuntimeError: If sample data has not been loaded yet.
    """
    if self._edf_header is None:
        raise RuntimeError("Cannot compute datetimes without an associated EDFHeader.")
    ts = self.timestamps()
    origin = numpy.datetime64(self._edf_header.start_datetime, "us")
    delta = (ts * 1_000_000).astype("timedelta64[us]")
    result = origin + delta
    nat_mask = numpy.isnan(ts)
    if nat_mask.any():
        result = result.copy()
        result[nat_mask] = numpy.datetime64("NaT")
    return result

Annotation(onset, duration, text) dataclass

A single EDF+ annotation parsed from a Time-stamped Annotation List (TAL).

Each Annotation holds exactly one text string. Multi-text TAL blocks produce multiple Annotation instances sharing the same onset and duration.

ATTRIBUTE DESCRIPTION
onset

Onset time in seconds from the recording start.

TYPE: float

duration

Duration of the annotated event in seconds, or None if the TAL entry did not include a duration field.

TYPE: float | None

text

The annotation text string for this entry.

TYPE: str