3、Python与MNE库入门:MNE安装、读取原始数据、基础可视化与epoch提取
好,咱们正式开始动手了。
前面两章聊了不少概念,什么脑电信号、迁移学习、跨被试的难点。我知道你肯定手痒了,想看看代码到底长什么样。这一章,我就带你走一遍MNE库的完整入门流程——从安装到读取数据,再到可视化,最后提取出我们真正关心的epoch。
说白了,MNE就是Python生态里处理脑电、脑磁数据的瑞士军刀。我当年刚接触脑机接口时,第一件事就是装MNE。那时候版本还老,踩了不少坑。现在好多了,pip一行命令就搞定。
3.1 MNE安装与环境配置
安装其实没什么技术含量,但有几个细节我建议你注意一下。
# 基础安装
pip install mne
# 如果你需要处理更复杂的格式,可以装全功能版
pip install mne[full]
# 验证安装
python -c "import mne; print(mne.__version__)"
我个人习惯用虚拟环境。为什么呢?因为MNE的依赖库版本有时候会和别的包打架。我曾经在一个项目里,MNE要求numpy版本不能太高,结果另一个库非要新版numpy,折腾了我一下午。所以,听我一句劝:
另外,如果你用的是Windows系统,可能会遇到一些底层库的编译问题。别慌,去官网下载预编译的wheel包就行。Mac和Linux用户一般没这烦恼。
3.2 读取原始数据
MNE支持的数据格式非常多。你想想看,不同厂商的脑电设备,文件后缀五花八门:.edf、.bdf、.set、.vhdr、.fif……MNE基本都能读。
我拿最常见的EDF格式举个例子。这是欧洲标准格式,很多公开数据集都用它。
import mne
# 读取EDF文件
raw = mne.io.read_raw_edf('sample_data.edf', preload=True)
# 看看基本信息
print(raw.info)
print(raw.ch_names)
# 查看数据时长
print(f"数据时长: {raw.times[-1]:.2f} 秒")
这里有个关键参数——preload=True。什么意思呢?就是把数据一次性加载到内存里。如果你的数据文件很大(比如几小时的连续记录),preload可能会把内存撑爆。我建议你先不加preload,等确定要处理哪一段时再加载。
raw.set_channel_types({'EOG1': 'eog', 'EOG2': 'eog'})
我曾经就因为这个疏忽,导致后续分析里眼电信号混进了脑电,结果伪迹去除得一塌糊涂。
3.3 基础可视化
数据读进来了,第一件事是什么?看波形啊!
MNE的可视化功能非常强大。我个人最喜欢的是它的交互式绘图,可以缩放、平移、标记事件。
# 绘制原始数据波形
raw.plot(n_channels=10, duration=5.0, start=0.0)
# 查看功率谱密度
raw.plot_psd(fmin=0.5, fmax=45.0)
# 查看通道位置(如果有电极坐标)
raw.plot_sensors(show_names=True)
你想想看,几十个通道的波形同时铺开,一眼就能看出哪些通道有坏道、哪些时段有肌电干扰。我一般会先快速扫一遍全时段波形,心里有个底。
嗯,这里要注意:raw.plot()默认会打开一个交互窗口。如果你在服务器上跑代码,没有图形界面,那就得用raw.plot(n_channels=10, block=True),或者干脆用raw.compute_psd()这种非交互的方式。
3.4 事件提取与epoch构建
好了,重头戏来了。
原始数据是连续的,但我们做脑机接口,关心的是刺激出现前后那一段时间的脑电反应。这就叫epoch。
提取epoch的第一步,是找到事件(events)。事件通常记录在刺激通道里,或者保存在一个单独的标记文件里。
# 从刺激通道提取事件
events = mne.find_events(raw, stim_channel='STI 014')
# 看看事件长什么样
print(events[:5])
print(f"共找到 {len(events)} 个事件")
events是一个numpy数组,三列:第一列是事件发生的时间点(采样点索引),第二列是前一个事件的值(一般用不到),第三列是事件ID。
有了事件,就可以提取epoch了:
# 定义epoch参数
tmin = -0.2 # 刺激前200ms
tmax = 0.8 # 刺激后800ms
# 提取epoch
epochs = mne.Epochs(raw, events, event_id={'left': 1, 'right': 2},
tmin=tmin, tmax=tmax, baseline=(-0.2, 0),
preload=True)
print(epochs)
print(f"epoch数量: {len(epochs)}")
这里baseline=(-0.2, 0)的意思是,用刺激前200ms的数据做基线校正。说白了,就是把每个epoch减去基线段的平均值,消除直流漂移。
epochs.drop_bad(reject={'eeg': 150e-6}) # 剔除幅值超过150微伏的epoch
这个阈值怎么设?我一般先看数据分布,取个合理的值。太严了把好数据也丢了,太松了坏数据混进来。
3.5 知识体系总览
这一章的内容其实就围绕一个核心流程:从原始数据到可用epoch。我画了一张图,帮你理清逻辑关系。
这张图把整个流程串起来了。你跟着走一遍,基本就掌握了MNE处理脑电数据的核心套路。
3.6 避坑指南
最后,我把自己踩过的坑总结一下,你遇到了可以直接翻回来查。
| 常见问题 | 原因 | 解决方案 |
|---|---|---|
| 读取数据时报错"File not found" | 路径问题或文件损坏 | 用绝对路径,检查文件完整性 |
| 事件提取不到 | 刺激通道名称不对 | 用raw.ch_names查看所有通道名 |
| epoch数量为0 | event_id设置错误 | 打印events查看实际ID值 |
| 可视化窗口卡死 | 数据量太大 | 用n_channels参数限制显示通道数 |
我曾经在提取epoch时,怎么都提不出来,折腾了两小时才发现是event_id写错了。你想想看,多冤啊。所以,遇到问题先打印数据看看,别闷头调参数。
好了,这一章就到这里。代码你都跑一遍,感受一下MNE的威力。下一章咱们会深入预处理,把数据洗得干干净净,为迁移学习打好基础。
公众号:蓝海资料掘金营,微信deep3321