edfwriter
index
/data/pluto/python/EDFlib-Python/src/EDFlib/edfwriter.py

#############################################################################
#
# Copyright (c) 2020 - 2023 Teunis van Beelen
# All rights reserved.
#
# Email: teuniz@protonmail.com
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#     * Redistributions of source code must retain the above copyright
#       notice, this list of conditions and the following disclaimer.
#     * Redistributions in binary form must reproduce the above copyright
#       notice, this list of conditions and the following disclaimer in the
#       documentation and/or other materials provided with the distribution.
#     * Neither the name of the copyright holder nor the names of its
#       contributors may be used to endorse or promote products derived from
#       this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ''AS IS'' AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES
# LOSS OF USE, DATA, OR PROFITS OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#
#############################################################################

 
Modules
       
io
numpy
sys

 
Classes
       
builtins.Exception(builtins.BaseException)
EDFexception
builtins.object
EDFwriter

 
class EDFexception(builtins.Exception)
    Common base class for all non-exit exceptions.
 
 
Method resolution order:
EDFexception
builtins.Exception
builtins.BaseException
builtins.object

Methods defined here:
__init__(self, message)
Initialize self.  See help(type(self)) for accurate signature.

Data descriptors defined here:
__weakref__
list of weak references to the object (if defined)

Methods inherited from builtins.Exception:
__new__(*args, **kwargs) from builtins.type
Create and return a new object.  See help(type) for accurate signature.

Methods inherited from builtins.BaseException:
__delattr__(self, name, /)
Implement delattr(self, name).
__getattribute__(self, name, /)
Return getattr(self, name).
__reduce__(...)
helper for pickle
__repr__(self, /)
Return repr(self).
__setattr__(self, name, value, /)
Implement setattr(self, name, value).
__setstate__(...)
__str__(self, /)
Return str(self).
with_traceback(...)
Exception.with_traceback(tb) --
set self.__traceback__ to tb and return self.

Data descriptors inherited from builtins.BaseException:
__cause__
exception cause
__context__
exception context
__dict__
__suppress_context__
__traceback__
args

 
class EDFwriter(builtins.object)
    A writer for EDF+ and BDF+ files.
 
EDF header:
 
offset (hex, dec) length
---------------------------------------------------------------------
0x00      0     8 ascii : version of this data format (0)
0x08      8    80 ascii : local patient identification
0x58     88    80 ascii : local recording identification
0xA8    168     8 ascii : startdate of recording (dd.mm.yy)
0xB0    176     8 ascii : starttime of recording (hh.mm.ss)
0xB8    184     8 ascii : number of bytes in header record
0xC0    192    44 ascii : reserved
0xEC    236     8 ascii : number of data records (-1 if unknown)
0xF4    244     8 ascii : duration of a data record, in seconds
0xFC    252     4 ascii : number of signals
 
      0x00           0     ns * 16 ascii : ns * label (e.g. EEG Fpz-Cz or Body temp)
 ns * 0x10    ns *  16     ns * 80 ascii : ns * transducer type (e.g. AgAgCl electrode)
 ns * 0x60    ns *  96     ns *  8 ascii : ns * physical dimension (e.g. uV or degreeC)
 ns * 0x68    ns * 104     ns *  8 ascii : ns * physical minimum (e.g. -500 or 34)
 ns * 0x70    ns * 112     ns *  8 ascii : ns * physical maximum (e.g. 500 or 40)
 ns * 0x78    ns * 120     ns *  8 ascii : ns * digital minimum (e.g. -2048)
 ns * 0x80    ns * 128     ns *  8 ascii : ns * digital maximum (e.g. 2047)
 ns * 0x88    ns * 136     ns * 80 ascii : ns * prefiltering (e.g. HP:0.1Hz LP:75Hz N:60)
 ns * 0xD8    ns * 216     ns *  8 ascii : ns * nr of samples in each data record
 ns * 0xE0    ns * 224     ns * 32 ascii : ns * reserved
 
ns: number of signals
 
All fields are left aligned and filled up with spaces, no NULL's.
 
Only printable ASCII characters are allowed.
 
Decimal separator (if any) must be a dot. No grouping characters in numbers.
 
