Skip to content

Commit

Permalink
Merge pull request #2 from mattansb/next
Browse files Browse the repository at this point in the history
TBT 2.5.0
  • Loading branch information
mattansb authored Sep 23, 2018
2 parents 9122815 + 8b37b6e commit 5193af3
Show file tree
Hide file tree
Showing 12 changed files with 282 additions and 29 deletions.
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
# TBT 2.5.0

- `pop_TBT` now returns the epochs in the correct order (and also runs a lot faster) (previously, due to the fucntion of `pop_mergeset`, the output EEG's trials were not necessarily in the same order as the input EEG - but the `urevents` could have been used as usual).
- When plotting in `pop_TBT`, a second array-plot is returned, allowing for a 'glance' at the rejected trials / channels.
- Fixed bug in `pop_eegmaxmin` where defult value for `winSize` did not make sense.
- Fixed bug in `pop_TBT` where the returned `EEG` dataset would sometimes consist of a single concatenated trial, and not epoced data (caused by `eeglab`'s `pop_mergeset`).
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ It also comes with an additional method for rejecting epochs - max-minus-min thr
## List of included functions

- `eegplugin_TBT` - EEGLAB plugin function.
- `tbt_bcr` - epoch-by-epoch channel interpolation, based on any rejection method used by EEGLAB, or a manual cell-array listing which channels to interpolate in which epochs.
- `tbt_bcr2` - epoch-by-epoch channel interpolation, based on any rejection method used by EEGLAB, or a manual cell-array listing which channels to interpolate in which epochs.
- `tbt_bool2cell` - converts a boolean channel-by-trial matrix to a tbt ready cell-list (see bellow).
- `tbt_cell2bool` - converts a tbt ready cell-list a boolean channel-by-trial matrix (see bellow).
- `pop_TBT` - call from EEGLAB menu of `tbt_bcr`. If no parameters are specified, pops a GUI window.
Expand All @@ -37,7 +37,9 @@ You will be asked to select a rejection method, and set its parameters, and also
2. **The maximum number of bad channels per trial.** If a trial has more than this number of bad channels, the trial will be removed.
3. Whether to plot the marked channels and trials before rejecting and interpolating the marked channels.

![](doc/tbt_plot.png)
![](doc/tbt_plot_eeg.png)
![](doc/tbt_plot_matrix.png)


## Scripting

Expand Down Expand Up @@ -112,7 +114,7 @@ tbt_bool2cell(EEG.reject.rejmaxminE, EEG)
```

### Intepolating all missing channels
### Interpolating all missing channels

By default, trial-by-trial interpolation interpolates *only* the channels that are marked on a single-trial basis. i.e., channels marked as bad across the whole data-set will not be re-added by interpolation. If you wish to add them back (or any other channel that may have been removed in any previous processing step), channel locations can be added to `pop_tbt`:

Expand Down
2 changes: 0 additions & 2 deletions doc/Using_eegmaxmin.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@

<!-- README.md is generated from README.Rmd. Please edit that file -->
eegmaxmin
=========

Expand Down
Binary file removed doc/tbt_plot.png
Binary file not shown.
Binary file added doc/tbt_plot_eeg.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added doc/tbt_plot_matrix.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
10 changes: 5 additions & 5 deletions eegmaxmin/eegmaxmin.m
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@

if isempty(chanRange), chanRange = 1:EEG.nbchan; end
if isempty(timeRange), timeRange = [EEG.xmin EEG.xmax]*1000; end % in ms
if isempty(winSize), winSize = 1000*(timeRange(2)-timeRange(1)); end % in ms
if isempty(stepSize), stepSize = round(winSize/10); end % in ms
if isempty(winSize), winSize = diff(timeRange); end % in ms
if isempty(stepSize), stepSize = ceil(winSize/10); end % in ms
if isempty(maW), maW = 0; end % in ms

% Prep data
t_ind = EEG.times >= timeRange(1) & EEG.times <= timeRange(2);
c_ind = chanRange;
cut_data = EEG.data(c_ind,t_ind,:);

maW = round(maW/(1000/EEG.srate));
if maW > 0
maW = round(maW/(1000/EEG.srate));
try
cut_data = movmean(cut_data,maW,2);
catch
warning(sprintf('moving average supported only on Matlab 2016b+\nWill not compute moving average'))
warning('moving average supported only on Matlab 2016b+\nWill not compute moving average')
end
end

Expand All @@ -25,7 +25,7 @@
w1 = [1:stepSize:(size(cut_data,2)-winSize) (size(cut_data,2)-winSize+1)];
we = w1+winSize-1;

rej = false([size(EEG.data)]);
rej = false(size(EEG.data));
rej = permute(rej,[1,3,2]);
rej = rej(:,:,1:length(w1));
for tw = 1:length(w1)
Expand Down
4 changes: 2 additions & 2 deletions eegmaxmin/pop_eegmaxmin.m
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
% pop_TBT() - Rejects and iterpolates channels on a epoch by epoch basis.
%
% Usage:
% >> [EEG, com] = pop_eegmaxmin(EEG); % pop-up interative window mode
% >> [EEG, com] = pop_eegmaxmin(EEG); % pop-up interactive window mode
% >> EEG = pop_eegmaxmin(EEG,chanRange,timeRange,minmaxThresh,winSize,stepSize,maW);
%
% Inputs:
Expand Down Expand Up @@ -54,7 +54,7 @@
{'Style', 'text', 'string', 'Channels' }...
{'Style', 'edit', 'string', ['1:' num2str(EEG.nbchan)] 'tag' 'chans' } ...
{'Style', 'text', 'string', 'Window Size (in ms)' }...
{'Style', 'edit', 'string', '500' 'tag' 'win' } ...
{'Style', 'edit', 'string', num2str(floor([EEG.xmax - EEG.xmin]/2*1000)) 'tag' 'win' } ...
{'Style', 'text', 'string', 'Step Size (in ms)' }...
{'Style', 'edit', 'string', '1' 'tag' 'step' } ...
{'Style', 'text', 'string', 'compute moving average of (in ms):' }...
Expand Down
12 changes: 5 additions & 7 deletions tbt/pop_TBT.m
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,8 @@
% interpolated back in.
%
% Outputs:
% EEG - output dataset. Note that due to the fucntion of
% `pop_mergeset`, the output EEG's trials are not necessarily
% in the same order as the input EEG (but the urevents can
% still be used as usual).
% EEG - output dataset.
% badlist - number of bad channel and epochs removed
%
% Copyright (C) 2017 Mattan S. Ben-Shachar
%
Expand Down Expand Up @@ -107,7 +105,7 @@
' ',...
'pop_rejspec';...
...
['[1:' num2str(EEG.nbchan) '],[' num2str([EEG.xmin EEG.xmax]*1000) '],200,' num2str([EEG.xmax - EEG.xmin]*1000) ',1,0'],...
['[1:' num2str(EEG.nbchan) '],[' num2str([EEG.xmin EEG.xmax]*1000) '],100,' num2str([EEG.xmax - EEG.xmin]*1000) ',1,0'],...
'chanRange,timeRange,minmaxThresh,winSize,stepSize,maW',...
'pop_eegmaxmin';...
};
Expand Down Expand Up @@ -166,9 +164,9 @@
else
if ~exist('plot_bads','var'), plot_bads = 1; end
if exist('chanlocs','var')
[EEG, nbadchan, nbadtrial] = tbt_bcr(EEG,bads,badsegs,badchans,plot_bads,chanlocs);
[EEG, nbadchan, nbadtrial] = tbt_bcr2(EEG,bads,badsegs,badchans,plot_bads,chanlocs);
else
[EEG, nbadchan, nbadtrial] = tbt_bcr(EEG,bads,badsegs,badchans,plot_bads);
[EEG, nbadchan, nbadtrial] = tbt_bcr2(EEG,bads,badsegs,badchans,plot_bads);
end

badlist.nbadchan = nbadchan;
Expand Down
60 changes: 50 additions & 10 deletions tbt/tbt_bcr.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@

function [EEG, nbadchan, nbadtrial] = tbt_bcr(EEG,bads,badsegs,badchans,plot_bads,chanlocs)

warning('tbt_bcr() is Depricated - use tbt_bcr2() instead!')

%% convert bads from cell to array
if iscell(bads)
fprintf('pop_TBT(): Converting cell-array.')
Expand Down Expand Up @@ -78,20 +80,48 @@
end

if plot_bads==1
mark = bads';

mark(:,6:end+5) = mark;
% Plot Matrix at a glance
forplot_bads = double(~bads);
forplot_bads(:,bTrial_ind) = EEG.trials + 1;
for tr = 1:EEG.trials
forplot_bads(bads(:,tr),tr) = tr;
end
forplot_bads(bChan_ind,:) = EEG.trials + 1;

figure; imagesc(forplot_bads);
colormap([1 1 1; rand([EEG.trials 3]); [1 .25 .25]])
title('Bad channels by trial')
xlabel('Trial'); ylabel('Channel');
yticks(1:EEG.nbchan); yticklabels({EEG.chanlocs.labels});
annotation('textbox', [0.1, 0.07, 0, 0],...
'String', 'Each trial is marked by a color. Red lines indicate a channel or trial that will be completelty removed. ',...
'FitBoxToText', 'on', 'LineStyle', 'none');

% Plot EEG
mark = ones([0,5] + size(bads'));
mark(:,6:end) = double(bads');
mark(:,1) = 1:EEG.pnts:EEG.pnts*EEG.trials; % start sample
mark(:,2) = mark(:,1)+EEG.pnts; % end sample
mark(~bTrial_ind,3:5) = 1; % color for good trials (white)
mark(bTrial_ind,3) = 1; % R for bad trials
mark(bTrial_ind,4) = 0.9; % G for bad trials
mark(bTrial_ind,5) = 0.7; % B for bad trials
eegplot(EEG.data(:,:,:),'srate', EEG.srate, 'limits', [EEG.xmin EEG.xmax]*1000 ,...
'events', EEG.event ,'winrej',mark);
eegplot(EEG.data(:,:,:), 'winrej',mark, 'events', EEG.event ,...
'srate', EEG.srate, 'limits', [EEG.xmin EEG.xmax]*1000);
uiwait(gcf);

choice = questdlg(sprintf('Proceed?'), ...
dlgmsstxt = sprintf(...
['%d trial and %d channels will be completely removed. Additionally:\n'...
'%d channel(s) are bad on at least 1 trial.\n'...
'%d trial(s) contain at least 1 bad channel\n'...
'\n',...
'Proceed?'],...
length(bTrial_num),...
length(bChan_lab),...
sum(any(bads(~bChan_ind,~bTrial_ind),2)),...
sum(any(bads(~bChan_ind,~bTrial_ind),1)));

choice = questdlg(...
dlgmsstxt, ...
'Confirm', ...
'Yes','No (do nothing)','Yes');
switch choice
Expand All @@ -100,8 +130,6 @@
end
end



if plot_bads==0
%% Remove bad channels and trials
% Remove bad channels
Expand All @@ -124,7 +152,7 @@
end

%% Interpolate bad channels (trial by trial)
EEG_old = EEG; % need the old channlocs an events for later
EEG_old = EEG; % need the old channlocs and events for later
if ~exist('chanlocs','var')
interp_all = true;
chanlocs = EEG_old.chanlocs;
Expand All @@ -136,6 +164,8 @@
tbt = tbt_bool2cell(bads,EEG);

if size(tbt,1)~=0
fprintf('pop_TBT(): %d channel(s) are bad on at least 1 trial.\n',sum(any(bads,2)))
fprintf('pop_TBT(): %d traisl(s) contain at least 1 bad channel.\n',sum(any(bads,1)))
fprintf('pop_TBT(): Splitting data')
for t = 1:size(tbt,1) % each trial with bad channels
if ~mod(t,5), fprintf('.'); end
Expand Down Expand Up @@ -168,9 +198,19 @@

% Merge
fprintf('pop_TBT(): Merging data (this might take a while)..')
old_ntrials = [];
if NEWEEG(end).trials==1 % this will cause problems when merging.
old_ntrials = EEG.trials;
NEWEEG(end+1) = EEG;
end
evalc('EEG = pop_mergeset(NEWEEG, [length(NEWEEG) 1:length(NEWEEG)-1], 0);');
% EEG = pop_mergeset(NEWEEG, [length(NEWEEG) 1:length(NEWEEG)-1], 0);

if ~isempty(old_ntrials)
evalc('EEG = pop_select( EEG,''notrial'',[1:old_ntrials]);');
% EEG = pop_select( EEG,'notrial',[1:old_ntrials]);
end

% remove extra trimmmmmmmm from urevent
old_ur_len = length(EEG_old.urevent)+1;
for e = 1:length(EEG.event)
Expand Down
148 changes: 148 additions & 0 deletions tbt/tbt_bcr2.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
% tbt_bcr2() - Rejects and iterpolates channels on a epoch by epoch basis.
%
% Usage:
% >> [EEG, nbadchan, nbadtrial] = tbt_bcr2(EEG,bads,badsegs,badchans,plot_bads,chanlocs);
%
% Inputs:
% EEG - input dataset
% bads - a boolean 2-d matrix (channels * trials) specifying "bad
% channels" for each epoch. OR a cell array (see pop_TBT
% for specs).
% badsegs - Number of max bad channels per epoch. If an epoch has
% more than this number of bad channels, the epoch is
% removed.
% badchans - Proportion (e.g., 0.3) of max bad epochs per channel. If
% a channel was found to be bad on more than this percent
% of trials, it is removed.
% plot_bads - [0|1] plot before executing. When plotting, will also ask
% to confirm. If no plotting, will execute immediately.
% chanlocs - [optional] a chanlocs struct (e.g., EEG.chanlocs). If
% provided, missing channels will be interpolated according
% to this struct, and not the input EEG. NOTE that if not
% provided, channel that have been rejected across the
% dataset (according to the badchans critirion) will not be
% interpolated back in.
%
% Outputs:
% EEG - output dataset
% nbadchan - number of channels rejected
% nbadtrial - number of epochs rejected
%

% Copyright (C) 2018 Mattan S. Ben-Shachar
%
% 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, nbadchan, nbadtrial] = tbt_bcr2(EEG,bads,badsegs,badchans,plot_bads,chanlocs)

%% convert bads from cell to array
if iscell(bads)
fprintf('pop_TBT(): Converting cell-array.')
bads = tbt_cell2bool(bads,EEG);
fprintf('.. done.\n')
end

%% Find bad trials and Channels

% Find trial with more than X bad channels:
trials_ind = 1:EEG.trials;
bTrial_ind = sum(bads,1)>=badsegs; % boolean list
bTrial_num = trials_ind(bTrial_ind); % trial list
nbadtrial = length(bTrial_num); % count bad trials

% Find channels that have been marked as bad in more than X% of trials:
bChan_ind = (sum(bads,2)/EEG.trials)>=badchans; % boolean list
bChan_lab = {EEG.chanlocs(bChan_ind).labels}; % Channel label list
nbadchan = length(bChan_lab); % count bad channels
bads(bChan_ind,:) = 1; % mark for plotting

%% Plot

if isempty(plot_bads)
plot_bads=0;
elseif ~any(plot_bads==[-1 0 1])
error('plot_bads can only be 0, 1 (or empty - defaults to 0).')
end

if plot_bads==1
plot_bads = tbt_plot(EEG,bads,bTrial_ind,bChan_ind);
end

if plot_bads==0
%% Remove bad channels and trials
% Remove bad channels
fprintf('\n')
if ~isempty(bChan_lab) % if any bad channels
fprintf('pop_TBT(): Dropping %d channel(s).',length(bChan_lab))
evalc('EEG = pop_select(EEG,''nochannel'',bChan_lab);');
% EEG = pop_select(EEG,'nochannel',bChan_lab); % remove from data
bads = bads(~bChan_ind,:); % update bads matrix
fprintf('.. done.\n')
end

% Remove bad trials
if ~isempty(bTrial_num) % if any bad trials
fprintf('pop_TBT(): Removing %d epoch(s).',length(bTrial_num))
evalc('EEG = pop_rejepoch(EEG, bTrial_num ,0);');
% EEG = pop_rejepoch(EEG, bTrial_num ,0); % remove from data
bads = bads(:,~bTrial_ind); % update bads matrix
fprintf('.. done.\n')
end

%% Interpolate bad channels (trial by trial)
if ~exist('chanlocs','var')
interp_all = false;
chanlocs = EEG.chanlocs;
else
interp_all = true;
end

% convert bool array to n-by-2 cell-list
tbt = tbt_bool2cell(bads,EEG);

if size(tbt,1)~=0
fprintf('pop_TBT(): %d channel(s) are bad on at least 1 trial.\n',sum(any(bads,2)))
fprintf(' %d trials(s) contain at least 1 bad channel.\n',sum(any(bads,1)))

fprintf('pop_TBT(): Interpolating epoch-by-epoch..')
for t = 1:size(tbt,1) % each trial with bad channels
if ~mod(t,5), fprintf('.'); end

% split
evalc('tempeeg(t) = pop_selectevent(EEG, ''epoch'',tbt{t,1});');

% remove bad channels
evalc('tempeeg(t) = pop_select(tempeeg(t),''nochannel'',tbt{t,2});');

% interp single trial
evalc('tempeeg(t) = pop_interp(tempeeg(t), chanlocs, ''spherical'');');
end

evalc('EEG = pop_interp(EEG, chanlocs, ''spherical'');'); % to match all channels to the chanloc channel
tempeeg = cat(3,tempeeg.data); % gather all data
EEG.data(:,:,cat(1,tbt{:,1})) = tempeeg; % re-add to EEG.data

fprintf('.. done.\n')
elseif interp_all
fprintf('pop_TBT(): Interpolating missing channels')
evalc('EEG = pop_interp(EEG, chanlocs, ''spherical'');');
fprintf('.. done.\n')
end

EEG = eeg_checkset(EEG);
end

end
Loading

0 comments on commit 5193af3

Please sign in to comment.