Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add excluded channels back to data after preprocessing #66

Open
wants to merge 17 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
ee462f6
store excluded channel data in memory during preprocessing
dominikwelke Jul 7, 2022
09ea042
store excluded channel data in memory during preprocessing pt2
dominikwelke Jul 7, 2022
101eecc
add optional parameter for reattaching excluded channels
dominikwelke Jul 7, 2022
2ec85ff
save memory if excluded channels should be dropped
dominikwelke Jul 7, 2022
193b920
add data from excluded channels back to EEG structure after preproces…
dominikwelke Jul 7, 2022
e5bb183
let quality assessment ignore excluded and readded channels
dominikwelke Jul 7, 2022
aea6db7
set default behavior to readd excluded channels
dominikwelke Jul 7, 2022
56fcb32
make it work with *no* excluded channel
dominikwelke Jul 7, 2022
82d2d57
fix quality assessment if now channels are excluded
dominikwelke Jul 7, 2022
f57aac8
option to keep ET data and events in final pipeline output
dominikwelke Jul 14, 2022
d505e2d
set parameter to readd excluded channels by default (hard coded by now)
dominikwelke Jul 15, 2022
abee7d2
fix channelselection for figures if excluded/ET channels were reattac…
dominikwelke Jul 15, 2022
04aa964
move ET data storage before OPTICAT step as this alters events etc
dominikwelke Jul 18, 2022
5442e85
make adding of ET chanloc more robust (maybe not needed anymore?)
dominikwelke Jul 18, 2022
64a46cb
calcQuality should handle cases with ET data but no excluded channels…
dominikwelke Jul 18, 2022
073df1b
calcQuality can now deal with added misc/ET cahnnels (doesnt take exc…
dominikwelke Jul 21, 2022
42c2808
allow interpolation to ignore readded misc channels
dominikwelke Jul 21, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions gui/mainGUI.m
Original file line number Diff line number Diff line change
Expand Up @@ -929,6 +929,7 @@ function createbutton_Callback(hObject, eventdata, handles)

handles.params.ChannelReductionParams.tobeExcludedChans = ...
str2num(get(handles.excludeedit, 'String'));
handles.params.ChannelReductionParams.readdExcludedChans = true; % hard coded for now!
else
handles.params.ChannelReductionParams = struct([]);
end
Expand Down
3 changes: 2 additions & 1 deletion preprocessing/RecommendedParameters.m
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@

DetrendingParams = struct();

ChannelReductionParams = struct('tobeExcludedChans', []);
ChannelReductionParams = struct('tobeExcludedChans', [], ...
'readdExcludedChans', false);


EEGSystem = struct('name', 'Others',...
Expand Down
9 changes: 7 additions & 2 deletions preprocessing/performETguidedICA.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function [EEG] = performETguidedICA(EEG, params)
function [EEG, ET] = performETguidedICA(EEG, params)

% For details see the underlying publication: Dimigen, 2020, NeuroImage

Expand Down Expand Up @@ -116,7 +116,12 @@
% %
% EEG = pop_detecteyemovements(EEG,[LX LY],[RX RY],THRESH,MINDUR,DEG_PER_PIXEL,SMOOTH,0,25,2,PLOTFIG,WRITESAC,WRITEFIX);

%% Create optimized data for ICA training (OPTICAT, Dimigen, 2018)
%% keep ET data for reattaching it at the end of the preprocessing stream
nbchan = length(ETparams.sync.importColumns);
ET = EEG;
ET.data = ET.data(end-nbchan+1:end, :); % keep only ET data
ET.nbchan = nbchan;
ET.chanlocs = ET.chanlocs(end-nbchan+1:end);

OW_PROPORTION = 1.0; % overweighting proportion
SACCADE_WINDOW = [str2double(params.from_edit) str2double(params.to_edit)]; % time window to overweight (-20 to 10 ms is default)
Expand Down
11 changes: 8 additions & 3 deletions preprocessing/performICLabel.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function EEG = performICLabel(EEG, varargin)
function [EEG, ET] = performICLabel(EEG, varargin)
% performICLabel perform Independent Component Analysis (ICA) on the high
% passsed EEG and classifies bad components using ICLabel.
% This function applies a high pass filter before the ICA. But the output
Expand Down Expand Up @@ -114,8 +114,9 @@
EEG_orig.automagic.iclabel.ETguidedICA.performed = 'no';
try
if ETguidedICA
[~, EEG] = evalc('performETguidedICA(EEG, addETdataParams)');
EEG.data = EEG.data(1:size(EEG_orig.data, 1), :); % remove ET data
[~, EEG, ET] = evalc('performETguidedICA(EEG, addETdataParams)');
% remove ET data
EEG.data = EEG.data(1:size(EEG_orig.data, 1), :);
EEG.nbchan = EEG_orig.nbchan;
EEG.chanlocs = EEG_orig.chanlocs;
EEG_orig.automagic.iclabel.ETguidedICA.performed = 'yes';
Expand All @@ -125,6 +126,10 @@
fprintf('\n ET guided ICA skipped. Continue with the standard ICA... \n')
end

if ~exist('ET','var')
ET = [];
end

%% Run ICA
[~, EEG, ~] = evalc('pop_runica(EEG, ''icatype'',''runica'',''chanind'',EEG.icachansind)');

Expand Down
69 changes: 64 additions & 5 deletions preprocessing/preprocess.m
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,6 @@
ICLabelParams.addETdataParams = p.Results.addETdataParams;
end


if isempty(Settings)
Settings = Recs.Settings;
end
Expand All @@ -144,12 +143,20 @@

data_orig = data; % for plot with original data

% Set system dependent parameters and eeparate EEG from EOG
[EEG, EOG, EEGSystem, MARAParams] = ...
% Set system dependent parameters and separate EEG from EOG
[EEG, EOG, EXCLUDED, EEGSystem, MARAParams] = ...
systemDependentParse(data, EEGSystem, ChannelReductionParams, ...
MARAParams, ORIGINAL_FILE);
EEGRef = EEG;

% free memory if EXCLUDED channel data is not supposed to be reattached
% later
if ~isempty(ChannelReductionParams)
if ~ChannelReductionParams.readdExcludedChans
clear EXCLUDED
end
end

% Trim data
EEG.automagic.TrimData.performed = 'no';
if isfield(TrimDataParams, 'changeCheck')
Expand Down Expand Up @@ -305,7 +312,7 @@
EEG.automagic.iclabel.performed = 'no';
if ( ~isempty(ICLabelParams) )
try
EEG = performICLabel(EEG, ICLabelParams);
[EEG, ET] = performICLabel(EEG, ICLabelParams);
EEG.icachansind = find(~EEG.automagic.preprocessing.removedMask);
catch ME
message = ['ICLabel is not done on this subject, continue with the next steps: ' ...
Expand Down Expand Up @@ -413,14 +420,41 @@
clear chan_nb re_chan;
end

% Write back output
% Write back output (ref channel)
if ~isempty(EEGSystem.refChan)
removedChans(removedChans >= EEGSystem.refChan.idx)=removedChans(removedChans >= EEGSystem.refChan.idx)+1;
EEG.automagic.autoBadChans = removedChans;

else
EEG.automagic.autoBadChans = removedChans;
end

% Put back excluded channels (only if wanted)
if ~isempty(EEG.automagic.channelReduction.params)
if (EEG.automagic.channelReduction.params.readdExcludedChans && ...
~isempty(EEG.automagic.channelReduction.excludedChannels))
excludedChans = sort(EEG.automagic.channelReduction.excludedChannels); % should be ordered (for step below)
for i_ch_excl = 1:length(excludedChans)
excluded_chan = excludedChans(i_ch_excl);
EEG.data = [EEG.data(1:excluded_chan-1,:); ...
EXCLUDED.data(i_ch_excl,:);...
EEG.data(excluded_chan:end,:)];
EXCLUDED.chanlocs(i_ch_excl).maraLabel = [];
EEG.chanlocs = [EEG.chanlocs(1:excluded_chan-1), ...
EXCLUDED.chanlocs(i_ch_excl), ...
EEG.chanlocs(excluded_chan:end)];
end
EEG.nbchan = size(EEG.data,1);
clear excluded_chan i_ch_excl

% Write back output (excluded channels)
for excluded_chan = excludedChans
removedChans(removedChans >= excluded_chan)=removedChans(removedChans >= excluded_chan)+1;
end
end
end
EEG.automagic.autoBadChans = removedChans;

EEG.automagic.params = params;
EEG.automagic = rmfield(EEG.automagic, 'preprocessing');

Expand All @@ -429,6 +463,26 @@
allSteps.EEGFinal = EEG;
end

% attach Eyetracker data (if wanted)
addETdata = true;
EEG.automagic.channelReduction.params.addETdata = addETdata; % hard coded for now
%addETevents = true; % might be seperately set, if preferred
if exist('ET', 'var')
if ~isempty(ET) && EEG.automagic.channelReduction.params.addETdata
EEG.data = [EEG.data; ET.data]; % add to the very end, such that it doesnt affect any index
f = fieldnames(ET.chanlocs);
toKeep = fieldnames(EEG.chanlocs);
toRemove = f(~ismember(f,toKeep));
EEG.chanlocs = [EEG.chanlocs, rmfield(ET.chanlocs, toRemove)];
EEG.nbchan = size(EEG.data,1);
% end
% if ~isempty(ET) && addETevents
EEG.event = ET.event;
EEG.urevent = ET.urevent;
%EEG.eventdescription = ET.eventdescription;
end
end

%% Creating the final figure to save

%%% Find the colormap selected
Expand Down Expand Up @@ -491,6 +545,8 @@
else
final_idx = 1:size(EEG.data, 1);
end
% only keep original EEG channels (no ET, no excluded channels)
final_idx = final_idx(ismember(final_idx, EEG.automagic.channelReduction.usedEEGChannels));

%eeg figure
subplot(13,1,2:3)
Expand Down Expand Up @@ -726,6 +782,9 @@
else
final_idx = 1:size(EEG.data, 1);
end
% only keep original EEG channels (no ET, no excluded channels)
final_idx = final_idx(ismember(final_idx, EEG.automagic.channelReduction.usedEEGChannels));

fig2 = figure('visible', 'off');
ax = gca;
% outerpos = ax.OuterPosition;
Expand Down
7 changes: 6 additions & 1 deletion preprocessing/systemDependentParse.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function [EEG, EOG, EEGSystem, MARAParams] = ...
function [EEG, EOG, EXCLUDED, EEGSystem, MARAParams] = ...
systemDependentParse(EEG, EEGSystem, ...
ChannelReductionParams, MARAParams, ORIGINAL_FILE) %#ok<INUSD>
% systemDependentParse parse EEG input depending on the EEG system.
Expand Down Expand Up @@ -449,9 +449,13 @@
clear all_chans;
end

% Store excluded channel data for later retrieval
[~, EXCLUDED] = evalc('pop_select( EEG , ''channel'', tobeExcludedChans)');

% Seperate EEG channels from EOG channels
[~, EOG] = evalc('pop_select( EEG , ''channel'', eog_channels)');
[~, EEG] = evalc('pop_select( EEG , ''channel'', eeg_channels)');

% Map original channel lists to new ones after the above separation
if ~isempty(EEGSystem.refChan)
[~, idx] = ismember(EEGSystem.refChan.idx, eeg_channels);
Expand All @@ -463,6 +467,7 @@
if chanSize(2) == 1
EEG.chanlocs = EEG.chanlocs';
EOG.chanlocs = EOG.chanlocs';
EXCLUDED.chanlocs = EXCLUDED.chanlocs';
end
clear chanSize;

Expand Down
49 changes: 36 additions & 13 deletions src/Block.m
Original file line number Diff line number Diff line change
Expand Up @@ -528,8 +528,17 @@ function excludeChannelsFromRBC(self, exclude_chans)
if(isempty(EEG))
return;
end
% index EEG channels for quality asssessment
eeg_chans = 1:EEG.nbchan;
if isfield(EEG.automagic, 'channelReduction')
if EEG.automagic.channelReduction.params.readdExcludedChans % in this case take original eeg indices. ET is also excluded by default
eeg_chans = EEG.automagic.channelReduction.usedEEGChannels;
elseif EEG.automagic.channelReduction.params.addETdata % in this case, original eeg indices dont work, but we have to excluce the ET channels at the end
eeg_chans = 1:EEG.nbchan-6; % hard coded for now
end
end
rating_badchans = [];
qScore = calcQuality(EEG, rating_badchans, ...
qScore = calcQuality(EEG, eeg_chans, rating_badchans, ...
self.project.qualityThresholds);
qScoreIdx.OHA = arrayfun(@(x) ceil(length(x.OHA)/2), qScore);
qScoreIdx.THV = arrayfun(@(x) ceil(length(x.THV)/2), qScore);
Expand Down Expand Up @@ -610,29 +619,43 @@ function interpolate(self)
InterpolationParams = self.params.InterpolationParams;
end

%save old ica data which gets corrupted in eeg_interp method:
orig_icasphere=EEG.icasphere;
orig_icachansind=EEG.icachansind;
orig_icaweights=EEG.icaweights;
orig_icawinv=EEG.icawinv;
% to avoid excluded misc and ET channels going into the interpolation,
% we also let the misc channels be interpolated (temporarily).
% draw info from automagic params
EEGtoInterp = EEG;
misc_chans = [];
if isfield(automagic, 'channelReduction')
usedEEGChannels = automagic.channelReduction.usedEEGChannels;
%misc_chans = [1:EEG.nbchan];
misc_chans = find(~cellfun('isempty', { EEGtoInterp.chanlocs.theta })); % only those with loc info, as others are ignored by interpolation function anyway..
misc_chans = misc_chans(~ismember(misc_chans, usedEEGChannels));
end

% do interpolation
if size(EEG.data,1)==length(interpolate_chans)
disp('All channels are bad. Skipping interpolation...');
filenamE = strsplit(EEG.comments,filesep);
filenamE = filenamE{end};
automagic.error_msg = ['Interpolation skipped because all channels are bad: ',filenamE];
else
EEG = eeg_interp(EEG ,interpolate_chans , InterpolationParams.method);
EEGtoInterp = eeg_interp(EEGtoInterp ,[interpolate_chans misc_chans], InterpolationParams.method);
end
%put the original icadata back into the structure
EEG.icasphere=orig_icasphere;
EEG.icachansind=orig_icachansind;
EEG.icaweights= orig_icaweights;
EEG.icawinv=orig_icawinv;
% now add only the interpolated EEG channels back to EEG struct
EEG.data(interpolate_chans,:) = EEGtoInterp.data(interpolate_chans,:);

rating_badchans = unique([self.finalBadChans interpolate_chans]);
rating_badchans = setdiff(rating_badchans, ...
self.project.manuallyExcludedRBCChans);
qScore = calcQuality(EEG, rating_badchans, ...
% index EEG channels for quality asssessment
eeg_chans = 1:EEG.nbchan;
if isfield(automagic, 'channelReduction')
if automagic.channelReduction.params.readdExcludedChans % in this case take original eeg indices. ET is also excluded by default
eeg_chans = automagic.channelReduction.usedEEGChannels;
elseif automagic.channelReduction.params.addETdata % in this case, original eeg indices dont work, but we have to excluce the ET channels at the end
eeg_chans = [1:EEG.nbchan-6]; % hard coded for now
end
end
qScore = calcQuality(EEG, eeg_chans, rating_badchans, ...
self.project.qualityThresholds);
qScoreIdx.OHA = arrayfun(@(x) ceil(length(x.OHA)/2), qScore);
qScoreIdx.THV = arrayfun(@(x) ceil(length(x.THV)/2), qScore);
Expand Down
9 changes: 7 additions & 2 deletions src/calcQuality.m
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
function Q = calcQuality(EEG, bad_chans, varargin)
function Q = calcQuality(EEG, eeg_chans, bad_chans, varargin)
% Calculates quality measures of a dataset based on the following metrics:
%
% - The ratio of data points that exceed the absolute value a certain
Expand Down Expand Up @@ -61,8 +61,13 @@
disp('No bad channel information...')
end
%% Data preparation
% Data
% only rate data that was actually prepocessed (without excluded and
% readded channels, or added ET channels)

% remove readded EXCLUDED and ET channels from data (have to remove it from EEG, as calcCHV uses it)
EEG.data = EEG.data(eeg_chans,:); % i think its not necessary to shift around bad_chans indices, as they are not used specifically?!
X = EEG.data;

% Get dimensions of data
t = size(X,2);
c = size(X,1);
Expand Down