Skip to content

Commit

Permalink
Merge pull request #111 from fahrenfort/bugfixes
Browse files Browse the repository at this point in the history
Bugfixes
  • Loading branch information
fahrenfort authored Mar 12, 2019
2 parents a278429 + 8acdce0 commit 5014cec
Show file tree
Hide file tree
Showing 12 changed files with 270 additions and 375 deletions.
14 changes: 4 additions & 10 deletions eeg_TFR/eeglab2ft.m
Original file line number Diff line number Diff line change
Expand Up @@ -89,19 +89,14 @@
case 'preprocessing'
for index = 1:EEG.trials
data.trial{index} = EEG.data(:,:,index);
%data.time{index} = linspace(EEG.xmin, EEG.xmax, EEG.pnts); % should be checked in FIELDTRIP
data.time{index} = EEG.times;
data.time{index} = linspace(EEG.xmin, EEG.xmax, EEG.pnts); % should be checked in FIELDTRIP
end;
data.label = { tmpchanlocs(1:EEG.nbchan).labels };


case 'timelockanalysis'
data.avg = mean(EEG.data, 3);
data.var = std(EEG.data, [], 3).^2;
% data.time = linspace(EEG.xmin, EEG.xmax, EEG.pnts); % should be checked in FIELDTRIP
data.time = EEG.times;
data.label = { tmpchanlocs(1:EEG.nbchan).labels };

data.time = linspace(EEG.xmin, EEG.xmax, EEG.pnts); % should be checked in FIELDTRIP
data.label = { tmpchanlocs(1:EEG.nbchan).labels };
case 'componentanalysis'
if isempty(EEG.icaact)
icaacttmp = eeg_getica(EEG);
Expand All @@ -118,8 +113,7 @@
catch

end;
%data.time{index} = linspace(EEG.xmin, EEG.xmax, EEG.pnts); % should be checked in FIELDTRIP
data.time{index} = EEG.times;
data.time{index} = linspace(EEG.xmin, EEG.xmax, EEG.pnts); % should be checked in FIELDTRIP
end;
data.label = [];
for comp = 1:size(EEG.icawinv,2)
Expand Down
2 changes: 2 additions & 0 deletions eeg_mvpa/adam_compute_group_MVPA.m
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@
reduce_dims = '';
freqlim = [];
plotsubjects = false;
singleplot = false;

% backwards compatibility
plot_dim = 'time_time'; % default, 'time_time' or 'freq_time'
Expand Down Expand Up @@ -174,6 +175,7 @@
channelpool = channels;
cfg.channelpool = channelpool;
end
cfg.singleplot = singleplot;

% check freqlimits
if (strcmpi(plot_dim,'freq_time') && ~strcmpi(reduce_dims,'avfreq')) && (numel(freqlim) == 1 || (~isempty(freqlim) && abs(diff(freqlim)) <= 2))
Expand Down
23 changes: 18 additions & 5 deletions eeg_preprocessing/adam_detrend_and_epoch.m
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ function adam_detrend_and_epoch(cfg)
% cfg.pad_length = wide epoch padding window used to fit the polynomial on, e.g.
% pad_length of 100 creates 50 second pads around both sides of a
% trial (Default: 50). This padding window is only used during
% detrending and is discarded after the trial itsel is epoched out
% detrending and is discarded after the trial itself is epoched out
% (when doing the final epoching).
% cfg.start_mask = in seconds, specifies when your 'cognitive' or other events of
% interest start
Expand All @@ -33,7 +33,11 @@ function adam_detrend_and_epoch(cfg)
% or only the currently relevant trial. If you have sufficiently long
% intertrial intervals you may also set this to false.
% cfg.event_codes = array containing the relevant event values for the experiment.
% cfg.remove_bad_chans = identifies and interpolates bad electrodes.
% cfg.remove_bad_chans = 'yes' or 'no' (default: 'yes'). Identifies and interpolates bad
% electrodes.
% cfg.mask_bad_data = 'yes' or 'no' (default: 'no'). Masks out bad data *only during
% detrending* (the bad data is left in and still needs to be removed
% later if desired).
%
% The function also saves a graph for each subject in the output folder, containg (from top to
% bottom):
Expand Down Expand Up @@ -65,7 +69,8 @@ function adam_detrend_and_epoch(cfg)
% cfg.pad_length = 50;
% cfg.mask_only_current = 'no';
% cfg.event_codes = [1001:1024 1101:1124];
% cfg.remove_bad_chans = true;
% cfg.remove_bad_chans = 'yes';
% cfg.mask_bad_data = 'yes';
%
% adam_detrend_eeg_and_epoch(cfg);
%
Expand All @@ -78,6 +83,7 @@ function adam_detrend_and_epoch(cfg)
pad_length = 50;
mask_only_current = true;
remove_bad_chans = true;
mask_bad_data = false;

