/
in_fopen_edf.m
515 lines (486 loc) · 23.1 KB
/
in_fopen_edf.m
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
function [sFile, ChannelMat, ImportOptions] = in_fopen_edf(DataFile, ImportOptions)
% IN_FOPEN_EDF: Open a BDF/EDF file (continuous recordings)
%
% USAGE: [sFile, ChannelMat, ImportOptions] = in_fopen_edf(DataFile, ImportOptions)
% @=============================================================================
% This function is part of the Brainstorm software:
% https://neuroimage.usc.edu/brainstorm
%
% Copyright (c) University of Southern California & McGill University
% This software is distributed under the terms of the GNU General Public License
% as published by the Free Software Foundation. Further details on the GPLv3
% license can be found at http://www.gnu.org/copyleft/gpl.html.
%
% FOR RESEARCH PURPOSES ONLY. THE SOFTWARE IS PROVIDED "AS IS," AND THE
% UNIVERSITY OF SOUTHERN CALIFORNIA AND ITS COLLABORATORS DO NOT MAKE ANY
% WARRANTY, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF
% MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE, NOR DO THEY ASSUME ANY
% LIABILITY OR RESPONSIBILITY FOR THE USE OF THIS SOFTWARE.
%
% For more information type "brainstorm license" at command prompt.
% =============================================================================@
%
% Authors: Francois Tadel, 2012-2021
% Parse inputs
if (nargin < 2) || isempty(ImportOptions)
ImportOptions = db_template('ImportOptions');
end
%% ===== READ HEADER =====
% Open file
fid = fopen(DataFile, 'r', 'ieee-le');
if (fid == -1)
error('Could not open file');
end
% Read all fields
hdr.version = fread(fid, [1 8], 'uint8=>char'); % Version of this data format ('0 ' for EDF, [255 'BIOSEMI'] for BDF)
hdr.patient_id = fread(fid, [1 80], '*char'); % Local patient identification
hdr.rec_id = fread(fid, [1 80], '*char'); % Local recording identification
hdr.startdate = fread(fid, [1 8], '*char'); % Startdate of recording (dd.mm.yy)
hdr.starttime = fread(fid, [1 8], '*char'); % Starttime of recording (hh.mm.ss)
hdr.hdrlen = str2double(fread(fid, [1 8], '*char')); % Number of bytes in header record
hdr.unknown1 = fread(fid, [1 44], '*char'); % Reserved ('24BIT' for BDF)
hdr.nrec = str2double(fread(fid, [1 8], '*char')); % Number of data records (-1 if unknown)
hdr.reclen = str2double(fread(fid, [1 8], '*char')); % Duration of a data record, in seconds
hdr.nsignal = str2double(fread(fid, [1 4], '*char')); % Number of signals in data record
% Check file integrity
if isnan(hdr.nsignal) || isempty(hdr.nsignal) || (hdr.nsignal ~= round(hdr.nsignal)) || (hdr.nsignal < 0)
error('File header is corrupted.');
end
% Read values for each nsignal
for i = 1:hdr.nsignal
hdr.signal(i).label = strtrim(fread(fid, [1 16], '*char'));
end
for i = 1:hdr.nsignal
hdr.signal(i).type = strtrim(fread(fid, [1 80], '*char'));
end
for i = 1:hdr.nsignal
hdr.signal(i).unit = strtrim(fread(fid, [1 8], '*char'));
end
for i = 1:hdr.nsignal
hdr.signal(i).physical_min = str2double(fread(fid, [1 8], '*char'));
end
for i = 1:hdr.nsignal
hdr.signal(i).physical_max = str2double(fread(fid, [1 8], '*char'));
end
for i = 1:hdr.nsignal
hdr.signal(i).digital_min = str2double(fread(fid, [1 8], '*char'));
end
for i = 1:hdr.nsignal
hdr.signal(i).digital_max = str2double(fread(fid, [1 8], '*char'));
end
for i = 1:hdr.nsignal
hdr.signal(i).filters = strtrim(fread(fid, [1 80], '*char'));
end
for i = 1:hdr.nsignal
hdr.signal(i).nsamples = str2num(fread(fid, [1 8], '*char'));
end
for i = 1:hdr.nsignal
hdr.signal(i).unknown2 = fread(fid, [1 32], '*char');
end
% Unknown record size, determine correct nrec
if (hdr.nrec == -1)
datapos = ftell(fid);
fseek(fid, 0, 'eof');
endpos = ftell(fid);
hdr.nrec = floor((endpos - datapos) / (sum([hdr.signal.nsamples]) * 2));
end
% Close file
fclose(fid);
%% ===== RECONSTRUCT INFO =====
% Individual signal gain
for i = 1:hdr.nsignal
% Interpet units
switch (hdr.signal(i).unit)
case 'mV', unit_gain = 1e3;
case {'uV', char([166 204 86])}, unit_gain = 1e6;
otherwise, unit_gain = 1;
end
% Check min/max values
if isempty(hdr.signal(i).digital_min) || isnan(hdr.signal(i).digital_min)
disp(['EDF> Warning: The digitial minimum is not set for channel "' hdr.signal(i).label '".']);
hdr.signal(i).digital_min = -2^15;
end
if isempty(hdr.signal(i).digital_max) || isnan(hdr.signal(i).digital_max)
disp(['EDF> Warning: The digitial maximum is not set for channel "' hdr.signal(i).label '".']);
hdr.signal(i).digital_max = -2^15;
end
if isempty(hdr.signal(i).physical_min) || isnan(hdr.signal(i).physical_min)
disp(['EDF> Warning: The physical minimum is not set for channel "' hdr.signal(i).label '".']);
hdr.signal(i).physical_min = hdr.signal(i).digital_min;
end
if isempty(hdr.signal(i).physical_max) || isnan(hdr.signal(i).physical_max)
disp(['EDF> Warning: The physical maximum is not set for channel "' hdr.signal(i).label '".']);
hdr.signal(i).physical_max = hdr.signal(i).digital_max;
end
if (hdr.signal(i).physical_min >= hdr.signal(i).physical_max)
disp(['EDF> Warning: Physical maximum larger than minimum for channel "' hdr.signal(i).label '".']);
hdr.signal(i).physical_min = hdr.signal(i).digital_min;
hdr.signal(i).physical_max = hdr.signal(i).digital_max;
end
% Calculate and save channel gain
hdr.signal(i).gain = unit_gain ./ (hdr.signal(i).physical_max - hdr.signal(i).physical_min) .* (hdr.signal(i).digital_max - hdr.signal(i).digital_min);
hdr.signal(i).offset = hdr.signal(i).physical_min ./ unit_gain - hdr.signal(i).digital_min ./ hdr.signal(i).gain;
% Error: The number of samples is not specified
if isempty(hdr.signal(i).nsamples)
% If it is not the first electrode: try to use the previous one
if (i > 1)
disp(['EDF> Warning: The number of samples is not specified for channel "' hdr.signal(i).label '".']);
hdr.signal(i).nsamples = hdr.signal(i-1).nsamples;
else
error(['The number of samples is not specified for channel "' hdr.signal(i).label '".']);
end
end
hdr.signal(i).sfreq = hdr.signal(i).nsamples ./ hdr.reclen;
end
% Find annotations channel
iAnnotChans = find(strcmpi({hdr.signal.label}, 'EDF Annotations')); % Mutliple "EDF Annotation" channels allowed in EDF+
iStatusChan = find(strcmpi({hdr.signal.label}, 'Status'), 1); % Only one "Status" channel allowed in BDF
iOtherChan = setdiff(1:hdr.nsignal, [iAnnotChans iStatusChan]);
% % Remove channels with lower sampling rates
% iIgnoreChan = find([hdr.signal(iOtherChan).sfreq] < max([hdr.signal(iOtherChan).sfreq])); % Ignore all the channels with lower sampling rate
% if ~isempty(iIgnoreChan)
% iOtherChan = setdiff(iOtherChan, iIgnoreChan);
% end
% Get all the other channels
if isempty(iOtherChan)
error('This file does not contain any data channel.');
end
% Read events preferencially from the EDF Annotations track
if ~isempty(iAnnotChans)
iEvtChans = iAnnotChans;
elseif ~isempty(iStatusChan)
iEvtChans = iStatusChan;
else
iEvtChans = [];
end
% % Detect channels with inconsistent sampling frenquency
% iErrChan = find([hdr.signal(iOtherChan).sfreq] ~= hdr.signal(iOtherChan(1)).sfreq);
% iErrChan = setdiff(iErrChan, iAnnotChans);
% if ~isempty(iErrChan)
% error('Files with mixed sampling rates are not supported yet.');
% end
% Detect interrupted signals (time non-linear)
hdr.interrupted = ischar(hdr.unknown1) && (length(hdr.unknown1) >= 5) && isequal(hdr.unknown1(1:5), 'EDF+D');
if hdr.interrupted
if ImportOptions.DisplayMessages
[res, isCancel] = java_dialog('question', ...
['Interrupted EDF file ("EDF+D") detected. It is recommended to convert it' 10 ...
'to a continuous ("EDF+C") file first. Do you want to continue reading this' 10 ...
'file as continuous and attempt to fix the timing of event markers?' 10 ...
'NOTE: This may not work as intended, use at your own risk!']);
hdr.fixinterrupted = ~isCancel && strcmpi(res, 'yes');
else
hdr.fixinterrupted = 1;
end
if ~hdr.fixinterrupted
warning(['Interrupted EDF file ("EDF+D"): requires conversion to "EDF+C". ' 10 ...
'Brainstorm will read this file as a continuous file ("EDF+C"), the timing of the samples after the first discontinuity will be wrong.' 10 ...
'This may not cause any major problem unless there are time markers in the file, they might be inaccurate in all the segments >= 2.']);
end
else
hdr.fixinterrupted = 0;
end
%% ===== CREATE BRAINSTORM SFILE STRUCTURE =====
% Initialize returned file structure
sFile = db_template('sfile');
% Add information read from header
sFile.byteorder = 'l';
sFile.filename = DataFile;
if (uint8(hdr.version(1)) == uint8(255))
sFile.format = 'EEG-BDF';
sFile.device = 'BDF';
else
sFile.format = 'EEG-EDF';
sFile.device = 'EDF';
end
sFile.header = hdr;
% Comment: short filename
[tmp__, sFile.comment, tmp__] = bst_fileparts(DataFile);
% No info on bad channels
sFile.channelflag = ones(hdr.nsignal,1);
% Acquisition date
sFile.acq_date = str_date(hdr.startdate);
%% ===== PROCESS CHANNEL NAMES/TYPES =====
% Try to split the channel names in "TYPE NAME"
SplitType = repmat({''}, 1, hdr.nsignal);
SplitName = repmat({''}, 1, hdr.nsignal);
for i = 1:hdr.nsignal
if ~isempty(hdr.signal(i).label)
% Removing trailing dots (eg. "Fc5." instead of "FC5", as in: https://www.physionet.org/pn4/eegmmidb/)
if (hdr.signal(i).label(end) == '.') && (length(hdr.signal(i).label) > 1)
hdr.signal(i).label(end) = [];
if (hdr.signal(i).label(end) == '.') && (length(hdr.signal(i).label) > 1)
hdr.signal(i).label(end) = [];
if (hdr.signal(i).label(end) == '.') && (length(hdr.signal(i).label) > 1)
hdr.signal(i).label(end) = [];
end
end
end
% Remove extra spaces
signalLabel = strrep(hdr.signal(i).label, ' - ', '-');
% Find space chars (label format "Type Name")
iSpace = find(signalLabel == ' ');
% Only if there is one space only
if (length(iSpace) == 1) && (iSpace >= 3)
SplitName{i} = signalLabel(iSpace+1:end);
SplitType{i} = signalLabel(1:iSpace-1);
% Accept also 2 spaces
elseif (length(iSpace) == 2) && (iSpace(1) >= 3)
SplitName{i} = strrep(signalLabel(iSpace(1)+1:end), ' ', '_');
SplitType{i} = signalLabel(1:iSpace(1)-1);
end
else
hdr.signal(i).label = sprintf('E%d', i);
end
end
% Remove the classification if it makes some names non unique
uniqueNames = unique(SplitName);
for i = 1:length(uniqueNames)
if ~isempty(uniqueNames{i})
iName = find(strcmpi(SplitName, uniqueNames{i}));
if (length(iName) > 1)
[SplitName{iName}] = deal('');
[SplitType{iName}] = deal('');
end
end
end
%% ===== CREATE EMPTY CHANNEL FILE =====
ChannelMat = db_template('channelmat');
ChannelMat.Comment = [sFile.device ' channels'];
ChannelMat.Channel = repmat(db_template('channeldesc'), [1, hdr.nsignal]);
chRef = {};
% For each channel
for i = 1:hdr.nsignal
% If is the annotation channel
if ~isempty(iAnnotChans) && ismember(i, iAnnotChans)
ChannelMat.Channel(i).Type = 'EDF';
ChannelMat.Channel(i).Name = 'Annotations';
elseif ~isempty(iStatusChan) && (i == iStatusChan)
ChannelMat.Channel(i).Type = 'BDF';
ChannelMat.Channel(i).Name = 'Status';
% Regular channels
else
% If there is a pair name/type already detected
if ~isempty(SplitName{i}) && ~isempty(SplitType{i})
ChannelMat.Channel(i).Name = SplitName{i};
ChannelMat.Channel(i).Type = SplitType{i};
else
% Channel name
ChannelMat.Channel(i).Name = hdr.signal(i).label(hdr.signal(i).label ~= ' ');
% Channel type
if ~isempty(hdr.signal(i).type)
if (length(hdr.signal(i).type) == 3)
ChannelMat.Channel(i).Type = hdr.signal(i).type(hdr.signal(i).type ~= ' ');
elseif isequal(hdr.signal(i).type, 'Active Electrode') || isequal(hdr.signal(i).type, 'AgAgCl electrode')
ChannelMat.Channel(i).Type = 'EEG';
else
ChannelMat.Channel(i).Type = 'Misc';
end
else
ChannelMat.Channel(i).Type = 'EEG';
end
end
% Extract reference name (at the end of the channel name, separated with a "-", eg. "-REF")
iDash = find(ChannelMat.Channel(i).Name == '-');
if ~isempty(iDash) && (iDash(end) < length(ChannelMat.Channel(i).Name))
chRef{end+1} = ChannelMat.Channel(i).Name(iDash(end):end);
end
end
ChannelMat.Channel(i).Loc = [0; 0; 0];
ChannelMat.Channel(i).Orient = [];
ChannelMat.Channel(i).Weight = 1;
% ChannelMat.Channel(i).Comment = hdr.signal(i).type;
end
% If the same reference is indicated for all the channels: remove it
if (length(chRef) >= 2)
% Get the shortest reference tag
lenRef = cellfun(@length, chRef);
minLen = min(lenRef);
% Check if all the ref names are equal (up to the max length - some might be cut because the channel name is too long)
if all(cellfun(@(c)strcmpi(c(1:minLen), chRef{1}(1:minLen)), chRef))
% Remove the reference tag from all the channel names
for i = 1:length(ChannelMat.Channel)
ChannelMat.Channel(i).Name = strrep(ChannelMat.Channel(i).Name, chRef{1}, '');
ChannelMat.Channel(i).Name = strrep(ChannelMat.Channel(i).Name, chRef{1}(1:minLen), '');
end
end
end
% If there are only "Misc" and no "EEG" channels: rename to "EEG"
iMisc = find(strcmpi({ChannelMat.Channel.Type}, 'Misc'));
iEeg = find(strcmpi({ChannelMat.Channel.Type}, 'EEG'));
if ~isempty(iMisc) && isempty(iEeg)
[ChannelMat.Channel(iMisc).Type] = deal('EEG');
iEeg = iMisc;
end
%% ===== DETECT MULTIPLE SAMPLING RATES =====
% Use the first "EEG" channel as the reference sampling rate (or the first channel if no "EEG" channels available)
if ~isempty(iEeg) && ismember(iEeg(1), iOtherChan)
iChanFreqRef = iEeg(1);
else
iChanFreqRef = iOtherChan(1);
end
% Mark as bad channels with sampling rates different from EEG
iChanWrongRate = find([sFile.header.signal.sfreq] ~= sFile.header.signal(iChanFreqRef).sfreq);
iChanWrongRate = intersect(iChanWrongRate, iOtherChan);
if ~isempty(iChanWrongRate)
sFile.channelflag(iChanWrongRate) = -1;
end
% Consider that the sampling rate of the file is the sampling rate of the first signal
sFile.prop.sfreq = hdr.signal(iChanFreqRef).sfreq;
sFile.prop.times = [0, hdr.signal(iChanFreqRef).nsamples * hdr.nrec - 1] ./ sFile.prop.sfreq;
sFile.prop.nAvg = 1;
%% ===== READ EDF ANNOTATION CHANNEL =====
if ~isempty(iEvtChans) % && ~isequal(ImportOptions.EventsMode, 'ignore')
% Set reading options
ImportOptions.ImportMode = 'Time';
ImportOptions.UseSsp = 0;
ImportOptions.UseCtfComp = 0;
% Read EDF annotations
if strcmpi(sFile.format, 'EEG-EDF')
evtList = {};
% By default: the file starts at 0s (definitions useful in case the first records are missing)
t0_file = 0;
prev_rec = 0;
prev_irec = 1;
% In EDF+, the first annotation channel has epoch time stamps (EDF
% calls epochs records). So read all annotation channels per epoch.
for irec = 1:hdr.nrec
for ichan = 1:length(iEvtChans)
bst_progress('text', sprintf('Reading annotations... [%d%%]', round((ichan + (irec-1)*length(iEvtChans))/length(iEvtChans)/hdr.nrec*100)));
% Sample indices for the current epoch (=record)
SampleBounds = [irec-1,irec] * sFile.header.signal(iEvtChans(ichan)).nsamples - [0,1];
% Read record
F = in_fread(sFile, ChannelMat, 1, SampleBounds, iEvtChans(ichan), ImportOptions);
% Find all the TALs (Time-stamped Annotations Lists): separated with [20][0]
% Possible configurations:
% Onset[21]Duration[20]Annot1[20]Annot2[20]..AnnotN[20][0]
% Onset[20]Annot1[20]Annot2[20]..AnnotN[20][0]
% Onset[20]Annot1[20][0]
% Onset[20][20][0] : First TAL of a record, with an empty annotation, indicating the offset from the beginning of the file
% Onset[20][20]Annot2[20]..AnnotN[20][0] : First TAL + extra annotations
iSeparator = [-1, find((F(1:end-1) == 20) & (F(2:end) == 0))];
% Loop on all the TALs
for iAnnot = 1:length(iSeparator)-1
% Get annotation
strAnnot = char(F(iSeparator(iAnnot)+2:iSeparator(iAnnot+1)-1));
% Split in blocks with [20]
splitAnnot = str_split(strAnnot, 20, 0);
% The first TAL in a record should be indicating the timing of the first sample of the record (empty annotation)
if (iAnnot == 1) && ((length(splitAnnot) == 1) || isempty(splitAnnot{2})) && ~any(splitAnnot{1} == 21)
% Ignore if this is not the first channel
if (ichan > 1)
continue;
end
% Get record time stamp
t0_rec = str2double(splitAnnot{1});
if isempty(t0_rec) || isnan(t0_rec)
continue;
end
if (irec == 1)
t0_file = t0_rec;
% Find discontinuities larger than 1 sample
elseif abs(t0_rec - prev_rec - (irec - prev_irec) * hdr.reclen) > (1 / sFile.prop.sfreq)
% Brainstorm fills partial/interrupted records with zeros
bstTime = prev_rec + hdr.reclen;
timeDiff = bstTime - t0_rec;
% If we want to fix timing, apply skip to initial timestamp
if hdr.fixinterrupted
t0_file = t0_file - timeDiff;
end
% Warn user of discontinuity
if timeDiff > 0
expectMsg = 'blank data';
else
expectMsg = 'skipped data';
end
startTime = min(t0_rec - t0_file - [0, timeDiff]); % before and after t0_file adjustment
endTime = max(t0_rec - t0_file - [0, timeDiff]);
fprintf('WARNING: Found discontinuity between %.3fs and %.3fs, expect %s in between.\n', startTime, endTime, expectMsg);
% Create event for users information
if timeDiff < 0
endTime = startTime; % no extent in this case, there is skipped time.
end
evtList(end+1,:) = {'EDF+D Discontinuity', [startTime; endTime]};
end
prev_rec = t0_rec;
prev_irec = irec;
% If there are extra annotations for this TAL: Create one event for each label in the TAL
for iLabel = 3:length(splitAnnot)
evtList(end+1,:) = {splitAnnot{iLabel}, (t0_rec-t0_file) + [0;0]};
end
% Regular TAL: indicating a marker
else
% Split time in onset/duration
splitTime = str_split(splitAnnot{1}, 21);
% Get time
t = str2double(splitTime{1});
if isempty(t) || isnan(t)
continue;
end
% Get duration
if (length(splitTime) > 1)
duration = str2double(splitTime{2});
% Exclude 1-sample long events
if isempty(duration) || isnan(duration) || (round(duration .* sFile.prop.sfreq) <= 1)
duration = 0;
end
else
duration = 0;
end
% Unnamed event
if (length(splitAnnot) == 1) || isempty(splitAnnot{2})
splitAnnot{2} = 'Unnamed';
end
% Create one event for each label in the TAL
for iLabel = 2:length(splitAnnot)
evtList(end+1,:) = {splitAnnot{iLabel}, (t-t0_file) + [0;duration]};
end
end
end
end
end
% If there are events: create a create an events structure
if ~isempty(evtList)
% Initialize events list
sFile.events = repmat(db_template('event'), 0);
% Events list
[uniqueEvt, iUnique] = unique(evtList(:,1));
uniqueEvt = evtList(sort(iUnique),1);
% Build events list
for iEvt = 1:length(uniqueEvt)
% Find all the occurrences of this event
iOcc = find(strcmpi(uniqueEvt{iEvt}, evtList(:,1)));
% Concatenate all times
t = [evtList{iOcc,2}];
% If second row is equal to the first one (no extended events): delete it
if all(t(1,:) == t(2,:))
t = t(1,:);
end
% Set event
sFile.events(iEvt).label = strtrim(uniqueEvt{iEvt});
sFile.events(iEvt).times = round(t .* sFile.prop.sfreq) ./ sFile.prop.sfreq;
sFile.events(iEvt).epochs = 1 + 0*t(1,:);
sFile.events(iEvt).select = 1;
sFile.events(iEvt).channels = cell(1, size(sFile.events(iEvt).times, 2));
sFile.events(iEvt).notes = cell(1, size(sFile.events(iEvt).times, 2));
end
end
% BDF Status line
elseif strcmpi(sFile.format, 'EEG-BDF') && ~strcmpi(ImportOptions.EventsTrackMode, 'ignore')
% Ask how to read the events
[events, ImportOptions.EventsTrackMode] = process_evt_read('Compute', sFile, ChannelMat, ChannelMat.Channel(iEvtChans).Name, ImportOptions.EventsTrackMode);
if isequal(events, -1)
sFile = [];
ChannelMat = [];
return;
end
% Report the events in the file structure
sFile.events = events;
% Remove the 'Status: ' string in front of the events
for i = 1:length(sFile.events)
sFile.events(i).label = strrep(sFile.events(i).label, 'Status: ', '');
end
% Group events by time
% sFile.events = process_evt_grouptime('Compute', sFile.events);
end
end