拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Cartool静息态EEG微状态分析全攻略:从个体到群体的完整流程

Cartool静息态EEG微状态分析全攻略:从个体到群体的完整流程

做静息态EEG分析的人,应该都绕不开“微状态”这个概念。没有任务刺激时,大脑自发活动在头皮上呈现为不断变化的地形图,看起来杂乱无章,但微状态分析可以把这些信号压缩成几个稳定的模板,再还原成一段离散的状态序列。这套方法在精神疾病、认知老化、睡眠研究里用得非常多。而提到微状态,Cartool是绕不过去的工具。它由日内瓦大学脑影像实验室开发,免费、轻量,却把GFP峰值提取、聚类、跨被试匹配这些核心步骤集成得很完整。这篇文章我会把从个体到群体的完整流程拆开讲清楚,包括个体水平聚类怎么做、群体水平该选哪种聚类策略、K值怎么定,以及那些官方文档里不会写的坑。内容偏实操,适合正在跑静息态EEG项目、或者想用Cartool做群体微状态分析但看英文文档一头雾水的研究生和科研人员。

1. 微状态分析底层逻辑与Cartool的选型理由

1.1 静息态EEG为什么需要“微状态”这套框架

先聊一个最基础的问题:静息态EEG到底在记录什么?没有任务刺激时,被试闭着眼睛坐在隔音室里,头皮上记录到的电位变化看起来就是一条随机波动的曲线。传统频谱分析能告诉你不同频带(delta、theta、alpha)的能量大小,但回答不了“大脑的空间网络是怎么组织的”。微状态分析补上的正是这个空间维度。

它的核心假设是:头皮电位地形图(topography)的变化并不是完全连续的,而是在少数几个准稳定状态下切换。每个状态持续大约60到120毫秒,之后跳转到下一个状态。这个过程有点像电影——一帧一帧静止的画面快速切换,看起来是流动的,但每一帧其实是独立的。EEG微状态做的就是找出这些“关键帧”,也就是稳定的地形图模板。

那为什么偏偏要在全局场功率(GFP,Global Field Power)的峰值时刻提取地形图?GFP是全部电极电压的空间标准差,反映了整个头皮电位分布的总体强度。峰值时刻意味着所有电极的电压离差最大,地形图的空间信噪比最高,也最稳定。可以说,GFP峰值时刻是大脑功能网络状态最“清晰”的时刻,在这些时刻抽样做聚类,提取出来的模板才可靠。

经典文献里静息态微状态往往报告4类模板,习惯上命名为A、B、C、D,分别对应视觉网络、视觉注意网络、突显或自我参照网络、背侧注意网络等不同的功能网络。但4类并不是硬性规定,越来越多人用5类、7类甚至更多模板去刻画细节。具体用几类,要看你的研究假设、数据质量,以及聚类结果的稳定性。

1.2 为什么选Cartool而不是直接写Python

很多人一上来就想用Python自己实现微状态分析,觉得既然是开源的、可复现的,为什么要用一个GUI软件?我的经验是:能用Cartool就用Cartool,省下的时间远比你想象中多。

原因有三点。第一,微状态分析流程里有非常多细节,比如GFP峰值的局部最大值判定、极性无关的空间相关匹配、层次聚类的凝聚规则,你在论文里看到的是几句话,自己在代码里实现才发现每个细节都是坑。Cartool把这些逻辑实现了并且经过大量文献验证,用它做核心计算,稳妥。第二,Cartool内置的聚类算法比较全:k-means、AAHC(Atomize and Agglomerate Hierarchical Clustering)、TAAHC都有。AAHC这种自适应层次聚类虽然原理不算难,但自己实现边界情况很容易出错。第三,可视化能力很强。聚类结果直接以色度地形图呈现,模板长什么样一目了然。这类“一眼看出结果是否合理”的能力,用代码加画图脚本也能做,但效率低得多。