% unpack cfg
v2struct(cfg);
Expand Down Expand Up @@ -121,16 +127,23 @@ function adam_detrend_and_epoch(cfg)
remove_bad_chans = 'no';
end
end
if ~ischar(mask_bad_data)
if mask_bad_data
mask_bad_data = 'yes';
else
mask_bad_data = 'no';
end
end
if ~ischar(event_codes)
event_codes = cond_string(event_codes);
end

% run analysis
if ~exist('qsub','var') || isempty(qsub) % run local
for cSubj = 1:numel(filenames)
detrend_and_epoch(datadir,filenames{cSubj},outputdir, start_epoch, end_epoch, polynomial_order, pad_length, start_mask, end_mask, mask_only_current, remove_bad_chans, event_codes);
detrend_and_epoch(datadir,filenames{cSubj},outputdir, start_epoch, end_epoch, polynomial_order, pad_length, start_mask, end_mask, mask_only_current, mask_bad_data, remove_bad_chans, event_codes);
end
else % or create qsub files
create_slurm_files(qsub.functionpath,'detrend_and_epoch',qsub, datadir, filenames, outputdir, start_epoch, end_epoch, polynomial_order, pad_length, start_mask, end_mask, mask_only_current, remove_bad_chans, event_codes);
create_slurm_files(qsub.functionpath,'detrend_and_epoch',qsub, datadir, filenames, outputdir, start_epoch, end_epoch, polynomial_order, pad_length, start_mask, end_mask, mask_only_current, mask_bad_data, remove_bad_chans, event_codes);
end

91 changes: 91 additions & 0 deletions eeg_preprocessing/adam_epoch.m
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
function adam_epoch(cfg)
% Reads in CONTINUOUS (!) EEGLAB data, epochs and writes out as EPOCHED (!) EEGLAB file.
% No pre-processing is applied other than mean removal from every trial after epoching.
% The output of this function can serve as input for ADAM_MVPA_FIRSTLEVEL.
%
% cfg.datadir = string specifiying the directory where the input files are
% located;
% cfg.outputdir = string specifying the directory where the results should be
% saved. Choose an informative name.
% cfg.filenames = N by 1 cell array containing N strings, in which each string
% contains the base of a filename (e.g. cfg.filenames =
% {'subject1', 'subject2'};). Do not add a file extension to the
% file name, ADAM will automatically look for EEGLAB .set files.
% cfg.start_epoch = in seconds (relative to target events)
% cfg.end_epoch = in seconds (relative to target events)
% cfg.event_codes = numeric array or comma separated list containing the relevant event
% values for the experiment.
%
% Part of the ADAM toolbox, J.J.Fahrenfort, UvA/VU 2019

v2struct(cfg);

