a b/edfread.m
1
2
function [hdr, record] = edfread(fname, varargin)
3
% Read European Data Format file into MATLAB
4
%
5
% [hdr, record] = edfread(fname)
6
%         Reads data from ALL RECORDS of file fname ('*.edf'). Header
7
%         information is returned in structure hdr, and the signals
8
%         (waveforms) are returned in structure record, with waveforms
9
%         associated with the records returned as fields titled 'data' of
10
%         structure record.
11
%
12
% [...] = edfread(fname, 'assignToVariables', assignToVariables)
13
%         If assignToVariables is true, triggers writing of individual
14
%         output variables, as defined by field 'labels', into the caller
15
%         workspace.
16
%
17
% [...] = edfread(...,'targetSignals',targetSignals)
18
%         Allows user to specify the names (or position numbers) of the
19
%         subset of signals to be read. |targetSignals| may be either a
20
%         string, a cell array of comma-separated strings, or a vector of
21
%         numbers. (Default behavior is to read all signals.)
22
%         E.g.:
23
%         data = edfread(mydata.edf,'targetSignals','Thoracic');
24
%         data = edfread(mydata.edf,'targetSignals',{'Thoracic1','Abdominal'});
25
%         or
26
%         data = edfread(mydata.edf,'targetSignals',[2,4,6:13]);
27
%
28
% FORMAT SPEC: Source: http://www.edfplus.info/specs/edf.html SEE ALSO:
29
% http://www.dpmi.tu-graz.ac.at/~schloegl/matlab/eeg/edf_spec.htm
30
%
31
% The first 256 bytes of the header record specify the version number of
32
% this format, local patient and recording identification, time information
33
% about the recording, the number of data records and finally the number of
34
% signals (ns) in each data record. Then for each signal another 256 bytes
35
% follow in the header record, each specifying the type of signal (e.g.
36
% EEG, body temperature, etc.), amplitude calibration and the number of
37
% samples in each data record (from which the sampling frequency can be
38
% derived since the duration of a data record is also known). In this way,
39
% the format allows for different gains and sampling frequencies for each
40
% signal. The header record contains 256 + (ns * 256) bytes.
41
%
42
% Following the header record, each of the subsequent data records contains
43
% 'duration' seconds of 'ns' signals, with each signal being represented by
44
% the specified (in the header) number of samples. In order to reduce data
45
% size and adapt to commonly used software for acquisition, processing and
46
% graphical display of polygraphic signals, each sample value is
47
% represented as a 2-byte integer in 2's complement format. Figure 1 shows
48
% the detailed format of each data record.
49
%
50
% DATA SOURCE: Signals of various types (including the sample signal used
51
% below) are available from PHYSIONET: http://www.physionet.org/
52
%
53
%
54
% % EXAMPLE 1:
55
% % Read all waveforms/data associated with file 'ecgca998.edf':
56
%
57
% [header, recorddata] = edfread('ecgca998.edf');
58
%
59
% % EXAMPLE 2:
60
% % Read records 3 and 5, associated with file 'ecgca998.edf':
61
%
62
% % E
63
% header = edfread('ecgca998.edf','AssignToVariables',true);
64
% % Header file specifies data labels 'label_1'...'label_n'; these are
65
% % created as variables in the caller workspace.
66
%
67
% Coded 8/27/09 by Brett Shoelson, PhD
68
% brett.shoelson@mathworks.com
69
% Copyright 2009 - 2012 MathWorks, Inc.
70
%
71
% Modifications:
72
% 5/6/13 Fixed a problem with a poorly subscripted variable. (Under certain
73
% conditions, data were being improperly written to the 'records' variable.
74
% Thanks to Hisham El Moaqet for reporting the problem and for sharing a
75
% file that helped me track it down.)
76
% 
77
% 5/22/13 Enabled import of a user-selected subset of signals. Thanks to
78
% Farid and Cindy for pointing out the deficiency. Also fixed the import of
79
% signals that had "bad" characters (spaces, etc) in their names.
80
%
81
% 10/30/14 Now outputs frequency field directly, and (thanks to Olga Imas
82
% for the suggestion.)
83
%
84
% 3/23/2015 Fixed a doc problem with an improperly named variable
85
% (desiredSignals->targetSignals).
86
% HEADER RECORD
87
% 8 ascii : version of this data format (0)
88
% 80 ascii : local patient identification
89
% 80 ascii : local recording identification
90
% 8 ascii : startdate of recording (dd.mm.yy)
91
% 8 ascii : starttime of recording (hh.mm.ss)
92
% 8 ascii : number of bytes in header record
93
% 44 ascii : reserved
94
% 8 ascii : number of data records (-1 if unknown)
95
% 8 ascii : duration of a data record, in seconds
96
% 4 ascii : number of signals (ns) in data record
97
% ns * 16 ascii : ns * label (e.g. EEG FpzCz or Body temp)
98
% ns * 80 ascii : ns * transducer type (e.g. AgAgCl electrode)
99
% ns * 8 ascii : ns * physical dimension (e.g. uV or degreeC)
100
% ns * 8 ascii : ns * physical minimum (e.g. -500 or 34)
101
% ns * 8 ascii : ns * physical maximum (e.g. 500 or 40)
102
% ns * 8 ascii : ns * digital minimum (e.g. -2048)
103
% ns * 8 ascii : ns * digital maximum (e.g. 2047)
104
% ns * 80 ascii : ns * prefiltering (e.g. HP:0.1Hz LP:75Hz)
105
% ns * 8 ascii : ns * nr of samples in each data record
106
% ns * 32 ascii : ns * reserved
107
% DATA RECORD
108
% nr of samples[1] * integer : first signal in the data record
109
% nr of samples[2] * integer : second signal
110
% ..
111
% ..
112
% nr of samples[ns] * integer : last signal
113
if nargin > 5
114
    error('EDFREAD: Too many input arguments.');