当然Cartool也有局限。它本质上是桌面GUI工具,批处理能力弱,上百个受试者逐一点菜单会崩溃。所以我通常的工作流是:EEGLAB做预处理(ICA去伪迹),Cartool做GFP提取、聚类、模板匹配,最后用Python做统计和可视化。工具各干各最擅长的事,没必要为了“纯代码”而全用代码。

1.3 从个体到群体的整体分析框架

整个流程可以归纳为四个阶段:预处理、个体水平微状态识别、群体水平模板构建、统计检验。

第一阶段没什么悬念,平均参考、滤波、去伪迹。第二阶段是每个受试者单独算GFP峰值,然后在峰值地形图上做聚类,得到个体水平的微状态模板。第三阶段是重头戏,所有受试者的地形图聚到一起,生成一个群体的公共模板集合。第四阶段用群体模板回代到每个受试者的连续EEG数据里,给每个时间点打上微状态标签,计算持续时间、覆盖率、转换概率等参数,再做组间比较。

很多人会在第二阶段和第三阶段之间卡住:个体模板已经出来了,怎么汇总成群体模板?直接平均个体模板?还是把所有人的原始地形图拼起来重新聚类?两种思路都能在文献里找到,但结果可能会有差异。我后面专门用一章讲清楚这两种策略各自的底层逻辑和适用场景。

2. 个体水平微状态分析实操要点

2.1 数据预处理:参考、滤波和伪迹处理

个体水平分析成果的上限,其实在预处理阶段就定死了。我见过太多因为预处理偷懒,导致后面聚类模板乱七八糟的例子。

先说参考。微状态分析的输入是头皮地形图,参考电极的选择会直接改变地形图形态。目前主流做法是平均参考(average reference),也就是把所有电极的均值作为参考电位。用Cartool处理时,导入数据后第一件事就是确认数据是不是已经做了平均参考。如果原始数据用的单侧乳突或Cz参考,先转成平均参考再往下走。

再说滤波。静息态研究通常做带通滤波,常见的是0.5到30赫兹或者1到35赫兹。下边界去掉直流漂移,上边界把高频肌电切掉。如果实验室环境有工频干扰,50赫兹陷波也建议做掉。滤波范围会影响地形图形态,所以同一批受试者一定要用完全相同的参数,这是组水平可比的底线。

伪迹处理是整个预处理里最关键的一环。眼电、肌电、心电伪迹如果没清干净,它们对应的地形图会被聚类成“伪模板”,而且这些模板往往在组内还有一定一致性,很难用后续统计筛掉。我的做法是:在EEGLAB里用ICA跑一遍,手动识别并移除眨眼和水平眼动成分,偶尔还有心跳成分,然后按幅度阈值分段剔除(比如超过±100微伏的时段直接丢掉),最后再用Cartool导入干净数据。Cartool也自带简单的滤波和坏导替换工具,但ICA这一步还是建议在外面做。

2.2 GFP计算与峰值地形图提取

GFP的计算公式非常简单:

GFP(t) = sqrt( (1/N) × Σ(Vi(t) - Vmean(t))² )

其中Vi是第i个电极在t时刻的电位,Vmean是所有电极在t时刻的平均电位,N是电极数。它反映的是整个电极阵列上电压的同质性——所有电极都差不多,GFP就低;电极间分化明显,GFP就高。

Cartool里计算GFP峰值时,有两个参数非常重要:最小峰值间隔和峰值选取规则。最小峰值间隔的意思是两个被选中的峰值之间至少要隔多少毫秒。我用500赫兹采样率的数据时,一般把间隔设在20到40毫秒。设得太小,会从同一个微状态内部提取出大量高度相似的连续地形图,聚类结果会被这些冗余样本带偏;设得太大,又会漏掉一些短暂的稳定状态。默认值其实可以先用着,关键是拿到结果后看一眼提取的峰值数量是否合理。

另一个是峰值定义。代码层面通常会要求某个时刻的GFP大于前后相邻时间点的GFP,才算局部最大。Cartool默认的做法也类似,但如果你想用Python核对,要特别注意边界条件的处理方式,否则数量对不上。