% do some checking
if ~exist('datadir','var')
error('You need to specify in cfg.datadir where the data are located');
end
if ~exist('outputdir','var')
error('You need to specify in cfg.outputdir where the results should be stored');
end
if ~exist('filenames','var')
error('You need to specify a cell array in cfg.filenames containing the filenames containing the data of each subject');
end
if ~exist('event_codes','var')
error('You need to specify in cfg.event_codes which event values you want to epoch on');
end
if ~exist('start_epoch','var')
error('You need to specify in cfg.start_epoch defining the onset time of your epoch (in seconds)');
end
if ~exist('end_epoch','var')
error('You need to specify in cfg.end_epoch defining the offset time of your epoch (in seconds)');
end

% some further input checking
if isempty(outputdir)
outputdir = filepath;
else
if ~exist(outputdir,'dir')
mkdir(outputdir);
end
end
if ischar(start_epoch);
start_epoch = string2double(start_epoch);
end
if ischar(end_epoch)
end_epoch = string2double(end_epoch);
end
if ~iscell(filenames) && (~isempty(strfind(filenames,'*')) || ~isempty(strfind(filenames,'?')))
if ~strcmp(filenames(end-3:end),'.set')
filenames = [filenames '.set'];
end
filenames = dir([filepath filesep filenames]);
filenames = {filenames(:).name};
end
if ~iscell(filenames)
filenames = regexp(filenames, ',', 'split');
end
% conditions can either be defined as a string of comma separated values,
% or a cell array of numbers or strings, and is converted to a ell array of
% strings to be fed into pop_epoch
if ischar(event_codes)
conditions = regexp(event_codes, ',', 'split');
end
if isnumeric(event_codes)
conditions = num2cell(event_codes);
end
% go
for filename = filenames
[~,fname,~] = fileparts(filename{1});
% load and filter
EEG = pop_loadset('filename',[fname '.set'],'filepath',datadir);
% epoch data
if ~isempty(conditions)
EEG = pop_epoch(EEG, conditions, [start_epoch end_epoch], 'newname', ['epoched_ ' fname], 'epochinfo', 'yes');
end
% remove mean of every trial
EEG = pop_rmbase( EEG,[]);
% save data
pop_saveset(EEG, 'filename',[fname '.set'],'filepath',outputdir);
end


6 changes: 5 additions & 1 deletion eeg_preprocessing/clean_rawdata0.32/clean_windows.m
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,11 @@
disp('done.');