115
end
116
if ~nargin
117
    error('EDFREAD: Requires at least one input argument (filename to read).');
118
end
119
[fid,msg] = fopen(fname,'r');
120
if fid == -1
121
    error(msg)
122
end
123
assignToVariables = false; %Default
124
targetSignals = []; %Default
125
for ii = 1:2:numel(varargin)
126
    switch lower(varargin{ii})
127
        case 'assigntovariables'
128
            assignToVariables = varargin{ii+1};
129
        case 'targetsignals'
130
            targetSignals = varargin{ii+1};
131
        otherwise
132
            error('EDFREAD: Unrecognized parameter-value pair specified. Valid values are ''assignToVariables'' and ''targetSignals''.')
133
    end
134
end
135
% HEADER
136
hdr.ver        = str2double(char(fread(fid,8)'));
137
hdr.patientID  = fread(fid,80,'*char')';
138
hdr.recordID   = fread(fid,80,'*char')';
139
hdr.startdate  = fread(fid,8,'*char')';% (dd.mm.yy)
140
% hdr.startdate  = datestr(datenum(fread(fid,8,'*char')','dd.mm.yy'), 29); %'yyyy-mm-dd' (ISO 8601)
141
hdr.starttime  = fread(fid,8,'*char')';% (hh.mm.ss)
142
% hdr.starttime  = datestr(datenum(fread(fid,8,'*char')','hh.mm.ss'), 13); %'HH:MM:SS' (ISO 8601)
143
hdr.bytes      = str2double(fread(fid,8,'*char')');
144
reserved       = fread(fid,44);%#ok
145
hdr.records    = str2double(fread(fid,8,'*char')');
146
if hdr.records == -1
147
    beep
148
    disp('There appears to be a problem with this file; it returns an out-of-spec value of -1 for ''numberOfRecords.''')
149
    disp('Attempting to read the file with ''edfReadUntilDone'' instead....');
150
    [hdr, record] = edfreadUntilDone(fname, varargin);
151
    return
152
end
153
hdr.duration   = str2double(fread(fid,8,'*char')');
154
% Number of signals
155
hdr.ns    = str2double(fread(fid,4,'*char')');
156
for ii = 1:hdr.ns
157
    hdr.label{ii} = regexprep(fread(fid,16,'*char')','\W','');
158
end
159
if isempty(targetSignals)
160
    targetSignals = 1:numel(hdr.label);
161
elseif iscell(targetSignals)||ischar(targetSignals)
162
    targetSignals = find(ismember(hdr.label,regexprep(targetSignals,'\W','')));
163
end
164
if isempty(targetSignals)
165
    error('EDFREAD: The signal(s) you requested were not detected.')
166
end
167
for ii = 1:hdr.ns
168
    hdr.transducer{ii} = fread(fid,80,'*char')';
169
end
170
% Physical dimension
171
for ii = 1:hdr.ns
172
    hdr.units{ii} = fread(fid,8,'*char')';
173
end
174
% Physical minimum
175
for ii = 1:hdr.ns
176
    hdr.physicalMin(ii) = str2double(fread(fid,8,'*char')');
177
end
178
% Physical maximum
179
for ii = 1:hdr.ns
180
    hdr.physicalMax(ii) = str2double(fread(fid,8,'*char')');
181
end
182
% Digital minimum
183
for ii = 1:hdr.ns
184
    hdr.digitalMin(ii) = str2double(fread(fid,8,'*char')');
185
end
186
% Digital maximum
187
for ii = 1:hdr.ns
188
    hdr.digitalMax(ii) = str2double(fread(fid,8,'*char')');
189
end
190
for ii = 1:hdr.ns
191
    hdr.prefilter{ii} = fread(fid,80,'*char')';
192
end
193
for ii = 1:hdr.ns
194
    hdr.samples(ii) = str2double(fread(fid,8,'*char')');
195
end
196
for ii = 1:hdr.ns
197
    reserved    = fread(fid,32,'*char')';%#ok
198
end
199
hdr.label = hdr.label(targetSignals);
200
hdr.label = regexprep(hdr.label,'\W','');
201
hdr.units = regexprep(hdr.units,'\W','');
202
hdr.frequency = hdr.samples./hdr.duration;
203
disp('Step 1 of 2: Reading requested records. (This may take a few minutes.)...');
204
if nargout > 1 || assignToVariables
205
    % Scale data (linear scaling)
206
    scalefac = (hdr.physicalMax - hdr.physicalMin)./(hdr.digitalMax - hdr.digitalMin);
207
    dc = hdr.physicalMax - scalefac .* hdr.digitalMax;
208
    
209
    % RECORD DATA REQUESTED
210
    tmpdata = struct;
211
    for recnum = 1:hdr.records
212
        for ii = 1:hdr.ns
213
            % Read or skip the appropriate number of data points
214
            if ismember(ii,targetSignals)
215
                % Use a cell array for DATA because number of samples may vary
216
                % from sample to sample
217
                tmpdata(recnum).data{ii} = fread(fid,hdr.samples(ii),'int16') * scalefac(ii) + dc(ii);
218
            else
219
                fseek(fid,hdr.samples(ii)*2,0);
220
            end
221
        end
222
    end
223
    hdr.units = hdr.units(targetSignals);
224
    hdr.physicalMin = hdr.physicalMin(targetSignals);
225
    hdr.physicalMax = hdr.physicalMax(targetSignals);
226
    hdr.digitalMin = hdr.digitalMin(targetSignals);
227
    hdr.digitalMax = hdr.digitalMax(targetSignals);
228
    hdr.prefilter = hdr.prefilter(targetSignals);
229
    hdr.transducer = hdr.transducer(targetSignals);
230
    
231
    record = zeros(numel(hdr.label), hdr.samples(1)*hdr.records);
232
    % NOTE: 5/6/13 Modified for loop below to change instances of hdr.samples to
233
    % hdr.samples(ii). I think this underscored a problem with the reader.
234
    
235
    % Sort by requested targetSignals order:
236
    
237
    disp('Step 2 of 2: Parsing data...');
238
    recnum = 1;
239
    for ii = 1:hdr.ns
240
        if ismember(ii,targetSignals)
241
            ctr = 1;
242
            for jj = 1:hdr.records
243
                try %#ok
244
                    record(recnum, ctr : ctr + hdr.samples(ii) - 1) = tmpdata(jj).data{ii};
245
                end
246
                ctr = ctr + hdr.samples(ii);
247
            end
248
            recnum = recnum + 1;
249
        end
250
    end
251
    hdr.ns = numel(hdr.label);
252
    hdr.samples = hdr.samples(targetSignals);
253
    if assignToVariables
254
        for ii = 1:numel(hdr.label)
255
            try %#ok
256
                eval(['assignin(''caller'',''',hdr.label{ii},''',record(ii,:))'])
257
            end
258
        end
259
        % Uncomment this line to duplicate output in a single matrix
260
        % ''record''. (Could be memory intensive if dataset is large.)
261
        record = [];% No need to output record data as a matrix?
262
    end
263
end
264
fclose(fid);