Station 9 · Lesson 2

A single-subject script

Starts from: eeglab_data.set (D1) · Nothing yet: the data as downloaded.Download the data (opens in a new tab)
Script to reach this state
MATLAB
% Start from a fresh MATLAB session: the script below does everything
clear
[ALLEEG, EEG, CURRENTSET, ALLCOM] = eeglab;

The idea in brief

In stations 1 to 5 you built the pipelineThe fixed sequence of processing steps from raw data to results. step by step: loading, then filteringApplying a filter to the data., then line noiseInterference from the mains supply at 60 Hz in Saudi Arabia or 50 Hz elsewhere., then bad channels, then the referenceThe point against which each channel's voltage is measured., then ICAA method that separates the recording into independent sources, some brain and some artifact., then ICLabelA tool that classifies each ICA component into seven classes with a probability for each., then epoching, baselineThe pre-stimulus period used as the zero level for comparison. and rejectionExcluding epochs or data that are unfit for analysis.. Each lesson had a scriptA MATLAB file of commands that runs processing steps in order. for its own step only.

Before and after

The sections of the single-subject script in the MATLAB editor, each starting with a %% line, with an arrow from each section to its lesson on the map: Load to .set and .fdt files in station 1, filtering, bad channels and reference to station 3 lessons, ICA and ICLabel to station 4, and epoching to station 5.

Your next questions, each answered in its own section:

Why do I need this step?

The reasons in detail, and the terms you need

Now you gather them into one file. This file is your real project: if all processed files are lost, it rebuilds them. If a reviewer asks “how did you clean the data?”, the answer is this file. If you want to change one thresholdA value beyond which the signal is treated as a problem., you change a number and rerun, instead of repeating twenty clicks.

The key idea: every line in the script is the same line you learned in its lesson, unchanged. Nothing is new in the processing; what is new is the order and the organization.

How do I do it in EEGLAB?

9 steps in the menus

First: open a new file with sections

  1. Type edit d1_pipeline.m in the command window. The MATLAB editor opens an empty file.
  2. Split the file with lines starting with %%. Each of them starts a section, and the editor can run each section alone (Run Section). Make one section per station.

Second: fill the sections from your lessons

  1. The first section is for settings: the data folder, the output folder, and the line frequencyThe number of cycles per second, measured in hertz (Hz).. Everything that changes between projects goes here, not in the middle of the code.
  2. For each step, copy the line from the “The equivalent script” section of its lesson, or from eegh after running it in the GUIThe windows and menus used with the mouse instead of typed commands., as in the command history lesson. Do not copy plotting and exercise lines, only processing lines.
  3. Save only two versions: the clean continuous version and the clean epochs. As in the file organization lesson, the rest is recomputed.
  4. At the end, add a section that prints a summary: how many bad channels, how many components were removed, and how many epochs remain.

Third: run it from scratch

  1. Close EEGLAB, type clear in the command window, then run the whole file with Run in the editor or by typing d1_pipeline.
  2. Do not click anything while it runs. If a window opens and waits for you, there is a line without enough arguments: fix it.
  3. Compare the output with what you made in the GUI in station 5: the same number of channels, and about the same number of epochs.

How do I write it as code?

The same steps in 101 lines of MATLAB
MATLAB
%% D1 single-subject pipeline: raw data to clean epochs
% Each line comes from the lesson of its step (station in the section title)

%% Settings
eeglab nogui
eeglabRoot = fileparts(which('eeglab'));
dataDir    = fullfile(eeglabRoot, 'sample_data');
outDir     = pwd;
lineFreq   = 60;   % 60 Hz in Saudi Arabia and the Americas, 50 Hz in Europe

%% Load (station 1)
EEG = pop_loadset('filename', 'eeglab_data.set', 'filepath', dataDir);
fullLocs = EEG.chanlocs;

%% Filtering and line noise (station 3)
% High-pass: passband edge at 0.1 Hz (removes slow drift)
EEG = pop_eegfiltnew(EEG, 'locutoff', 0.1);
% Notch: remove a narrow band around the line frequency (revfilt = 1)
EEG = pop_eegfiltnew(EEG, 'locutoff', lineFreq - 2, 'hicutoff', lineFreq + 2, 'revfilt', 1);

%% Bad channels and interpolation (station 3)
% Detection with clean_rawdata channel criteria only:
% no drift removal, no ASR, no window rejection, EOG channels ignored
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', {'EOG1', 'EOG2'});

% Names of the channels it removed
badChans = setdiff({EEG.chanlocs.labels}, {EEG_test.chanlocs.labels}, 'stable');

% Add channels you found by eye, e.g. badChans = [badChans {'T7'}];
fprintf('Bad channels: %s\n', strjoin(badChans, ', '));

% Remove them by name, then rebuild them from the full channel list (fullLocs)
if ~isempty(badChans)
    EEG = pop_select(EEG, 'rmchannel', badChans);
end
if ~isempty(badChans)
    EEG = pop_interp(EEG, fullLocs, 'spherical');
end

%% Average reference (station 3)
% Average reference, EOG channels excluded
eogIdx = find(ismember({EEG.chanlocs.labels}, {'EOG1', 'EOG2'}));
EEG = pop_reref(EEG, [], 'exclude', eogIdx);
EEG.setname = 'D1 main';

%% ICA on a 1-Hz copy, weights copied back (station 4)
% Main dataset (high-pass 0.1 Hz, line noise removed, average reference) is in EEG
EEG_main = EEG;

% ICA copy: same data, high-pass at 1 Hz
EEG_ica = pop_eegfiltnew(EEG_main, 'locutoff', 1);
EEG_ica.setname = 'D1 ica1hz';

