forked from fieldtrip/fieldtrip
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathft_preprocessing.m
707 lines (634 loc) · 32.8 KB
/
ft_preprocessing.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
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
function [data] = ft_preprocessing(cfg, data)
% FT_PREPROCESSING reads MEG and/or EEG data according to user-specified trials
% and applies several user-specified preprocessing steps to the signals.
%
% Use as
% [data] = ft_preprocessing(cfg)
% or
% [data] = ft_preprocessing(cfg, data)
%
% The first input argument "cfg" is the configuration structure, which contains all
% details for the dataset filename, trials and the preprocessing options.
%
% If you are calling FT_PREPROCESSING with only the configuration as first
% input argument and the data still has to be read from file, you should
% specify
% cfg.dataset = string with the filename
% cfg.trl = Nx3 matrix with the trial definition, see FT_DEFINETRIAL
% cfg.padding = length (in seconds) to which the trials are padded for filtering (default = 0)
% cfg.padtype = string, type of padding (default: 'data' padding or
% 'mirror', depending on feasibility)
% cfg.continuous = 'yes' or 'no' whether the file contains continuous data
% (default is determined automatic)
%
% Instead of specifying the dataset in the configuration, you can also explicitly
% specify the name of the file containing the header information and the name of the
% file containing the data, using
% cfg.datafile = string with the filename
% cfg.headerfile = string with the filename
%
% If you are calling FT_PREPROCESSING with the second input argument "data", then
% that should contain data that was already read from file in a previous call to
% FT_PREPROCESSING. In that case only the configuration options below apply.
%
% The channels that will be read and/or preprocessed are specified with
% cfg.channel = Nx1 cell-array with selection of channels (default = 'all'),
% see FT_CHANNELSELECTION for details
% cfg.chantype = string or Nx1 cell-array with channel types to be read (only for NeuroOmega)
%
% The preprocessing options for the selected channels are specified with
% cfg.lpfilter = 'no' or 'yes' lowpass filter (default = 'no')
% cfg.hpfilter = 'no' or 'yes' highpass filter (default = 'no')
% cfg.bpfilter = 'no' or 'yes' bandpass filter (default = 'no')
% cfg.bsfilter = 'no' or 'yes' bandstop filter (default = 'no')
% cfg.dftfilter = 'no' or 'yes' line noise removal using discrete fourier transform (default = 'no')
% cfg.medianfilter = 'no' or 'yes' jump preserving median filter (default = 'no')
% cfg.lpfreq = lowpass frequency in Hz
% cfg.hpfreq = highpass frequency in Hz
% cfg.bpfreq = bandpass frequency range, specified as [lowFreq highFreq] in Hz
% cfg.bsfreq = bandstop frequency range, specified as [low high] in Hz (or as Nx2 matrix for notch filter)
% cfg.dftfreq = line noise frequencies in Hz for DFT filter (default = [50 100 150])
% cfg.lpfiltord = lowpass filter order (default set in low-level function)
% cfg.hpfiltord = highpass filter order (default set in low-level function)
% cfg.bpfiltord = bandpass filter order (default set in low-level function)
% cfg.bsfiltord = bandstop filter order (default set in low-level function)
% cfg.lpfilttype = digital filter type, 'but' or 'firws' or 'fir' or 'firls' (default = 'but')
% cfg.hpfilttype = digital filter type, 'but' or 'firws' or 'fir' or 'firls' (default = 'but')
% cfg.bpfilttype = digital filter type, 'but' or 'firws' or 'fir' or 'firls' (default = 'but')
% cfg.bsfilttype = digital filter type, 'but' or 'firws' or 'fir' or 'firls' (default = 'but')
% cfg.lpfiltdir = filter direction, 'twopass' (default), 'onepass' or 'onepass-reverse' or 'onepass-zerophase' (default for firws) or 'onepass-minphase' (firws, non-linear!)
% cfg.hpfiltdir = filter direction, 'twopass' (default), 'onepass' or 'onepass-reverse' or 'onepass-zerophase' (default for firws) or 'onepass-minphase' (firws, non-linear!)
% cfg.bpfiltdir = filter direction, 'twopass' (default), 'onepass' or 'onepass-reverse' or 'onepass-zerophase' (default for firws) or 'onepass-minphase' (firws, non-linear!)
% cfg.bsfiltdir = filter direction, 'twopass' (default), 'onepass' or 'onepass-reverse' or 'onepass-zerophase' (default for firws) or 'onepass-minphase' (firws, non-linear!)
% cfg.lpinstabilityfix = deal with filter instability, 'no', 'reduce', 'split' (default = 'no')
% cfg.hpinstabilityfix = deal with filter instability, 'no', 'reduce', 'split' (default = 'no')
% cfg.bpinstabilityfix = deal with filter instability, 'no', 'reduce', 'split' (default = 'no')
% cfg.bsinstabilityfix = deal with filter instability, 'no', 'reduce', 'split' (default = 'no')
% cfg.lpfiltdf = lowpass transition width (firws, overrides order, default set in low-level function)
% cfg.hpfiltdf = highpass transition width (firws, overrides order, default set in low-level function)
% cfg.bpfiltdf = bandpass transition width (firws, overrides order, default set in low-level function)
% cfg.bsfiltdf = bandstop transition width (firws, overrides order, default set in low-level function)
% cfg.lpfiltwintype = lowpass window type, 'hann' or 'hamming' (default) or 'blackman' or 'kaiser' (firws)
% cfg.hpfiltwintype = highpass window type, 'hann' or 'hamming' (default) or 'blackman' or 'kaiser' (firws)
% cfg.bpfiltwintype = bandpass window type, 'hann' or 'hamming' (default) or 'blackman' or 'kaiser' (firws)
% cfg.bsfiltwintype = bandstop window type, 'hann' or 'hamming' (default) or 'blackman' or 'kaiser' (firws)
% cfg.lpfiltdev = lowpass max passband deviation (firws with 'kaiser' window, default 0.001 set in low-level function)
% cfg.hpfiltdev = highpass max passband deviation (firws with 'kaiser' window, default 0.001 set in low-level function)
% cfg.bpfiltdev = bandpass max passband deviation (firws with 'kaiser' window, default 0.001 set in low-level function)
% cfg.bsfiltdev = bandstop max passband deviation (firws with 'kaiser' window, default 0.001 set in low-level function)
% cfg.dftreplace = 'zero' or 'neighbour', method used to reduce line noise, 'zero' implies DFT filter, 'neighbour' implies spectrum interpolation (default = 'zero')
% cfg.dftbandwidth = bandwidth of line noise frequencies, applies to spectrum interpolation, in Hz (default = [1 2 3])
% cfg.dftneighbourwidth = bandwidth of frequencies neighbouring line noise frequencies, applies to spectrum interpolation, in Hz (default = [2 2 2])
% cfg.plotfiltresp = 'no' or 'yes', plot filter responses (firws, default = 'no')
% cfg.usefftfilt = 'no' or 'yes', use fftfilt instead of filter (firws, default = 'no')
% cfg.medianfiltord = length of median filter (default = 9)
% cfg.demean = 'no' or 'yes', whether to apply baseline correction (default = 'no')
% cfg.baselinewindow = [begin end] in seconds, the default is the complete trial (default = 'all')
% cfg.detrend = 'no' or 'yes', remove linear trend from the data (done per trial) (default = 'no')
% cfg.polyremoval = 'no' or 'yes', remove higher order trend from the data (done per trial) (default = 'no')
% cfg.polyorder = polynome order for poly trend removal (default = 2; note that all lower-order trends will also be removed when using cfg.polyremoval)
% cfg.derivative = 'no' or 'yes', computes the first order derivative of the data (default = 'no')
% cfg.hilbert = 'no', 'abs', 'complex', 'real', 'imag', 'absreal', 'absimag' or 'angle' (default = 'no')
% cfg.rectify = 'no' or 'yes' (default = 'no')
% cfg.precision = 'single' or 'double' (default = 'double')
% cfg.absdiff = 'no' or 'yes', computes absolute derivative (i.e. first derivative then rectify)
%
% Preprocessing options that only apply to MEG data are
% cfg.coordsys = string, 'head' or 'dewar' (default = 'head')
% cfg.coilaccuracy = can be empty or a number (0, 1 or 2) to specify the accuracy (default = [])
% cfg.coildeffile = can be empty or a string to a custom coil_def.dat file (default = [])
%
% Preprocessing options that you should only use for EEG data are
% cfg.reref = 'no' or 'yes' (default = 'no')
% cfg.refchannel = cell-array with new EEG reference channel(s), this can be 'all' for a common average reference
% cfg.refmethod = 'avg', 'median', 'rest', 'bipolar' or 'laplace' (default = 'avg')
% cfg.groupchans = 'yes' or 'no', should channels be rereferenced in separate groups for bipolar and laplace methods,
% this requires channnels to be named using an alphanumeric code, where letters represent the group
% and numbers represent the order of the channel whithin its group (default = 'no')
% cfg.leadfield = leadfield structure, this is required when cfg.refmethod='rest', see FT_PREPARE_LEADFIELD
% cfg.implicitref = 'label' or empty, add the implicit EEG reference as zeros (default = [])
% cfg.montage = 'no' or a montage structure, see FT_APPLY_MONTAGE (default = 'no')
%
% Preprocessing options that you should only use when you are calling FT_PREPROCESSING with
% also the second input argument "data" are
% cfg.trials = 'all' or a selection given as a 1xN vector (default = 'all')
%
% Preprocessing options that you should only use when you are calling
% FT_PREPROCESSING with a single cfg input argument are
% cfg.method = 'trial' or 'channel', read data per trial or per channel (default = 'trial')
%
% To facilitate data-handling and distributed computing you can use
% cfg.inputfile = ...
% cfg.outputfile = ...
% If you specify one of these (or both) the input data will be read from a *.mat
% file on disk and/or the output data will be written to a *.mat file. These mat
% files should contain only a single variable, corresponding with the
% input/output structure.
%
% See also FT_DEFINETRIAL, FT_REDEFINETRIAL, FT_APPENDDATA, FT_APPENDSPIKE
% Undocumented local options:
% cfg.paddir = direction of padding, 'left'/'right'/'both' (default = 'both')
% cfg.artfctdef =
% cfg.removemcg =
% cfg.montage = (in combination with meg-data in the input) applies montage to both data and grad-structure)
%
% You can use this function to read data from one format, filter it, and
% write it to disk in another format. The reading is done either as one
% long continuous segment or in multiple trials. This is achieved by
% cfg.export.dataset = string with the output file name
% cfg.export.dataformat = string describing the output file format, see FT_WRITE_DATA
% Copyright (C) 2003-2022, Robert Oostenveld, SMI, FCDC
%
% This file is part of FieldTrip, see http://www.fieldtriptoolbox.org
% for the documentation and details.
%
% FieldTrip is free software: you can redistribute it and/or modify
% it under the terms of the GNU General Public License as published by
% the Free Software Foundation, either version 3 of the License, or
% (at your option) any later version.
%
% FieldTrip is distributed in the hope that it will be useful,
% but WITHOUT ANY WARRANTY; without even the implied warranty of
% MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
% GNU General Public License for more details.
%
% You should have received a copy of the GNU General Public License
% along with FieldTrip. If not, see <http://www.gnu.org/licenses/>.
%
% $Id$
% these are used by the ft_preamble/ft_postamble function and scripts
ft_revision = '$Id$';
ft_nargin = nargin;
ft_nargout = nargout;
% do the general setup of the function
ft_defaults
ft_preamble init
ft_preamble debug
ft_preamble loadvar data
ft_preamble provenance data
% the ft_abort variable is set to true or false in ft_preamble_init
if ft_abort
return
end
% return immediately after distributed execution
if ~isempty(ft_getopt(cfg, 'distribute'))
return
end
% check if the input cfg is valid for this function
cfg = ft_checkconfig(cfg, 'forbidden', {'channels', 'trial'}); % prevent accidental typos, see issue 1729
cfg = ft_checkconfig(cfg, 'renamed', {'blc', 'demean'});
cfg = ft_checkconfig(cfg, 'renamed', {'blcwindow', 'baselinewindow'});
cfg = ft_checkconfig(cfg, 'renamed', {'output', 'export'});
% set the defaults
cfg.method = ft_getopt(cfg, 'method', 'trial');
cfg.channel = ft_getopt(cfg, 'channel', 'all');
cfg.removemcg = ft_getopt(cfg, 'removemcg', 'no');
cfg.removeeog = ft_getopt(cfg, 'removeeog', 'no');
cfg.precision = ft_getopt(cfg, 'precision', 'double');
cfg.padding = ft_getopt(cfg, 'padding', 0); % padding is only done when filtering
cfg.paddir = ft_getopt(cfg, 'paddir', 'both');
cfg.montage = ft_getopt(cfg, 'montage', 'no');
cfg.updatesens = ft_getopt(cfg, 'updatesens', 'no'); % in case a montage or rereferencing is specified
cfg.dataformat = ft_getopt(cfg, 'dataformat'); % is passed to low-level function, empty implies autodetection
% these options relate to the actual preprocessing, it is necessary to specify here because of padding
cfg.dftfilter = ft_getopt(cfg, 'dftfilter', 'no');
cfg.lpfilter = ft_getopt(cfg, 'lpfilter', 'no');
cfg.hpfilter = ft_getopt(cfg, 'hpfilter', 'no');
cfg.bpfilter = ft_getopt(cfg, 'bpfilter', 'no');
cfg.bsfilter = ft_getopt(cfg, 'bsfilter', 'no');
cfg.medianfilter = ft_getopt(cfg, 'medianfilter', 'no');
cfg.padtype = ft_getopt(cfg, 'padtype', 'data');
% these options relate to the actual preprocessing, it is necessary to specify here because of channel selection
cfg.reref = ft_getopt(cfg, 'reref', 'no');
cfg.refchannel = ft_getopt(cfg, 'refchannel', {});
cfg.refmethod = ft_getopt(cfg, 'refmethod', 'avg');
cfg.groupchans = ft_getopt(cfg, 'groupchans', 'no');
cfg.implicitref = ft_getopt(cfg, 'implicitref');
% construct the low-level options as key-value pairs, these are passed to FT_READ_HEADER and FT_READ_DATA
headeropt = {};
headeropt = ft_setopt(headeropt, 'headerformat', ft_getopt(cfg, 'headerformat')); % is passed to low-level function, empty implies autodetection
headeropt = ft_setopt(headeropt, 'readbids', ft_getopt(cfg, 'readbids')); % is passed to low-level function
headeropt = ft_setopt(headeropt, 'coordsys', ft_getopt(cfg, 'coordsys', 'head')); % is passed to low-level function
headeropt = ft_setopt(headeropt, 'coilaccuracy', ft_getopt(cfg, 'coilaccuracy')); % is passed to low-level function
headeropt = ft_setopt(headeropt, 'coildeffile', ft_getopt(cfg, 'coildeffile')); % is passed to low-level function
headeropt = ft_setopt(headeropt, 'checkmaxfilter', ft_getopt(cfg, 'checkmaxfilter')); % this allows to read non-maxfiltered neuromag data recorded with internal active shielding
headeropt = ft_setopt(headeropt, 'chantype', ft_getopt(cfg, 'chantype', {})); % 2017.10.10 AB required for NeuroOmega files
if ~isfield(cfg, 'feedback')
if strcmp(cfg.method, 'channel')
cfg.feedback = 'none';
else
cfg.feedback = 'text';
end
end
% support for the following options was removed on 20 August 2004 in Revision 1.46
if isfield(cfg, 'emgchannel'), ft_error('EMG specific preprocessing is not supported any more'); end
if isfield(cfg, 'emghpfreq'), ft_error('EMG specific preprocessing is not supported any more'); end
if isfield(cfg, 'emgrectify'), ft_error('EMG specific preprocessing is not supported any more'); end
if isfield(cfg, 'emghilbert'), ft_error('EMG specific preprocessing is not supported any more'); end
if isfield(cfg, 'eegchannel'), ft_error('EEG specific preprocessing is not supported any more'); end
if isfield(cfg, 'resamplefs'), ft_error('resampling is not supported any more, see RESAMPLEDATA'); end
if isfield(cfg, 'lnfilter') && strcmp(cfg.lnfilter, 'yes')
ft_error('line noise filtering using the option cfg.lnfilter is not supported any more, use cfg.bsfilter instead')
end
% this relates to a previous fix to handle 32 bit neuroscan data
if isfield(cfg, 'nsdf')
% FIXME this should be handled by ft_checkconfig, but ft_checkconfig does not allow yet for
% specific errors in the case of forbidden fields
ft_error('The use of cfg.nsdf is deprecated. FieldTrip tries to determine the bit resolution automatically. You can overrule this by specifying cfg.dataformat and cfg.headerformat. See: http://www.fieldtriptoolbox.org/faq/i_have_problems_reading_in_neuroscan_.cnt_files._how_can_i_fix_this');
end
if isfield(cfg, 'export') && ~isempty(cfg.export)
% export the data to an output file
if ~strcmp(cfg.method, 'trial')
ft_error('exporting to an output file is only possible when processing all channels at once')
end
end
% data can be passed by the user, but might also have been loaded from cfg.inputfile
hasdata = exist('data', 'var');
if hasdata
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% do preprocessing of data that has already been read into memory
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% this is used to convert the data back to timelock later
convert = ft_datatype(data);
% check if the input data is valid for this function, the input data must be raw
data = ft_checkdata(data, 'datatype', {'raw+comp', 'raw'}, 'hassampleinfo', 'yes');
% check if the input cfg is valid for this function
cfg = ft_checkconfig(cfg, 'forbidden', {'trl', 'dataset', 'datafile', 'headerfile'});
if cfg.padding>0
if strcmp(cfg.dftfilter, 'yes') || ...
strcmp(cfg.lpfilter, 'yes') || ...
strcmp(cfg.hpfilter, 'yes') || ...
strcmp(cfg.bpfilter, 'yes') || ...
strcmp(cfg.bsfilter, 'yes') || ...
strcmp(cfg.medianfilter, 'yes')
padding = round(cfg.padding * data.fsample);
if strcmp(cfg.padtype, 'data')
ft_warning('datapadding not possible with in-memory data - padding will be performed by data mirroring');
cfg.padtype = 'mirror';
end
else
% no filtering will be done, hence no padding is necessary
padding = 0;
end
% update the configuration (in seconds) for external reference
cfg.padding = padding / data.fsample;
else
% no padding was requested
padding = 0;
end
% some options don't make sense on component data
if isfield(data, 'comp')
if ~isempty(cfg.montage)
ft_error('the application of a montage on component data is not supported');
end
if strcmp(cfg.reref, 'yes')
ft_error('rereferencing component data is not supported');
end
end
% set the defaults
cfg.trials = ft_getopt(cfg, 'trials', 'all', 1);
% select trials of interest
tmpcfg = keepfields(cfg, {'trials', 'channel', 'latency', 'tolerance', 'showcallinfo', 'trackcallinfo', 'trackusage', 'trackdatainfo', 'trackmeminfo', 'tracktimeinfo', 'checksize'});
data = ft_selectdata(tmpcfg, data);
% restore the provenance information
[cfg, data] = rollback_provenance(cfg, data);
% this will contain the newly processed data
% some fields from the input should be copied over in the output
dataout = keepfields(data, {'hdr', 'fsample', 'grad', 'elec', 'opto', 'sampleinfo', 'trialinfo', 'topo', 'topolabel', 'unmixing'});
ft_progress('init', cfg.feedback, 'preprocessing');
ntrl = length(data.trial);
dataout.trial = cell(1, ntrl);
dataout.time = cell(1, ntrl);
for i=1:ntrl
ft_progress(i/ntrl, 'preprocessing trial %d from %d\n', i, ntrl);
nsamples = numel(data.time{i});
% pad data by mirroring
if nsamples>padding
% the trial is already longer than the total length requested
begpadding = 0;
endpadding = 0;
if padding > 0
ft_warning('no padding applied because the padding duration is shorter than the trial');
end
else
switch cfg.paddir
case 'both'
% begpadding+nsamples+endpadding = total length of data
begpadding = ceil((padding-nsamples)/2);
endpadding = floor((padding-nsamples)/2);
case 'left'
begpadding = padding-nsamples;
endpadding = 0;
case 'right'
begpadding = 0;
endpadding = padding-nsamples;
otherwise
ft_error('unsupported requested direction of padding');
end
end
data.trial{i} = ft_preproc_padding(data.trial{i}, cfg.padtype, begpadding, endpadding);
data.time{i} = ft_preproc_padding(data.time{i}, 'nan', begpadding, endpadding); % pad time-axis with nans (see bug2220)
% do the filtering etc.
[dataout.trial{i}, dataout.label, dataout.time{i}, cfg] = preproc(data.trial{i}, data.label, data.time{i}, cfg, begpadding, endpadding);
end % for all trials
% convert back to input type if necessary
switch convert
case 'timelock'
dataout = ft_checkdata(dataout, 'datatype', 'timelock');
otherwise
% keep the output as it is
end
ft_progress('close');
else
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% read the data from file and do the preprocessing
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
if isfield(cfg, 'trialdef') && ~isfield(cfg, 'trl')
ft_error('you must call FT_DEFINETRIAL prior to FT_PREPROCESSING');
end
% check if the input cfg is valid for this function
cfg = ft_checkconfig(cfg, 'dataset2files', 'yes');
cfg = ft_checkconfig(cfg, 'required', {'headerfile', 'datafile'});
cfg = ft_checkconfig(cfg, 'renamed', {'datatype', 'continuous'});
cfg = ft_checkconfig(cfg, 'renamedval', {'continuous', 'continuous', 'yes'});
% read the header
hdr = ft_read_header(cfg.headerfile, headeropt{:});
% this option relates to reading over trial boundaries in a pseudo-continuous dataset
if ~isfield(cfg, 'continuous')
if hdr.nTrials==1
cfg.continuous = 'yes';
else
cfg.continuous = 'no';
end
end
if ~isfield(cfg, 'trl')
% treat the data as continuous if possible, otherwise define all trials as indicated in the header
if strcmp(cfg.continuous, 'yes')
trl = zeros(1, 3);
trl(1,1) = 1;
trl(1,2) = hdr.nSamples*hdr.nTrials;
trl(1,3) = -hdr.nSamplesPre;
else
trl = zeros(hdr.nTrials, 3);
for i=1:hdr.nTrials
trl(i,1) = (i-1)*hdr.nSamples + 1;
trl(i,2) = (i )*hdr.nSamples ;
trl(i,3) = -hdr.nSamplesPre;
end
end
cfg.trl = trl;
elseif ischar(cfg.trl)
% load the trial information from file
cfg.trl = loadvar(cfg.trl, 'trl');
end
% the code below expects an Nx3 matrix with begsample, endsample and offset
if istable(cfg.trl)
trl = table2array(cfg.trl(:,1:3));
else
trl = cfg.trl(:,1:3);
end
% this should be a cell-array
if ~iscell(cfg.channel) && ischar(cfg.channel)
cfg.channel = {cfg.channel};
end
% this should be a cell-array
if ~iscell(cfg.refchannel) && ischar(cfg.refchannel)
cfg.refchannel = {cfg.refchannel};
end
% do a sanity check for the re-referencing
if strcmp(cfg.reref, 'no') && ~isempty(cfg.refchannel)
ft_warning('no re-referencing is performed');
cfg.refchannel = {};
end
% translate the channel groups (like 'all' and 'MEG') into real labels
cfg.channel = ft_channelselection(cfg.channel, hdr);
assert(~isempty(cfg.channel), 'the selection of channels is empty');
if ~isempty(cfg.implicitref)
% add the label of the implicit reference channel to these cell-arrays
cfg.channel = cat(1, cfg.channel(:), cfg.implicitref);
end
cfg.refchannel = ft_channelselection(cfg.refchannel, cfg.channel);
% determine the length in samples to which the data should be padded before filtering is applied
% the filter padding is done by reading a longer segment of data from the original data file
if cfg.padding>0
if strcmp(cfg.dftfilter, 'yes') || ...
strcmp(cfg.lpfilter, 'yes') || ...
strcmp(cfg.hpfilter, 'yes') || ...
strcmp(cfg.bpfilter, 'yes') || ...
strcmp(cfg.bsfilter, 'yes') || ...
strcmp(cfg.medianfilter, 'yes')
padding = round(cfg.padding * hdr.Fs);
else
% no filtering will be done, hence no padding is necessary
padding = 0;
end
% update the configuration (in seconds) for external reference
cfg.padding = padding / hdr.Fs;
else
% no padding was requested
padding = 0;
end
if any(strmatch('reject', fieldnames(cfg))) || ...
any(strmatch('rejecteog', fieldnames(cfg))) || ...
any(strmatch('rejectmuscle', fieldnames(cfg))) || ...
any(strmatch('rejectjump', fieldnames(cfg)))
% this is only for backward compatibility
ft_error('you should call FT_REJECTARTIFACT prior to FT_PREPROCESSING, please update your scripts');
end
ntrl = size(trl,1);
if ntrl<1
ft_error('no trials were selected for preprocessing, see FT_DEFINETRIAL for help');
end
% compute the template for MCG and the QRS latency indices, and add it to the configuration
if strcmp(cfg.removemcg, 'yes')
cfg = template_mcg(cfg);
mcgchannel = ft_channelselection(cfg.artfctdef.mcg.channel, hdr.label);
mcgindx = match_str(cfg.channel, mcgchannel);
for i=1:length(mcgchannel)
ft_info('removing mcg on channel %s\n', mcgchannel{i});
end
end
% determine the channel numbers of interest for preprocessing
[chnindx, rawindx] = match_str(cfg.channel, hdr.label);
if strcmp(cfg.method, 'channel')
% read one channel at a time, loop over channels and over trials
chnloop = mat2cell(chnindx, ones(length(chnindx), 1), 1);
rawloop = mat2cell(rawindx, ones(length(chnindx), 1), 1);
elseif strcmp(cfg.method, 'trial')
% read all channels simultaneously, only loop trials
chnloop = {chnindx};
rawloop = {rawindx};
else
ft_error('unsupported option for cfg.method');
end
for j=1:length(chnloop)
% read one channel group at a time, this speeds up combined datasets
% a multiplexed dataformat is faster if you read all channels, one trial at a time
chnindx = chnloop{j};
rawindx = rawloop{j};
ft_info('processing channel { %s}\n', sprintf('''%s'' ', hdr.label{rawindx}));
% initialize cell arrays
cutdat = cell(1, ntrl);
time = cell(1, ntrl);
ft_progress('init', cfg.feedback, 'reading and preprocessing');
for i=1:ntrl
ft_progress(i/ntrl, 'reading and preprocessing trial %d from %d\n', i, ntrl);
% data padding is used for filtering and line noise removal
nsamples = trl(i,2)-trl(i,1)+1;
if nsamples>padding
% the trial is already longer than the total length requested
begsample = trl(i,1);
endsample = trl(i,2);
offset = trl(i,3);
begpadding = 0;
endpadding = 0;
if padding > 0
ft_warning('no padding applied because the padding duration is shorter than the trial');
end
else
switch cfg.paddir
case 'both'
% begpadding+nsamples+endpadding = total length of raw data that will be read
begpadding = ceil((padding-nsamples)/2);
endpadding = floor((padding-nsamples)/2);
case 'left'
begpadding = padding-nsamples;
endpadding = 0;
case 'right'
begpadding = 0;
endpadding = padding-nsamples;
otherwise
ft_error('unsupported requested direction of padding');
end
if strcmp(cfg.padtype, 'data')
begsample = trl(i,1) - begpadding;
endsample = trl(i,2) + endpadding;
else
% padding will be done below
begsample = trl(i,1);
endsample = trl(i,2);
end
if begsample<1
ft_warning('cannot apply enough padding at begin of file');
begpadding = begpadding - (1 - begsample);
begsample = 1;
end
if endsample>(hdr.nSamples*hdr.nTrials)
ft_warning('cannot apply enough padding at end of file');
endpadding = endpadding - (endsample - hdr.nSamples*hdr.nTrials);
endsample = hdr.nSamples*hdr.nTrials;
end
offset = trl(i,3) - begpadding;
end
% read the raw data with padding on both sides of the trial - this
% includes datapadding
dat = ft_read_data(cfg.datafile, 'header', hdr, 'begsample', begsample, 'endsample', endsample, 'chanindx', rawindx, 'checkboundary', strcmp(cfg.continuous, 'no'), 'dataformat', cfg.dataformat, headeropt{:});
% convert the data to another numeric precision, i.e. double, single or int32
if ~isempty(cfg.precision)
dat = cast(dat, cfg.precision);
end
% pad in case of no datapadding
if ~strcmp(cfg.padtype, 'data')
dat = ft_preproc_padding(dat, cfg.padtype, begpadding, endpadding);
tim = offset2time(offset+begpadding, hdr.Fs, size(dat,2));
else
tim = offset2time(offset, hdr.Fs, size(dat,2));
end
% do the preprocessing on the padded trial data and remove the padding after filtering
[cutdat{i}, label, time{i}, cfg] = preproc(dat, hdr.label(rawindx), tim, cfg, begpadding, endpadding);
if isfield(cfg, 'export') && ~isempty(cfg.export)
% write the processed data to an original manufacturer format file
newhdr = [];
newhdr.Fs = hdr.Fs;
newhdr.label = label;
newhdr.nChans = length(newhdr.label);
% only append for the second and consecutive trials
ft_write_data(cfg.export.dataset, cutdat{i}, 'dataformat', cfg.export.dataformat, 'header', newhdr, 'append', i~=1);
if nargout==0
% don't keep the processed data in memory
cutdat(i) = [];
end
end
end % for all trials
ft_progress('close');
% don't keep hdr.orig in the output data if it is too large
% hdr.orig can be large when caching data from specific file formats, such as bci2000_dat and mega_neurone
if isfield(hdr, 'orig')
s = hdr.orig;
s = whos('s');
if s.bytes>3*1024^2
hdr = rmfield(hdr, 'orig');
end
end
dataout = [];
dataout.hdr = hdr; % header details of the datafile
dataout.label = label; % labels of channels that have been read, can be different from labels in file due to montage
dataout.time = time; % vector with the timeaxis for each individual trial
dataout.trial = cutdat;
dataout.fsample = hdr.Fs;
if istable(cfg.trl)
% we always want the sampleinfo to be numeric
dataout.sampleinfo = table2array(cfg.trl(:,1:2));
else
dataout.sampleinfo = cfg.trl(:,1:2);
end
if size(cfg.trl,2) > 3
dataout.trialinfo = cfg.trl(:,4:end); % this can be a numeric array or a table
end
if isfield(hdr, 'grad')
dataout.grad = hdr.grad; % MEG gradiometer information in header (f.e. headerformat = 'ctf_ds')
end
if isfield(hdr, 'elec')
dataout.elec = hdr.elec; % EEG electrode information in header (f.e. headerformat = 'neuromag_fif')
end
if isfield(hdr, 'opto')
dataout.opto = hdr.opto; % NIRS optode information in header (f.e. headerformat = 'artinis')
end
end % for all channel groups
end % if hasdata
if strcmp(cfg.updatesens, 'yes')
% updating the sensor descriptions can be done on basis of the montage or the rereference settings
if ~isempty(cfg.montage) && ~isequal(cfg.montage, 'no')
montage = cfg.montage;
elseif strcmp(cfg.reref, 'yes')
if strcmp(cfg.refmethod, 'bipolar') || strcmp(cfg.refmethod, 'avg') || strcmp(cfg.refmethod, 'laplace')
tmpcfg = keepfields(cfg, {'refmethod', 'implicitref', 'refchannel', 'channel', 'groupchans'});
tmpcfg.showcallinfo = 'no';
montage = ft_prepare_montage(tmpcfg, data);
else
% do not update the sensor description
montage = [];
end
else
% do not update the sensor description
montage = [];
end
if ~isempty(montage)
% apply the linear projection also to the sensor description
if issubfield(montage, 'type')
bname = montage.type;
else
bname = 'preproc';
end
if isfield(dataout, 'grad')
ft_info('applying the montage to the grad structure\n');
dataout.grad = ft_apply_montage(dataout.grad, montage, 'feedback', 'none', 'keepunused', 'no', 'balancename', bname);
end
if isfield(dataout, 'elec')
ft_info('applying the montage to the elec structure\n');
dataout.elec = ft_apply_montage(dataout.elec, montage, 'feedback', 'none', 'keepunused', 'no', 'balancename', bname);
end
if isfield(dataout, 'opto')
ft_info('applying the montage to the opto structure\n');
dataout.opto = ft_apply_montage(dataout.opto, montage, 'feedback', 'none', 'keepunused', 'no', 'balancename', bname);
end
end
end % if updatesens
% do the general cleanup and bookkeeping at the end of the function
ft_postamble debug
ft_postamble previous data
% rename the output variable to accomodate the savevar postamble
data = dataout;
ft_postamble provenance data
ft_postamble history data
ft_postamble savevar data