Skip to content

Reading, writing, and wrangling EEG signal files of BDF and EDF format in Julia

License

Notifications You must be signed in to change notification settings

wherrera10/EDFPlus.jl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

EDFPlus.jl

Build status Build Status CI Coverage Status

Julia for handling BDF+ and EDF+ EEG and similar signal data files.

Heavily influenced by the C EEG library edflib.

Data Structs and Functions

Structs:

mutable struct ChannelParam      # this structure contains all the relevant EDF-signal parameters of one signal
    label::String                  # label (name) of the signal, eg "C4" if in 10-20 labeling terms
    transducer::String             # signal transducer type
    physdimension::String          # physical dimension (uV, bpm, mA, etc.)
    physmax::Float64               # physical maximum, usually the maximum input of the ADC
    physmin::Float64               # physical minimum, usually the minimum input of the ADC
    digmax::Int                    # digital maximum, usually the maximum output of the ADC, cannot be higher than 32767 for EDF or 8388607 for BDF
    digmin::Int                    # digital minimum, usually the minimum output of the ADC, cannot be lower than -32768 for EDF or -8388608 for BDF
    smp_per_record::Int            # number of samples of this signal in a datarecord
    prefilter::String              # channel prefiltering settings if any
    reserved::String               # header reserved ascii text, 32 bytes
    offset::Float64                # offset of center of physical data value from center of digital values
    bufoffset::Int                 # bytes from start of record to start of this channel (zero for first channel)
    bitvalue::Float64              # physical data value of one unit change in digital value
    annotation::Bool               # true if is an annotation not a binary mapped signal data channel
    ChannelParam() = new("","","",0.0,0.0,0,0,0,"","",0.0,0,0.0,false)
end

Parameters for each channel in the EEG record.


mutable struct Annotation
    onset::Float64
    duration::String
    annotation::Array{String,1}
    Annotation() = new(0.0,"",[])
    Annotation(o,d,arr) = new(o, d, typeof(arr) == String ? [arr] : arr)
end

These are text strings within the file denoting a time, optionally duration, and a list of notes about the signal at that particular time in the recording. The first onset time of the annotation channel gives a fractional second offset adjustment of the start time of that record, which is specified in whole seconds in the header.


mutable struct BEDFPlus                   # signal file data for EDF, BDF, EDF+, and BDF+ files
    ios::IOStream                         # file handle for the file containing the data
    path::String                          # file pathname
    writemode::Bool                       # true if is intended for writing to file
    version::String                       # version of the file format
    edf::Bool                             # EDF?
    edfplus::Bool                         # EDF+?
    bdf::Bool                             # BDF?
    bdfplus::Bool                         # BDF+?
    discontinuous::Bool                   # discontinuous (EDF+D?)
    filetype::FileStatus                  # @enum FileStatus as above
    channelcount::Int                     # total number of EDF signal bands in the file INCLUDING annotation channels
    file_duration::Float64                # duration of the file in seconds expressed as 64-bit floating point
    startdate_day::Int                    # startdate of study, day of month of startdate of study
    startdate_month::Int                  # startdate month
    startdate_year::Int                   # startdate year
    starttime_subsecond::Float64          # starttime offset in seconds, should be < 1 sec in size. Only used by EDFplus and BDFplus
    starttime_second::Int                 # this is in integer seconds, the field above makes it more precise
    starttime_minute::Int                 # startdate and time, minutes
    starttime_hour::Int                   # 0 to 23, midnight is 00:00:00
    # next 11 fields are for EDF+ and BDF+ files only
    patient::String                       # contains patientfield of header, is always empty when filetype is EDFPLUS or BDFPLUS
    recording::String                     # contains recordingfield of header, is always empty when filetype is EDFPLUS or BDFPLUS
    patientcode::String                   # empty when filetype is EDF or BDF
    gender::String                        # empty when filetype is EDF or BDF
    birthdate::String                     # empty when filetype is EDF or BDF
    patientname::String                   # empty when filetype is EDF or BDF
    patient_additional::String            # empty when filetype is EDF or BDF
    admincode::String                     # empty when filetype is EDF or BDF
    technician::String                    # empty when filetype is EDF or BDF
    equipment::String                     # empty when filetype is EDF or BDF
    recording_additional::String          # empty when filetype is EDF or BDF
    datarecord_duration::Float64          # duration of one datarecord in units of seconds
    datarecords::Int64                    # number of datarecords in the file
    startdatestring::String               # date recording started in dd-uuu-yyyy format
    reserved::String                      # reserved, 32 byte string
    headersize::Int                       # size of header in bytes
    recordsize::Int                       # size of one data record in bytes, these follow header
    annotationchannel::Int                # position in record of annotation channel
    mapped_signals::Array{Int,1}          # positions in record of channels carrying data
    signalparam::Array{ChannelParam,1}    # Array of structs which contain the per-signal parameters
    annotations::Array{Array{Annotation,1},1} # Array of lists of annotations
    EDFsignals::Array{Int16,2}    # 2D array, each row a record, columns are channels including annotations
    BDFsignals::Array{Int32,2}    # Note that either EDFsignals or BDFsignals is used
    BEDFPlus() = new(IOStream("nothing"),"",false,"",false,false,false,false,false,READ_ERROR,0,0.0,0,0,0,0.0,0,0,0,
                        "","","","","","","","","","","",0.0,0,"","",0,0,0,
                        Array{Int,1}(undef,0),Array{ChannelParam,1}(undef,0),
                        Array{Array{Annotation,1},1}(undef,0),Array{Int16,2}(undef,0,0),Array{Int32,2}(undef,0,0))