% sort z scores into quantiles
swz = sort(wz);
if size(wz,1) > 1 % fixed this in case the function is operating on a single channel
swz = sort(wz);
else
swz = wz;
end
% determine which windows to remove
remove_mask = false(1,size(swz,2));
if max(zthresholds)>0
Expand Down
110 changes: 68 additions & 42 deletions eeg_preprocessing/detrend_and_epoch.m
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
function detrend_and_epoch(datadir,filename,outputdir, start_epoch, end_epoch, polynomial_order, pad_length, start_mask, end_mask, mask_only_current, remove_bad_chans, event_codes)
% function detrend_and_epoch(datadir,filename,outputdir, start_epoch, end_epoch, polynomial_order, pad_length, start_mask, end_mask, mask_only_current, remove_bad_chans, event_codes)
function detrend_and_epoch(datadir,filename,outputdir, start_epoch, end_epoch, polynomial_order, pad_length, start_mask, end_mask, mask_only_current, mask_bad_data, remove_bad_chans, event_codes)
% function detrend_and_epoch(datadir,filename,outputdir, start_epoch, end_epoch, polynomial_order, pad_length, start_mask, end_mask, mask_only_current, mask_bad_data, remove_bad_chans, event_codes)
% detrend_and_epoch is an internal function of the ADAM toolbox. Loads EEGLAB data and performs a
% multivariate classification procedure. Refer to the help of ADAM_DETREND_AND_EPOCH for proper
% instructions on how to use this function.
Expand Down Expand Up @@ -38,6 +38,13 @@ function detrend_and_epoch(datadir,filename,outputdir, start_epoch, end_epoch, p
mask_only_current = false;
end
end
if ischar(mask_bad_data)
if strcmpi(mask_bad_data,'yes')
mask_bad_data = true;
else
mask_bad_data = false;
end
end
if ischar(remove_bad_chans)
if strcmpi(remove_bad_chans,'yes')
remove_bad_chans = true;
Expand Down Expand Up @@ -77,43 +84,55 @@ function detrend_and_epoch(datadir,filename,outputdir, start_epoch, end_epoch, p

[~,fname,~] = fileparts(filename);

%% load EEGLAB data to identify bad channels
if remove_bad_chans
EEG = pop_loadset('filename',[fname '.set'],'filepath',datadir);
% strong 1 Hz highpass filter
EEG = pop_eegfiltnew(EEG, 1);
try
eeg_channels = select_channels({EEG.chanlocs(:).labels},'EEG');
catch
error('Stopping now, there are no EEG channels in this set? ADAM only works with standard 10-20 EEG labels.');
end
% double check whether channel location information is present
nopos_channels = [];
for cEl=1:length(EEG.chanlocs)
if (any(isempty(EEG.chanlocs(1,cEl).X)&isempty(EEG.chanlocs(1,cEl).Y)&isempty(EEG.chanlocs(1,cEl).Z)&isempty(EEG.chanlocs(1,cEl).theta)&isempty(EEG.chanlocs(1,cEl).radius)))
nopos_channels = [nopos_channels cEl];
end
end
EEG = pop_select(EEG, 'channel', eeg_channels);
% look up electrode info
if any(ismember(eeg_channels,nopos_channels))
disp(['WARNING: Channels ' num2str(nopos_channels) ' have incomplete location information. Now attempting to read in location information.']);
EEG = pop_chanedit(EEG, 'lookup', trycapfile);
%% load EEGLAB data and make sure there is electrode position data
EEG = pop_loadset('filename',[fname '.set'],'filepath',datadir);
% next identify bad channels
try
eeg_channels = select_channels({EEG.chanlocs(:).labels},'EEG');
catch
error('Stopping now, there are no EEG channels in this set? ADAM only works with standard 10-20 EEG labels.');
end
% double check whether channel location information is present
nopos_channels = [];
for cEl=1:length(EEG.chanlocs)
if (any(isempty(EEG.chanlocs(1,cEl).X)&isempty(EEG.chanlocs(1,cEl).Y)&isempty(EEG.chanlocs(1,cEl).Z)&isempty(EEG.chanlocs(1,cEl).theta)&isempty(EEG.chanlocs(1,cEl).radius)))
nopos_channels = [nopos_channels cEl];
end
% identify and remove bad channels from the EEG, interpolate and write bad channels to text file
orig_chanlocs = EEG.chanlocs;
EEG = clean_artifacts(EEG,'Highpass','off','WindowCriterion','off','burst_crit','off');
clean_chanlocs = EEG.chanlocs;
rejected_electrodes = setdiff({orig_chanlocs.labels},{clean_chanlocs.labels});
end
EEG = pop_select(EEG, 'channel', eeg_channels);
% look up electrode info
if any(ismember(eeg_channels,nopos_channels))
disp(['WARNING: Channels ' num2str(nopos_channels) ' have incomplete location information. Now attempting to read in location information.']);
EEG = pop_chanedit(EEG, 'lookup', trycapfile);
end

%% load original EEGLAB data again to apply detrending to
EEG = pop_loadset('filename',[fname '.set'],'filepath',datadir);

% reject bad channels
%% identify bad channels and bad data
if remove_bad_chans || mask_bad_data
% strong 1 Hz highpass filter (temporary, only for identifying bad channels/data)
EEG_filt = pop_eegfiltnew(EEG, 1);
end
if remove_bad_chans
EEG = pop_select(EEG, 'nochannel', rejected_electrodes);
% identify and remove bad channels
EEG_nobadchans = clean_channels(EEG_filt);
orig_chanlocs = EEG.chanlocs;
clean_chanlocs = EEG_nobadchans.chanlocs;
rej_channels = setdiff({orig_chanlocs.labels},{clean_chanlocs.labels});
% reject bad channels
EEG = pop_select(EEG, 'nochannel', rej_channels);
EEG_filt = pop_select(EEG_filt, 'nochannel', rej_channels);
end
if mask_bad_data
% EEG_filt = pop_eegfiltnew(EEG_filt, 110, 140); % interested in muscle artefacts in particular, so band-pass between 110 and 140
% create mask of the data that contains clean data (to mask out dirty parts for detrending)
for cChan = 1:size(EEG_filt.data,1)
temp_EEG = pop_select(EEG_filt, 'channel', cChan);
[~,clean_mask(cChan,:)] = clean_windows(temp_EEG,[],[-25,25]);
end
%[~,clean_mask] = clean_windows(EEG_filt);
end
clear EEG_filt EEG_nobadchans temp_EEG;

%% apply detrending

% little hack: apply filtering if polynomial_order < 0, so we can compare filtering to detrending
if polynomial_order < 0
Expand Down Expand Up @@ -148,7 +167,12 @@ function detrend_and_epoch(datadir,filename,outputdir, start_epoch, end_epoch, p
eeg_time = padarray(eeg_time,[0 pad_length*srate],NaN,'both');

% create a mask for all trials
eeg_mask = ones(size(eeg_data));
if mask_bad_data
eeg_mask = padarray(clean_mask,[0 pad_length*srate],'both','symmetric');
else
eeg_mask = ones(size(eeg_data));
end
% mask out all trials
if ~mask_only_current
for cTrials = 1:size(trialinfo,1)
mask_startind = nearest(eeg_time,trialinfo(cTrials,2)+start_mask);
Expand All @@ -175,10 +199,12 @@ function detrend_and_epoch(datadir,filename,outputdir, start_epoch, end_epoch, p
pad_time = eeg_time((start_ind-pad_length/2*srate):(stop_ind+pad_length/2*srate));
pad_data = eeg_data(:,(start_ind-pad_length/2*srate):(stop_ind+pad_length/2*srate));
if mask_only_current % mask only current trial
pad_mask = ones(1,size(pad_data,2));
mask_zero_ind = nearest(pad_time,trialinfo(cTrials,2)+start_mask);
mask_stop_ind = nearest(pad_time,trialinfo(cTrials,2)+end_mask);
pad_mask(mask_zero_ind:mask_stop_ind) = 0;
temp_mask = eeg_mask;
mask_startind = nearest(eeg_time,trialinfo(cTrials,2)+start_mask);
mask_stopind = nearest(eeg_time,trialinfo(cTrials,2)+end_mask);
temp_mask(mask_startind:mask_stopind) = 0;
pad_mask = temp_mask((start_ind-pad_length/2*srate):(stop_ind+pad_length/2*srate));
clear temp_mask;
else % mask all trials
pad_mask = eeg_mask((start_ind-pad_length/2*srate):(stop_ind+pad_length/2*srate));
end
Expand Down Expand Up @@ -308,11 +334,11 @@ function detrend_and_epoch(datadir,filename,outputdir, start_epoch, end_epoch, p
EEG = ft2eeglab(DETRENDED_FT_EEG);

%% now that detrending is complete, interpolate faulty electrodes if required
if remove_bad_chans && ~isempty(rejected_electrodes)
if remove_bad_chans && ~isempty(rej_channels)
EEG = pop_interp(EEG, orig_chanlocs, 'spherical');
fid = fopen([outputdir filesep 'bad_channels_' fname '.txt'], 'wt' );
for c = 1:numel(rejected_electrodes)
fprintf( fid, '%s\n', rejected_electrodes{c});
for c = 1:numel(rej_channels)
fprintf( fid, '%s\n', rej_channels{c});
end
fclose(fid);
end
Expand Down
Loading

0 comments on commit 5014cec

Please sign in to comment.