2 % GET_REGRESS_EOG tries to obtain the regression coefficients
3 %
for EOG correction. According to [1], some extra recordings
4 % with large eye movements (i.e. EOG artifacts) are needed.
5 % GET_REGRESS_EOG tries to identify
this data.
11 % file filename which should be corrected.
12 % usually, the eye movements are stored in a different file.
13 % Some lab-specific heuristics is used to identify the file with the eye movements.
14 % Mode
'REG' [
default] regression with one or two bipolar EOG channels [1]
15 %
'REG+CAR' regression and common average reference
16 % removes 2 bipolar + averaged monopolar EOG
17 %
'REG+PCA' regression and PCA,
18 % removes 3
"EOG" components
19 %
'REG+ICA' regression + ICA [3]
20 % removes 3
"EOG" components
21 %
'PCA-k' removes the k-largest PCA components, k must be a positive integer
22 %
'ICA-k' removes the k-largest ICA components [3], k must be a positive integer
23 % others like
'NGCA-k',
'TDSEP-k',
'TDSEP3-k',
'TDSEP1',
'FFDIAG'
24 %
'msec' same as PCA-3, modified (without averaging) MSEC method [2]
25 %
'bf-' beamformer, assume zero-activity reference electrode
26 %
'bf+' beamformer, take into account activity of reference electrode
27 %
'Hurst' ICA components selected by the method of[5]
32 % The following modifiers can be combined with any of the above
33 %
'FILT###-###Hz' filtering between ### and ### Hz. ### must be numeric
34 %
'Fs=###Hz' downsampling to ### Hz, ### must be numeric
35 %
'x' 2nd player of season2 data
38 % hdr.REGRESS.r0 correction coefficients
40 % The EOG correction will be applied to the channels CHAN with any of these commands:
41 % HDR =
sopen(file,
'r',hdr.REGRESS.r0(:,CHAN)); [s,HDR]=
sread(HDR); HDR=
sclose(HDR);
42 % [s,HDR] =
sload(file,hdr.REGRESS.r0(:,CHAN));
43 % [s,HDR] =
sload(file,CHAN,
'EOG_CORRECTION',
'ON');
45 % See also: SLOAD, IDENTIFY_EOG_CHANNELS, BV2BIOSIG_EVENTS, REGRESS_EOG
48 % [1] Schlogl A, Keinrath C, Zimmermann D, Scherer R, Leeb R, Pfurtscheller G.
49 % A fully automated correction method of EOG artifacts in EEG recordings.
50 % Clin Neurophysiol. 2007 Jan;118(1):98-104. Epub 2006 Nov 7.
53 % [2] Berg P, Scherg M.
54 % A multiple source approach to the correction of eye artifacts.
55 % Electroencephalogr Clin Neurophysiol. 1994 Mar;90(3):229-41.
56 % [3] JADE algorithm, Jean-François Cardoso.
57 % [4] Boudet S., Peyrodie L., P Gallois, C Vasseur,
58 % Filtering by optimal projectsion and application to automatic artifact removal from EEG
59 % Signal Processing 87 (2007) 1987-1992.
60 % [5] Vorobyov and Cichocki (2002)
61 % Blind noise reduction
for multisensory signals
using ICA and subspace
62 % filtering, with application to EEG analysis.
63 % Biol Cybern. 2002 Apr;86(4):293-303.
64 % [6] C.A. Joyce, I.F. Gorodnitsky, M.Kutas
65 % Automated removal of eye movement and blink artifats from EEG data
using blind component separation.
66 % Psychobiology, 41 (2004), 313-325
67 % [7] Barbati et al (2004)
68 % [8] Frank Meinecke, Andreas Ziehe, Motoaki Kawanabe, and Klaus-Robert Müller.
69 % A Resampling Approach to Estimate the Stability of One-Dimensional or Multidimensional Independent Components.
70 % IEEE Transactions on Biomedical Engineering, 49(12):1514-1525, 2002.
71 % [9] Blanchard G., Kawanabe M., Sugiyama M., Spokoiny V., Muller K.-R. (2006).
72 % In search of non-gaussian components of a high-dimensional distribution.
73 % Journal of Machine Learning Research 7, 247-282.
74 % [10] Kawanabe M., Sugiyama M., Blanchard G, Müller K.-R. (2007)
75 % A
new algorithm of non-Gaussian component analysis with radial kernel functions
76 % Annals of the Institute of Statistical Mathematics, 59(1):2007
77 % [11] K.H. Ting, P.C.W. Fung, C.Q.Chang, F.H.Z.Chan
78 % automatec correction of artifact from single-trial
event-related potentials bz blind separation
using second order statistics only.
79 % Medical Engineering & Physics, 28, 780-794 (2006)
82 % Copyright (C) 2006,2007 by Alois Schloegl
83 % This is part of the BIOSIG-toolbox http:
85 % This library is free software; you can redistribute it and/or
86 % modify it under the terms of the GNU Library General Public
87 % License as published by the Free Software Foundation; either
88 % Version 2 of the License, or (at your option) any later version.
90 % extract information from BBCI data
for EOG correction
98 [p,f,e] = fileparts(fn);
100 %%%=== search
for eye-movement recordings (in order to obtain clean EOG artifacts) ===%%%
101 % an heuristic approach is to support lab-specific standards
102 % currently, the convention of the GrazBCI and the BBCI Lab are supported
104 f0 = dir(fullfile(p,[
'arte*',e])); % BBCI files
107 % hack to ignore
"arte*eog_mono" data
108 ix = regexp(strvcat({f0.name}),
'eog_mono');
117 elseif length(f0)==1,
118 feog = fullfile(p,f0.name);
121 f0 = dir(fullfile(p,
'EOG/*.gdf')); % Graz BCI files
124 feog = fullfile(p,
'EOG',f0.name);
126 f0 = dir(fullfile(p,
'*03.bkr')); % Graz BCI files
128 f0 = dir(fullfile(p,
'*12.bkr')); % Graz BCI files
131 feog = fullfile(p,f0.name);
140 fprintf(
'error: no artefact file found. Use specified file.\n');
143 fprintf(
'error: more than one artefact file found!\n');
148 % load eye movement calibration data
149 [s00,h0] =
sload(feog);
152 FLAG.SEASON2DATA_PLAYER2 = 0;
153 if strcmp(LAB_ID,
'BBCI'),
154 if ~isempty(strfind(Mode,
'x')) %%%%
this is a hack
for the SEASON2DATASET
155 FLAG.SEASON2DATA_PLAYER2 = 1;
158 % resting EEG with eyes open
159 ix = find([h0.EVENT.TYP==hex2dec(
'0114')] | [h0.EVENT.TYP==hex2dec(
'0115')]);
162 r00 = [r00; s00(h0.EVENT.POS(ix(k))+(0:h0.EVENT.DUR(ix(k))),:)];
166 % find eye movements in BBCI recordings
167 tmp = bitand(2^15-1,h0.EVENT.TYP);
168 tmp = find((tmp > hex2dec(
'0430')) & (tmp<=hex2dec(
'0439')));
172 % extract segment with large eye movements/EOG artifacts
173 s00 = s00(h0.EVENT.POS(ix1):h0.EVENT.POS(ix2)+h0.EVENT.DUR(ix2),:);
175 if size(s00,1)<h0.SampleRate*10,
176 fprintf(2,
'WARNING GET_REGRESS_EOG: in %s no eye movements identified\n',h0.FileName);
179 elseif strcmp(LAB_ID,
'Graz22'),
180 f0 = dir(fullfile(p,
'*02.bkr')); % Graz BCI files
182 f0 = dir(fullfile(p,
'*11.bkr')); % Graz BCI files
185 feog = fullfile(p,f0.name);
187 [r00,h0r] =
sload(feog);
192 FLAG.HURST = ~isempty(strfind(Mode,
'HURST')); % Vorobyov and Cichocki (2002)
193 FLAG.AFOP = ~isempty(strfind(Mode,'AFOP')); % Boudet et al 2007
194 FLAG.FOP = ~FLAG.AFOP & ~isempty(strfind(Mode,'FOP')); % Boudet et al 2007
195 FLAG.TDSEP1 = ~isempty(strfind(Mode,'TDSEP1'));
196 FLAG.TDSEP2 = ~isempty(strfind(Mode,'TDSEP2'));
197 FLAG.FFDIAG = ~isempty(strfind(Mode,'FFDIAG'));
198 FLAG.Joyce = ~isempty(strfind(Mode,'JOYCE'));
199 FLAG.Barbati = ~isempty(strfind(Mode,'BARBATI'));
200 FLAG.Meinecke = ~isempty(strfind(Mode,'MEINECKE'));
202 ix = strfind(Mode,'NGCA-');
203 FLAG.NGCA = ~isempty(ix);
205 FLAG.NGCA =
str2double(strtok(Mode(ix+5:end),' '));
208 ix = strfind(Mode,'TDSEP3-');
209 FLAG.TDSEP3 = ~isempty(ix);
211 FLAG.TDSEP3 =
str2double(strtok(Mode(ix+7:end),' '));
214 ix = strfind(Mode,'TDSEP-');
215 FLAG.TDSEP = ~isempty(ix);
217 FLAG.TDSEP =
str2double(strtok(Mode(ix+6:end),' '));
220 ix = strfind(Mode,'ICA-');
221 FLAG.ICA = ~isempty(ix);
223 FLAG.ICA =
str2double(strtok(Mode(ix+4:end),' '));
225 ix = strfind(Mode,'PCA-');
226 FLAG.PCA = ~isempty(ix);
228 FLAG.PCA =
str2double(strtok(Mode(ix+4:end),' '));
230 if ~isempty(strfind(upper(Mode),'MSEC'));
234 FLAG.REG = ~isempty(strfind(upper(Mode),'REG'));
237 ix1 = strfind(upper(Mode),'FS=');
239 ix2 = strfind(upper(Mode(ix1:end)),'HZ');
240 Fs = Mode(ix1+3:ix1+ix2-2);
242 if length(Fs)==1 & isfinite(Fs),
243 s00 =
rs(s00,h0.SampleRate,Fs);
246 fprintf(2,'Warning GET_REGRESS_EOG: Fs="%s" could not be decoded. No filter is applied\n',tmp);
251 ix1 = strfind(Mode,'FILT');
253 ix2 = strfind(upper(Mode(ix1:end)),'HZ');
254 tmp = Mode(ix1+4:ix1+ix2-2);
255 tmp((tmp=='-')|(tmp=='='))=' ';
257 if (length(B)==2) & all(isfinite(B))
259 tmp = center(s00,1); tmp(isnan(tmp)) = 0;
260 tmp = fft(tmp); f=[0:size(s00,1)-1]*h0.SampleRate/size(s00,1);
261 w = ((f>B(1)) & (f<B(2))) | ((f>h0.SampleRate-B(2)) & (f<h0.SampleRate-B(1)));
263 s00 = real(ifft(tmp));
266 fprintf(2,'Warning GET_REGRESS_EOG: Filter="%s" could not be decoded. No filter is applied\n',tmp);
270 %%%%% define EEG and EOG channels %%%%%
271 CHANTYP = repmat(' ',h0.NS,1);
272 CHANTYP([(h0.LeadIdCode>=996) & (h0.LeadIdCode<=1302)])='E'; % EEG
273 if strcmp(LAB_ID, 'Graz22');
277 CHANTYP(any(eogchan,2)) = 'O'; % EOG
278 if FLAG.SEASON2DATA_PLAYER2,
279 CHANTYP = CHANTYP([65:128,1:64]);
280 eogchan = eogchan([65:128,1:64],:);
283 if isempty(strfind(upper(Mode),'MSEC1'))
284 % MSEC,MSEC2 [default]
285 CHAN = find((CHANTYP=='E') | (CHANTYP=='O'));
287 CHAN = find(CHANTYP=='E');
292 %%%%% identify EOG components %%%%%
296 error('Mode AFOP not supported');
299 % Optimal Projection (Boudet et al, 2007)
300 chan = find(CHANTYP=='E' | CHANTYP=='O');
303 [P,D] = eig(covm(r00(:,chan),'D'),covm(s00(:,chan),'D'));
304 TH = 0.3; % Boudet et al. 2007, p 1989.
305 eogchan = sparse(chan,1:length(chan),1,h0.NS,length(chan))*P(:,diag(D)<TH);
306 if size(eogchan,2)>5,
307 eogchan=eogchan(:,1:5);
311 elseif ~isempty(strfind(Mode,'bf-'))
312 % beamformer approach
313 T = load('EOG_DIPOLS3'); % load lead field matrix
314 T.Label = T.sa.clab_electrodes;
317 for k=find(h0.LeadIdCode)',
318 ix = find(T.LeadIdCode==h0.LeadIdCode(k));
320 V(k,:) = T.v(ix,4:6);
324 if FLAG.SEASON2DATA_PLAYER2,
325 eogchan = eogchan([h0.NS/2+1:h0.NS,1:h0.NS/2],:);
328 elseif ~isempty(strfind(Mode,'bf+'))
329 % beamformer approach
330 T = load('EOG_DIPOLS3'); % load lead field matrix
331 T.Label = T.sa.clab_electrodes;
334 for k=find(h0.LeadIdCode)',
335 ix = find(T.LeadIdCode==h0.LeadIdCode(k));
337 V(k,:) = T.v(ix,4:6);
341 R(1:54,1:54)=R(1:54,1:54)-1/54;
342 eogchan = [R*pinv(V(1:64,:))';zeros(64,3)];
343 if FLAG.SEASON2DATA_PLAYER2,
344 eogchan = eogchan([h0.NS/2+1:h0.NS,1:h0.NS/2],:);
347 elseif FLAG.Meinecke,
348 CHAN = find(CHANTYP=='E');
349 chan = sparse(CHAN,1:length(CHAN),1,h0.NS,length(CHAN));
350 r = [chan,eogchan]; % CAR and bipolar EOG
352 tmp = tmp(~any(isnan(tmp),2),:);
354 % opt.verbose: 0 = no progress reports
355 % 1 = progress reports (default)
356 % 2 = progress reports and plot
357 % opt.title:
string containing title of the data set
358 % opt.B: Bootstrap size (default B=100)
359 % opt.sel: time lag values for TDSEP
362 opt.title = 'BSS resampling';
364 opt.sel = 0:round(.15*h0.SampleRate)
365 if ~isempty(strfind(upper(Mode),'JADE'));
371 ica = ica_resamp(tmp',alg,opt)
373 % ica.x: the given mixtures
374 % ica.A: the estimated mixing matrix
375 % ica.s: the estimated source signals
376 % ica.S: the separability matrix
377 % ica.perm: permutation of the estimated sources that makes the
378 % block structure visible
381 ochan = pinv(ica.A)';
385 elseif ~isempty(strfind(Mode,'JOYCE'))
386 CHAN = find(CHANTYP=='E');
387 chan = sparse(CHAN,1:length(CHAN),1,h0.NS,length(CHAN));
388 r = [chan,eogchan]; % mono and bipolar EOG
389 tmp = s00(~any(isnan(s00),2),:);
391 sel = round(.15*h0.SampleRate);
393 W1 = bss(zscore(tmp*[chan, eogchan]),meth,[],sel);
394 W2 = bss(zscore(tmp*[chan,-eogchan]),meth,[],sel);
397 c1 = corrcoef(tmp*[chan, eogchan]*W1,tmp*[chan, -eogchan]*W2);
398 [sel2,ix]=find(c1 < -0.5);
401 c2 = corrcoef(s00*eogchan,s00*[chan, eogchan]*W1);
402 sel3 = find(any(abs(c2)>.3));
405 c3 = rms(diff(zscore(s00*[chan, eogchan])*W1(:,sel3),[],1));
406 sel4 = find(c3 < 0.2 * 500 / h0.SampleRate);
408 sel = unique([sel2(:)',sel3(sel4)]);
413 eogchan = [chan, eogchan]*W1(:,sel);
415 elseif ~isempty(strfind(Mode,'KIERKELS'))
419 eegchan = find(CHANTYP=='E');
420 eogchan = find(CHANTYP=='O');
421 chan = [eegchan(:);eogchan(:)];
423 tmp = tmp(~any(isnan(tmp),2),:);
425 sel = round([0,1,2,3,5,10,20]/250*h0.SampleRate);
426 W = bss(tmp,meth,[],sel);
428 r = corrcoef(s00(:,chan)*W,s00(:,eogchan));
430 eogchan = sparse(chan,1:length(chan),1,h0.NS,length(chan)) * W * A(:,sel);
433 elseif ~isempty(strfind(Mode,'TING'))
434 eegchan = find(CHANTYP=='E');
435 eogchan = find(CHANTYP=='O');
436 chan = [eegchan(:);eogchan(:)];
438 tmp = tmp(~any(isnan(tmp),2),:);
440 sel = round(.15*h0.SampleRate);
442 W = bss(tmp,meth,[],sel);
445 c1 = max(abs(tmp),[],1) * max(abs(A),[],2);
446 up = chan(length(eegchan)+1);
447 lo = chan(length(eegchan)+2);
450 c2 = abs(A(:,up)-A(:,lo)) - max(max(A(:,1:length(eegchan))));
451 c3 = abs(A(:,le)-A(:,ri)) - max(max(A(:,1:length(eegchan))));
454 [tmp,sel(1)]=max(c1);
455 [tmp,sel(2)]=max(c2);
456 [tmp,sel(3)]=max(c3);
458 %find(c1>100),find(c2>0),find(c3>0),
459 sel=[find(c1>100),find(c2>0),find(c3>0)]
460 eogchan = sparse(chan,1:length(chan),1,h0.NS,length(chan)) * W * A(:,sel);
463 elseif (FLAG.PCA>0) | (FLAG.ICA>0) | (FLAG.NGCA>0) | (FLAG.TDSEP>0) |(FLAG.TDSEP1>0) | (FLAG.TDSEP2>0) | (FLAG.TDSEP3>0) | (FLAG.HURST) | (FLAG.FFDIAG),
464 % identify channels used for PCA
467 chan = sparse(chan,1:nc,1,h0.NS,nc)*(eye(nc) - 1/nc); % Common Average Reference (CAR)
468 r = [chan,eogchan]; % CAR and bipolar EOG
470 tmp = tmp(~any(isnan(tmp),2),:);
472 [u,s,ochan] = svds(tmp, FLAG.PCA); % get k (i.e. FLAG.PCA) largests PCA's
477 chan = sparse(chan,1:nc,1,h0.NS,nc)*(eye(nc) - 1/nc); % Common Average Reference (CAR)
478 r = [chan,eogchan]; % CAR and bipolar EOG
480 tmp = tmp(~any(isnan(tmp),2),:);
482 [A] = jade(tmp', FLAG.ICA); % get k (i.e. FLAG.PCA) largests PCA's
487 chan = sparse(chan,1:nc,1,h0.NS,nc)*(eye(nc) - 1/nc); % Common Average Reference (CAR)
488 r = [chan,eogchan]; % CAR and bipolar EOG
490 tmp = tmp(~any(isnan(tmp),2),:);
492 A = jade(tmp', min(20,size(tmp,1)));
494 H = hurst(s0*r*U)*log(2) % account for c=1/2
495 ix = find((H>=.58) & (H<=0.64));
499 elseif FLAG.NGCA, % NonGaussian Component Analysis
501 par.nbng = FLAG.NGCA;
502 chan = sparse(chan,1:nc,1,h0.NS,nc);
504 tmp = tmp(~any(isnan(tmp),2),:);
505 [ngmatrix, projdata, signalmatrix] = NGCA(tmp',par);
506 eogchan = chan*ngmatrix;
508 elseif FLAG.FFDIAG, %
509 chan = sparse(chan,1:nc,1,h0.NS,nc);
511 tmp = tmp(~any(isnan(tmp),2),:);
513 sel = 0:round(.15*h0.SampleRate);
514 C= zeros(nc,nc,length(sel));
516 C(:,:,k)= tdCorr(tmp', sel(k));
519 W = (W./repmat(sqrt(sum(W.*W,2)),1,nc))';
520 [tmp,ix]=sort(-rms(s00*chan*W,1));
521 eogchan = chan*W(:,ix(1:3));
524 chan = sparse(chan,1:nc,1,h0.NS,nc)*(eye(nc) - 1/nc); % Common Average Reference (CAR)
526 tmp = tmp(~any(isnan(tmp),2),:);
528 sel = round([0,1,2,3,5,10,20]/256*h0.SampleRate);
530 [W,d] = tdsep(tmp',sel);
531 eogchan = chan*W(1:3,:)';
534 chan1= sparse(chan,1:nc,1,h0.NS,nc);
536 tmp = tmp(~any(isnan(tmp),2),:);
537 sel = 0:round(.15*h0.SampleRate);
538 W = bss(tmp',alg,[],sel);
540 %%% select 1st 3 components %%%
541 eogchan = chan*W(1:3,:)';
543 %%% select 3 components with minumum angle to b0 %%%
547 phi(k) = subspace(h0.REGRESS.b0(2:3,:)',iW(:,k));
549 [phi,ix] = sort(phi);
550 eogchan = chan1*W(ix(1:3),:)';
552 elseif FLAG.TDSEP1, %
553 chan1= sparse(chan,1:nc,1,h0.NS,nc);
555 tmp = tmp(~any(isnan(tmp),2),:);
556 sel = 0:round(.15*h0.SampleRate);
557 W = tdsep0(tmp',sel);
562 phi(k) = subspace(h0.REGRESS.b0(2:3,:)',iW(:,k));
564 [phi,ix] = sort(phi);
565 eogchan = chan1*W(ix(1:3),:)';
567 elseif FLAG.TDSEP2, %
568 chan1= sparse(chan,1:nc,1,h0.NS,nc);
570 tmp = tmp(~any(isnan(tmp),2),:);
571 sel = 0:round(.15*h0.SampleRate);
572 W = tdsep0(tmp',sel);
578 P = eye(length(chan));
581 phi(k) = subspace(P*h0.REGRESS.b0(2:3,:)',P*iW(:,k));
583 [sp,ix(k0)] = min(phi);
586 P = P*[eye(nc)-w*inv(w'*w)*w'];
588 eogchan = chan1*W(ix,:)';
590 elseif FLAG.TDSEP3, %
592 chan = sparse(chan,1:nc,1,h0.NS,nc);
593 r = [chan,eogchan]; % CAR and bipolar EOG
595 tmp = tmp(~any(isnan(tmp),2),:);
597 sel = round([0,1,2,3,5,10,20]/256*h0.SampleRate);
598 [y,W,d] = tdsep3(tmp',sel);
599 eogchan = chan*W(1:3,:)';
603 elseif FLAG.REG, %REGRESSION
604 % define xEOG channels
606 if ~isempty(strfind(upper(Mode),'REG+CAR1'));
607 eogchan = [eogchan,any(eogchan,2)];
608 elseif ~isempty(strfind(upper(Mode),'REG+CAR2'));
609 eogchan = [eogchan,
double([CHANTYP=='O']/sum(CHANTYP=='O')-[CHANTYP>' ']/sum(CHANTYP>' '))];
613 % remove EOG channels for list of corrected channels
615 chan = find(CHANTYP=='E');
616 % regression analysis - compute correction coefficients.
618 h0.REGRESS.FLAG = FLAG;
620 %%%%% post-regression improvement
622 if ~isempty(strfind(Mode([1:4,6,7]),'REG+CA'))
623 % select EEG and EOG channels and do CAR
624 echan = find(CHANTYP>' ');
626 echan = sparse(echan,1:nc,1,h0.NS,nc)*(eye(nc) - 1/nc); % Common Average Reference (CAR)
630 if isempty(nc) | isnan(nc),
634 tmp = center(s01,1)*echan;
635 tmp = tmp(~any(isnan(tmp),2),:);
636 if ~isempty(strfind(Mode,'REG+PCA'))
638 [u,s,zeog] = svds(tmp, nc);
639 elseif ~isempty(strfind(Mode,'REG+ICA'))
640 %%% ICA: identify one component
641 [A,s] = jade(tmp',nc);
644 fprintf(2,'Mode %s not supported - regression is used only.\n',Mode);
648 w = sparse([eogchan,h0.REGRESS.r0*echan*zeog]);
656 h0.REGRESS.FLAG = FLAG;
659 %%% quantifying the noise