For more info about the EDF and EDF+ format, visit: https://edfplus.info/specs/
 
For more info about the BDF and BDF+ format, visit: https://www.teuniz.net/edfbrowser/bdfplus%20format%20description.html
 
note: In EDF, the sensitivity (e.g. uV/bit) and offset are stored using four parameters:
digital maximum and minimum, and physical maximum and minimum.
Here, digital means the raw data coming from a sensor or ADC. Physical means the units like uV.
The sensitivity in units/bit is calculated as follows:
 
units per bit = (physical max - physical min) / (digital max - digital min)
 
The digital offset is calculated as follows:
 
offset = (physical max / units per bit) - digital max
 
For a better explanation about the relation between digital data and physical data,
read the document "Coding Schemes Used with Data Converters" (PDF):
 
https://www.ti.com/general/docs/lit/getliterature.tsp?baseLiteratureNumber=sbaa042
 
note: An EDF file usually contains multiple so-called datarecords. One datarecord usually has a duration of one second (this is the default but it is not mandatory!).
In that case a file with a duration of five minutes contains 300 datarecords. The duration of a datarecord can be freely chosen but, if possible, use values from
0.1 to 1 second for easier handling. Just make sure that the total size of one datarecord, expressed in bytes, does not exceed 10MByte (15MBytes for BDF(+)).
 
The RECOMMENDATION of a maximum datarecordsize of 61440 bytes in the EDF and EDF+ specification was useful in the time people were still using DOS as their main operating system.
Using DOS and fast (near) pointers (16-bit pointers), the maximum allocatable block of memory was 64KByte.
This is not a concern anymore so the maximum datarecord size now is limited to 10MByte for EDF(+) and 15MByte for BDF(+). This helps to accommodate for higher samplingrates
used by modern Analog to Digital Converters.
 
EDF header character encoding: The EDF specification says that only (printable) ASCII characters are allowed.
When writing the header info, EDFlib will assume you are using Latin1 encoding and it will automatically convert
characters with accents, umlauts, tilde, etc. to their "normal" equivalent without the accent/umlaut/tilde/etc.
in order to create a valid EDF file.
 
The description/name of an EDF+ annotation on the other hand, is encoded in UTF-8.
 
author: Teunis van Beelen
 
  Methods defined here:
__init__(self, p_path:str, f_file_type:int, number_of_signals:int)
Creates an instance of an EDF writer.
 
Path is the path to the EDF file.
Filetype must be EDFLIB_FILETYPE_EDFPLUS or EDFLIB_FILETYPE_BDFPLUS.
Number of signals: the number of signals that you are planning to write into the file.
close(self) -> int
Finalizes and closes the file.
 
This function is required after writing. Failing to do so will cause a corrupted and incomplete file.
Returns 0 on success, otherwise -1.
setAdditionalPatientInfo(self, additional:str) -> int
Sets the additional information related to the patient.
 
String must contain printable ASCII only.
This function is optional and can be called only before the first sample write action.
Returns 0 on success, otherwise -1.
setAdditionalRecordingInfo(self, additional:str) -> int
Sets the additional info related to the recording.
 
String must contain printable ASCII only.
This function is optional and can be called only before the first sample write action.
Returns 0 on success, otherwise -1.
setAdministrationCode(self, admin_code:str) -> int
Sets the administration code.
 
String must contain printable ASCII only.
This function is optional and can be called only before the first sample write action.
Returns 0 on success, otherwise -1.
setDataRecordDuration(self, duration:int) -> int
Sets the datarecord duration.
 
This function is optional, normally you don't need to change the default value of one second.
This function is NOT REQUIRED but can be called only before the first sample write action.
 
This function can be used when you want to use a non-integer samplerate.
For example, if you want to use a samplerate of 0.5 Hz, set the samplefrequency to 5 Hz and
the datarecord duration to 10 seconds, or alternatively, set the samplefrequency to 1 Hz and
the datarecord duration to 2 seconds.
This function can also be used when you want to use a very high samplerate.
For example, if you want to use a samplerate of 5 GHz,
set the samplefrequency to 5000 Hz and the datarecord duration to 1 microSecond.
Do not use this function if not necessary.
 