一个常见的静息态记录是5分钟、500赫兹采样、64导联,预处理后大概能提取出800到2000个GFP峰值地形图。这个数量级做个体聚类完全够用。

2.3 聚类算法选择与类数确定

GFP峰值地形图提取出来后,接下来就是个体水平的聚类。Cartool支持三种聚类算法:k-means、AAHC、TAAHC。

k-means是经典的分割聚类,你得先指定K值,算法再把所有地形图分成K簇。优点是快、可解释性强;缺点也很明显——K值怎么选。Cartool里跑k-means时会对不同的K分别计算类内离差(W值)和交叉验证标准,你可以对比不同K之间的曲线来选数。

AAHC是自适应层次聚类,它的思路和k-means完全不同。算法开始时,每个输入地形图都是一个“原子”,每次迭代找出与整体数据相关性最差的那个地形图(或者凝聚组),“打散”后重新归并到最近的簇里,直到剩下指定的类别数。因为它是层级式地不断聚合,所以不依赖初始中心点的随机选择,稳定性比k-means好,在微状态分析里更常用。

TAAHC则是在AAHC基础上加入了空间拓扑约束,聚类出来的模板空间分布更平滑,适合地形图噪声比较大、或者高密度导联数据(比如128导以上)。我自己跑64导静息态数据时,通常以AAHC为主。

K值的确定是新手最纠结的地方。我的做法是:对同一个受试者分别跑4类、5类、6类、7类,记录每个K值的全局解释方差(GEV,Global Explained Variance),然后画一条“K值-GEV”曲线。曲线在某个范围后斜率明显变缓,那个拐点附近就是你需要的K值范围。静息态数据4到5类往往已经解释了65%到75%以上的方差,再往上加类,收益快速下降。如果你发现7类了GEV还在飞速上升,先别急着高兴,大概率是伪迹没有清理干净。

还有一个不能跳过的步骤:模板排序。聚类出来的模板编号是随机的,比如被试甲的“第3个模板”和被试乙的“第3个模板”在地形图形态上可能根本不是一类。做组水平比较之前,必须人工按空间形态给模板排序和命名。比如A类模板左后侧正极、右前侧负极;B类则反过来;C类和D类都是前后方向分布但极性相反。这些形态特征是跨被试对齐的依据。

2.4 个体微状态参数:持续时间、覆盖率和转换概率

聚类得到模板之后,还要把这些模板回代到连续数据里,给每个时间点打标签。Cartool的做法是计算每个时间点的地形图和所有模板的空间相关(通常用Pearson相关),取相关系数最大的模板作为该时刻的微状态标签。这个过程依赖一个重要的性质:空间相关对地形图极性不敏感。也就是说,一个模板和它的“正负翻转版”会被当作同一个模板。这非常合理,因为视觉上正负翻转的地形图代表的是相同的空间分布模式,只是参考方向不同。

打完标签之后,可以直接从标签序列里统计四个经典参数:

  • 平均持续时间(mean duration):每个微状态类连续出现的平均时长,单位毫秒;
  • 时间覆盖率(coverage):某类微状态占总时间的百分比;
  • 每秒出现次数(occurrence):某类微状态每秒平均出现多少次;
  • 转换概率(transition probability):从当前微状态跳转到其他微状态的频率。

有些人还会额外计算“GFP峰值处的GEV”,用来衡量模板对GFP峰值时刻地形图的总体解释程度,这相当于一个全局拟合优度指标,审稿人比较喜欢看。

个体水平出这些参数之后,你可能会想:如果只是报告每个受试者自己的微状态模板和参数,直接用个体聚类结果不就好了?为什么要费劲做群体模板?因为组间比较的前提是“模板是同一个东西”。如果被试A的模板和被试B的模板不是严格对应关系,比较它们的覆盖率就没有意义。所以找到一套所有被试共用、且语义一致的群体模板,是整个微状态分析从“个体描述”走向“群体统计”的关键。

3. 从个体到群体:聚类策略选择与实战流程

3.1 两种主流群体聚类策略