% Expected rank: channels - interpolated channels - 1 (average reference)
expectedRank = EEG_ica.nbchan - numel(badChans) - 1;
fprintf('Expected data rank: %d\n', expectedRank);

% Extended Infomax on the ICA copy
EEG_ica = pop_runica(EEG_ica, 'icatype', 'runica', 'extended', 1);
% If "Data rank (N)" is missing or N differs from expectedRank, run instead:
% EEG_ica = pop_runica(EEG_ica, 'icatype', 'runica', 'extended', 1, 'pca', expectedRank);

% Copy the decomposition back to the main dataset
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_test   % free memory

%% ICLabel: remove eye and muscle components, then low-pass (station 4)
% Classify all components with the default ICLabel classifier
EEG = pop_iclabel(EEG, 'default');

% Flag components above 90% Muscle or Eye
% Row order: Brain, Muscle, Eye, Heart, Line Noise, Channel Noise, Other
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);

% Remove them from the main dataset
if ~isempty(artifactComps)
    EEG = pop_subcomp(EEG, artifactComps, 0);
end
EEG.setname = 'D1 main clean';

% Low-pass at 30 Hz, postponed until after ICA cleaning
EEG = pop_eegfiltnew(EEG, 'hicutoff', 30);
EEG.setname = 'D1 main clean lp30';
EEG = pop_saveset(EEG, 'filename', 'D1_main_clean_lp30.set', 'filepath', outDir);

%% Epochs, baseline, rejection (station 5)
% Cut 3-s epochs around every 'square' event: 1 s before to 2 s after
EEG = pop_epoch(EEG, {'square'}, [-1 2], 'newname', 'D1 epochs', 'epochinfo', 'yes');
nEpochsAll = EEG.trials;

% Subtract the mean of -200 to 0 ms from every channel and epoch
EEG = pop_rmbase(EEG, [-200 0]);
EEG.setname = 'D1 epochs bl';

% Mark epochs where any channel goes beyond +/-100 uV anywhere in the epoch
EEG = pop_eegthresh(EEG, 1, 1:EEG.nbchan, -100, 100, EEG.xmin, EEG.xmax, 0, 0);
badEpochs = find(EEG.reject.rejthresh);
fprintf('Marked %d of %d epochs\n', numel(badEpochs), EEG.trials);

% Remove the marked epochs
if ~isempty(badEpochs)
    EEG = pop_rejepoch(EEG, EEG.reject.rejthresh, 0);
end
EEG.setname = 'D1 epochs clean';
EEG = eeg_checkset(EEG);
EEG = pop_saveset(EEG, 'filename', 'D1_epochs_clean.set', 'filepath', outDir);

%% Summary
fprintf('\n--- D1 pipeline summary ---\n');
fprintf('Bad channels interpolated: %d (%s)\n', numel(badChans), strjoin(badChans, ', '));
fprintf('ICA components removed:    %d (%s)\n', numel(artifactComps), num2str(artifactComps(:)'));
fprintf('Epochs kept:               %d of %d\n', EEG.trials, nEpochsAll);
fprintf('Saved: D1_main_clean_lp30.set and D1_epochs_clean.set in %s\n', outDir);

Which value should I choose?

Suitable values for your analysis goal
DecisionChoice in this lessonWhy
SettingsIn the Settings section at the top of the fileWhat changes between projects (folders, line frequency) is in one place. No number hidden in the middle of the code.
Splitting the fileOne %% section per stationYou run one section alone when testing, and the title tells you which lesson to review if an error appears.
What is savedTwo versions: clean continuous and clean epochsThe first is enough for any new epoching, and the second goes into averaging and statistics (file organization lesson).
Bad channelsAutomatic detection, with a line to add what you found by eyeThe script does not see what you see. Review the data once in the GUI and write what you found in badChans (bad channels lesson).
Repeating ICADiffers slightly on every runrunica starts from a random state taken from the clock (runica.m). For an identical rerun, add 'reset_randomseed', 'no' to pop_runica.

How do I check it worked?

4 quick checks
  • After clear, the script runs from start to end with no window waiting for you.
  • The summary prints the number of bad channels, removed components, and epochs remaining out of 80.
  • The working folder holds two files: D1_main_clean_lp30.set and D1_epochs_clean.set.
  • size(EEG.data) at the end is 32 × 384 × the number of remaining epochs, as in the epoch rejection lesson.

What mistakes do people make?

5 mistakes and how to avoid them
  • Changing lines while copying. Every line here is taken as it is from its lesson. If you “improve” a value while copying, the result will not match what the lesson explained.
  • Relying on variables from an earlier session. A script that works only because EEG or badChans already exists will fail tomorrow. Always run it after clear.
  • Keeping plotting lines. pop_viewprops, figure and the like open windows, slow the run, and stop the script on a computing cluster. The script is for processing; plotting goes in a separate script.
  • Wrong order. The low-pass comes after ICA, epoching after ICA, and the reference after interpolation. This order is explained in its lessons; do not move a step without a reason.
  • Fixed paths from your computer. fullfile(eeglabRoot, 'sample_data') works on any computer. C:\Users\... works on yours only.

How do I write it in my paper?

A Methods template: edit the data, settings and versions to match what you ran

A template: edit the data, settings and versions to match what you ran.

Data were high-pass filtered (passband edge 0.1 Hz; firfilt plugin 2.8, EEGLAB 2026.1), and line noise was attenuated with a band-stop filter (passband edges 58 and 62 Hz). Bad channels were identified with clean_rawdata (clean_rawdata plugin 2.11; flatline 5 s, channel correlation 0.8) and interpolated ([n] channels), 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 ([n] components). 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 ([n] of 80).

Where can I read more?

3 sources, official docs first

Written for EEGLAB 2026.1 · 2026-10-28

Need help?

Ask, share feedback, or report an error