duration is expressed in microSeconds, range: 1 - 60000000  (1uSec. - 60 sec.)
Returns 0 on success, otherwise -1.
setDigitalMaximum(self, s:int, dig_max:int) -> int
Sets the maximum digital value of signal s.
 
Usually it's the extreme output of the ADC. The maximum value is 32767 for EDF and 8388607 for BDF.
It is the highest value that the equipment is able to record. It does not necessarily mean the signal recorded reaches this level.
Must be higher than digital minimum.
This function is required for every signal and can be called only before the first sample write action.
 
s is the signal number (zero-based).
dig_max is the maximum output value (<= 32767 for EDF and <= 8388607 for BDF).
 
Returns 0 on success, otherwise -1.
 
note: In EDF, the sensitivity (e.g. uV/bit) and offset are stored using four parameters:
digital maximum and minimum, and physical maximum and minimum.
Here, digital means the raw data coming from a sensor or ADC. Physical means the units like uV.
Usually they are the extreme input and output values of the ADC.
The sensitivity in units/bit is calculated as follows:
 
units per bit = (physical max - physical min) / (digital max - digital min)
 
The digital offset is calculated as follows:
 
offset = (physical max / units per bit) - digital max
setDigitalMinimum(self, s:int, dig_min:int) -> int
Sets the minimum digital value of signal s.
 
Usually it's the extreme output of the ADC. The minimum value is -32768 for EDF and -8388608 for BDF.
It is the lowest value that the equipment is able to record. It does not necessarily mean the signal recorded reaches this level.
Must be lower than digital maximum.
This function is required for every signal and can be called only before the first sample write action.
 
s is the signal number (zero-based).
dig_min is the minimum output value (>= -32768 for EDF and >= -8388608 for BDF).
 
Returns 0 on success, otherwise -1.
 
note: In EDF, the sensitivity (e.g. uV/bit) and offset are stored using four parameters:
digital maximum and minimum, and physical maximum and minimum.
Here, digital means the raw data coming from a sensor or ADC. Physical means the units like uV.
Usually they are the extreme input and output values of the ADC.
The sensitivity in units/bit is calculated as follows:
 
units per bit = (physical max - physical min) / (digital max - digital min)
 
The digital offset is calculated as follows:
 
offset = (physical max / units per bit) - digital max
setEquipment(self, equipment:str) -> int
Sets the description of the equipment used for the recording.
 
String must contain printable ASCII only.
This function is optional and can be called only before the first sample write action.
Returns 0 on success, otherwise -1.
setNumberOfAnnotationSignals(self, annot_signals:int) -> int
Sets the number of annotation signals.
 
The default value is 1.
This function is optional and, if used, must be called before the first sample write action.
Normally you don't need to change the default value. Only when the number of annotations
you want to write is higher than the number of datarecords in the recording, you can use
this function to increase the storage space for annotations.
setPatientBirthDate(self, year:int, month:int, day:int) -> int
Sets the patients' birthdate.
 
This function is optional and can be called only before the first sample write action.
 
year: 1800 - 3000
month: 1 - 12
day: 1 - 31
 
Returns 0 on success, otherwise -1.
setPatientCode(self, code:str) -> int
Sets the patientcode.
 
String must contain printable ASCII only.
This function is optional and can be called only before the first sample write action.
Returns 0 on success, otherwise -1.
setPatientGender(self, gender:int) -> int
Sets the patient's gender.
 
gender: 0 = female, 1 = male, 2 = unknown or not applicable (default)
This function is optional and can be called only before the first sample write action.
Returns 0 on success, otherwise -1.
setPatientName(self, name:str) -> int
Sets the patientname.
 
String must contain printable ASCII only.
This function is optional and can be called only before the first sample write action.
Returns 0 on success, otherwise -1.
setPhysicalDimension(self, s:int, physical_dimension:str) -> int
Sets the physical_dimension (unit) of signal s.
 
("uV", "BPM", "mA", "Degr.", etc.) String must contain printable ASCII only.
This function recommended for every signal and can be called only before the first sample write action.
 
