常见问题(FAQ)#
MNE-Python 常见问题#
求助!我的 Python 和 MNE-Python 无法正常工作!#
请参阅我们的安装说明。
我还是搞不定!#
参见获取帮助。
在 Windows 下无法使用 PyVista/3D 绘图#
如果在 Jupyter Notebook 中 PyVista 绘图效果不佳,使用 IPython 魔法命令 %gui qt 通常会有帮助。
%gui qt
在 macOS 上 Python 运行极慢,即使只是简单命令!#
在使用 Spyder 或 PyCharm 等 IDE 时,Python 的某些后端会干扰 macOS 的节能设置。要验证这一点,请先 import time 并运行:
start = time.time(); time.sleep(0.0005); print(time.time() - start)
如果耗时达到数秒,你可以任选一种方式:
安装
appnope模块,并在脚本中运行:import appnope appnope.nope()
在终端中运行以下命令来更改配置默认值:
$ defaults write org.python.python NSAppSleepDisabled -bool YES
如何引用 MNE?#
参见如何引用 MNE-Python。
我不确定如何用我的 Y 数据完成 X 分析步骤……#
要弄清楚处理 EEG 和 MEG 数据时“正确的做法”并不容易。我们通过 MNE Forum(MNE 论坛)讨论不同类型数据的分析策略。值得先搜索论坛存档,看看过去是否有相关讨论;如果其中还没有答案,也不必犹豫,直接提出新问题。
我好像发现了一个 bug,该怎么办?#
当你遇到错误信息或意外结果时,往往很难判断原因究竟是 MNE-Python 的 bug、用户代码中的错误、数据文件损坏,还是数据本身存在异常。寻求帮助的第一步应当是 MNE Forum,而不是 GitHub。这一点值得反复强调:GitHub issue tracker 不是用来获取使用帮助的——它用于报告软件 bug、提出功能请求以及改进文档。如果你提交的 issue 只包含使用问题,我们会关闭该 issue 并引导你前往论坛。如果你确信遇到的问题是一个软件 bug(而不是数据问题或用户操作失误):
请确认你使用的是最新版本。可以在 shell 提示符下用以下命令检查:
$ mne sys_info
这还会给出 MNE-Python 重要依赖项的版本信息。
如果你已经使用最新版本,在可能的情况下请尝试最新的开发版,因为自最新发行版以来该 bug 可能已被修复。如果无法试用最新开发版,请在 GitHub issues 页面搜索,看看该问题是否已被报告和/或修复。
请尝试用某个 MNE 示例数据集复现该问题。如果无法用内置数据集复现,请提供一段能够触发该错误的、经过匿名化处理的小规模数据链接。
如果问题仍然存在,请新建一个 issue,并附上能够复现该错误的最小代码示例。将代码示例粘贴到 issue 中,并在代码行的上方和下方各加一行三个反引号(```)。这个最小可复现示例应当是自包含的,也就是说 MNE-Python 的贡献者应当能够复制粘贴你提供的代码片段,并在自己的电脑上复现该 bug。
为什么用“pickle”保存 MNE-Python 对象和数据以备后用是危险的?#
用 pickle 保存数据和 MNE-Python 对象以备日后使用,因其简单通用而颇具吸引力,但通常并非最佳选择。pickle 并非为稳定的长期存储而设计,很可能在不久的将来你就无法读取这些数据。详情请参阅:
MNE-Python 为其对象提供了自有的文件保存格式(通常基于 FIF 标准),一般通过 save 方法或 write_* 方法实现,例如 mne.io.Raw.save()、mne.Epochs.save()、mne.write_evokeds()、mne.SourceEstimate.save()。如果你有想保存的数据却不知如何操作,可以在 MNE Forum 或 GitHub issues 页面提问。
如果你想将自己的数据(例如受试者的行为评分)写入磁盘,我们强烈建议使用 h5io,它基于 HDF5 格式和 h5py,能以快速、可长期兼容的标准格式保存数据。
我已经下载过一次数据集,但 MNE-Python 又要求重新下载,为什么?#
MNE 示例数据的默认存放位置是 ~/mne_data。如果你已经下载了数据,而某个示例却询问是否要再次下载,请确认数据位于 examples 目录中,并且你是在该目录下运行脚本:
$ cd examples/preprocessing
然后在 Python 中可以执行:
In [1]: %run plot_find_ecg_artifacts.py
有关所有可用数据集的列表以及一些高级配置选项(例如为数据集指定自定义存储位置),请参阅 Datasets Overview。
我没有指定,某个函数却使用了多个 CPU 核心,为什么?#
通常,在 MNE-Python 中通过 n_jobs 变量让 parallel 模块使用多个核心。然而,像 mne.preprocessing.maxwell_filter() 这样使用 scipy.linalg 的函数并没有 n_jobs 参数,却仍可能使用多个核心。这是因为 scipy.linalg 所依赖的线性代数库原生支持多线程:
Intel Math Kernel Library(MKL),它使用 OpenMP
Accelerate,Apple silicon 上 macOS 的 PyPI NumPy 和 SciPy wheel 所使用的库
要控制 mne.preprocessing.maxwell_filter() 这类线性代数密集型函数使用的核心数,可以将环境变量设置为期望的核心数。具体哪个变量有效,取决于你的 BLAS 在构建时所链接的线程层,而不仅仅取决于用的是哪种 BLAS。mne.sys_info() 会同时报告这两者,例如 OpenBLAS 0.3.33 with 10 threads (openmp threading layer)。
使用 pthreads 线程层的 OpenBLAS(通常由 PyPI 上的 NumPy 和 SciPy wheel 提供):使用
OPENBLAS_NUM_THREADS。使用 openmp 线程层的 OpenBLAS(通常由 conda-forge 和独立安装程序提供):使用
OMP_NUM_THREADS。在这些构建中OPENBLAS_NUM_THREADS会被静默忽略,因为 OpenBLAS 将线程管理交给了 OpenMP。MKL:使用
MKL_NUM_THREADS;对于任何基于 OpenMP 的线程层,也可以使用OMP_NUM_THREADS。Accelerate:没有有效的设置方法,也不需要设置。Accelerate 自行管理线程,并会忽略上述所有变量;
VECLIB_MAXIMUM_THREADS至多仅供参考。其性能相对于线程设置基本是平坦的,因此没有什么可调的。
使用更多线程并不总是更快。尤其是像 mne.preprocessing.maxwell_filter() 和 mne.compute_covariance()(method="shrunk")这类分解密集型的操作,当 OpenBLAS 或 MKL 占用所有逻辑 CPU 核心时可能慢得多。如果其中某个操作异常缓慢,可以用你分析中有代表性的一部分数据来比较几种设置,例如 1、2、3、4 以及物理 CPU 核心数。最佳设置取决于具体的操作、CPU 和 BLAS 库。
请在导入 MNE-Python、NumPy 或 SciPy 之前设置环境变量。例如,在 shell 中这样启动分析:
$ OMP_NUM_THREADS=4 python analysis.py
在线性代数库加载之后再做的修改,在同一个 Python 会话中可能不会生效。
我手上有一个不知道是什么类型的 FIF 文件,该怎么读取?#
mne.what() 函数可以作用于任何 .fif 文件,用于识别文件中包含的数据类型。这有助于判断该用 mne.read_cov()、mne.read_epochs()、mne.read_evokeds() 等哪个函数。还有对应的命令行工具 mne what:
$ mne what sample_audvis_eog-eve.fif
events
重采样与降采样数据#
重采样、降采样和分箱数据的这些选项都是什么意思?#
MNE-Python 中有许多函数可以改变数据的有效采样率。这里我们讨论其中几个主要的函数及其影响:
mne.io.Raw.resample()用于对原始数据重采样(通常是降采样)。重采样是一个两步过程:先应用低通 FIR 滤波器,再从数据中抽取部分样本。通常不建议在构建
mne.Epochs进行最终分析之前用该函数对数据重采样,因为这样做实际上会损失事件时间的精度(并使其抖动),可参阅这个 gist 中的演示。不过,对原始数据重采样至少在以下情形中是有用的:在低通或带通滤波后的数据上计算投影算子
探索数据
mne.preprocessing.ICA.fit()会在不做低通滤波的情况下降采样数据,但它仅用于对数据拟合统计模型。mne.Epochs.decimate()与mne.Epochs构造函数中的decim参数作用相同,会在每个事件前后每隔 \(N^{th}\) 个样本抽取一个。为避免混叠伪影,降采样前应对原始数据做充分的低通滤波。建议按照 Nyquist 准则,使用mne.io.Raw.filter()并将h_freq设为新采样率的一半(fs/2N)或更低,以确保有效衰减高于该阈值的频率成分。mne.Epochs.resample()、mne.Evoked.resample()和mne.SourceEstimate.resample()都会对数据重采样。由于重采样过程中会应用低通滤波器,因此可以避免潜在的混叠伪影。然而,这种滤波会引入边缘伪影。使用mne.io.Raw.resample()时同样存在边缘伪影,但那里的边缘伪影只局限于两个时间点:记录的起点和终点。而使用这三种方法时,每一段数据(或mne.Evoked、mne.SourceEstimate数据)的起点和终点都会被引入边缘伪影,这往往对数据产生更明显的影响。mne.SourceEstimate.bin()可用于降采样,并可选择是否进行“分箱”(对数据点取平均)。这相当于对数据施加滑动平均(boxcar)滤波器后再降采样。时域的 boxcar 对应频域的 sinc,因此它相当于一个简单而非理想的低通滤波器;如果数据此前未被充分低通滤波,这会减轻但不会消除混叠。当“滤波器”或箱宽为单个样本(即冲激)时,该操作就简化为不经过滤波的降采样。
对原始数据重采样耗时太久!该怎么办?#
mne.io.Raw.resample() 有一个 npad 参数,可以设为 'auto'。这是默认值,如果你改动过,可以试着改回 'auto',或许会有帮助。
如果你有 NVIDIA GPU,还可以尝试使用“在 Linux 上修复 dock 图标”中介绍的方法,这有时能让滤波和重采样操作快上一个数量级。
正向解与逆向解#
应该如何对协方差矩阵做正则化?#
估计得到的协方差可能在数值上不稳定,并且容易使估计的源幅值与可用样本数之间产生相关性。因此建议对噪声协方差矩阵进行正则化(参见正则化噪声协方差矩阵),尤其是在样本量很少时。遗憾的是,有效样本数并不容易确定,因而也难以选择合适的正则化强度。在 MNE-Python 中,正则化使用文献 [1] 中描述的高级正则化方法,可通过 'auto' 选项启用。使用该选项时,将通过交叉验证来学习最优的正则化参数:
>>> import mne
>>> epochs = mne.read_epochs(epochs_path)
>>> cov = mne.compute_covariance(epochs, tmax=0., method='auto')
该过程利用未见数据的负对数似然,通过衡量白化数据的程度来定量评价噪声协方差。最终结果也可以进行目视检查。假设基线中不包含系统性信号(与感兴趣事件锁时),则白化后的基线信号应服从多元高斯分布,即在某一时间采样点上,白化基线信号应位于 -1.96 与 1.96 之间。基于同样的推理,全局场功率(GFP)的期望值为 1(计算 GFP 时应考虑真实的自由度,例如有 2 个活动 SSP 向量时取 ddof=3):
>>> evoked = epochs.average()
>>> evoked.plot_white(cov)
该图同时显示各通道的白化诱发信号和白化 GFP。GFP 面板中的数字表示数据的估计秩,也就是计算白化 GFP 时对传感器平方和进行除法所用的有效自由度。白化 GFP 还有助于发现虚假的晚期诱发成分,这些成分可能是正则化过强或不足的结果。
注意,如果数据已使用信号空间分离(SSS)[2] 处理过,梯度计和磁力计将联合显示,因为两者都是由相同的 SSS 基向量、以相同的数值秩重建的。这也意味着这两种传感器类型不再线性无关。
这些评估方法可用于评估模型违背情况。更多入门材料可在此处找到。
对于专家级用例或调试,还可以比较其他替代估计器:
>>> covs = mne.compute_covariance(epochs, tmax=0., method='auto', return_estimators=True)
>>> evoked = epochs.average()
>>> evoked.plot_white(covs)
这会绘制最优估计器的白化诱发数据,并在相应面板中以多条独立曲线显示所有估计器的 GFP。
我的 watershed BEM 网格看起来不正确#
在使用 mne watershed_bem 或 mne.bem.make_watershed_bem() 之后,你可能会发现脑、内颅骨、外颅骨和/或头皮表面的 BEM 网格在 mne.viz.plot_alignment() 和 mne.viz.plot_bem() 中看起来不正确。
MNE 依赖 FreeSurfer 的 mri_watershed 计算 BEM 网格。FreeSurfer 的 watershed BEM 策略是:
计算外层皮肤(头皮)表面
将外层皮肤向内收缩,形成“外颅骨”
计算脑表面
将脑表面向外扩张,形成“内颅骨”
一个常见的问题是看到:
the surface inner skull is not completely inside surface outer skull
观察网格时,内颅骨表面(扩张后的脑表面)会出现缺陷,这些缺陷会突入外颅骨表面(收缩后的头皮表面)。遇到这种情况,可以尝试以下办法(大致按难度从低到高排列):
调整
mne watershed_bem中的--preflood/-p参数。调整
mne watershed_bem的--atlas和--gcaatlas选项。手动编辑网格(参见这个教程)。
手动运行
mri_watershed并尝试各种 FreeSurfer 标志(例如用-less修正输出)。回到 FreeSurfer 流程的更早阶段来解决问题。特别是
mri/brainmask.mgz可能由autorecon1步骤错误生成,导致脑掩膜中包含部分硬脑膜和/或颅骨。你可以用freeview或其他 MRI 查看工具进行检查。查阅 FreeSurfer 关于修复错误的文档。
尝试通过
xopts调整mri_normalize的参数,例如:$ mri_normalize -mprage -b 20 -n 5
尝试手动设置控制点和/或使用
-gentle。检查 talairach 变换是否不太正确,如有问题则手动调整。
在 FreeSurfer 邮件列表(listserv)中搜索其他思路
先在一个干净的目录中运行
recon_all -autorecon1 -xopts xopts.txt可能会有所帮助,看看这样能否解决全部问题;如果不行,再退而手动设置控制点和/或调整 talairach。当-autorecon1结束时一切看起来都正常,你就可以运行mne watershed_bem检查输出是否良好。一旦输出正常(且brainmask.mgz正确),就可以继续执行recon_all -autorecon2和recon_all -autorecon3,从而有效地完成所有recon_all步骤。