end

Data struct for EDF, EDF+, BDF, and BDF+ EEG type signal files.


DataFormat

enum for types this package handles. Current format for a potential translation is also /same/.


FileStatus

enum for type or state of file: type of data detected, whether any errors


Functions:

loadfile(path::String, read_annotations=true)

Load a BDF+ or EDF+ type file. Takes a pathname. Will ignore annotations if second argument is set false. Returns a BEDFPlus structure including header and data.


writefile!(edfh, newpath; acquire=dummyacquire, sigformat=same)

Write to data in the edfh struct to the file indicated by newpath Returns the file handle of the file written, opened for reading NOTE: The header needs to be completely specified at function start except for the final number of records, which will be updated after all data records are written. For a system that is recording the data as it is written, the acquire(edfh) function should write the data according the the header parameters. Note that if the function converts from BDF to EDF or EDF to BDF, the edfh struct is changed.


epoch_iterator(edfh, epochsecs; channels, startsec, endsec, physical)

Make an iterator for EEG epochs of a given duration between start and stop times. Required arguments:

  • edfh: BEDFPlus struct
  • epochsecs: second duration of each epoch

Optional arguments:

  • channels: List of channel numbers for data, defaults to all signal channels

  • startsec: Starting position from 0 at start of file, defaults to file start

  • endsec: Ending position in seconds from start of file, defaults to file end

  • physical: Whether to return data as translated to the physical units, defaults to true


    annotation_epoch_iterator(edfh, epochsecs; startsec, endsec)

Return an iterator for a group of annotations for a given epoch as in epoch_iterator


dummyacquire(edfh)

Dummy function for call in writefile! for optional acquire function If using package for data acquisition will need to custom write the acquire function for your calls to writefile!


channeltimesegment(edfh, channel, startsec, endsec, physical)

Get the channel's data between the time points


multichanneltimesegment(edfh, chanlist, startsec, endsec, physical)

Get an multichannel array of lists of datapoints over time segment Works best if all datapoint signal rates are the same


signalindices(edfh, channelnumber)

Get a pair of indices of a channel's bytes within each of the data records


digitalchanneldata(edfh, channelnumber)

Get a single digital channel of data in its entirety. Arguments:

  • edfh: the BEDFPlus struct

  • channelnumber: the channel number in the records


    physicalchanneldata(edfh, channelnumber)

Get a single data channel in its entirely, in the physical units stated in the header Arguments:

  • edfh: the BEDFPlus struct

  • channelnumber: the channel number in the records-- a channel in the mapped_signals list


    samplerate(edfh, channel)

Get sample (sampling) rate (fs) on the channel in sec^-1 units


notchfilter(signals, fs, notchfreq=60, q = 35)

Notch filter signals in array signals, return filtered signals


highpassfilter(signals, fs, cutoff=1.0, order=4)

Apply high pass filter to signals, return filtered data


lowpassfilter(signals, fs, cutoff=25.0, order=4)

Apply low pass filter to signals, return filtered data


closefile!(edfh)

Close the file opened by loadfile and loaded to the BEDFPlus struct May therefore let GC release memory from read data in edfh


readdata!(edfh)

Helper function for loadfile, reads signal data into the BEDFPlus struct


signaldata(edfh)

Return which BEDFPlus variable holds the signal data


recordslice(edfh, startpos, endpos)

Get a slice of the data in the recording from one data entry position to another


bytesperdatapoint(edfh)

Return how many bytes used per data point entry: 2 for EDF (16-bit), 3 for BDF (24-bit) data.


datapointinterval(edfh, channel)

Time interval in fractions of a second between individual signal data points


recordindexat(edfh, secondsafterstart)

Index of the record point at or closest just before a given time from recording start Translates a values in seconds to a position in the signal data matrix, returns that record's position


signalat(edfh, secondsafter, channel)

Get the position in the signal data of the data point at or closest after a given time from recording start. Translates a value in seconds to a position in the signal channel matrix, returns that signal data point's 2D position as list


epochmarkers(edfh, secs)

Get a set of (start, stop) positional markers for epochs (sequential windows) given an epoch duration in seconds


checkfile!(edfh)

Check an input file to be valid EDF/BDF/+ format.


readannotations!(edfh)

Read the annotations of an input file into the EDFPlus struct.


translate24to16bits!(edfh)

Translate data in 24-bit BDF to 16-bit EDF format


translate16to24bits!(edfh)

Translate 16 bit data to 32-bit width, for change to 24-bit data for writefile!


addannotation!(edfh, onset, duration, description)

Add an annotation at the given onset timepoint IF there is room Note the description arg is expected to be a String or similar


latintoascii(str)

Change non-ascii characters to similar ascii chars


readBiosemiStatus(edfh)

Export BDF Status channel data.

Returns a Dict structure containing trigger data in the format:

  • Code: => vector of triggerbit (Int),
  • Index: => vector of index data (Int)
  • Onset: => vector of onset times (Float)
  • Duration: => vector of durations (Float)


Installation:

To install from a Julia REPL command line session:

using Pkg
Pkg.add("EDFPlus"))

Note that the test files include a 23 mb test file. You may need to allow extra time for that to download when installing.