Introduction

The European Data Format (EDF) is a simple and flexible format for exchange and storage of multichannel biological and physical signals. It was developed by a few European ‘medical’ engineers who first met at the 1987 international Sleep Congress in Copenhagen. See http://www.edfplus.info/

The original EDF specification has been expanded in several ways. EDF+ supports the addition of annotations and non-continuous recordings. The BioSemi Data Format BDF format uses 24 bits per sample (in stead of the 16 bits per sample in EDF). And BDF+ is an EDF+ like extension of BDF.

This packages supports all these variants.

Both EDF and BDF files consist of an header followed by one or more data records with the recorded signals, either ordinary signals or annotation signals.

This package follows this structure by providing two basic functions: readEdfHeader and readEdfSignals (see the help pages for details)

The examples below are based on the following two files:

Both files are derived from the “test_generator_2” test files from http://www.teuniz.net/edf_bdf_testfiles

libDir <- system.file ("extdata", package="edfReader")
CFile <- paste (libDir, '/edfPlusC.edf', sep='') # a continuously recorded EDF file
DFile <- paste (libDir, '/bdfPlusD.bdf', sep='') # discontinuously recorded BDF file

EDF headers objects

Introduction

The readEdfHeader function returns a list of class ‘ebdfHeader’ with all the data from the EDF or BDF file header. Part of this list is a data frame of class ‘ebdfSHeader’ which contains the signal headers.

The ebdfHeader

A file header can be read with readEdfHeader()

library (edfReader)
CHdr  <- readEdfHeader (CFile)            
DHdr  <- readEdfHeader (DFile)                  

Summaries of the header data can be shown with the S3 summary () and print() functions

CHdr
##  Patient               : X X X X 
##  RecordingId           : Startdate 10-DEC-2009 X X test_generator 
##  StartTime             : 2009-12-10 12:44:02 
##  Continuous recording  : TRUE 
##  Signal labels         : squarewave ramp pulse ECG noise sine 1 Hz sine 8 Hz sine 8.5 Hz sine 15 Hz sine 17 Hz sine 50 Hz EDF Annotations
summary (DHdr)
##  File name             : /Library/Frameworks/R.framework/Versions/3.2/Resources/library/edfReader/extdata/bdfPlusD.bdf 
##  File type             : BDF+ 
##  Version               : "255"BIOSEMI 
##  Patient               : X X X X 
##  RecordingId           : Startdate 10-DEC-2009 X X test_generator 
##  StartTime             : 2009-12-10 12:45:54 
##  Continuous recording  : FALSE 
##  Recording period      : 10 sec (= 00:00:10) 
##  Ordinary signals      : 11 
##  Annotation signals    : 1 
##  Signal labels         : squarewave ramp pulse ECG noise sine 1 Hz sine 8 Hz sine 8.5 Hz sine 15 Hz sine 17 Hz sine 50 Hz BDF Annotations

The ebdfSHeader

Summaries of the signal header data can be shown with the S3 print() and summary () functions.

CHdr$sHeader
##  Signal labels         : squarewave ramp pulse ECG noise sine 1 Hz sine 8 Hz sine 8.5 Hz sine 15 Hz sine 17 Hz sine 50 Hz EDF Annotations
summary (DHdr$sHeader)
##    signal           label transducer sampleRate preFilter
## 1       1      squarewave                   200          
## 2       2            ramp                   200          
## 3       3           pulse                   200          
## 4       4             ECG                   200          
## 5       5           noise                   200          
## 6       6       sine 1 Hz                   200          
## 7       7       sine 8 Hz                   200          
## 8       8     sine 8.5 Hz                   200          
## 9       9      sine 15 Hz                   200          
## 10     10      sine 17 Hz                   200          
## 11     11      sine 50 Hz                   200          
## 12     12 BDF Annotations                    NA

EDF signal objects

Introduction

The readEdfSignals function with simplify=FALSE returns a list of class ‘ebdfSignals’ with the signals selected from the EDF / BDF file.

The signals in this list are of the following :

The list of signals

The signals in an EDF or BDF file can be read with the readEdfSignals function.

CSignals <- readEdfSignals (CHdr)
DSignals <- readEdfSignals (DHdr)

NOTE In case only one signal was read the list of one will be simplified to this singe object. This can be prevented by using the ‘simplify=FALSE’ parameter. So readEdfSignals (CHdr, signals=7, simplify=FALSE)[[1]] and readEdfSignals (CHdr, signals=7) will return the same object. If required the reading can be restricted to a specific number of signals and/or a particular period.

