-
Notifications
You must be signed in to change notification settings - Fork 1
/
eeg_nemar_preprocess.m
211 lines (189 loc) · 8.46 KB
/
eeg_nemar_preprocess.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
% NEMAR preprocessing pipeline
% Required input:
% EEG - [struct] input dataset
% Optional inputs:
% pipeline - [cell] list of preprocessing steps in order
% logdir - [string] directory to log execution output
% resave - [boolean] whether to save processed data back on disk. Default true
% Output:
% EEG - [struct] processed dataset
% status - [boolean] whether dataset was processed successfully (1) or not (0)
% To do: ignore non-EEG channel types instead of removing them
function [EEG, status] = eeg_nemar_preprocess(EEG, varargin)
pipeline_all = {'check_import', 'check_chanloc', 'remove_chan', 'cleanraw', 'runica', 'iclabel'};
opt = finputcheck(varargin, { ...
'pipeline' 'cell' {} pipeline_all; ... % preprocessing steps
'logdir' 'string' {} './eeg_nemar_logs'; ...
'modeval' 'string' {'new', 'resume', 'rerun'} 'resume'; ... % if import mode, pipeline will overwrite existing outputdir. rerun won't
'resave' 'boolean' {} true; ...
}, 'eeg_nemar_preprocess');
if isstr(opt), error(opt); end
if ~exist(opt.logdir, 'dir')
logdirstatus = mkdir(opt.logdir);
end
try
which eeglab;
catch
try
addpath('/expanse/projects/nemar/eeglab');
eeglab nogui;
catch
error('EEGLAB load failed.')
end
end
resume = strcmp(opt.modeval, "resume");
[filepath, filename, ext] = fileparts(EEG.filename);
disp(filename)
log_file = fullfile(opt.logdir, filename);
if exist(log_file, 'file')
delete(log_file)
end
diary(log_file);
fprintf('Processing %s\n', fullfile(EEG.filepath, EEG.filename));
status_file = fullfile(opt.logdir, [filename '_preprocess.csv']);
% if status_file exists, read it
if exist(status_file, 'file') && strcmp(opt.modeval, "resume")
status_tbl = readtable(status_file);
else
status_tbl = array2table(zeros(1, numel(pipeline_all)));
status_tbl.Properties.VariableNames = pipeline_all;
writetable(status_tbl, status_file);
end
disp(status_tbl)
status = table2array(status_tbl);
splitted = split(EEG.filename(1:end-4),'_');
modality = splitted{end};
fprintf('Running pipeline sequence %s\n', strjoin(opt.pipeline, '->'));
try
for i=1:numel(opt.pipeline)
disp(fullfile(EEG.filepath, EEG.filename))
operation = opt.pipeline{i};
if strcmp(operation, "check_import")
if resume && status_tbl.check_import
fprintf('Skipping check_import\n');
continue
end
if exist(fullfile(EEG.filepath, EEG.filename), 'file')
status_tbl.check_import = 1;
end
end
if strcmp(operation, "check_chanloc")
if resume && status_tbl.check_chanloc
fprintf('Skipping check_chanloc\n');
continue
end
if isfield(EEG.chanlocs, 'theta') && (strcmp(modality, 'eeg') || strcmp(modality, 'meg'))
thetas = [EEG.chanlocs.theta];
if isempty(thetas)
error("Error: No channel locations detected");
end
end
status_tbl.check_chanloc = 1;
end
if strcmp(operation, "remove_chan")
if resume && status_tbl.remove_chan
fprintf('Skipping remove_chan\n');
continue
end
% % remove non-ALLEEG channels (it is also possible to process ALLEEG data with non-ALLEEG data
% get non-EEG channels
% keep only EEG channels
rm_chan_types = {'AUDIO','EOG','ECG','EMG','EYEGAZE','GSR','HEOG','MISC','PPG','PUPIL','REF','RESP','SYSCLOCK','TEMP','TRIG','VEOG'};
if isfield(EEG.chanlocs, 'type')
EEG = pop_select(EEG, 'rmchantype', rm_chan_types);
if strcmp(modality, 'eeg')
types = {EEG.chanlocs.type};
eeg_indices = strmatch('EEG', types)';
if ~isempty(eeg_indices)
EEG = pop_select(EEG, 'chantype', 'EEG');
else
warning("No EEG channel type detected (for first EEG file). Keeping all channels");
end
end
else
warning("Channel type not detected (for first recording file)");
end
status_tbl.remove_chan = 1;
end
if strcmp(operation, "cleanraw")
if resume && status_tbl.cleanraw
fprintf('Skipping cleanraw\n');
continue
end
if EEG.trials > 1
error('Epoched data given to cleanraw');
end
% remove offset
EEG = pop_rmbase( EEG, [],[]);
% Highpass filter
EEG = pop_eegfiltnew(EEG, 'locutoff',0.5);
% clean data using the clean_rawdata plugin
options = {'FlatlineCriterion',4,'ChannelCriterion',0.85, ...
'LineNoiseCriterion',4,'Highpass', 'off' ,'BurstCriterion',20, ...
'WindowCriterion',0.25,'BurstRejection','on','Distance','Euclidian', ...
'WindowCriterionTolerances',[-Inf 7] ,'fusechanrej',1}; % based on Arnaud paper
EEG = pop_clean_rawdata( EEG, options{:});
status_tbl.cleanraw = 1;
end
if strcmp(operation, "runica")
if resume && status_tbl.runica
fprintf('Skipping runica\n');
continue
end
% run ICA reducing the dimention by 1 to account for average reference
K = 4; % TODO: use Arno's formula
if K >= 5
fprintf('Running amica\n');
options = {'batch', 1};
EEG = runamica17_nsg(EEG, options{:});
else
fprintf('Running extended ICA\n');
nChans = EEG.nbchan;
lrate = 0.00065/log(mean(nChans))/10; % not the runica default - suggested by Makoto approximately 12/22
options = {'icatype','runica','concatcond','on', 'extended', 1, 'lrate', 1e-5, 'maxsteps', 2000};
EEG = pop_runica(EEG, options{:});
end
status_tbl.runica = 1;
end
if strcmp(operation, "iclabel") && strcmp(modality, 'eeg')
if resume && status_tbl.iclabel
fprintf('Skipping iclabel\n');
continue
end
% % run ICLabel and flag artifactual components
% if strcmp(EEG.etc.datatype, 'EEG')
options = {'default'};
EEG = pop_iclabel(EEG, options{:});
options = {[NaN NaN;0.9 1;0.9 1;NaN NaN;NaN NaN;NaN NaN;NaN NaN]};
EEG = pop_icflag( EEG, options{:});
% end
status_tbl.iclabel = 1;
end
% if reached, operation completed without error and result should be saved
if opt.resave
disp('Saving EEG file')
pop_saveset(EEG, 'filepath', EEG.filepath, 'filename', EEG.filename);
end
% write status file
writetable(status_tbl, status_file);
status = table2array(status_tbl);
end
catch ME
fprintf('%s\n%s\n',ME.identifier, ME.getReport());
end
% close log file
diary off
end
%{
if strcmp(operation, "avg_ref")
if resume && status_tbl.avg_ref
fprintf('Skipping avg_ref\n');
continue
end
% recompute average reference interpolating missing channels (and removing
% them again after average reference - STUDY functions handle them automatically)
options = {[], 'interpchan', []};
EEG = pop_reref( EEG,options{:});
status_tbl.avg_ref = 1;
end
%}