从个体到群体,文献里主要有两条路线。

策略一是两阶段聚类:先对每个受试者的GFP峰值地形图做一次个体聚类,得到个体水平的模板(比如每人4个),再把所有人的个体模板汇总到一起做第二次聚类,得到群体模板。这个策略的好处是每个受试者以相同数量的模板进入群体分析,不会被某个峰值特别多的人“带跑偏”。坏处也很明显:个体聚类这一步已经做了信息压缩,如果个体模板本身质量参差不齐,群体聚类就是“废料进、废料出”。而且两阶段聚类的类数量很难控制得干净——是拿个体全部模板拼一起聚类,还是先做加权平均再聚类,不同做法结果差异不小。

策略二是直接拼接聚类:把所有受试者的GFP峰值地形图全部拼接成一个大数据集,当作一个“虚拟受试者”,在这个拼接数据集上做一次整体聚类,直接得到群体模板。这是目前更主流的做法。它利用了全部地形图的完整信息,没有中间的压缩损耗,流程也更简洁。但它有个前提:每个受试者提供的峰值数量要尽量均衡,否则数据量大的那个人会主导聚类方向。

两种策略的对比可以看下面这个表:

对比维度两阶段聚类直接拼接聚类
信息损耗第一次聚类有压缩损耗无中间损耗
个体均衡性天然均衡需手动平衡样本量
流程复杂度两步聚类,耗时较长一步聚类,较快
适用场景个体模板质量高、组内模板稳定数据量适中、样本量均衡

我个人的偏好是直接拼接聚类,尤其当所有受试者记录条件一致、数据质量良好时。它更贴近“从个体到群体”的语义:群体模板是从所有人的峰值地形图里自然涌现出来的,而不是从个体模板里二次抽象出来的。

3.2 Cartool里的群体聚类操作步骤

下面讲具体操作。

第一步,确保每个受试者的GFP峰值地形图已经准备好。你可以在Cartool里对每个连续EEG数据文件执行GFP峰值提取,软件会为每个受试者生成一份峰值地形图索引。我通常会在这一步顺手看一眼每个受试者提取了多少个峰值,如果某个人明显偏离平均值(比如别人都是800个,他只有150个),那要先检查是不是伪迹剔除过度了。

第二步,把所有人的峰值地形图拼接成一个文件。Cartool提供了一个“连接/合并”的操作,可以把多个数据文件的峰值地形图按顺序拼成一个“虚拟数据集”。实际操作中也可以直接把多个受试者的地形图文件依次加载合并,具体入口不同版本略有差异,但核心逻辑是一样的。

第三步,对拼接后的数据集运行群体聚类。因为拼接后的数据本身就是一系列地形图(每个GFP峰值对应一个地形图),不需要再重新计算GFP峰值,直接用AAHC或k-means对地形图集合聚类。这里要特别强调样本量均衡:如果被试A贡献了1200个峰值、被试B只贡献了400个,最好在拼接前做平衡处理。常用的做法是设定一个目标数量(比如每人500个),从每个受试者的峰值列表里均匀抽样。抽样时可以完全随机,也可以优先保留GFP更高、信噪比更好的峰值,后者在信噪比参差不齐时更推荐。

第四步,观察聚类模板。群体聚类完成后同样有几类模板,肉眼检查它们是否符合经典的空间分布规律。如果出现明显不合理的模板,回到第三步调整K值或检查预处理。

3.3 群体模板回代匹配与标签对齐

群体模板造好了,接下来要把它用回每个受试者的原始连续EEG数据。Cartool里这一步一般叫“fit”或“back-fit”,本质上是把每个时间点的地形图与K个群体模板做空间相关,选择最匹配的模板作为当前时刻的微状态标签。

这里有两个实操中一定会遇到的坑。

第一个是极性翻转问题。前面提到空间相关对极性不敏感,所以“模板本身”和“它翻转180度后的版本”在匹配时是等价的。但到了画图和展示阶段,如果不同受试者回代时使用的是不同极性的模板,算出来的地形图组平均就会乱套。解决方法是:在回代前先统一每个群体模板的极性方向。常用的约定是把模板的最大正极固定在某个解剖位置(比如右侧或前侧),所有被试都用同样极性方向的模板去匹配。