s is the signal number (zero-based).
physical_dimension is the physical dimension description.
Returns 0 on success, otherwise -1.
setPhysicalMaximum(self, s:int, phys_max:float) -> int
Sets the maximum physical value of signal s.
 
This is the value of the input of the ADC when the output equals the value of "digital maximum".
It is the highest value that the equipment is able to record. It does not necessarily mean the signal recorded reaches this level.
Must be un-equal to physical minimum.
This function is required for every signal and can be called only before the first sample write action.
 
s is the signal number (zero-based).
phys_max is the maximum input value.
 
Returns 0 on success, otherwise -1.
 
note: In EDF, the sensitivity (e.g. uV/bit) and offset are stored using four parameters:
digital maximum and minimum, and physical maximum and minimum.
Here, digital means the raw data coming from a sensor or ADC. Physical means the units like uV.
Usually they are the extreme input and output values of the ADC.
The sensitivity in units/bit is calculated as follows:
 
units per bit = (physical max - physical min) / (digital max - digital min)
 
The digital offset is calculated as follows:
 
offset = (physical max / units per bit) - digital max
setPhysicalMinimum(self, s:int, phys_min:float) -> int
Sets the minimum physical value of signal s.
 
This is the value of the input of the ADC when the output equals the value of "digital minimum".
It is the lowest value that the equipment is able to record. It does not necessarily mean the signal recorded reaches this level.
Must be un-equal to physical maximum.
This function is required for every signal and can be called only before the first sample write action.
 
s is the signal number (zero-based).
phys_min is the minimum input value.
 
Returns 0 on success, otherwise -1.
 
note: In EDF, the sensitivity (e.g. uV/bit) and offset are stored using four parameters:
digital maximum and minimum, and physical maximum and minimum.
Here, digital means the raw data coming from a sensor or ADC. Physical means the units like uV.
Usually they are the extreme input and output values of the ADC.
The sensitivity in units/bit is calculated as follows:
 
units per bit = (physical max - physical min) / (digital max - digital min)
 
The digital offset is calculated as follows:
 
offset = (physical max / units per bit) - digital max
setPreFilter(self, s:int, prefilter:str) -> int
Sets the prefilter description of signal s.
 
(e.g. "HP:0.05Hz", "LP:250Hz", "N:60Hz", etc.) String must contain printable ASCII only.
This function is optional and can be called only before the first sample write action.
 
s is the signal number (zero-based).
prefilter is the prefilter description.
Returns 0 on success, otherwise -1.
setSampleFrequency(self, s:int, sf:int) -> int
Sets the samplefrequency of signal s.
 
(In reallity, it sets the number of samples in a datarecord.)
The samplefrequency of a signal is determined as: sf = number of samples in a datarecord / datarecord duration.
The samplefrequency equals the number of samples in a datarecord only when the datarecord duration is set to the default of one second.
This function is required for every signal and can be called only before the first sample write action.
 
s is the signal number (zero-based).
sf is the samplefrequency.
Returns 0 on success, otherwise -1.
setSignalLabel(self, s:int, label:str) -> int
Sets the label (name) of signal s.
 
(e.g. "FP1", "SaO2", etc.) String must contain printable ASCII only.
This function is recommended for every signal and can be called only before the first sample write action.
 
s is the signal number (zero-based).
label is the signallabel.
Returns 0 on success, otherwise -1.
setStartDateTime(self, year:int, month:int, day:int, hour:int, minute:int, second:int, subsecond:int) -> int
Sets the startdate and starttime.
 
If not called, the system date and time at runtime will be used.
This function is optional and can be called only before the first sample write action.
If subsecond precision is not needed or not applicable, leave it at zero.
Note: for anonymization purposes, the consensus is to use 1985-01-01 00:00:00 for the startdate and starttime.
 
year: 1985 - 2084
month: 1 - 12
day: 1 - 31
hour: 0 - 23
minute: 0 - 59
second: 0 - 59
subsecond: 0 - 9999  expressed in units of 100 microSeconds
 
Returns 0 on success, otherwise -1.
setTechnician(self, technician:str) -> int
Sets the name or id of the technician who performed the recording.
 