The signals are then identified by their label, signal number or signal type (‘Ordinary’ or ‘Annotations’).

someCSignalsPeriod <- readEdfSignals (CHdr, signals=c(3, "5", "sine 8.5 Hz"), from=5.1, till=18)
annoDSignalsPeriod <- readEdfSignals (DHdr, signals='Annotations', from=5.1, till=18)

Note that the period read will be - apart from rounding errors - [10.1,45). See the section “Samples, time and periods” for details.

NOTE In this case DFile is an BDF file with one annotation signal. So readEdfSignals (DHdr, signals=‘Annotations’) will return the same object as readEdfSignals (DHdr, signals=‘Annotations’, simplify=FALSE)[[‘BDF Annotations’]]

Summaries of the list of signals can be shown with the S3 print() and summary () functions.

someCSignalsPeriod
## Continuous recording  TRUE 
## Recorded period       20 
## Recording segment     from 5.1 sec (= 00:00:05.1) till 18 sec (= 00:00:18) 
## bits per sample       16 
## Ordinary signals: 
##   signal       label transducer sampleRate samples preFilter
## 1      3       pulse                   200    2580          
## 2      5       noise                   200    2580          
## 3      8 sine 8.5 Hz                   200    2580
summary (DSignals)
## Continuous recording  FALSE 
## Total recording time  20 
## Recorded period       10 
## Recording segment     whole recording 
## bits per sample       24 
## Annotation signal: 
##   signal recordStartAnnotations otherAnnotations
## 1     12                      0                1
## Ordinary signals: 
##    signal       label transducer sampleRate samples preFilter
## 1       1  squarewave                   200    4000          
## 2       2        ramp                   200    4000          
## 3       3       pulse                   200    4000          
## 4       4         ECG                   200    4000          
## 5       5       noise                   200    4000          
## 6       6   sine 1 Hz                   200    4000          
## 7       7   sine 8 Hz                   200    4000          
## 8       8 sine 8.5 Hz                   200    4000          
## 9       9  sine 15 Hz                   200    4000          
## 10     10  sine 17 Hz                   200    4000          
## 11     11  sine 50 Hz                   200    4000

NOTE In case only one signal was read the list of one will be simplified to this singe object. This can be prevented by using the ‘simplify=FALSE’ parameter. So readEdfSignals (CHdr, signals=7, simplify=FALSE)[[1]] and readEdfSignals (CHdr, signals=7) will return the same object.

Ordinary signals, continuously recorded

Summaries of a continuously recorded ordinary signal can be shown with the S3 print() and summary () functions.

someCSignalsPeriod[[1]]
##  Signal number          3 
##  Label                  pulse 
##  Continuous recording   TRUE 
##  Recorded period        20 sec (= 00:00:20) 
##  Recording segment      from 5.1 sec (= 00:00:05.1) till 18 sec (= 00:00:18) 
##  Number of samples      2580 
##  Sample rate            200 
##  Transducer              
##  Range                  -1000 : 1000 uV 
##  Prefilter
summary (CSignals$pulse)         # a signals has its label as its name
##  Signal number          3 
##  Label                  pulse 
##  Continuous recording   TRUE 
##  Recorded period        20 sec (= 00:00:20) 
##  Recording segment      whole recording 
##  Number of samples      4000 
##  Sample rate            200 
##  Transducer              
##  Range                  -1000 : 1000 uV 
##  Prefilter               
##  Signal summary:
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
##  0.01526  0.01526  0.01526  2.01500  0.01526 99.99000

Ordinary signals, not continuously recorded

Ordinary signals that are not continuously recorded can be read in two different ways:

The latter method will use a more complex data structure, the first may result in a (much too) huge object.

CDSignals <- readEdfSignals (DHdr, from=5.1, till=18)
FDSignals <- readEdfSignals (DHdr, fragments=TRUE)

The objects of class ebdfCSignal are summarised in the same way as continuously recorded signals.

