|
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); |