String must contain printable ASCII only.
This function is optional and can be called only before the first sample write action.
Returns 0 on success, otherwise -1.
setTransducer(self, s:int, transducer:str) -> int
Sets the transducer description of signal s.
 
("AgAgCl cup electrodes", etc.) String must contain printable ASCII only.
This function is optional and can be called only before the first sample write action.
 
s is the signal number (zero-based).
transducer is the transducer description.
Returns 0 on success, otherwise -1.
version(self) -> int
If version is 1.00 then it will return 100.
writeAnnotation(self, onset:int, duration:int, description:str) -> int
Writes an annotation/event to the file.
 
onset is relative to the starttime of the recording and must be >= 0.
onset and duration are in units of 100 microSeconds. Resolution is 0.0001 second.
E.g. 34.071 seconds must be written as 340710.
If duration is unknown or not applicable: set a negative number (-1).
Description is a string containing the text that describes the event.
This function is optional.
writeSamples(self, buf:<built-in function array>) -> int
Write samples.
 
Writes sf samples into the file.
Buf must be a one-dimensional numpy array containing samples of one signal of datatype int32, float_ or float64.
For EDF, dataype int16 can also be used.
If buf is of type integer, the samples are written into the file without any conversion.
If buf is of type float, the physical samples will be converted to digital samples using the
values of physical maximum, physical minimum, digital maximum and digital minimum.
The number of samples written is equal to the samplefrequency of the signal.
(actually, it's the value that is set with setSampleFrequency()).
Size of buf should be equal to or bigger than the samplefrequency.
Call this function for every signal in the file. The order is important!
When there are 4 signals in the file,  the order of calling this function
must be: signal 0, signal 1, signal 2, signal 3, signal 0, signal 1, signal 2, etc.
The end of a recording must always be at the end of a complete cycle.
 
buf is a one-dimensional numpy array of datatype int32, float_ or float64. For EDF, dataype int16 can also be used.
Returns 0 on success, otherwise -1.

Data descriptors defined here:
__dict__
dictionary for instance variables (if defined)
__weakref__
list of weak references to the object (if defined)

Data and other attributes defined here:
EDFLIB_DATARECORD_SIZE_TOO_BIG = -26
EDFLIB_DIGMAX_LOWER_THAN_DIGMIN = -24
EDFLIB_DIGMIN_IS_DIGMAX = -23
EDFLIB_DO_NOT_READ_ANNOTATIONS = 0
EDFLIB_FILETYPE_BDF = 2
EDFLIB_FILETYPE_BDFPLUS = 3
EDFLIB_FILETYPE_EDF = 0
EDFLIB_FILETYPE_EDFPLUS = 1
EDFLIB_FILETYPE_ERROR = -7
EDFLIB_FILE_ALREADY_OPENED = -6
EDFLIB_FILE_CLOSED = -13
EDFLIB_FILE_CONTAINS_FORMAT_ERRORS = -3
EDFLIB_FILE_IS_DISCONTINUOUS = -10
EDFLIB_FILE_READ_ERROR = -5
EDFLIB_FILE_WRITE_ERROR = -8
EDFLIB_INVALID_ARGUMENT = -12
EDFLIB_INVALID_READ_ANNOTS_VALUE = -11
EDFLIB_MALLOC_ERROR = -1
EDFLIB_MAXFILES_REACHED = -4
EDFLIB_MAXSIGNALS = 640
EDFLIB_MAX_ANNOTATION_LEN = 512
EDFLIB_NO_SAMPLES_IN_RECORD = -22
EDFLIB_NO_SIGNALS = -20
EDFLIB_NO_SUCH_FILE_OR_DIRECTORY = -2
EDFLIB_NUMBER_OF_SIGNALS_INVALID = -9
EDFLIB_PHYSMIN_IS_PHYSMAX = -25
EDFLIB_READ_ALL_ANNOTATIONS = 2
EDFLIB_READ_ANNOTATIONS = 1
EDFLIB_TIME_DIMENSION = 10000000
EDFLIB_TOO_MANY_SIGNALS = -21
EDFLIB_VERSION = 108
EDFSEEK_CUR = 1
EDFSEEK_END = 2
EDFSEEK_SET = 0