第二个是标签对齐问题。群体聚类出来的类别编号是随机的,不代表A类还是D类。你需要在得到群体模板后,根据模板的空间形态把编号重新排序并命名。经典的四类模板判别方法我前面提过,不赘述。如果模板数量超过4类,就需要对照已有文献或者用自己的命名规则,并在方法部分写清楚怎么对齐的。这一步不能省,跨研究可比性全靠它。

匹配完成后,每个受试者会得到一个逐时间点的微状态标签序列。从这个序列里可以统计前面说的四个参数。Cartool本身能输出部分参数,但灵活性和批量导出能力有限,我一般会把标签序列导出到Python里自己算。注意导出格式不同版本的差异,先拿一个小样本核对数值,再批量跑。

3.4 群体微状态参数统计与展示

拿到每个受试者的微状态参数后,统计就比较常规了。

如果只有两组被试,最常见的做法是对每个微状态类的每个参数分别做独立样本t检验,比如“A类覆盖率在患者组和对照组之间的差异”。多组数据用单因素方差分析。参数不满足正态分布时用Mann-Whitney U或Kruskal-Wallis,也可以用置换检验,更稳健。

因为前面已经做了K个模板、多类参数的多次比较,统计显著之前一定要做多重比较校正。常见的选择是FDR(False Discovery Rate)校正,比Bonferroni温和一些,但也足够控制假阳性率。我的习惯是:先报告未校正的所有结果,再报告FDR校正后依然显著的部分,这样既全面又不容易被审稿人质疑。

可视化方面,最标准的输出就是一张包含所有微状态模板的组平均地形图,通常排列在图形上方,下方配误差线柱状图或箱线图展示各参数差异。转换概率可以画成热图,能直观看出组间状态转换模式的差异。这些都是很成熟的展示方式,但记住一句:模板地形图一定要极性对齐后再平均,不然组平均图会看起来“糊”得不像任何模板。

4. 常见问题与排查技巧实录

4.1 聚类模板老是“四不像”怎么办

我最早跑微状态时,最崩溃的瞬间就是:聚类出来4个模板,没有一个是文献里那种漂亮的A/B/C/D形态。后来排查多了,发现绝大多数情况不是算法问题,而是数据问题。

第一优先检查伪迹。肌电伪迹在时域上表现为高频毛刺,它的地形图往往是局部的、变化剧烈的,聚类时很容易形成单独的“假模板”。眼电伪迹残留则会产生额部强梯度的模板。所以当你看到模板集中在额叶或者单侧颞区、形态奇怪时,先回原始数据看是否有伪迹段没清干净。

第二检查GFP峰值数量。如果某个受试者峰值太少(比如只有几十个),聚类结果会非常不稳定,很容易出现反直觉的模板。这种情况常见于过度剔除伪迹或者数据本身信噪比差。可以考虑放宽幅度阈值,或者允许更小的峰值间隔,先把样本量补回来。

第三检查K值。静息态4类模板是经验值,但如果你数据采自特定人群(比如老年人、患者),大脑网络组织模式本来就可能偏离健康年轻人的经典模板。这时候别硬套4类,多跑几个K值,看哪个K值下模板最稳定、解释方差最好。

4.2 个体匹配后统计参数普遍偏低或为零

匹配完群体模板后,有时候会发现个别受试者的某些参数是0,比如“C类覆盖率0%”、“D类平均持续时间只有30毫秒”。这通常不是大脑真的没有这个状态,而是群体模板和该受试者的地形图匹配度太差。

最常见的原因是预处理不一致。如果你整个项目里,有的人做了ICA,有的人没做;有的用0.5到30赫兹滤波,有的用1到40赫兹滤波,那地形图形态差异会非常大,群体模板自然“拟合不上”某一部分被试。解决方法很笨但很有效:全项目统一预处理流程,做好记录,不搞特殊化。