CDSignals[[8]]
##  Signal number          8 
##  Label                  sine 8.5 Hz 
##  Continuous recording   FALSE 
##  Total recording time   20 sec (= 00:00:20) 
##  Recorded period        10 sec (= 00:00:10) 
##  Recording segment      from 5.1 sec (= 00:00:05.1) till 18 sec (= 00:00:18) 
##  Number of samples      2580 
##  Sample rate            200 
##  Transducer              
##  Range                  -1000 : 1000 uV 
##  Prefilter
summary (CDSignals$`sine 8.5 Hz`)      # note the "`" quotes for a name with spaces.
##  Signal number          8 
##  Label                  sine 8.5 Hz 
##  Continuous recording   FALSE 
##  Total recording time   20 sec (= 00:00:20) 
##  Recorded period        10 sec (= 00:00:10) 
##  Recording segment      from 5.1 sec (= 00:00:05.1) till 18 sec (= 00:00:18) 
##  Number of samples      2580 
##  Sample rate            200 
##  Transducer              
##  Range                  -1000 : 1000 uV 
##  Prefilter               
##  Signal summary:
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max.     NA's 
## -100.000  -70.990   -3.141   -1.537   68.450  100.000     1200

Summaries of a not continuously recorded ordinary signal stored in fragments can be shown with the S3 print() and summary () functions.

FDSignals[[8]]
##  Signal number          8 
##  Label                  sine 8.5 Hz 
##  Continuous recording   FALSE 
##  Total recording time   20 sec (= 00:00:20) 
##  Recorded period        10 sec (= 00:00:10) 
##  Recording segment      whole recording 
##  Number of fragments    8 
##  Sample rate            200 
##  Transducer              
##  Range                  -1000 : 1000 uV 
##  Prefilter
summary (FDSignals$`sine 8.5 Hz`) 
##  Signal number          8 
##  Label                  sine 8.5 Hz 
##  Continuous recording   FALSE 
##  Total recording time   20 sec (= 00:00:20) 
##  Recorded period        10 sec (= 00:00:10) 
##  Recording segment      whole recording 
##  Number of fragments    8 
##  Sample rate            200 
##  Transducer              
##  Range                  -1000 : 1000 uV 
##  Prefilter               
##   fragment start samples    Min. X1st.Qu.     Median       Mean X3rd.Qu.
## 1        1     0     200  -99.89   -65.24  9.4110000  3.7230000    71.81
## 2        2     3     200 -100.00   -71.81 -9.4110000 -3.7220000    65.24
## 3        3     5     200 -100.00   -71.81 -9.4110000 -3.7220000    65.24
## 4        4     7     200 -100.00   -71.81 -9.4110000 -3.7220000    65.24
## 5        5    10     400 -100.00   -70.71  0.0000596  0.0000596    70.71
## 6        6    13     200 -100.00   -71.81 -9.4110000 -3.7220000    65.24
## 7        7    16     400 -100.00   -70.71  0.0000596  0.0000596    70.71
## 8        8    19     200 -100.00   -71.81 -9.4110000 -3.7220000    65.24
##     Max.
## 1 100.00
## 2  99.89
## 3  99.89
## 4  99.89
## 5 100.00
## 6  99.89
## 7 100.00
## 8  99.89
## All fragments:
##     Min.  1st Qu.   Median     Mean  3rd Qu.     Max. 
## -100.000  -71.810   -3.141   -1.489   69.590  100.000

Annotation signals

Summaries of an annotation signal (ASignal) can be shown with the S3 print() and summary () functions.

CSignals$`EDF Annotations`
##  Signal number             12 
##  Signal label              EDF Annotations 
##  Recorded period           20 sec (= 00:00:20) 
##  Recording segment         whole recording 
##  Record start annotations  0 
##  Other annotations         2
summary(annoDSignalsPeriod)
##  Signal number             12 
##  Signal label              BDF Annotations 
##  Recorded period           10 sec (= 00:00:10) 
##  Recording segment         from 5.1 sec (= 00:00:05.1) till 18 sec (= 00:00:18) 
##  Record start annotations  0 
##  Other annotations         0

The “Record start annotations = 0” indicates that the record start annotations were not included, i.e. readEdfSignals was used with the parameter recordStartTimes = FALSE.
‘Other annotations’ shows the number of ‘real’ annotations.

Samples, time and periods.

Samples and time

As usual, a recording starts at time 0 with sample 1.

Consequently sample n will be at time (n-1)/sRate, where sRate denotes the sample rate.

Samples and periods

Apart from rounding errors, a from - till period in readEdfSignals will be the period [from,till), i.e. starting at from and up to be but not including till.

This may sound strange, but this convention has the following properties

Object details

Header details

Header attributes

The header data encompass the following:

