做术前评估和电极计划这几年,我一直在用FreeSurfer做脑区分割、用3D Slicer做电极定位与三维可视化。这两套工具单独用都不算难,难的是把它们串成一条能稳定复现的流程:FreeSurfer的aparc+aseg结果怎么进Slicer、术后CT和术前MRI怎么配准、电极触点坐标怎么落到具体的脑回和皮质下核团上。这篇文章把我实际项目里跑通的全流程拆开讲清楚,包括每一步的操作命令、参数选择和最容易翻车的地方,适合正在做SEEG电极计划、皮层电极定位或者功能影像分区的新手参考,也适合已经在用其中某一个工具、想把流程补齐的同行。
先说个整体印象:FreeSurfer和3D Slicer这俩组合,属于“医学图像分析界的瑞士军刀+瑞士手表”。FreeSurfer负责的是脑皮层表面重建和逐个体素的精细分区,它背后的算法做了几十年的优化,单是recon-all一个命令就能输出几十个文件,覆盖白质表面、软膜表面、皮层厚度、曲率、标准图谱配准结果等等,这些信息是后续脑区归属判断的基础。3D Slicer则擅长把多模态影像放在同一个三维空间里联动显示,术后CT带电极伪影、术前MRI带解剖细节、FreeSurfer带脑区分割,三者一融合,一个电极触点落在哪个脑回、距离某个皮层下核团多少毫米,一目了然。
在开始之前,我把整条流程划分成了四个阶段:环境准备、FreeSurfer脑区分割、Slicer电极重建、两套数据的坐标融合。下面按这条线逐步拆解。
1. 为什么我最终选择了FreeSurfer+Slicer这套组合方案
1.1 脑区分割环节的几个可选方案
脑区分割不是只有FreeSurfer一条路。我最早试过直接在Slicer里用Total Segmentation之类的工具跑,这类工具胜在快,几分钟就能出一个粗略的分割结果,但问题在于它用的模型偏向于语义分割,分区颗粒度和神经外科日常关心的大脑沟回并不完全对齐。做电极定位时,我们需要的是经过个体化皮层表面重建后的分区,而不仅仅是体素标签,因为触点位置经常需要沿着皮层表面量距离,没有表面网格这个操作很难做准确。
我又试过用SPM或CAT12,它们也能做分区和皮层厚度分析,但集成的工具链偏统计分析,对单个患者的可视化呈现不够直接。FreeSurfer则天生就是围绕“大脑个体差异”设计的:它会对每个被试的T1像做Talairach空间变换、灰度归一化、去颅骨、分割白质、重建灰白质交界面的三角网格,再做拓扑校正和平滑,最终在这套个体化表面上映射图谱标签。这套流程虽然费时间(一台普通工作站跑完recon-all通常需要4到8小时),但结果稳定,输出格式也很开放,后续转给Slicer几乎不需要额外改造。
1.2 电极定位环节为什么绕不开Slicer
电极定位这件事,本质上是“在三维空间里确定一系列点的坐标,并把这些点和解剖结构关联起来”。这个需求正好踩在3D Slicer的核心功能上:它原生支持DICOM和NIfTI,有灵活的Markups模块放点、连线、建管状模型,有General Registration模块做影像配准,还有Volume Rendering模块做三维体绘制。我可以在一个界面里同时看到CT上的电极高亮、MRI上的脑沟回形态、FreeSurfer给的脑区分割标签,不用来回切换软件。
另外,Slicer是NA-MIC社区长期维护的开源项目,扩展模块很多,其中就包括专门为SEEG电极设计的工作流:术后CT上电极伪影表现为一串明显的点状高亮,可以通过阈值分割加连通域分析半自动提取电极触点,再拟合成直线或曲线,输出每个触点的坐标。这套流程做下来,比起在二维断层图上手动数层厚再算坐标,效率和准确度完全是两个量级。
1.3 整体流程的设计思路
我把整个项目拆成三个子任务:
- 任务一:用FreeSurfer对术前T1像跑完整形态学分析,得到皮层表面网格和Desikan-Killiany图谱下的脑区分割,以及皮层下核团分割(包括海马、杏仁核、丘脑、壳核、尾状核等)。
- 任务二:用3D Slicer导入术后CT和术前MRI,做配准,把术后空间里的电极触点坐标变换到术前MRI空间。
- 任务三:把FreeSurfer输出的分割标签叠加到配准后的MRI上,计算每个电极触点到脑区分割标签的空间关系,输出最终的可视化和数据表。
这个顺序的好处是每个任务相对独立,可以随时停下检查中间结果:FreeSurfer跑得对不对,配准误差大不大,电极触点识别得准不准,都能在进入下一步之前验证。
2. FreeSurfer端:从原始T1到Desikan-Killiany图谱分区的完整链路
2.1 安装与License配置
FreeSurfer的安装和配置是老生常谈,但我在实际帮同事装的时候发现几个高频问题值得写一下。首先,FreeSurfer的下载页面写着支持Linux和macOS,不建议在Windows上原生跑,Windows用户最好是装WSL2或者直接用Linux服务器。其次,安装后必须设置环境变量,并且把License文件放到指定位置,否则recon-all会报错退出。
按照7.x版本的规范流程:
# 下载当前稳定版并解压到 /opt/freesurfer export FREESURFER_HOME=/opt/freesurfer export SUBJECTS_DIR=/your/project/subjects source $FREESURFER_HOME/SetUpFreeSurfer.shLicense的申请很简单,在FreeSurfer官网上用学术邮箱注册,会收到一个包含用户名和数字的license.txt,把它放到$FREESURFER_HOME/license.txt就行。注意版本号越新,对系统依赖库的要求越高,如果你在Ubuntu 18.04这类老系统上装7.4又不想升级,可以退回6.0版本,6.0的运行兼容性好一些,而且recon-all的参数几乎没变。过往版本在FreeSurfer官方Wiki的“Previous Versions”页面能找到下载入口,这一点对很多需要稳定复现研究流程的人来说很重要。
2.2 recon-all 每个核心阶段在干什么
FreeSurfer最常用的就是一条命令:
recon-all -i /path/to/T1.nii.gz -s patient01 -all这条命令会把整个处理链跑完,但它内部不是黑盒,我建议把主要阶段拆分理解,这样出问题时才知道是哪一步挂了。
- Motion Correction:如果输入的T1像有多个序列或者多次采集,这一步会把它们对齐平均。我的习惯是:送给FreeSurfer之前先在Slicer或ITK-SNAP里把T1像的原始方向整理好,尽量保持放射学标准方向(LAS或LPS),减少FreeSurfer内部转换时出问题的概率。
- Talairach Transform:计算一个从个体脑到Talairach标准空间的仿射变换矩阵,这一步是为后续图谱先验对齐做铺垫。
- Normalization:做灰度归一化,把不同扫描仪、不同场强带来的强度差异压平。某台机器出来的T1像偏暗、另一台偏亮,归一化后都能进入同一个处理通道。
- Skull Strip:去头皮和颅骨,这一步偶尔会“切过头”,尤其是脑萎缩比较明显的老年患者或者有占位效应的大脑。结果可以单独检查:
mri/ brainmask.mgz如果边缘明显缺损,可以用recon-all -skullstrip -s patient01配合-brainmask参数人工修一下再继续。 - White Matter Segmentation:在归一化和去颅骨后的脑组织里分割白质,这是构建表面网格的基础,分错会导致后面白质表面跟着错。
- Surface Generation:用三角形网格把灰白质界面和软膜表面分别建出来,输出为
surf/lh.white、surf/rh.white、surf/lh.pial、surf/rh.pial。这一阶段是最容易出错的,我也遇到过网格自交的情况,多半是灰白质分割时局部区域被异常信号污染。 - Surface Smoothing 和 Inflation:对网格做平滑并膨胀,为后续折叠(folding)和拓扑校正做准备。
- Parcellation:把Desikan-Killiany图谱标签按个体化的沟回形态映射到皮层表面网格上,结果保存在
mri/aparc+aseg.mgz中,这也是我们最关心的输出之一。
需要明确的是,aparc+aseg.mgz把皮层分区分成了两类信息来源:皮层下区域来自体素水平的分割(aseg部分),皮层区域则是基于表面网格的标签映射(aparc部分),最后两者被合并到一个体积文件里,方便我们这类后续流程直接用,不用自己再去拼接。
2.3 分割结果的质量检查与常见失败处理
跑完recon-all不要急着拿结果去配准,质量控制必不可少。我的检查套路是:
freeview -v $SUBJECTS_DIR/patient01/mri/T1.mgz \ $SUBJECTS_DIR/patient01/mri/aparc+aseg.mgz:colormap=lut \ -f $SUBJECTS_DIR/patient01/surf/lh.white:edgecolor=blue \ $SUBJECTS_DIR/patient01/surf/lh.pial:edgecolor=red \ $SUBJECTS_DIR/patient01/surf/rh.white:edgecolor=blue \ $SUBJECTS_DIR/patient01/surf/rh.pial:edgecolor=red打开后重点看三件事:白质表面和灰质表面是否贴合灰白质边界,有没有明显的穿出或凹陷;半脑的脑沟回形态是否自然平滑,有没有网格自交导致的“打结”区域;皮质下结构分割有没有出现一侧缺失或者明显的体积异常。
如果发现局部表面穿出,可以回到白质分割层面修,用recon-all -s patient01 -white前加一个-wmmask选项手动编辑白质mask,这类操作需要一些经验。我的实际经验是:大概率问题在T1像本身,运动伪影、金属伪影(比如有牙科种植体)、前后处理流程造成的方向错乱,都会导致表面重建失败。遇到这种情况不要硬修,先看看原始图像质量,必要时重新采集或者选质量更好的序列作为输入。
3. 3D Slicer端:电极重建与空间定位的实操细节
3.1 术前影像导入与组织分割
Slicer的启动界面很简洁,但功能入口分散,刚上手会有点懵。我的习惯是把所有数据放在同一个文件夹里,用Add Data一次性加载。Load后先在Slice面板里确认三个正交断面的方向标记,这个动作虽然小但很重要,因为后续所有坐标转换都建立在方向正确的前提上。
如果只是导入术前MRI,直接拖进Slicer即可,可以看到Volume节点创建成功。接下来我会顺手操作两个步骤:
- 用Volume Rendering模块调一个窗宽窗位合适的预设观察脑沟回和灰白质边界,给后续人工核对电极触点提供一个立体参考。
- 把FreeSurcer生成的aparc+aseg.mgz也拖进来,作为分割标签Volume。如果拖进来是黑的,多半是ColorTable和显示范围的问题,右键进入Display属性,把LUT换成FreeSurfer的
FSL Color Table,阈值范围从0开到200以上,就可以看到彩色分区了。
我这里反复强调“先把分割结果加载进Slicer看一眼”,是因为很多人跑完FreeSurfer就直接进入电极定位,最后发现坐标系根本没对齐,白忙一场。
3.2 术后CT上的电极触点识别与半自动提取
电极触点在CT里呈现为亮白色小圆点,因为金属产生的X射线衰减极高,和周围骨组织的CT值不在一个频道。这里有两种做法:
纯手动放置:在Markups模块新建一个Fiducial列表,在横断位上逐层寻找高亮触点,一个触点一个点。这种方式在触点总数几百个的时候工作量很大,而且容易数漏,我不推荐在手术量大的场景下使用。
半自动提取:先用Threshold工具提取CT中所有大于某个阈值的体素,阈值范围通常设置在800~1500 HU附近,具体要看扫描参数,我习惯用“Split Islands into Objects”把连通域拆开,这样能快速获得候选触点。之后通过检查每个候选物体的体积和中心点在三个断面的位置,剔除骨性尖刺或噪声,剩下的就是触点。
从这些候选点确定下来之后,我可以把它们添加到Markups里,并在SEEG专用扩展模块里按电极命名和排序。这一步也是后面计算脑区归属的基础,触点顺序错了,后续所有通道标签都跟着错。
3.3 电极线的三维可视化与模型导出
当触点坐标整理好之后,通常还要顺带把电极的方向线或者管状模型建出来,这样在三维视图里看到的就不只是一串点,而是带走向的立体结构。Slicer里可以用Markups模块的Curve功能把同一根电极上的触点连起来,再通过扩展模块生成管状模型,给每根电极分配一个颜色。
因为立体的电极路径比单点更直观,我在给同事解释解剖位置时基本都靠这个三维视图:管状模型穿过某个脑回,触点在皮层表面底下1厘米处,层层叠叠的关系马上就能看出来。
4. 坐标统一:FreeSurfer结果与Slicer场景融合的关键一步
4.1 FreeSurfer的输出到底用的什么坐标
这是整条流程里最容易出问题的一个环节。FreeSurfer内部处理时使用的是LPS坐标方向,而3D Slicer内部统一使用RAS方向显示和计算。不过,由于NIfTI和MGH/MGZ格式的header里都带有方向余弦信息,Slicer在加载这类文件时会自动进行方向调整,所以很多情况下直接拖入也能对上。但这里面有个坑:如果你把FreeSurfer输出的mgz先转成了nii,再用其他工具做了一次resample或者cropping,方向信息和原点信息很可能被改掉。
我的建议是:不要随便对FreeSurfer输出做“目的不明”的空间变换,保持原始几何信息是最稳妥的。如果确实需要在FreeSurfer和Slicer之间传递数据,直接传递mgz文件给Slicer;如果需要转成nii供其他程序使用,用FreeSurfer自带的转换工具:
mri_convert -i aparc+aseg.mgz -o aparc+aseg.nii.gz --in_orientation LPI --out_orientation RAS转完以后,务必在Slicer里加载一个T1像和这个分割标签,用Volume Information检查IJKToRAS矩阵是否和T1像相差一个合理的平移量(不该有大幅旋转),再做一次目测确认。
4.2 术后CT与术前MRI配准的两个务实方案
电极触点坐标是在术后CT空间里确定的,脑区分割是在术前MRI空间里做完的,要让两者可比,必须做配准。这个配准是刚性的还是非刚性的,取决于临床场景:
- 如果患者是进行SEEG立体定向植入,头部用头架固定,那么术前术后头部位置虽然变了,但脑组织本身没有明显位移,刚性配准就够。
- 如果术中打开了硬膜、有脑脊液流失或者脑移位,就需要考虑非刚性配准,这时候要把形变场导出来,电极坐标也要跟着形变场变换,复杂度上一个台阶。
Slicer的General Registration(BRAINS)模块里,我会把术后CT作为Moving Volume,把术前MRI(已经带FreeSurfer分割的那个空间)作为Fixed Volume。先选刚体(Rigid)跑一遍,再视情况用BSpline跑一遍精细调整。配准完成后,把得到的Transform应用到Markups节点上,这一步千万别漏,因为很多时候坐标已经算出来了,却忘了把Transform应用进去,后面导出坐标表仍然是术后空间的,错得莫名其妙。
配准质量怎么判断?我通常在Slicer里把两个Volume用半透明叠加方式显示,或者用Horus Viewer这类模块的Checkerboard功能扫几层断面,看脑室边缘、颅骨内板、眼眶这些高对比结构是否重叠。如果偏差在1~2毫米以内,对电极触点级别的定位基本可接受。
4.3 验证对齐效果的实用手段
一个高效方法是把FreeSurfer的脑表面网格也加载到Slicer里。FreeSurfer的surf/lh.pial是三角网格文件,Slicer可以通过Add Data直接读取,但格式上不一定能自动识别,这时候可以先用FreeSurfer转成Slicer更顺手的格式。我更常用的方式是:直接加载分割标签Volume,然后把FreeSurfer T1.mgz的Volume Rendering打开,看三维脑表面和分割标签的边界是否吻合。
如果出现了分割标签整体往右侧偏移而灰质表面正常的情况,多半是FreeSurfer那步的几何变换没有保持全流程一致性。这种偏一两毫米的误差,在体素级别堆叠的流程里并不罕见,但在电极定位这种亚毫米级别的场景里是致命的。所以务必在配准完、应用Transform后,立即人工复核至少三个断面。
5. 实战中那些文档里不会写的坑
5.1 版本兼容性与过往版本获取
我在给不同环境搭这套流程时,发现版本匹配最让人头疼。FreeSurfer 7.4在较新Ubuntu上的兼容性不错,但对GPU加速库有隐式依赖,如果你用了某些精简版Linux发行版,可能跑着跑着因为缺少libpng或libGL相关组件挂掉。Slicer这边,4.11以上的版本对python脚本生态更友好,但如果要装一些依赖特定版本的扩展模块(例如某些和FreeSurfer对接的扩展),还是建议先查一下模块要求的Slicer版本。
前面提到过FreeSurfer官网能下载过往版本,这个链接在重装环境、复现旧研究时非常关键:新版本有时候会在白质分割拓扑修正的细节上略有调整,直接导致同一批数据不同版本跑出来的分区结果在边界上有局部差异。做临床回顾性研究时,最好锁定一个版本不动。
5.2 数据预处理中的细节陷阱
- 方向标识错误:这是最隐蔽的问题。DICOM转NIfTI时,dcm2niix导出的方向一般是符合DICOM标签的,但如果你的数据来源于某个自研软件导出的nii,方向cosine可能被写成identity矩阵,结果整个脑就是镜像或翻转的。FreeSurfer和Slicer对这类数据不会主动报错,只会“看起来怪怪的”。
- 运动伪影:FreeSurfer对运动伪影的容忍度一般,轻微运动会导致皮层表面出现不自然的凹凸。我处理这类数据时,会先看原始T1的矢状位,如果脑表面像波浪一样抖,就考虑用ANTs的稳健平均工具把多次采集的T1先配准做平均。
- 整体偏头:不用在预处理时做“转正”操作,FreeSurfer的Talairach变换和Slicer的坐标系统都自带方向处理,强行转正反而可能弄坏坐标。
另外提醒一句:电极植入后,电极本身在CT里能看得很清楚,但在MRI里往往表现为条状信号缺失区,千万别把这种信号缺失当成病变。做脑区归属判断时,应该以CT上确定的触点坐标为准,回填到配准后的MRI上,不要直接在MRI上肉眼估触点位置。
5.3 结果报告和临床沟通的经验
脑区分割和电极定位的最后产出,不只是给算法看的,最终要能在临床讨论中直接使用。我的做法是每个patient目录下保存三样东西:
- 坐标表:电极名称、触点编号、触点中心在术前MRI空间的RAS坐标、所在Desikan-Killiany脑区标签、到最近灰质表面的正切距离,导成CSV。
- 三维场景截图:从Slicer里导出几个固定视角的视图,最好带上同一视角的三维脑分割渲染,让医生一眼看出电极和脑区的关系。
- Slicer场景文件:把整个场景保存成mrb文件,存到PACS系统旁边或者局域网共享盘,任何人打开Slicer就能继续旋转、测量,不需要重新搭建流程。
这里还涉及一个计算距离的问题:Slicer的Markups模块有测量距离的功能,但如果触点数量大、需要批量计算和每个脑区的归属,我一般会把触点和分割标签都导出成NIfTI,再用Python(SimpleITK)做逐点检查:取触点所在体素的分割标签值,用FreeSurfer的FreeSurferColorLUT.txt映射到解剖名称。这一步写个小脚本就能跑完,比在Slicer里手工逐一查标签值高效得多。
6. 最后分享两个让我省时间的小习惯
第一个习惯是给所有文件起名时直接带坐标空间标识,比如T1_pre.nii.gz、CT_post.nii.gz、aparc+aseg_pre-in-MRIspace.nii.gz,这样过了两三个月再回头看数据,不用打开头文件猜这是哪个空间的东西。第二个习惯是每跑完一步就保存一个Slicer场景文件,版本号递增,因为配准参数调了好几次但没注意记,是最容易造成“当时调好了现在复现不出来”的情况。
这套FreeSurfer加3D Slicer的流程,我用了几十例数据,整体跑下来非常稳。前期最花时间的是把FreeSurfer跑完和配准验证做扎实,一旦这两关过了,后续的电极重建和脑区归属就是流程化操作,不会出现那种“做完了才发现全错了”的情况。如果是刚开始接触,建议先用公开数据集(比如HCP或IXI里的一例T1和模拟植入电极)把整条链路跑通,再上手真实患者数据,试错成本会低很多。