-
Notifications
You must be signed in to change notification settings - Fork 18
/
pop_clean_rawdata.m
244 lines (220 loc) · 14 KB
/
pop_clean_rawdata.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
% pop_clean_rawdata(): Launches GUI to collect user inputs for clean_artifacts().
% ASR stands for artifact subspace reconstruction.
% To disable method(s), enter -1.
% Usage:
% >> EEG = pop_clean_rawdata(EEG);
%
% see also: clean_artifacts
% Author: Arnaud Delorme, Makoto Miyakoshi and Christian Kothe, SCCN,INC,UCSD
% History:
% 07/2019. Reprogrammed from Scratch (Arnaud Delorme)
% 07/31/2018 Makoto. Returns error if input data size is 3.
% 04/26/2017 Makoto. Deletes existing EEG.etc.clean_channel/sample_mask. Try-catch to skip potential error in vis_artifact.
% 07/18/2014 ver 1.4 by Makoto and Christian. New channel removal method supported. str2num -> str2num due to str2num([a b]) == NaN.
% 11/08/2013 ver 1.3 by Makoto. Menu words changed. asr_process() line 168 bug fixed.
% 10/07/2013 ver 1.2 by Makoto. Help implemented. History bug fixed.
% 07/16/2013 ver 1.1 by Makoto and Christian. Minor update for help and default values.
% 06/26/2013 ver 1.0 by Makoto. Created.
% Copyright (C) 2013, Arnaud Delorme, Makoto Miyakoshi and Christian Kothe, SCCN,INC,UCSD
%
% This program 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 2 of the License, or
% (at your option) any later version.
%
% This program 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 this program; if not, write to the Free Software
% Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
function [EEG, com] = pop_clean_rawdata(EEG, varargin)
% Check input
com = '';
if ndims(EEG(1).data) == 3
error('Input data must be continuous. This data seems epoched.')
end
fusechanrej = false;
if nargin < 2
% Obtain user inputs.
cb_filter = 'if get(gcbo, ''value''), set(findobj(gcbf, ''userdata'', ''filter''), ''enable'', ''on''); else set(findobj(gcbf, ''userdata'', ''filter''), ''enable'', ''off'', ''value'', 0); end';
cb_chan = 'if get(gcbo, ''value''), set(findobj(gcbf, ''userdata'', ''chan'') , ''enable'', ''on''); else set(findobj(gcbf, ''userdata'', ''chan'') , ''enable'', ''off'', ''value'', 0); end';
cb_asr = 'if get(gcbo, ''value''), set(findobj(gcbf, ''userdata'', ''asr'') , ''enable'', ''on''); else set(findobj(gcbf, ''userdata'', ''asr'') , ''enable'', ''off''); end';
cb_rej = 'if get(gcbo, ''value''), set(findobj(gcbf, ''userdata'', ''rej'') , ''enable'', ''on''); else set(findobj(gcbf, ''userdata'', ''rej'') , ''enable'', ''off''); end';
cb_select1 = 'pop_chansel(get(gcbf, ''userdata''), ''field'', ''labels'', ''handle'', findobj(''parent'', gcbf, ''tag'', ''chanuse'')); set(findobj(''parent'', gcbf, ''tag'', ''chanuseflag''), ''value'', 1); set(findobj(''parent'', gcbf, ''tag'', ''chanignoreflag''), ''value'', 0);';
cb_select2 = 'pop_chansel(get(gcbf, ''userdata''), ''field'', ''labels'', ''handle'', findobj(''parent'', gcbf, ''tag'', ''chanignore'')); set(findobj(''parent'', gcbf, ''tag'', ''chanignoreflag''), ''value'', 1); set(findobj(''parent'', gcbf, ''tag'', ''chanuseflag''), ''value'', 0);';
winsize = max(0.5,1.5*EEG(1).nbchan/EEG(1).srate);
uilist = {...
{'style' 'checkbox' 'string' 'Remove channel drift (data not already high-pass filtered)' 'fontweight' 'bold' 'tag' 'filter' 'callback' cb_filter} ...
{} {'style' 'text' 'string' 'Linear filter (FIR) transition band [lo hi] in Hz ' 'userdata' 'filter' 'enable' 'off' } ...
{'style' 'edit' 'string' '0.25 0.75', 'enable' 'off' 'tag','filterfreqs', 'userdata' 'filter' 'tooltipstring', wordwrap('The first number is the frequency below which everything is removed, and the second number is the frequency above which everything is retained. There is a linear transition in between. For best performance of subsequent processing steps the upper frequency should be close to 1 or 2 Hz, but you can go lower if certain activities need to be retained.',80)} ...
...
{} ...
{'style' 'checkbox' 'string' 'Process/remove channels' 'fontweight' 'bold' 'tag' 'chanrm' 'callback' cb_chan 'value' 1 } ...
...
{} { 'style' 'checkbox' 'string' 'Only consider these channels' 'tag' 'chanuseflag' 'userdata' 'chan' } ...
{ 'style' 'pushbutton' 'string' '...' 'userdata' 'chan' 'callback' cb_select1 } ...
{'style' 'edit' 'string' '' 'userdata' 'chan' 'tag' 'chanuse' } ...
...
{} {'style' 'checkbox' 'string' 'Ignore these channels (ECG, EMG, ...)' 'userdata' 'chan' 'value' 0 'tag' 'chanignoreflag' } ...
{'style' 'pushbutton' 'string' '...', 'userdata' 'chan' 'callback' cb_select2 } ...
{'style' 'edit' 'string' '', 'userdata' 'chan' 'tag' 'chanignore' } ...
...
{} {'style' 'checkbox' 'string' 'Remove channel if it is flat for more than (seconds)' 'tag' 'rmflat' 'userdata' 'chan' 'value' 1 } ...
{'style' 'edit' 'string' '5', 'userdata' 'chan' 'tag' 'rmflatsec' 'tooltipstring', wordwrap('If a channel has a longer flatline than this, it will be removed. In seconds.',80)} ...
...
{} {'style' 'checkbox' 'string' 'Max acceptable high-frequency noise std dev' 'value' 1 'tag' 'rmnoise' 'userdata' 'chan' } ...
{'style' 'edit' 'string' '4', 'userdata' 'chan' 'tag' 'rmnoiseval' 'tooltipstring', wordwrap('If a channel has more line noise relative to its signal than this value, in standard deviations relative to the overall channel population, it will be removed.',80)} ...
...
{} {'style' 'checkbox' 'string' 'Min acceptable correlation with nearby chans [0-1]' 'value' 1 'tag' 'rmcorr' 'userdata' 'chan' } ...
{'style' 'edit' 'string' '0.8', 'userdata' 'chan' 'tag' 'rmcorrval' 'tooltipstring', wordwrap('If a channel has lower correlation than this to an estimate of its activity based on other channels, and this applies to more than half of the recording, the channel will be removed. This method requires that channel locations are available and roughly correct; otherwise a fallback criterion will tried used using a default setting; you can customize the fallback method by directly calling clean_channels_nolocs in the command line.',80)} ...
...
{} {'style' 'checkbox' 'string' 'Perform Artifact Subspace Reconstruction bad burst correction/rejection' 'fontweight' 'bold' 'value' 1 'tag' 'asr' 'callback' cb_asr } ...
{} {'style' 'text' 'string' sprintf('Max acceptable %1.1f second window std dev', winsize) 'value' 1 'userdata' 'asr' } ...
{'style' 'edit' 'string' '20', 'tag' 'asrstdval' 'userdata' 'asr' 'tooltipstring', wordwrap('Standard deviation cutoff for removal of bursts. Data portions whose variance is larger than this threshold relative to the calibration data are considered missing data and will be removed. The most aggressive value that can be used without losing much EEG is 3. A reasonably conservative value is 5, but some extreme EEG bursts (e.g., sleep spindles) can cross even 5. For new users it is recommended to at first visually inspect the difference between the original and cleaned data to get a sense of the removed content at various levels.',80)} ...
{} {'style' 'checkbox' 'string' 'Use Riemanian distance metric (not Euclidean) - beta' 'userdata' 'asr' 'value' 0 'tag' 'distance' } {} ...
{} {'style' 'checkbox' 'tag' 'asrrej' 'string' 'Remove bad data periods (when uncheck, correct using ASR)' 'value' 1 'userdata' 'asr'} {} ...
...
{} {'style' 'checkbox' 'string' 'Additional removal of bad data periods' 'fontweight' 'bold' 'value' 1 'tag' 'rejwin' 'callback' cb_rej } ...
{} {'style' 'text' 'tag' 'asrwintext' 'string' 'Acceptable [min max] channel RMS range (+/- std dev)' 'userdata' 'rej'} ...
{'style' 'edit' 'string' '-Inf 7','tag', 'rejwinval1', 'userdata' 'rej' 'tooltipstring', wordwrap('If a time window has a larger fraction of simultaneously corrupted channels than this (after the other cleaning attempts), it will be cut out of the data. This can happen if a time window was corrupted beyond the point where it could be recovered.',80)} ...
{} {'style' 'text' 'tag' 'asrwintext' 'string' 'Maximum out-of-bound channels (%)' 'userdata' 'rej'} ...
{'style' 'edit' 'string' '25','tag', 'rejwinval2', 'userdata' 'rej' 'tooltipstring', wordwrap('If a time window has a larger fraction of simultaneously corrupted channels than this (after the other cleaning attempts), it will be cut out of the data. This can happen if a time window was corrupted beyond the point where it could be recovered.',80)} ...
...
{} {'style' 'checkbox' 'string' 'Pop up scrolling data window with rejected data highlighted' 'tag' 'vis' 'value' fastif(length(EEG) > 1, 0, 1) 'enable' fastif(length(EEG) > 1, 'off', 'on') }};
% channel labels
% --------------
if ~isempty(EEG(1).chanlocs)
tmpchanlocs = EEG(1).chanlocs;
else
tmpchanlocs = [];
for index = 1:EEG(1).nbchan
tmpchanlocs(index).labels = int2str(index);
tmpchanlocs(index).type = '';
end
end
row = [0.1 1 0.3];
row4 = [0.1 0.8 0.2 0.3];
row2 = [0.1 1.2 0.1];
row3 = [0.9 0.2 0.3];
geom = { 1 row 1 1 row4 row4 row row row 1 1 row row2 row2 1 1 row row 1 1 };
geomvert = [ 1 1 0.3 1 1 1 1 1 1 0.3 1 1 1 1 0.3 1 1 1 0.3 1 ];
if length(EEG) > 1
uilist = { uilist{1:6} {} {'style' 'checkbox' 'string' 'Fuse channel rejection for datasets with same subject and session' 'userdata' 'chan' 'fontweight' 'bold' 'tag' 'commonrej' 'value' 1} {} uilist{7:end} };
geom = [ geom(1:4) { row2 } geom(5:end)];
geomvert = [ geomvert(1:4) 1 geomvert(5:end) ];
end
[res,~,~,outs] = inputgui('title', 'pop_clean_rawdata()', 'geomvert', geomvert, 'geometry', geom, 'uilist',uilist, 'helpcom', 'pophelp(''clean_artifacts'');', 'userdata', tmpchanlocs);
% Return error if no input.
if isempty(res) return; end
% process multiple datasets
% -------------------------
options = {};
opt.FlatlineCriterion = 'off';
opt.ChannelCriterion = 'off';
opt.LineNoiseCriterion = 'off';
opt.Highpass = 'off';
opt.BurstCriterion = 'off';
opt.WindowCriterion = 'off';
opt.BurstRejection = 'off';
opt.Distance = 'Euclidian';
if outs.filter, opt.Highpass = str2num(outs.filterfreqs); end
if outs.chanrm
if outs.chanignoreflag
[ chaninds, chanlist ] = eeg_decodechan(EEG(1).chanlocs, outs.chanignore);
if isempty(chanlist), chanlist = chaninds; end
opt.channels_ignore = chanlist;
end
if outs.chanuseflag
[ chaninds, chanlist ] = eeg_decodechan(EEG(1).chanlocs, outs.chanuse);
if isempty(chanlist), chanlist = chaninds; end
opt.channels = chanlist;
end
if outs.rmflat, opt.FlatlineCriterion = str2num(outs.rmflatsec); end
if outs.rmcorr, opt.ChannelCriterion = str2num(outs.rmcorrval); end
if outs.rmnoise, opt.LineNoiseCriterion = str2num(outs.rmnoiseval); end
end
if outs.asr
opt.BurstCriterion = str2num(outs.asrstdval);
if outs.distance, opt.Distance = 'Riemannian'; end
end
if outs.rejwin
opt.WindowCriterionTolerances = str2num(outs.rejwinval1);
opt.WindowCriterion = str2num(outs.rejwinval2)/100;
end
if outs.asrrej && ~strcmpi(opt.BurstCriterion, 'off')
opt.BurstRejection = 'on';
end
if isfield(outs,'commonrej') && outs.commonrej
opt.fusechanrej = outs.commonrej;
fusechanrej = true;
end
% convert structure to cell
options = fieldnames(opt);
options(:,2) = struct2cell(opt);
options = options';
options = options(:)';
else
options = varargin;
for iOpt = 1:2:length(options)
if strcmpi(options{iOpt}, 'fusechanrej')
fusechanrej = true;
end
end
end
if length(EEG) > 1
% process multiple datasets
if nargin < 2
[ EEG, com ] = eeg_eval( 'clean_artifacts', EEG, 'warning', 'on', 'params', options );
else
[ EEG, com ] = eeg_eval( 'clean_artifacts', EEG, 'params', options );
end
if fusechanrej
if exist('pop_fusechanrej')
EEG = pop_fusechanrej(EEG);
else
warning('Upgrade your version of EEGLAB to fuse channel rejection for datasets with same subject and session');
end
end
com = sprintf('EEG = pop_clean_rawdata(EEG, %s);', vararg2str(options));
return;
end
% Delete EEG.etc.clean_channel_mask and EEG.etc.clean_sample_mask if present.
if isfield(EEG.etc, 'clean_channel_mask')
EEG.etc = rmfield(EEG.etc, 'clean_channel_mask');
disp('EEG.etc.clean_channel_mask present: Deleted.')
end
if isfield(EEG.etc, 'clean_sample_mask')
EEG.etc = rmfield(EEG.etc, 'clean_sample_mask');
disp('EEG.etc.clean_sample_mask present: Deleted.')
end
cleanEEG = clean_artifacts(EEG, options{:});
% Apply Christian's function before and after comparison visualization.
if nargin < 2 && outs.vis == 1
try
vis_artifacts(cleanEEG,EEG);
catch
warning('vis_artifacts failed. Skipping visualization. Could be because of duplicate channel label.')
end
end
% Update EEG.
EEG = cleanEEG;
% Output eegh.
com = sprintf('EEG = pop_clean_rawdata(EEG, %s);', vararg2str(options));
% Display the ending message.
disp('Done.')
function outtext = wordwrap(intext,nChars)
outtext = '';
while ~isempty(intext)
if length(intext) > nChars
cutoff = nChars+find([intext(nChars:end) ' ']==' ',1)-1;
outtext = [outtext intext(1:cutoff-1) '\n']; %#ok<*AGROW>
intext = intext(cutoff+1:end);
else
outtext = [outtext intext];
intext = '';
end
end
outtext = sprintf(outtext);