距离上一篇VBM/SBM预处理笔记隔了挺久,这次的主题是准备做统计分析前绕不开的两个东西:感兴趣区(ROI)和全脑体积(TIV)。简单说,前者解决“我只关心某个局部脑区,怎么把数据提出来”的问题,后者解决“不同人脑袋大小不一样,怎么比较才算公平”的问题。这篇笔记适合刚跑完CAT12预处理、正准备做第二层统计的同学,或者想复现文献里“提取某脑区体积/厚度”流程的读者。我会把文件类型、TIV原理、ROI定义方式、批量提取步骤和踩过的坑都过一遍,尽量做到拿过来就能照着跑。
1. 先把手里的文件理清楚,ROI和TIV才有落脚点
1.1 VBM跑完,真正需要关注的几个文件
CAT12跑完VBM后,每个被试的目录下会多出一堆以mwp1、smwp1、p0开头的文件,很多人到这里就开始晕。先说结论:做ROI体积提取和TIV统计时,最常用的是这几个——mwp1开头的文件是经过DARTEL配准到MNI空间、并且用雅可比行列式调制过的灰质概率图,体素值可以理解为“该体素内灰质体积的贡献量”;smwp1开头的文件是把这个mwp1做空间平滑后的结果,做全脑VBM统计时基本都用它;p0开头的文件则保留了原生空间的组织分割信息,对应灰质、白质、脑脊液三张概率图。
如果你想提取某个ROI的灰质体积,操作上会同时用到二值化图谱mask和个体灰质图——在原生空间还是MNI空间差别很大。我的一般习惯是:在MNI空间做ROI分析,mask直接对齐smwp1或mwp1,省去从原生空间到模板空间重新变换的麻烦;只有在做基于坐标的单被试验证时,才需要回到原生空间小心翼翼处理。
这里有个细节特别提醒:不要用p0图直接和标准空间下的ROI模板相乘。p0是原生空间图像,体素大小和矩阵维度都和被试原始T1一致,直接把AAL模板贴上去会完全错位。任何ROI提取之前,第一件事永远是确认mask和目标图像是否在同一空间。
1.2 SBM跑完,另一个维度的“ROI”
SBM的产物不是体素概率图,而是一堆表面网格文件,通常放在surf子目录里,比如lh.central、rh.central,还有皮层厚度、折叠度、面积等度量图。这时候的“ROI”不再是三维体素团块,而是皮层表面上的一个patch,需要按表面图谱来定义。
CAT12里自带了若干表面图谱选项,比如Desikan-Killiany、Destrieux这种常见的分区方案。提取时,把图谱分区映射到每个被试的表面上,然后计算该patch内厚度、面积或体积的平均值。和VBM的差异在于:表面ROI的空间操作依赖网格的顶点(vertex),而不依赖体素格子,所以不能用普通三维mask去提取,必须用表面工具处理。
很多新手用VBM的思路去提取SBM厚度,结果得到的数值很奇怪,多半是因为用了体积mask去配到表面文件上。记住一个原则:VBM的ROI是三维mask,SBM的ROI是表面标签(label),两套体系不能混用。后面第5部分我会专门讲怎么避免踩这个坑。
1.3 ROI和TIV为什么必须放到一起聊
ROI解决“提哪里”的问题,TIV解决“提出来之后怎么比”。很多人单独学ROI时很顺,但一到写统计方法就卡壳——“要不要把TIV放到协变量里?”“ROI体积和全脑体积是不是一回事?”“CAT12输出的TIV和文献里的ICV是不是同一个概念?”这些问题只有把两个概念串起来才能理清。
再加上现在审稿人越来越挑剔,除非你只做全脑层面的探索性分析,否则只要涉及局部体积或皮层厚度比较,基本都会追问你“是否校正了总体头围/颅内体积”。所以干脆放在同一篇笔记里,一次性把原理、提取、统计三个环节讲透。
2. TIV,全脑体积这个“隐藏变量”很多新手都是后面才明白
2.1 TIV是什么,为什么第二层统计一定要想清楚它
TIV的全称是Total Intracranial Volume,即颅内总体积,大约等于灰质体积加白质体积加脑脊液体积之和。它的含义是“这个颅腔里能装多少脑组织”,本质上是衡量头部大小和颅腔容量的一项指标。你可能听过另一个类似说法叫ICV(Intracranial Volume),两者在脑影像文献里基本可以互换。
为什么要校正TIV?因为人和人的头围差异在脑影像统计里是真实存在的“系统误差”。一个180cm身高的人和一个160cm身高的人,大脑灰质体积差值可能很显著,但这个差异更多来自身体尺寸,而不是认知障碍或疾病影响。如果直接拿ROI灰质体积做组间比较,头部较大的组天然更容易得到更大的体积数,这会混淆真正的病理效应。
统计校正的常见做法是把TIV作为协变量放进ANCOVA模型,在SPSS、R或SPM的第二层模型里都行。但这里有个前提:只有当组间头围差异与分组不存在交互作用时,简单放入协变量才合适。如果两组头围差异本身就很大,比如儿童和成人比较、男女混合样本,建议先做协变量回归检验,看数据是否满足平行线假设。
还有另一种更粗暴的做法:直接用ROI体积除以TIV得到一个比值。我不推荐作为主要分析手段,因为比值变量往往不满足正态性,而且“乘除关系”会扭曲体积和TIV的真实线性关联。放在统计分析里,比例法损失的信息比协变量法多得多。
2.2 CAT12其实已经替你算好了TIV
CAT12的预处理在完成分割和配准后,会自动估算TIV,并把结果写进每个被试的XML报告文件里,文件名类似cat_sub-01_T1w.xml。用文本编辑器直接打开这个文件,搜索“TIV”,就能看到数字,单位通常是毫升(ml),正常人大概在1300到1700左右,具体随性别、年龄、种族有波动。
如果想在MATLAB里批量读取,可以用CAT12自带的cat_io_xml函数,大致写成下面这样:
x = cat_io_xml('cat_sub-01_T1w.xml'); disp(x.estimation.TIV);不过不同版本的CAT12内部字段路径有可能不一样,我在CAT12.7和CAT12.8里都试过,规律不完全一致。最稳妥的方法是先用一次textread或者直接mentor来看这个XML的结构体,再写对应的读取代码。批量提取时建议遍历所有被试文件夹,把每个XML里的TIV取出来,存进一个CSV表。
这里要强调:CAT12算TIV用的不是单纯把三张分割图累加那么简单。它的内部流程是从原生空间分割结果出发,结合DARTEL模板空间的形变场做统一估算,比手动只对c1+c2+c3求和的方式更稳定。除非你确认自己手动算法和CAT结果的差异在可接受范围内,否则论文里报告TIV时,优先用CAT12输出的值。
2.3 不做CAT自动输出时的手动TIV算法
有时候你只拿到了前人分割好的c1/c2/c3文件,没有XML报告,那就只能手动估算。算法非常简单:读取原生空间的灰质、白质、脑脊液概率图,把三张图的体素值相加,再乘以单个体素的体积,得到颅内总体积。在MATLAB里可以这样写:
V1 = spm_vol('c1_subject.nii'); V2 = spm_vol('c2_subject.nii'); V3 = spm_vol('c3_subject.nii'); Y1 = spm_read_vols(V1); Y2 = spm_read_vols(V2); Y3 = spm_read_vols(V3); voxvol = abs(det(V1.mat(1:3, 1:3))); % 单位 mm^3 tiv_mm3 = (sum(Y1(:)) + sum(Y2(:)) + sum(Y3(:))) * voxvol; tiv_ml = tiv_mm3 / 1000;这个算法的重要前提是c1/c2/c3必须来自同一个被试的同一个原生空间,不能混用其他文件。另外,如果分割时把颅骨、头皮等非脑组织也纳入了分割结果,TIV就会被高估,所以手动算完之后,最好和CAT自动估算值对一下,偏差超过20ml就应该返回去检查分割质量。
如果你的数据只保留了MNI空间的mwp1系列文件,那就不要再用它们手动算TIV了。经过配准、调制和重采样之后,体素值已经不再直接对应原生空间的物理体积,强行累加会引入误差。这一点也是我见过最多人踩的坑。
2.4 用TIV做协变量的两个具体场景
场景一:ROI体积分析。比如比较抑郁症患者和健康对照的海马体积,分组差别可能既来自海马本身萎缩,也可能来自个体颅腔大小。此时把TIV作为协变量放进ANCOVA,可以校正掉头围差异带来的混杂。实操上一般是在SPSS里用菜单:Analyze → General Linear Model → Univariate,把ROI体积设为因变量,分组设为固定因子,TIV和年龄设为协变量。
场景二:表面面积或体积分析。SBM里的皮层面积和皮层体积同样受到头颅大小强烈影响,文献中常见做法也是把TIV放进协变量。皮层厚度对头围的敏感度相对低一些,但审稿人仍然经常问,所以即便最后结果不显著,也建议在补充材料里报告“加入TIV作为协变量后结果保持一致”。
有一个情况我建议不用TIV校正:如果研究问题本身就把全脑总体积差异当作关注点,比如想回答“疾病组是否存在广泛灰质丢失”,那把TIV作为协变量会错误地吸收掉一部分真实效应。这时候更合适的做法是报告全局灰质体积(GMV)作为补充结果,而不是默默校正掉。
3. ROI定义的三条路径,不存在谁比谁绝对好
3.1 图谱模板ROI:省事、可复现、适合写方法
图谱模板是ROI分析里最常用的方式。核心逻辑是:用一张已经被专家分区好的标准大脑模板,去定义你关心的脑区,然后把模板里的每个标签编号对应到被试的个体空间或MNI空间图像上。常见选择包括AAL、AAL2、AAL3,以及Harvard-Oxford等。
AAL3是目前扩展较细的版本,保留了经典AAL脑区的基础上又拆出了一些更小的亚区。对于海马、杏仁核、壳核这类文献频繁报告的结构,AAL系列基本够用。如果你想让结果能和其他研究直接对比,建议在方法部分写清楚用的是哪个模板、哪个版本的标签编号,因为AAL2和AAL3里同一个名称的脑区编号可能发生变化,不写版本很容易被审稿人挑刺。
使用图谱模板时,要注意模板的MNI空间版本要和你的数据一致。CAT12输出的mwp1文件默认在标准MNI空间,体素大小通常是1.5mm或与模板一致;而很多图谱模板是2mm或1mm的。体素大小不匹配时,先做重采样到目标图像的空间,再做mask提取,不要直接拿不同分辨率的两个矩阵去对应。
3.2 坐标球形ROI:验证性分析的高频选择
坐标球形ROI是另一种很常见的定义方式:以某个MNI坐标为中心,画一个半径5到10mm的球体,落在球内的体素就是这个ROI。它特别适合做“验证性分析”——比如文献报告某个核心脑区峰值点在MNI坐标(-30, -15, -20),你想在新数据里看看这个位置的灰质体积是否和临床指标相关,直接画个球提取就可以了。
画球的逻辑不复杂,但坐标转换容易出问题。球形ROI的中心是MNI坐标系的毫米坐标,而你需要把它转换到目标图像的体素索引,再用距离公式筛选体素。Python里可以用仿射矩阵的逆来完成转换。以下是一段参考脚本:
import nibabel as nib import numpy as np img = nib.load('mwp1_subject.nii') affine = img.affine center_mni = np.array([-30, -15, -20, 1.0]) center_voxel = np.linalg.inv(affine) @ center_mni center_voxel = np.round(center_voxel[:3]).astype(int) radius_mm = 6 data = img.get_fdata() i, j, k = np.indices(data.shape).astype(float) dist = np.sqrt((i - center_voxel[0]) ** 2 + (j - center_voxel[1]) ** 2 + (k - center_voxel[2]) ** 2) sphere_mask = dist <= radius_mm注意这个距离计算是拿体素坐标直接算的,如果体素是各向同性的就没问题,但如果各向异性明显,需要先把体素间隔换算到毫米再算距离。常见的MNI模板大多是各向同性,我实际跑过的大多数数据不受影响,但养成先看header习惯总没坏处。
球形ROI最大的优点是结果可复现、对图谱边界误差不敏感。缺点是脑区边界可能不是规则球体,球体经常会包含临近脑回或白质纤维。建议在文章里明确写出“以peak坐标为中心、半径6mm的球形ROI”,方便别人重复。
3.3 手工ROI:小样本精细结构才值得这么干
手工绘制是最累但最精细的方法,适用于个体差异特别大的结构、或者需要精确切割某一解剖亚区的场景。比如你想单独划出海马头、体、尾三个亚区,图谱模板基本做不到,自动分割算法也不一定靠谱,这时只能在个体高分辨率T1上逐层勾勒。
工具推荐ITK-SNAP,免费开源,界面友好,支持多种半自动分割算法。绘制的流程大致是:先加载重采样后的个体T1,然后在冠状面或矢状面逐层描出目标结构,最后对三维体数据进行填充,输出为一个二值mask。
一个关键步骤是:手工ROI的mask必须最终转换到和统计分析相同的空间。如果你的统计模型是基于MNI空间的smwp1图像,那么需要把手绘mask从原生空间用DARTEL形变场配准到MNI空间;如果模型直接在原生空间做,那mask就不要随便搬到模板空间去。空间一致性没解决好,手绘精度再高也白搭。
手绘ROI并不适合大批量样本,一个被试一个结构动辄半小时到一小时,几十个人做下来非常痛苦。我自己的建议是:在课题启动前算好样本量,如果ROI数少、目标结构小且特殊,手工值得;如果涉及数十个脑区或大样本,老老实实用图谱模板。
3.4 怎么选择:我的实际判断标准
我判断用哪种ROI定义方式,基本看三个问题。第一,研究目标是探索性还是验证性?探索性怕漏检,优先用图谱覆盖多个脑区;验证性怕假阳性,优先用坐标球形或精确图谱。第二,目标结构是否在标准模板里稳定可识别?海马、杏仁核这类大核团图谱问题不大,但下丘脑、蓝斑这类小而边界模糊的核团,图谱模板容易错位,手工或专用分割工具更合适。第三,样本量是否支持手工?小样本精细结构,手工ROI能显著提升准确度;大样本追求可重复性和效率,自动图谱是更理性的选择。
没有绝对正确的ROI定义方法,只有适合当前研究问题的方案。写作时把定义流程写清楚,比纠结哪种“更高级”重要得多。
4. 实操:一条能把ROI体积和TIV同时拿到的流水线
4.1 用CAT12内置ROI工具做自动图谱提取
CAT12本身提供了ROI提取工具,不用自己写代码,适合大多数常规分析。操作路径大致是:在CAT12的主界面里进入Statistical Analysis模块,然后选择ROI Analyses,GUI会要求你选择分析的图像类型,比如VBM的灰质图或SBM的皮层厚度图。接着指定图谱或自定义mask,设定输出形式,运行后就能得到每个被试、每个ROI的数值表。
我用这个工具时习惯先把所有被试的mwp1或smwp1文件整理到一个清单里,按组别命名排序,然后在ROI工具里一次性选中所有文件。它输出的结果通常是一个文本文件,行对应被试,列对应ROI,前面几列可能是被试文件名、组别标志等。把这个文本保存为CSV或Excel格式,后续进统计软件就很方便。
需要注意,CAT12界面里有一些选项对应不同的处理策略,比如“体积”和“浓度”。体积模式适合报告ROI内灰质总体积,浓度模式更像ROI内平均灰质概率或者说灰质密度。选择之前先想好研究假设,别两个都跑一遍然后不知道该报哪个。
4.2 自定义mask和smwp1的“空间对齐”是成败关键
如果你不用内置图谱,而是用自己的二值mask,那空间对齐就是整个流程中最重要的环节。理想状态下,mask和smwp1图像必须满足三个条件:空间坐标系一致,都是在MNI空间;矩阵维度一致,比如都是91×109×91,或者重采样到相同大小;体素大小一致,比如都是2mm各向同性。
如果发现不满足,最省事的解决办法是使用Nilearn里的resample_to_img函数,把mask重采样到目标图像的空间,同时采用nearest插值方式保留二值属性:
import nibabel as nib from nilearn.image import resample_to_img mask = nib.load('ROI_manual.nii') ref = nib.load('smwp1_sub-01.nii') mask_rs = resample_to_img(mask, ref, interpolation='nearest') nib.save(mask_rs, 'ROI_manual_resampled.nii')重采样后用下面这段脚本,逐个被试提取ROI内灰质体积:
import nibabel as nib import numpy as np from glob import glob roi = nib.load('ROI_manual_resampled.nii').get_fdata().astype(bool) voxvol = None for path in sorted(glob('smwp1/*_smwp1.nii')): img = nib.load(path) data = img.get_fdata() if voxvol is None: voxvol = np.prod(img.header.get_zooms()[:3]) roi_volume = np.sum(data[roi]) * voxvol print(path.split('/')[-1], roi_volume)我特别建议在正式跑大样本前,先抽一个被试目测检查一下mask的边界是否贴合目标结构。用MRIcron或FSLeyes把mask叠加在平均图像或个体图像上,翻几个层面看看有没有明显偏到脑沟外。这一步很简单,但能省掉后面整个样本数据全部错位的风险。
4.3 从XML报告里批量读取TIV
TIV已经在每个被试的XML报告里了,现在只需要批量抽出来。如果是少量被试,手动打开XML搜索TIV也能接受;如果是几十上百个被试,建议直接写脚本遍历文件夹。MATLAB里可以用cat_io_xml,但字段路径存在版本差异,所以更通用的做法是直接用文本处理函数搜索关键字。
Python的xml解析可以这样:
import os import xml.etree.ElementTree as ET subdirs = ['sub-01', 'sub-02', 'sub-03'] results = [] for sd in subdirs: xml_path = os.path.join(sd, 'cat_sub-' + sd.split('-')[-1] + '_T1w.xml') tree = ET.parse(xml_path) root = tree.getroot() # 具体字段路径取决于版本,这里假设常见路径 for elem in root.iter(): if elem.tag == 'TIV': results.append((sd, float(elem.text))) break for r in results: print(r)如果xml结构变了,用root.iter()搜索TIV标签通常还是能匹配到。实在不行就用grep命令在Linux终端里先把所有XML里的TIV字段抓出来,再手动整理进表格。这种“土办法”虽然不优雅,但胜在稳定。
拿到TIV后,还要顺手检查数值是否在合理范围。成年人TIV大多在1200到1800ml,女性和体型较小的人群略低;如果某被试的TIV只有900ml或超过2000ml,优先怀疑分割失败,而不是直接拿来用。
4.4 把提取结果整理成可直接进入统计软件的表
到这一步,你手里应该有三类数据:每个被试的ROI体积值、TIV值、以及分组和人口学信息。真正进入统计之前,我建议把这些汇总成一张“宽表”,每行一个被试,每列一个变量。比如:subject_id、group、age、sex、TIV、ROI1_volume、ROI2_volume、ROI1_thickness等。
整理表的时候有两个容易忽略的细节。一是保持文件命名的可追溯性,ROI数值来源的mask和图像版本最好也记录在备注列里,否则半年后回来看数据会想不起来用的哪个模板。二是检查缺失值,CAT12预处理如果某个被试失败,对应XML里可能没有TIV,ROI表里也可能缺一行。提前统一处理缺失值,别让统计软件自动剔除你还没意识到的被试。
导出成CSV之后,我一般会用SPSS或R快速跑一遍描述性统计,看每组ROI体积的均值和标准差,再检查正态性和方差齐性。这里不需要做完整分析,但看看分布有助于发现异常值。如果某个被试的ROI体积比组均值偏离超过3倍标准差,多半是头动伪影或分割问题,回到QA报告里核查。
5. 现场排雷:ROI和TIV环节我踩过的典型问题
5.1 TIV异常的三类原因
TIV数值不正常,我遇到过的主要原因有三类。第一类是头骨去除不彻底,分割时把颅骨、头皮组织算了进去,TIV整体偏大。判断方法很简单:在CAT12的QA界面上查看分割结果,看颅骨区域是否残留灰质概率值。第二类是原始图像伪影,比如运动噪声、金属伪影导致分割算法失灵,TIV可能出现极端值。第三类是配准失败,DARTEL形变估计不收敛,导致体积估算偏差。
定位TIV异常最快的方法是回到CAT12生成的QA贴图或者质量参数。CAT12会在XML里写一堆质量指标,比如噪声、偏置、分辨率、空间失真等,不正常时会有明显预警。我处理数据时习惯在跑完预处理后,第一件事先看所有被试的QA评分,第二件事再检查TIV分布。两个指标都正常,ROI分析才有底气。
5.2 ROI加工时最容易犯的坐标错误
坐标错误是ROI分析里最普遍的坑。用坐标画球形ROI时,坐标来源必须是MNI空间或者与目标图像相同的空间。很多文献里报告的peak坐标是Talairach空间,和MNI空间并不完全等价,需要先做坐标转换,否则球画偏了整层分析都失效。
还有一种常见情况是手动把MRIcron里的坐标直接填进脚本,但MNI模板版本不一致导致实际位置错位。我在实际项目中就碰到过一个被试的海马ROI整体偏到侧脑室,原因是画球时用的模板是1mm的,而分析图像是2mm的,坐标边界没有按体素大小换算,导致偏移。最后只能把被试剔除,血的教训。
解决方法只有一个:画完ROI后,把mask叠在平均T1或目标图像上目测检查。别嫌这一步麻烦,它比任何代码检查都直观有效。
5.3 用“体积”还是“浓度”,背后是研究假设问题
很多新手在ROI提取时忽略“体积”和“浓度”的区别,其实这背后是两个不同的研究假设。灰质体积(volume)是把ROI内所有体素的灰质概率值乘以体素大小再累加,反映的是这个脑区总的灰质“量”;灰质浓度(concentration)是ROI内灰质概率的平均值,更多反映局部灰质占比或密度。
如果你关心的是“疾病导致某脑区组织丢失”,报告体积更合适;如果你关心的是“某脑区在局部范围内灰质相对比例的变化”,可能浓度更贴合。更关键的是,体积和浓度对TIV校正的敏感性不同:体积受头围影响更大,浓度相对稳健,但仍然建议报告TIV校正前后的结果。
我在实操里通常两个都算,然后以体积作为主要指标,以浓度作为稳健性检验。这样写作时可以把“结果在体积和浓度分析中一致”写进论文,审稿人有疑问也能拿出补充材料回应。
5.4 SBM的ROI提取不能照抄VBM
SBM的ROI提取有自己的一套逻辑,作为VBM习惯者很容易跑偏。VBM里用三维mask提取体素均值或体积,SBM则需要在皮层表面上定义ROI,然后用surface data tool去提取厚度或面积。CAT12里做表面ROI操作时,默认的自带图谱在首次使用可能需要联网下载,网络条件不好时容易卡住。
最稳妥的做法是在CAT12的surface界面下选择图谱后,先跑一个被试看看输出,确认图谱能正常标记到个体表面上,再批量跑。一次跑完大批量之后才发现图谱下载失败,非常浪费时间。我踩过这个坑后,现在处理任何SBM项目第一步就是提前把模板和图谱准备好。
另外,注意SBM里不同指标存放的文件扩展名不同,thickness和area的提取要用不同的surface数据,别都用同一张图。提取厚度用thickness文件,提取面积或体积用各自的面积图和体积图,这一步选错了结果肯定不对。
5.5 处理顺序和留档的一个小建议
最后分享一个个人习惯:从预处理一开始,我就维护一个总表,包含被试ID、预处理完成日期、QA评分、TIV、ROI提取版本和结果。每做完一个步骤就更新一次。这样做的好处是,半年后回看数据时,每个数字的来源都清清楚楚,不会因为换了一个模板版本就抓瞎。
ROI和TIV这两个看似小步骤,在实际分析流程里却能决定统计结果的可靠性。我自己的经验是,宁可多花一个小时做完空间核查和异常值检查,也不要急着把结果扔进统计软件。脑影像分析里,数据质量永远是第一位的。
这些坑我基本都踩过一遍,也希望看到这篇笔记的你,能在正式分析前就避开。如果后续有机会,我再把ROI结果进入统计模型之后的那部分内容,整理成新一篇笔记。