非官方中文翻译 本页译自 MNE-Python 官方文档: 查看英文原文 ↗。 文档版权归 MNE Developers 所有(BSD-3-Clause)。

常见问题(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 所依赖的线性代数库原生支持多线程:

  • OpenBLAS

  • 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 策略是:

  1. 计算外层皮肤(头皮)表面

  2. 将外层皮肤向内收缩,形成“外颅骨”

  3. 计算脑表面

  4. 将脑表面向外扩张,形成“内颅骨”

一个常见的问题是看到:

the surface inner skull is not completely inside surface outer skull

观察网格时,内颅骨表面(扩张后的脑表面)会出现缺陷,这些缺陷会突入外颅骨表面(收缩后的头皮表面)。遇到这种情况,可以尝试以下办法(大致按难度从低到高排列):

  1. 调整 mne watershed_bem 中的 --preflood / -p 参数。

  2. 调整 mne watershed_bem 的 --atlas 和 --gcaatlas 选项。

  3. 手动编辑网格(参见这个教程)。

  4. 手动运行 mri_watershed 并尝试各种 FreeSurfer 标志(例如用 -less 修正输出)。

  5. 回到 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 步骤。

参考文献#