str (CHdr,  max.level=1)
## List of 16
##  $ fileName      : chr "/Library/Frameworks/R.framework/Versions/3.2/Resources/library/edfReader/extdata/edfPlusC.edf"
##  $ fileType      : chr "EDF"
##  $ version       : chr "0"
##  $ patient       : chr "X X X X"
##  $ recordingId   : chr "Startdate 10-DEC-2009 X X test_generator"
##  $ startTime     : POSIXlt[1:1], format: "2009-12-10 12:44:02"
##  $ headerLength  : int 3328
##  $ reserved      : chr "EDF+C"
##  $ nRecords      : int 20
##  $ recordDuration: num 1
##  $ nSignals      : int 12
##  $ recordedPeriod: num 20
##  $ sampleBits    : num 16
##  $ isPlus        : logi TRUE
##  $ isContinuous  : logi TRUE
##  $ sHeaders      :Classes 'ebdfSHeaders' and 'data.frame':   12 obs. of  15 variables:
##  - attr(*, "class")= chr "ebdfHeader"

The fields version, patient, recordingId, startTime, headerLength, reserved, nRecords, recordDuration, and nSignals are from the file header. The others are derived, but see below for sHeaders.

Signal header attributes

The signal header data encompass the following:

str (CHdr$sHeader, max.level=1)
## Classes 'ebdfSHeaders' and 'data.frame': 12 obs. of  15 variables:
##  $ label           : chr  "squarewave" "ramp" "pulse" "ECG" ...
##  $ transducerType  : chr  "" "" "" "" ...
##  $ physicalDim     : chr  "uV" "uV" "uV" "uV" ...
##  $ physicalMin     : num  -1000 -1000 -1000 -1000 -1000 -1000 -1000 -1000 -1000 -1000 ...
##  $ physicalMax     : num  1000 1000 1000 1000 1000 1000 1000 1000 1000 1000 ...
##  $ digitalMin      : int  -32768 -32768 -32768 -32768 -32768 -32768 -32768 -32768 -32768 -32768 ...
##  $ digitalMax      : int  32767 32767 32767 32767 32767 32767 32767 32767 32767 32767 ...
##  $ preFilter       : chr  "" "" "" "" ...
##  $ samplesPerRecord: int  200 200 200 200 200 200 200 200 200 200 ...
##  $ reserved        : chr  "" "" "" "" ...
##  $ gain            : num  0.0305 0.0305 0.0305 0.0305 0.0305 ...
##  $ offset          : num  0.0153 0.0153 0.0153 0.0153 0.0153 ...
##  $ sRate           : num  200 200 200 200 200 200 200 200 200 200 ...
##  $ isAnnotation    : logi  FALSE FALSE FALSE FALSE FALSE FALSE ...
##  $ sLength         : int  4000 4000 4000 4000 4000 4000 4000 4000 4000 4000 ...

The fields label, transducerType, physicalDim, physicalMin, physicalMax, digitalMin, digitalMax, preFilter, samplesPerRecord, and reserved are from the file header. The others are derived. Gain and offset are used to map the digital sample values to the range of physical values.

For annotation signals the only relevant fields are “label” which must have the value “EDF Annotations” (or “BDF Annotations”) and “samplesPerRecord”.

Signal details

Ordinary signal objects of class ebdfCSignal

The data for ordinary signal objects of class ebdfCSignal encompass the following:

str(CSignals$pulse, max.level=1) 
## List of 15
##  $ signalNumber  : int 3
##  $ label         : chr "pulse"
##  $ isContinuous  : logi TRUE
##  $ from          : num 0
##  $ till          : num Inf
##  $ start         : num 0
##  $ fromSample    : num 1
##  $ transducerType: chr ""
##  $ sampleBits    : num 16
##  $ sRate         : num 200
##  $ range         : chr "-1000 : 1000 uV"
##  $ preFilter     : chr ""
##  $ signal        : num [1:4000] 99.9924 99.9924 99.9924 99.9924 0.0153 ...
##  $ recordedPeriod: num 20
##  $ totalPeriod   : num 20
##  - attr(*, "class")= chr "ebdfCSignal"

The attributes signalNumber, label, isContinuous, transducerType, sampleBits, sRate, range, preFilter, recordedPeriod and totalPeriod are (derived) from the header data.

For a continuously recorded signal the totalPeriod is equal to the recordedPeriod.

The attributes from and till contain the values of the corresponding actual readEdfSignals parameters. The default values are 0 and Inf.

