# -*- coding: utf-8 -*-
# Copyright (c) 2019 - 2020 Simon Kern
# Copyright (c) 2015 - 2020 Holger Nahrstaedt
# Copyright (c) 2011, 2015, Chris Lee-Messer
# Copyright (c) 2016-2017 The pyedflib Developers
# <https://github.com/holgern/pyedflib>
# See LICENSE for license details.
from datetime import datetime
import numpy as np
from ._extensions._pyedflib import CyEdfReader
__all__ = ['EdfReader', 'DO_NOT_READ_ANNOTATIONS',
'READ_ANNOTATIONS', 'READ_ALL_ANNOTATIONS', 'CHECK_FILE_SIZE',
'DO_NOT_CHECK_FILE_SIZE', 'REPAIR_FILE_SIZE_IF_WRONG']
DO_NOT_READ_ANNOTATIONS = 0
READ_ANNOTATIONS = 1
READ_ALL_ANNOTATIONS = 2
CHECK_FILE_SIZE = 0
DO_NOT_CHECK_FILE_SIZE = 1
REPAIR_FILE_SIZE_IF_WRONG = 2
def _debug_parse_header(filename):
"""
A debug function that reads a header and outputs everything that
is contained in the header
"""
import json
from collections import OrderedDict
header = OrderedDict()
with open(filename, 'rb') as f:
f.seek(0)
header['version'] = f.read(8).decode()
header['patient'] = f.read(80).decode().strip()
header['recording'] = f.read(80).decode().strip()
header['startdate'] = f.read(8).decode()
header['starttime'] = f.read(8).decode()
header['n_bytes'] = f.read(8).decode()
header['reserved'] = f.read(44).decode().strip()
header['n_records'] = f.read(8).decode()
header['duration'] = f.read(8).decode()
header['n_signals'] = f.read(4).decode()
print('\n## Header')
print(json.dumps(header, indent=2))
ns = int(header['n_signals'])
label = [f.read(16).decode() for i in range(ns)]
transducer = [f.read(80).decode().strip() for i in range(ns)]
dimension = [f.read(8).decode().strip() for i in range(ns)]
pmin = [f.read(8).decode() for i in range(ns)]
pmax = [f.read(8).decode() for i in range(ns)]
dmin = [f.read(8).decode() for i in range(ns)]
dmax = [f.read(8).decode() for i in range(ns)]
prefilter = [f.read(80).decode().strip() for i in range(ns)]
n_samples = [f.read(8).decode() for i in range(ns)]
reserved = [f.read(32).decode() for i in range(ns)]
_ = zip(label, transducer, dimension, pmin, pmax, dmin, dmax, prefilter, n_samples, reserved)
fields = ['label', 'transducer', 'dimension', 'pmin', 'pmax', 'dmin', 'dmax', 'prefilter', 'n_samples', 'reserved']
sheaders = [{field:globals()[field][i] for field in fields} for i in range(ns)]
print('## Signal Headers')
print(json.dumps(sheaders, indent=2))
[docs]class EdfReader(CyEdfReader):
"""
This provides a simple interface to read EDF, EDF+, BDF and BDF+ files.
"""
def __enter__(self):
return self
def __del__(self):
self._close()
def __exit__(self, exc_type, exc_val, ex_tb):
self._close() # cleanup the file
[docs] def close(self):
"""
Closes the file handler
"""
self._close()
[docs] def getNSamples(self):
return np.array([self.samples_in_file(chn)
for chn in np.arange(self.signals_in_file)])
[docs] def readAnnotations(self):
"""
Annotations from a edf-file
Parameters
----------
None
"""
annot = self.read_annotation()
annot = np.array(annot)
if (annot.shape[0] == 0):
return np.array([]), np.array([]), np.array([])
ann_time = self._get_float(annot[:, 0])
ann_text = annot[:, 2]
ann_text_out = ["" for x in range(len(annot[:, 1]))]
for i in np.arange(len(annot[:, 1])):
ann_text_out[i] = self._convert_string(ann_text[i])
if annot[i, 1] == '':
annot[i, 1] = '-1'
ann_duration = self._get_float(annot[:, 1])
return ann_time/10000000, ann_duration, np.array(ann_text_out)
def _get_float(self, v):
result = np.zeros(np.size(v))
for i in np.arange(np.size(v)):
try:
if not v[i]:
result[i] = -1
else:
result[i] = int(v[i])
except ValueError:
result[i] = float(v[i])
return result
def _convert_string(self,s):
if isinstance(s, bytes):
return s.decode("latin")
else:
return s.decode("utf-8", "strict")
[docs] def getTechnician(self):
"""
Returns the technicians name
Parameters
----------
None
.. code-block:: python
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getTechnician()==''
True
>>> f.close()
"""
return self._convert_string(self.technician.rstrip())
[docs] def getRecordingAdditional(self):
"""
Returns the additional recordinginfo
Parameters
----------
None
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getRecordingAdditional()==''
True
>>> f.close()
"""
return self._convert_string(self.recording_additional.rstrip())
[docs] def getPatientName(self):
"""
Returns the patientname
Parameters
----------
None
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getPatientName()=='X'
True
>>> f.close()
"""
return self._convert_string(self.patientname.rstrip())
[docs] def getPatientCode(self):
"""
Returns the patientcode
Parameters
----------
None
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getPatientCode()==''
True
>>> f.close()
"""
return self._convert_string(self.patientcode.rstrip())
[docs] def getPatientAdditional(self):
"""
Returns the additional patientinfo.
Parameters
----------
None
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getPatientAdditional()==''
True
>>> f.close()
"""
return self._convert_string(self.patient_additional.rstrip())
[docs] def getEquipment(self):
"""
Returns the used Equipment.
Parameters
----------
None
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getEquipment()=='test generator'
True
>>> f.close()
"""
return self._convert_string(self.equipment.rstrip())
[docs] def getAdmincode(self):
"""
Returns the Admincode.
Parameters
----------
None
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getAdmincode()==''
True
>>> f.close()
"""
return self._convert_string(self.admincode.rstrip())
[docs] def getGender(self):
"""
Returns the Gender of the patient.
Parameters
----------
None
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getGender()==''
True
>>> f.close()
"""
return self._convert_string(self.gender.rstrip())
[docs] def getFileDuration(self):
"""
Returns the duration of the file in seconds.
Parameters
----------
None
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getFileDuration()==600
True
>>> f.close()
"""
return self.file_duration
[docs] def getStartdatetime(self):
"""
Returns the date and starttime as datetime object
Parameters
----------
None
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getStartdatetime()
datetime.datetime(2011, 4, 4, 12, 57, 2)
>>> f.close()
"""
# denoted as long long in nanoseconds, we need to transfer it to microsecond
subsecond = np.round(self.starttime_subsecond/100).astype(int)
return datetime(self.startdate_year, self.startdate_month, self.startdate_day,
self.starttime_hour, self.starttime_minute, self.starttime_second,
subsecond)
[docs] def getBirthdate(self, string=True):
"""
Returns the birthdate as string object
Parameters
----------
None
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getBirthdate()=='30 jun 1969'
True
>>> f.close()
"""
if string:
return self._convert_string(self.birthdate.rstrip())
else:
return datetime.strptime(self._convert_string(self.birthdate.rstrip()), "%d %b %Y")
[docs] def getSampleFrequencies(self):
"""
Returns samplefrequencies of all signals.
Parameters
----------
None
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> all(f.getSampleFrequencies()==200.0)
True
>>> f.close()
"""
return np.array([round(self.samplefrequency(chn))
for chn in np.arange(self.signals_in_file)])
[docs] def getSampleFrequency(self,chn):
"""
Returns the samplefrequency of signal edfsignal.
Parameters
----------
chn : int
channel number
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getSampleFrequency(0)==200.0
True
>>> f.close()
"""
if 0 <= chn < self.signals_in_file:
return np.round(self.samplefrequency(chn), decimals=3)
else:
raise IndexError('Trying to access channel {}, but only {} ' \
'channels found'.format(chn, self.signals_in_file))
[docs] def getSignalLabels(self):
"""
Returns all labels (name) ("FP1", "SaO2", etc.).
Parameters
----------
None
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getSignalLabels()==['squarewave', 'ramp', 'pulse', 'noise', 'sine 1 Hz', 'sine 8 Hz', 'sine 8.1777 Hz', 'sine 8.5 Hz', 'sine 15 Hz', 'sine 17 Hz', 'sine 50 Hz']
True
>>> f.close()
"""
return [self._convert_string(self.signal_label(chn).strip())
for chn in np.arange(self.signals_in_file)]
[docs] def getLabel(self,chn):
"""
Returns the label (name) of signal chn ("FP1", "SaO2", etc.).
Parameters
----------
chn : int
channel number
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getLabel(0)=='squarewave'
True
>>> f.close()
"""
if 0 <= chn < self.signals_in_file:
return self._convert_string(self.signal_label(chn).rstrip())
else:
raise IndexError('Trying to access channel {}, but only {} ' \
'channels found'.format(chn, self.signals_in_file))
[docs] def getPrefilter(self,chn):
"""
Returns the prefilter of signal chn ("HP:0.1Hz", "LP:75Hz N:50Hz", etc.)
Parameters
----------
chn : int
channel number
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getPrefilter(0)==''
True
>>> f.close()
"""
if 0 <= chn < self.signals_in_file:
return self._convert_string(self.prefilter(chn).rstrip())
else:
raise IndexError('Trying to access channel {}, but only {} ' \
'channels found'.format(chn, self.signals_in_file))
[docs] def getPhysicalMaximum(self,chn=None):
"""
Returns the maximum physical value of signal edfsignal.
Parameters
----------
chn : int
channel number
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getPhysicalMaximum(0)==1000.0
True
>>> f.close()
"""
if chn is not None:
if 0 <= chn < self.signals_in_file:
return self.physical_max(chn)
else:
raise IndexError('Trying to access channel {}, but only {} ' \
'channels found'.format(chn, self.signals_in_file))
else:
physMax = np.zeros(self.signals_in_file)
for i in np.arange(self.signals_in_file):
physMax[i] = self.physical_max(i)
return physMax
[docs] def getPhysicalMinimum(self,chn=None):
"""
Returns the minimum physical value of signal edfsignal.
Parameters
----------
chn : int
channel number
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getPhysicalMinimum(0)==-1000.0
True
>>> f.close()
"""
if chn is not None:
if 0 <= chn < self.signals_in_file:
return self.physical_min(chn)
else:
raise IndexError('Trying to access channel {}, but only {} ' \
'channels found'.format(chn, self.signals_in_file))
else:
physMin = np.zeros(self.signals_in_file)
for i in np.arange(self.signals_in_file):
physMin[i] = self.physical_min(i)
return physMin
[docs] def getDigitalMaximum(self, chn=None):
"""
Returns the maximum digital value of signal edfsignal.
Parameters
----------
chn : int
channel number
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getDigitalMaximum(0)
32767
>>> f.close()
"""
if chn is not None:
if 0 <= chn < self.signals_in_file:
return self.digital_max(chn)
else:
raise IndexError('Trying to access channel {}, but only {} ' \
'channels found'.format(chn, self.signals_in_file))
else:
digMax = np.zeros(self.signals_in_file)
for i in np.arange(self.signals_in_file):
digMax[i] = self.digital_max(i)
return digMax
[docs] def getDigitalMinimum(self, chn=None):
"""
Returns the minimum digital value of signal edfsignal.
Parameters
----------
chn : int
channel number
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getDigitalMinimum(0)
-32768
>>> f.close()
"""
if chn is not None:
if 0 <= chn < self.signals_in_file:
return self.digital_min(chn)
else:
raise IndexError('Trying to access channel {}, but only {} ' \
'channels found'.format(chn, self.signals_in_file))
else:
digMin = np.zeros(self.signals_in_file)
for i in np.arange(self.signals_in_file):
digMin[i] = self.digital_min(i)
return digMin
[docs] def getTransducer(self, chn):
"""
Returns the transducer of signal chn ("AgAgCl cup electrodes", etc.).
Parameters
----------
chn : int
channel number
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getTransducer(0)==''
True
>>> f.close()
"""
if 0 <= chn < self.signals_in_file:
return self._convert_string(self.transducer(chn).rstrip())
else:
raise IndexError('Trying to access channel {}, but only {} ' \
'channels found'.format(chn, self.signals_in_file))
[docs] def getPhysicalDimension(self, chn):
"""
Returns the physical dimension of signal edfsignal ("uV", "BPM", "mA", "Degr.", etc.)
Parameters
----------
chn : int
channel number
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> f.getPhysicalDimension(0)=='uV'
True
>>> f.close()
"""
if 0 <= chn < self.signals_in_file:
return self._convert_string(self.physical_dimension(chn).rstrip())
else:
raise IndexError('Trying to access channel {}, but only {} ' \
'channels found'.format(chn, self.signals_in_file))
[docs] def readSignal(self, chn, start=0, n=None, digital=False):
"""
Returns the physical data of signal chn. When start and n is set, a subset is returned
Parameters
----------
chn : int
channel number
start : int
start pointer (default is 0)
n : int
length of data to read (default is None, by which the complete data of the channel are returned)
digital: bool
will return the signal in original digital values instead of physical values
Examples
--------
>>> import pyedflib
>>> f = pyedflib.data.test_generator()
>>> x = f.readSignal(0,0,1000)
>>> int(x.shape[0])
1000
>>> x2 = f.readSignal(0)
>>> int(x2.shape[0])
120000
>>> f.close()
"""
if start < 0:
return np.array([])
if n is not None and n < 0:
return np.array([])
nsamples = self.getNSamples()
if 0 <= chn < len(nsamples):
if n is None:
n = nsamples[chn]
elif n > nsamples[chn]:
return np.array([])
dtype = np.int32 if digital else np.float64
x = np.zeros(n, dtype=dtype)
if digital:
self.read_digital_signal(chn, start, n, x)
else:
self.readsignal(chn, start, n, x)
return x
else:
raise IndexError('Trying to access channel {}, but only {} ' \
'channels found'.format(chn, self.signals_in_file))
[docs] def file_info(self):
print("file name:", self.file_name)
print("signals in file:", self.signals_in_file)
[docs] def file_info_long(self):
"""
Returns information about the opened EDF/BDF file
"""
self.file_info()
for ii in np.arange(self.signals_in_file):
print("label:", self.getSignalLabels()[ii], "fs:",
self.getSampleFrequencies()[ii], "nsamples",
self.getNSamples()[ii])