另一个原因是通道数和电极坐标不一致。如果有人记录时某个电极坏了,用插值补上,而另一个人是直接用坏导数据进Cartool,那地形图拓扑结构差异会被放大。一定要确保所有受试者使用同一导联集合、同一插值方式。

还有可能是极性对齐问题。回代匹配时,如果某个模板在所有被试里都被匹配成“负极性版本”,那么覆盖率再高,模板展示图中看起来也是翻转的。建议在最终计算参数前,先跑一个小样本核对匹配结果的可视化地形图,确认极性没有系统性偏移。

4.3 不同工具算出来的GFP峰值数量对不上

有人喜欢在Python里自己算GFP峰值,再导入Cartool做聚类。想法是好的,但很容易发现两边算出来的峰值数量不一样。

原因通常是到处在边界条件。Cartool的峰值定义是:一个GFP值要大于前后各若干个采样点,才算峰值。不同工具设定的“前后窗口”不一样,峰值数量自然不同。另一个潜在问题是滤波阶段,如果Python里滤波器的阶数和截止频率和Cartool不完全一致,信号波形有细微差异,局部最大值的数量也会跟着变。

我的建议是:核心的GFP提取和峰值定位直接在Cartool里做,不要跨工具导来导去。如果确实需要Python精确复现,先用一段已知数据把两边峰值数量调到一致,再跑全量数据。

4.4 大数据集跑不动,有什么提速办法

Cartool毕竟是桌面软件,遇到几十上百人的数据集,逐个人点菜单确实很痛苦。提速可以从两个方向入手。

一个是降低单次聚类的输入规模。群体聚类前,每个受试者的峰值地形图没有必要全部进聚类,比如500个和1200个峰值在聚类效果上没有本质差别,但计算时间差距很大。把每个受试者的峰值数量统一控制在400到600个,既均衡又够用。

另一个是分段验证。先随机抽5到10个受试者,跑通“GFP提取-群体聚类-回代匹配”整条流程,确认参数和模板形态都没问题,再批量处理剩余数据。这一步还能提前发现个别受试者数据格式异常的问题,不会等到最后才返工。

另外提醒一点:聚类算法本身有随机性。k-means对初始中心点敏感,AAHC虽然稳定性好但也不排除极小概率下陷入不同的局部解。如果换了K值或者重跑一次,模板形态完全不一样,那说明数据问题很大,先回预处理排查。如果只是模板顺序变了,那正常,重新排序就好。

4.5 与纯代码实现之间的核对思路

如果你最后还是想用Python复算一遍,我给一个核对的可行路径。

第一步,在Cartool里导出某个受试者的GFP峰值索引和对应的地形图矩阵。第二步,在Python里用相同数据复算GFP,确认峰值数量一致。第三步,用同样的聚类参数(比如指定K=4的k-means)跑聚类,比较模板地形图是否一致。这三步只要每一步都对上了,代码基本就没问题。

空间相关的计算也要注意。常用的是Pearson相关,它对每个向量的均值和方差做了归一化,所以即使两个地形图整体振幅不同,也能比较形态相似度。如果用欧氏距离做匹配,振幅差异会影响结果,这在微状态匹配里通常不是想要的特性。遇到“空间相关还是欧氏距离”的讨论时,记住微状态分析关心的是地形图形态,不是绝对幅度——这也是我总跟人说“别看欧氏距离算得爽,匹配还是用相关靠谱”的原因。

如果你愿意走“Cartool算核心、Python做批量和统计”这条路,其实已经接近我当前工作流的最优解了。整套流程跑顺后,换新数据也就几天的适配工作量。我现在的习惯是,拿到新数据集先跑一遍5类模板的群体聚类,看看地形图形态是否符合预期,再回头微调预处理参数。这个习惯是无数次返工换来的,每次看到模板形态异常,第一反应都是去查眼动成分有没有清干净,而不是去怀疑聚类算法——十次里有八次都是这么回事。

返回列表