第3章:MNE-Python入门:Raw数据结构、读取EDF/CNT/BDF文件、基础可视化
好,咱们进入实战环节了。前面两章把信号处理的基础理论过了一遍,现在该上手真家伙了。这一章,我带你搞定MNE-Python这个神经信号处理的瑞士军刀。说白了,它就是Python生态里处理脑电/脑磁数据的事实标准。你想想看,没有它之前,大家读个EDF文件都得自己写解析器,那叫一个痛苦。
3.1 Raw数据结构:MNE的核心
MNE里最核心的数据结构就是Raw对象。它长什么样呢?我习惯把它想象成一个“带时间戳的矩阵”。
Raw对象内部存储:
- 数据矩阵:形状为 (通道数, 采样点数)。比如你有64个电极,采集了10秒,采样率1000Hz,那矩阵就是 (64, 10000)。
- 通道信息:每个通道的名字(比如'Fz', 'Cz')、类型(eeg, eog, stim等)、位置坐标。
- 时间信息:采样频率sfreq,以及每个采样点对应的时间戳。
- 元数据:测量日期、受试者ID、实验条件等。
核心要点:Raw对象不直接存储“时间轴”,而是通过采样频率和采样点数动态计算。所以千万别手动改sfreq,否则时间轴全乱套了。我曾经有个学生,为了“对齐数据”把采样率从1000改成了500,结果所有事件标记都错位了,排查了整整一下午。
咱们来看一下Raw对象的基本属性:
import mne
# 假设raw已经加载好了
print(f"通道数: {raw.info['nchan']}")
print(f"采样率: {raw.info['sfreq']} Hz")
print(f"数据时长: {raw.times[-1]:.2f} 秒")
print(f"通道名称: {raw.ch_names[:5]}") # 只看前5个
嗯,这里要注意:raw.info是一个字典,里面装着所有元数据。我个人习惯用raw.info['sfreq']而不是raw.sfreq,因为前者更明确,不容易被重载属性搞晕。
3.2 读取EDF/CNT/BDF文件
不同厂商的脑电设备,文件格式五花八门。MNE统一了接口,你只需要记住一个函数:mne.io.read_raw_xxx()。
| 文件格式 | 读取函数 | 常见来源 |
|---|---|---|
| EDF / EDF+ | mne.io.read_raw_edf() |
欧洲标准格式,很多医院在用 |
| BDF | mne.io.read_raw_bdf() |
EDF的24位扩展版,BioSemi设备常用 |
| CNT | mne.io.read_raw_cnt() |
Neuroscan设备专用格式 |
| BrainVision | mne.io.read_raw_brainvision() |
Brain Products设备,.vhdr + .eeg + .vmrk |
实战代码:读取EDF文件
import mne
# 读取EDF文件
raw = mne.io.read_raw_edf('sample.edf', preload=False)
# 读取BDF文件(注意:BDF文件通常较大,建议preload=False先看看)
raw_bdf = mne.io.read_raw_bdf('session1.bdf', preload=False)
# 读取CNT文件(需要指定通道类型)
raw_cnt = mne.io.read_raw_cnt('subj01.cnt', eog=['HEOG', 'VEOG'], preload=True)
避坑指南:我曾经在处理一个CNT文件时,发现眼电通道被识别成了EEG通道。后来才发现,read_raw_cnt()的eog参数必须显式指定眼电通道名。否则MNE会按文件头信息默认分类,而有些老设备的文件头写得并不规范。
关于preload参数:
preload=False:只读取元数据,不加载数据到内存。适合先看看文件有多大。preload=True:一次性全部加载到内存。处理速度快,但大文件可能爆内存。- 我个人习惯:先用
preload=False看看,确认没问题了再raw.load_data()手动加载。
3.3 基础可视化:一眼看穿数据质量
数据读进来了,第一件事是什么?不是跑分析,而是看数据!我见过太多人上来就滤波、跑ICA,结果发现原始数据里有个通道根本没接好——白忙活一场。
3.3.1 原始波形图
# 绘制前10秒的数据
raw.plot(n_channels=10, duration=5, start=0,
scalings='auto', block=True)
这个函数会弹出一个交互式窗口。你可以:
- 滚动查看不同时间段
- 点击通道名隐藏/显示该通道
- 右键标记坏段
- 按住Shift+左键可以缩放
注意:block=True会让程序停在这里,等你关掉绘图窗口才继续执行。如果你在Jupyter Notebook里用,建议改成block=False,否则整个kernel会卡住。
3.3.2 通道频谱图
想看每个通道的频率成分?用raw.plot_psd():
# 绘制功率谱密度
raw.plot_psd(fmin=0.5, fmax=45, tmin=0, tmax=60,
average=True, spatial_colors=True)
这个图能帮你快速判断:
- 有没有50Hz工频干扰(国内电网频率)
- 低频段有没有漂移(0.5Hz以下能量过高说明基线不稳)
- 各通道的噪声水平是否一致
3.3.3 通道位置图
如果你有电极坐标信息,可以画个拓扑图:
# 绘制2D电极位置
raw.plot_sensors(kind='topomap', show_names=True)
这个图特别有用。我记得有一次,我发现某个通道的波形异常,一看位置图——好家伙,那个电极正好在头皮上一个疤痕旁边,阻抗肯定高得离谱。
3.4 本章知识体系
下面这张图,是我自己梳理的MNE入门知识框架。你跟着这个路径走,不会迷路。
这张图把本章的三个核心模块串起来了。你从左边开始,先学会读文件;然后理解Raw对象里到底存了什么;最后用可视化手段验证数据质量。这三步走完,你就能自信地说“我入门MNE了”。
我的个人习惯:每次拿到新数据,我都会先跑一遍raw.plot()和raw.plot_psd(),然后把截图发给合作者确认。这一步看似简单,但能避免后面90%的返工。你想想看,如果原始数据里有个通道是平的(电极掉了),你后面做再花哨的分析也是白搭。
好了,这一章的内容就到这里。记住:工具是死的,但你的判断力是活的。MNE给了你强大的武器,但怎么用,还得靠你对数据的理解。
公众号:蓝海数据掘金营,微信deep3321