MNE 预处理踩坑合集:顺序、阈值与那些不报错的坑

2026 年 10 月 5 日MNE预处理工具

在 181 个 EDF 文件上把预处理流程跑通,踩了不少坑。这里记录那些不报错但会让结果变错的问题。

坑 1:坏通道检测必须在平均参考之前

平均参考会把单个坏通道的噪声“分摊”到所有通道上。一旦做了平均参考,通道之间的相关性结构就变了,correlation 和 RANSAC 类判据会失准。

正确顺序:坏通道检测与插值 → 滤波 → 重参考 → ICA。

坑 2:用绝对相关阈值找坏通道,会把外侧电极整片误判

我们最初用“与其余通道的平均相关 < 0.4”当判据。结果每个文件都有 6–8 个通道被判为坏道,全是 T3/T4/T5/T6/O1/O2/F7/F8。

原因是:19 通道稀疏 montage 上,这些外侧电极与其它电极本来就离得远,相关天然偏低。

改法:用“相对”判据 —— 只有当某个通道的相关性显著低于全体通道的中位数(稳健 z 分数 < −4)时才判坏,另加一个绝对下限兜底。

改完之后,绝大多数文件只剩 0–2 个坏道。

坑 3:45 Hz 低通之后再陷波 50 Hz 是多余的

45 Hz 低通已经把 50 Hz 成分滤掉了,再陷波没有意义。

正确顺序:先陷波(去掉工频干扰),再带通。

坑 4:ICLabel 是在 1–100 Hz 数据上训练的

我们的分析需要低通到 45 Hz,但 ICLabel 的训练数据是 1–100 Hz。在 45 Hz 数据上调用它会打印一条 not filtered between 1 and 100 Hz 的警告。

影响:肌电(>45 Hz 已被滤掉)和线噪这两类的判定可靠性下降,但眨眼成分仍能正确识别。这是一个可以接受、但需要在论文里说明的取舍。

坑 5:make_fixed_length_epochs 不重叠时,末尾不足一段的数据会被丢掉

5 分钟的录音切成 2 秒一段是 150 段,不是 151 段。做数据量统计时要记得这一点。

坑 6:坏段剔除只给峰值阈值,死通道不会被剔除

drop_bad(reject=dict(eeg=100e-6)) 只管峰值超限。如果某个通道整段是平坦的(电极脱落),它的峰值很小,反而不会被剔掉。

改法:补一个平坦判据 flat=dict(eeg=1e-6)。

坑 7:standard_1020 这个 montage 名字在新版 MNE 里被弃用了

新版会提示改用 colin27_1020。写兼容回退可以避免升级 MNE 之后直接报错。

坑 8:中文模块名 + GBK 控制台 = 直接崩

脚本里打印中文或 ▶ ✅ ⚠ 这类符号时,Windows 控制台默认 GBK 编码会抛 UnicodeEncodeError,整个批处理中断。

改法:在脚本最开头把 stdout 强制为 UTF-8:

import sys
for s in ("stdout", "stderr"):
    getattr(sys, s).reconfigure(encoding="utf-8", errors="replace")

坑 9(最隐蔽):MNE 的家目录不可写时,连 import 都过不去

MNE 在 import 时会去读写 %USERPROFILE%\.mne\mne-python.json —— 注意是即使只读也用 r+ 模式打开。

所以在“家目录不可写”的环境里(受限账户、沙箱、CI),你会看到一个和 MNE 毫无关系的 PermissionError,而程序连第一行都没跑完。

改法:把家目录重定向到项目内部,顺便让项目变成绿色免安装:

import os
root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
os.environ["USERPROFILE"] = os.path.join(root, ".mne_home")
os.environ["HOME"] = os.environ["USERPROFILE"]
os.environ["MPLCONFIGDIR"] = os.path.join(root, ".mplcache")
os.environ["MNE_DONTWRITE_HOME"] = "true"

必须在 import mne 之前执行。

小结

这份清单里,坑 1、2、6、9 都会静默地把结果做错或让程序莫名失败,而不会给你任何明确提示。

预处理流程最难的地方不是“会不会调 API”,而是知道每一步为什么在这个位置。