Simply EEGLAB
Batch processing
Station 9 · Lesson 3 · EEGLAB 2026.1
In the GUI
- FileImport dataUsing EEGLAB functions and pluginsFrom BIDS folder structureOk
rawdatatrial_type - Datasets
EEG061EEG062EEG063EEG064
Script
eeglab nogui
projectRoot = pwd;
bidsRoot = fullfile(projectRoot, 'rawdata');
derivRoot = fullfile(projectRoot, 'derivatives', 'eeglab');
logRoot = fullfile(projectRoot, 'logs');
subjects = {'sub-002', 'sub-003', 'sub-004'};
cfg.lineFreq = 50; % UK recording: 50 Hz mains
cfg.eogLabels = {'EEG061', 'EEG062'}; % HEOG, VEOG
cfg.dropLabels = {'EEG063', 'EEG064'}; % ECG channels
cfg.epochEvents = {'famous_new', 'unfamiliar_new', 'scrambled_new'};
cfg.epochWindow = [-1 2];
cfg.thresholdUv = 100;
if ~exist(logRoot, 'dir'), mkdir(logRoot); end
logFile = fullfile(logRoot, 'batch_log.txt');
[STUDY, ALLEEG] = pop_importbids(bidsRoot, 'eventtype', 'trial_type', ...
'bidsevent', 'on', 'bidschanloc', 'on', 'subjects', subjects, ...
'outputdir', derivRoot, 'studyName', 'faces');
clear STUDY ALLEEG % each subject is reloaded from disk inside the loop
nSub = numel(subjects);
status = repmat({'not run'}, nSub, 1);
nBad = nan(nSub, 1);
nIcRem = nan(nSub, 1);
nEpochs = nan(nSub, 1);
for s = 1:nSub
subj = subjects{s};
fprintf('\n===== %s (%d of %d) =====\n', subj, s, nSub);
try
inFile = fullfile(derivRoot, subj, 'eeg', sprintf('%s_task-FaceRecognition_eeg.set', subj));
res = process_subject(inFile, cfg);
status{s} = 'ok';
nBad(s) = res.nBadChans;
nIcRem(s) = res.nIcRemoved;
nEpochs(s) = res.nEpochs;
catch err
status{s} = 'failed';
fid = fopen(logFile, 'a');
fprintf(fid, '%s %s %s\n', datestr(now, 'yyyy-mm-dd HH:MM'), subj, err.message);
fclose(fid);
fprintf(2, 'FAILED %s: %s\n', subj, err.message);
end
end
summary = table(subjects(:), status, nBad, nIcRem, nEpochs, ...
'VariableNames', {'subject', 'status', 'bad_channels', 'ics_removed', 'epochs_kept'});
disp(summary);
writetable(summary, fullfile(derivRoot, 'batch_summary.csv'));
function res = process_subject(inFile, cfg)
[inDir, baseName] = fileparts(inFile);
baseName = strrep(baseName, '_eeg', '');
EEG = pop_loadset('filename', [baseName '_eeg.set'], 'filepath', inDir);
EEG = pop_select(EEG, 'rmchannel', cfg.dropLabels);
fullLocs = EEG.chanlocs;
EEG = pop_eegfiltnew(EEG, 'locutoff', 0.1);
EEG = pop_eegfiltnew(EEG, 'locutoff', cfg.lineFreq - 2, 'hicutoff', cfg.lineFreq + 2, 'revfilt', 1);
EEG_test = pop_clean_rawdata(EEG, 'FlatlineCriterion', 5, 'ChannelCriterion', 0.8, ...
'LineNoiseCriterion', 4, 'Highpass', 'off', 'BurstCriterion', 'off', ...
'WindowCriterion', 'off', 'BurstRejection', 'off', 'Distance', 'Euclidian', ...
'channels_ignore', cfg.eogLabels);
badChans = setdiff({EEG.chanlocs.labels}, {EEG_test.chanlocs.labels}, 'stable');
fprintf('Bad channels: %s\n', strjoin(badChans, ', '));
if ~isempty(badChans)
EEG = pop_select(EEG, 'rmchannel', badChans);
end
if ~isempty(badChans)
EEG = pop_interp(EEG, fullLocs, 'spherical');
end
clear EEG_test
eogIdx = find(ismember({EEG.chanlocs.labels}, cfg.eogLabels));
EEG = pop_reref(EEG, [], 'exclude', eogIdx);
EEG_main = EEG;
EEG_ica = pop_eegfiltnew(EEG_main, 'locutoff', 1);
EEG_ica = pop_runica(EEG_ica, 'icatype', 'runica', 'extended', 1);
EEG_main.icaweights = EEG_ica.icaweights;
EEG_main.icasphere = EEG_ica.icasphere;
EEG_main.icachansind = EEG_ica.icachansind;
EEG_main.icawinv = []; % recomputed by eeg_checkset
EEG_main.icaact = [];
EEG = eeg_checkset(EEG_main);
clear EEG_main EEG_ica
EEG = pop_iclabel(EEG, 'default');
thresh = [NaN NaN; 0.9 1; 0.9 1; NaN NaN; NaN NaN; NaN NaN; NaN NaN];
EEG = pop_icflag(EEG, thresh);
artifactComps = find(EEG.reject.gcompreject);
if ~isempty(artifactComps)
EEG = pop_subcomp(EEG, artifactComps, 0);
end
EEG = pop_eegfiltnew(EEG, 'hicutoff', 30);
EEG.setname = [baseName ' clean'];
EEG = pop_saveset(EEG, 'filename', [baseName '_desc-clean_eeg.set'], 'filepath', inDir);
EEG = pop_epoch(EEG, cfg.epochEvents, cfg.epochWindow, 'epochinfo', 'yes');
EEG = pop_rmbase(EEG, [-200 0]);
EEG = pop_eegthresh(EEG, 1, 1:EEG.nbchan, -cfg.thresholdUv, cfg.thresholdUv, EEG.xmin, EEG.xmax, 0, 0);
if any(EEG.reject.rejthresh)
EEG = pop_rejepoch(EEG, EEG.reject.rejthresh, 0);
end
EEG.setname = [baseName ' epochs'];
EEG = pop_saveset(EEG, 'filename', [baseName '_desc-epochs_eeg.set'], 'filepath', inDir);
res.nBadChans = numel(badChans);
res.nIcRemoved = numel(artifactComps);
res.nEpochs = EEG.trials;
endWhich value should I choose?
| Setting | Value in this lesson |
|---|---|
| Line frequency | 50 Hz |
| Eye and heart channels | EEG061 and EEG062 for the eyes, EEG063 and EEG064 for the heart |
| Events for epoching | famous_new, unfamiliar_new and scrambled_new |
| Import | pop_importbids once before the loop |
| Where settings live | The cfg variable at the top |
| What happens on error | A line in batch_log.txt, status failed, then the next participant |
For your Methods
Data from [n] participants (ds002718; EEG-BIDS plugin 10.5, EEGLAB 2026.1) were high-pass filtered (passband edge 0.1 Hz; firfilt plugin 2.8), and line noise was attenuated with a band-stop filter (passband edges 48 and 52 Hz). Bad channels were identified with clean_rawdata (clean_rawdata plugin 2.11; flatline 5 s, channel correlation 0.8) and interpolated, then data were average-referenced, EOG excluded. Independent component analysis (extended Infomax; Lee et al., 1999) was computed on a 1 Hz high-passed copy, and components labelled Eye or Muscle with probability ≥ 0.9 (ICLabel 1.7; Pion-Tonachini et al., 2019) were removed. After low-pass filtering (passband edge 30 Hz), epochs from −1 to 2 s were baseline-corrected (−200 to 0 ms), and epochs beyond ±100 µV were rejected.