The attributes start and fromSample contains the sample time and number for the first sample in the from-till period. They are calculated as follows: from = max (0, from)
fromSample = ceiling (sRate * from) + 1 start = (fromSample - 1) / sRate

The signal attribute contains the sample data from the EDF / BDF data records. If read with the readEdfSignals parameter physical=TRUE, the default, the digital sample values are mapped to physical values. With physical=FALSE, signals contains the digital sample values.

The physical values are calculated as follows:
physicalValue = a * digitalValue + b,
with: a = (physicalMax - physicalMin) / (digitalMax - digitalMin)
b = physicalMax - a* digitalMax

Ordinary signal objects of class ebdfFSignal

The data for ordinary signal objects of class ebdfFSignal encompass the following:

str(FDSignals$`sine 8.5 Hz`, max.level=1) 
## List of 15
##  $ signalNumber  : int 8
##  $ label         : chr "sine 8.5 Hz"
##  $ isContinuous  : logi FALSE
##  $ from          : num 0
##  $ till          : num Inf
##  $ start         : num 0
##  $ fromSample    : num 1
##  $ transducerType: chr ""
##  $ sampleBits    : num 24
##  $ sRate         : num 200
##  $ range         : chr "-1000 : 1000 uV"
##  $ preFilter     : chr ""
##  $ fragments     :List of 8
##  $ recordedPeriod: num 10
##  $ totalPeriod   : num 20
##  - attr(*, "class")= chr "ebdfFSignal"

For the attributes signalNumber, label, isContinuous, from, till, start, fromSample, transducerType, sampleBits, sRate, range, preFilter, recordedPeriod and totalPeriod see the previous section.

For a not continuously recorded signal the total period equals the start of the last data record plus its duration.

The fragments attribute contains the list of recorded fragments.

Signal fragment data

The data of a signal fragment in objects of class ebdfFSignal encompass the following:

str(FDSignals$`sine 8.5 Hz`$fragments[[1]], max.level=1) 
## List of 3
##  $ start     : num 0
##  $ fromSample: num 1
##  $ signal    : num [1:200] 26.4 50.9 71.8 87.6 97.2 ...

The fromSample contains the sample number of the first sample in this fragment (as if it were a continuous recording). The start attribute the sample time for this sample.

The signal contains the fragment’s sample values. These may be physical values (the default) or digital values (see above).

Annotation signals

The data for annotation signal objects of class ebdfASignal encompass the following:

str(CSignals$`EDF Annotations`, max.level=1) 
## List of 8
##  $ signalNumber  : int 12
##  $ label         : chr "EDF Annotations"
##  $ isContinuous  : logi TRUE
##  $ from          : num 0
##  $ till          : num Inf
##  $ annotations   :'data.frame':  2 obs. of  5 variables:
##  $ recordedPeriod: num 20
##  $ totalPeriod   : num 20
##  - attr(*, "class")= chr "ebdfASignal"

For the attributes signalNumber, label, isContinuous, from, till, recordedPeriod and totalPeriod see the section for objects of class ebdfCSignal.

The annotations attribute contains a data frame with the individual annotations.

Annotation data

The data for a single annotation encompass the following:

str(CSignals$`EDF Annotations`$annotations, max.level=1) 
## 'data.frame':    2 obs. of  5 variables:
##  $ record       : int  1 2
##  $ onset        : num  0 600
##  $ duration     : num  NA NA
##  $ isRecordStart: logi  FALSE FALSE
##  $ annotations  : chr  "RECORD START" "REC STOP"

The record attribute refers to the data record that contains the annotation.

The onset attribute contains the time of the annotation relative to the start of the recording.

The duration attribute contains the duration of the annotated event.

The isRecordStart indicates whether or not this annotation is the first one in a data record (and indicates the start time of the recording in that record).

The annotations attribute contains one or more annotations associated with the onset and duration.

Acknowledgement

This package has used code from

References

  1. Specification of EDF
    http://www.edfplus.info/specs/edf.html

  2. Specification of EDF+
    http://www.edfplus.info/specs/edfplus.html

  3. Specification of EDF++
    http://195.154.67.227/en/contribute/edf/

  4. Specification of BDF
    see ‘Which file format does BioSemi use’ at
    http://www.biosemi.com/faq/file_format.htm

  5. Specification of BDF+ http://www.teuniz.net/edfbrowser/bdfplus%20format%20description.html

Other useful EDF related sources can be found at:
http://www.edfplus.info/downloads/