阶段一 · 打好地基 · 模块 2 · 第 5 课 / 共 28 课

批量预处理实战:把流程变成脚本

30 个被试点鼠标点一遍 = 一整天的机械劳动 + 无法复现。这一课把 2.3 的流水线 改写成一份可以在一夜之间跑完全部被试的脚本——这也是科研规范(可复现)的起点。

🕐 60 分钟🎯 难度:进阶✅ 前置:2.3 预处理全流程

🎯 本节学习目标

  • 理解 EEGLAB "GUI 操作即命令"的设计,会用 eegh 查菜单对应的命令
  • 掌握核心 pop_* 函数:loadset / eegfilt / runica / epoch / saveset
  • 能独立写出多被试批量预处理脚本,带参数区、循环体、日志与错误处理
  • 学会给每个被试输出质控图,人工抽查代替人工操作
  • 知道批处理最常见的 5 个坑及预防办法

一、从 GUI 到脚本:eegh 的秘密

EEGLAB 的菜单操作全部会转成命令执行,并在内部历史里记录下来。在命令行输入 eegh 可以回放你在 GUI 里做过的每一步。这意味着:你在 2.3 用鼠标完成的流程,脚本版本已经"自动生成"了, 只需要把它们抄进 .m 文件、包一层 for 循环。

Matlab
% 在你做完一遍 GUI 预处理后,命令行输入:
eegh
% 会输出类似:
%   EEG = pop_eegfilt(EEG, 0.1, 45, [], []);
%   EEG = pop_runica(EEG, 'extended', 1);
%   EEG = pop_epoch(EEG, {'stimulus'}, [-0.2 0.8], 'newname', ...);
% 这就是你的脚本原料。

二、核心函数速查

函数对应 GUI 操作常用参数
pop_loadsetFile > Load'filename','sub01.set','filepath',dir
pop_eegfilt滤波EEG, 0.1, 45(高通, 低通)
pop_eegfiltnew零相位滤波(推荐)EEG, 'fcutoff',50,'ftype','stop' 陷波
pop_select选通道/删通道'channel',[1:32]
pop_runicaICA 分解'extended',1(默认算法 infomax)
pop_rejcont连续坏段剔除(自动阈值)阈值设置见 help
pop_epoch分段EEG, {'stim'}, [-0.2 0.8]
pop_rmbase基线校正EEG, [-200 0](ms)
pop_saveset保存'filename', outname

三、批量模板(可直接改造使用)

Matlab · batch_preprocess.m
%% ============ 参数区:只改这里 ============
clear; clc; close all;
dataDir   = 'D:\project\raw\';      % 原始数据目录(英文路径!)
outDir    = 'D:\project\processed\';% 输出目录
qcDir     = 'D:\project\qc\';       % 质控图目录
subjects  = {'sub01','sub02','sub03','sub04'};   % 被试列表
hp = 0.1; lp = 45;                  % 滤波参数
epochWin = [-0.2 0.8];              % 分段窗口 (s)
baseline = [-200 0];                % 基线 (ms)

if ~exist(outDir, 'dir'), mkdir(outDir); end
if ~exist(qcDir, 'dir'),  mkdir(qcDir);  end

%% ============ 循环区:所有被试同一流程 ============
for s = 1:length(subjects)
    sub = subjects{s};
    fprintf('==== [%d/%d] %s ====\n', s, length(subjects), sub);
    try
        % 1. 读入
        EEG = pop_loadset('filename', [sub '_raw.set'], 'filepath', dataDir);

        % 2. 滤波(零相位)
        EEG = pop_eegfiltnew(EEG, 'fcutoff', hp, 'ftype', 'highpass');
        EEG = pop_eegfiltnew(EEG, 'fcutoff', lp, 'ftype', 'lowpass');

        % 3. ICA(长数据时耗时,可在此跳过调试)
        EEG = pop_runica(EEG, 'extended', 1);
        % TODO: ICA 成分剔除 —— 建议人工查看后把成分编号写进一个表

        % 4. 分段 + 基线
        EEG = pop_epoch(EEG, {'stimulus'}, epochWin, 'newname', [sub '_ep']);
        EEG = pop_rmbase(EEG, baseline);

        % 5. 试次级剔除(±100 uV)
        EEG = pop_rejtrend(EEG, 1, EEG.nbchan, -100, 100);

        % 6. 保存
        pop_saveset(EEG, 'filename', [sub '_pp.set'], 'filepath', outDir);

        % 7. 质控图:ERP 总平均 + 剩余试次数写进日志
        figure('Visible','off');
        plot(EEG.times, mean(EEG.data, 2));       % 通道平均的粗略 ERP
        title(sprintf('%s  trials=%d', sub, EEG.trials));
        print('-dpng', '-r150', [qcDir sub '_erp.png']);
        close;

        fprintf('    OK, %d trials kept\n', EEG.trials);
    catch err
        % 单个被试失败不中断整批
        fprintf(2, '    FAILED: %s\n', err.message);
        fid = fopen([qcDir 'error_log.txt'], 'a');
        fprintf(fid, '%s\t%s\n', sub, err.message);
        fclose(fid);
    end
end
disp('全部完成,请检查 qc 目录。');

四、质控:抽查代替操作

脚本跑完后不要直接进入分析——打开 qc 目录,逐个检查每张 ERP 预览图和剩余试次数。 质控清单:剩余试次 ≥ 设计的 70%?波形形态符合预期?有没有个别被试整段异常? 有问题的被试回到原始数据单独处理,不要让一个坏被试污染组平均。

五、常见坑清单

  1. 中文路径 / 空格路径:IO 报错十有八九来自它。数据目录全英文;
  2. 被试文件命名不统一(sub1、subject01、S1 混用):循环拼文件名失败。开工前先统一命名;
  3. EEG 变量被覆盖:循环里忘记每次重新 load,上一个被试的数据被重复处理;
  4. 内存不足:高密度通道 + 高采样率 + 多被试同时在内存。一次处理一个,及时 clear EEG
  5. 事件名不一致:有的被试标记叫 stim 有的叫 stimuluspop_epoch 匹配不到就 0 个试次——检查 EEG.event.type
💡 调试建议

先用 1 个被试跑通全流程,确认输出无误后,再把 subjects 列表换成全部被试整夜运行。永远不要第一次就用全量数据试跑。

📌 本节小结

  • eegh 回放 GUI 操作历史——GUI 会用,脚本就会写
  • 批处理脚本三段式:参数区 / 循环区 / 质控输出,参数只改一处
  • try-catch 保证单个被试失败不中断整批,错误写入日志
  • 跑完必抽查质控图:试次剩余率、波形形态、异常被试
  • 批处理五大坑:中文路径、命名不统一、变量覆盖、内存、事件名不一致

✏️ 课后练习

  1. 把 2.3 的完整流程(含 ICA 成分剔除)改写成脚本,对你手头(或公开数据集里)的 2 个"被试"文件跑通。
  2. 故意把一个被试的文件名改错,验证 try-catch 是否正常记录日志并继续处理其他人。
  3. 在质控图上叠加显示每个被试剩余试次数,写一段 10 行以内的代码,自动把剩余试次 < 50% 的被试列入"需复查"列表。