fMRI原始数据分割全流程:从DICOM到可分析数据
发布时间:2026/10/5 11:43:21 锦皓数字建站

做fMRI数据处理这些年我最想跟刚入门的同行强调的一件事是真正容易让人崩溃的环节往往不在最后的统计模型而在最原始的第一步——fMRI原始数据分割。我说的“分割”并不是某个单一操作而是从扫描仪倒出那一堆看起来乱糟糟的DICOM文件开始到把数据整理成能进预处理流程的规范格式为止的所有工作。这一步做不好后面不管是用SPM、FSL还是fMRIPrep结果都不可信。这篇笔记是我自己反复踩坑后整理出的完整流程覆盖DICOM序列分拣、格式转换、元数据检查、slice timing设置、头动校正以及质量控制。无论你是刚拿到第一批扫描数据的研一新生还是想把手动流程改成半自动流水线的老手这套思路都能直接搬过去用能省下大量试错时间。1. 核心概念fMRI原始数据分割到底在做什么1.1 一次扫描后你手里真正握着的数据去扫描中心考完数据你会拿到一个或者几个文件夹里面装着数百到数千个DICOM文件。这些文件绝不是单纯的fMRI功能像而是一次完整扫描会话里所有序列的混合产物。以常规的BOLD实验为例扫描员一般会按这样一个顺序跑完整个session先打一个low-dose的定位像localizer再采一个高分辨率的T1结构像然后是任务态或静息态BOLD序列如果研究者还设计了场图矫正那还会有一个或多组fieldmap序列甚至中间可能插入DTI。这些序列都会被扫描仪软件逐层保存成独立的DICOM文件。问题在于不同厂商、不同型号的机器甚至同一个中心不同操作员的习惯导出的文件夹组织方式都完全不同。西门子通常按SeriesNumber建子目录GE常见是一长串以I.开头的文件堆在一起飞利浦的命名规则也是自成一派。面对几百个文件如果不先做序列级的分割和识别你根本不知道哪一组是BOLD、哪一组是T1、哪一组只是定位像。我见过有的同学把localizer当成功能像跑完整条pipeline也见过把T1和BOLD倒过来当输入最后统计结果连激活都出不来还不知道问题出在哪儿。所以第一步必须建立清晰的意识fMRI原始数据分割的第一层任务就是把混在一起的不同序列精确地分拣出来并搞清楚每一组数据的采集参数。1.2 “分割”这个词在这个阶段至少有三层含义很多教程直接用“数据转换”这个词我觉得容易把问题说小了。“分割”这件事从原始数据到可分析数据其实贯穿了三个不同层次每一层都值得单独理解。第一层是序列级别的分割也就是刚才说的把DICOM按SeriesDescription、SeriesNumber、ProtocolName分门别类把T1、BOLD、fieldmap、DTI各归各的文件夹。这个动作通常靠dcm2niix这类工具自动完成也可以自己写脚本按头信息区分。第二层是时间维度上的组织与分割。BOLD序列的本质是一个4D数据空间上是一个个slice时间上是一个个volume。原始DICOM保存时经常是按slice逐一存放的你需要把它们正确拼接成“一个volume内若干slice、一个run内若干volume”的结构并保留每段时间信息。dcm2niix会自动完成这一步但你需要理解它做了什么——比如一个48层的run采集了200个TR那最终输出的nii.gz应该包含48×200个slice组织成(x, y, z48, t200)的4D数组。如果这里组织错了后续所有时间相关的分析都是错的。第三层是预处理中的组织分割也就是把T1像分成灰质、白质、脑脊液三个空间或者把BOLD数据按组织信号、生理噪声、头动伪影分开。这一层虽然发生在后面的配准与标准化阶段但如果没有前两层的“分割”打底第三层再好的算法也救不回来。把这三层想清楚你就理解了为什么“原始数据分割”不是一件可以草草带过的小事。1.3 为什么这一步决定了整个项目的成败原因是连锁式的。DICOM序列分拣错了BOLD数据可能混入localizer或fieldmap的slice时间点数直接不对转换时元数据没有保留好TR、slice timing、相位编码方向全都丢失后面slice timing校正无从谈起头动过大的问题如果不在这一步早期发现等到组分析时才暴露整个被试做废返工成本极高。我自己的体会是fMRI数据分析里最贵的是“早期质量控制”。在原始数据阶段花一小时把数据体检完能省下后面几天甚至几周的返工时间。而这一步的回报率也是全流程里最高的——你不需要懂复杂的数学只需要愿意认真对待文件、参数和检查报告就够了。这篇笔记后面的内容就是围绕这个目标来展开的。2. 工具选型从DICOM到NIfTI我用这套方案2.1 转换工具dcm2niix为什么是首选目前的DICOM转NIfTI工具不少但我强烈建议优先用dcm2niix。它是神经影像领域的事实标准转换器跨平台命令行效率高能自动识别绝大多数序列类型并且会同时生成一个JSON sidecar文件把TR、TE、FlipAngle、SliceTiming、PhaseEncodingDirection这些关键采集参数全部记录下来。这个JSON文件对后续预处理的价值怎么强调都不为过没有它slice timing、fieldmap矫正都要靠手工翻DICOM头信息硬凑。下面这张表是我过去用过的几种转换方案的对比方便你结合自己的场景判断。工具优点缺点适用场景dcm2niix快速、自动识别序列、输出NIfTIJSON、跨平台参数多需要花点时间熟悉绝大多数场景首选SPM DICOM Import有GUI、能直接按SPM习惯导入速度一般、元数据保留有限只想在MATLAB里快速处理少量数据MRIcroGL自带转换图形化、上手快不支持复杂批量、参数项少单次数据快速预览、应急厂商自带工具与机器绑定、格式最原始通常不输出标准BIDS结构需要原始信息时偶尔用我现在的习惯是只要数据是DICOM格式就直接用dcm2niix一条命令搞定顺便让它生成BIDS风格的JSON。如果数据已经是NIfTI但没有JSON我会额外用脚本补读DICOM头信息尽量补齐元数据。2.2 预处理工具链SPM、FSL还是fMRIPrep转换完成之后数据要进入预处理阶段这时候要选择主流程工具链。SPM好用在与MATLAB深度绑定GUI和批处理都成熟适合传统的voxel-level分析和灵活的分组模型建模。FSL的命令行工具非常强大FEAT、MELODIC、FLIRT、FNIRT这些模块在配准和ICA清理方面口碑很好。FreeSurfer则长于皮层重建和表面分析。但如果你不想被这些工具的差异困扰还有一个现代方案是fMRIPrep。它把dcm2niix、ANTs、FSL、SPM、FreeSurfer等工具包在一个自动化pipeline里输入BIDS格式数据自动完成slice timing、头动校正、配准、分割、空间标准化还输出一份html质量控制报告以及一份可以直接用于统计建模的confounds文件。它的最大优势是可复现性强、省心但代价是运行时间较长同时对计算资源要求高。我的建议很直接新手想快速看到结果、不想被参数折磨从fMRIPrep开始如果做纵向项目、需要精细控制每个预处理步骤那就踏踏实实走SPM或FSL的一条龙流程。两条路线并不冲突很多实验室是fMRIPrep做批量预处理再用SPM做统计建模。2.3 我常用的工作流组合方式分享一套我自己在用的组合先用dcm2niix做DICOM转换和BIDS整理然后用MRIQC给每个run打一个图像质量分再用fMRIPrep做预处理并生成confounds最后把confounds和预处理好的数据交给SPM做一阶和二阶统计。这样做的好处是每一步都有明确的输入输出出了问题可以快速定位。如果项目对某些细节有特别要求——比如需要保留某个特定的slice timing顺序、需要自定义平滑核或者需要手动处理头动过大的run——我会放弃fMRIPrep改用SPM12的batch脚本走传统的“slice timing → realign → coregister → segment → normalize → smooth”流程。后面的实操部分我就以这个传统流程为主线方便你用SPM复现同时也会指出fMRIPrep里对应的自动处理方式。3. 实操过程一套可以直接照做的分割与预处理流程3.1 先用BIDS把源数据理清楚准备阶段的第一件事是建立清晰的目录结构。神经影像社区的标准叫BIDSBrain Imaging Data Structure它是目前普遍接受的fMRI数据组织规范。即便是单次扫描的小项目我也建议至少参考BIDS的组织方式因为后续工具fMRIPrep、MRIQC、许多分析脚本默认按这个格式读取数据。下面是一个最小化的BIDS结构示例对应一名被试的一次session包含T1和两个run的静息态BOLDrawdata/ ├── sub-01/ │ ├── anat/ │ │ ├── sub-01_T1w.nii.gz │ │ └── sub-01_T1w.json │ └── func/ │ ├── sub-01_task-rest_run-1_bold.nii.gz │ ├── sub-01_task-rest_run-1_bold.json │ ├── sub-01_task-rest_run-2_bold.nii.gz │ └── sub-01_task-rest_run-2_bold.json在转换之前建议先把原始DICOM拷到一个独立目录里比如sourcedata/sub-01/不要直接对原始文件夹动手。原始数据是唯一拿得出手的资产任何转换都是可重复过程绝对不能破坏原件。实际操作中我会先用MRIcroGL或ITK-SNAP打开几个DICOM文件确认序列内容和扫描方向顺便看一眼是否存在重建伪影。这个动作花不了几分钟但能让你在后续批量处理前就对数据类型心里有数。3.2 dcm2niix批量转换参数怎么设接下来进入核心的转换环节。假设你的原始DICOM放在/data/sourcedata/sub-01/想输出到/data/rawdata/sub-01/func/文件名直接带上任务和run信息用下面这条命令dcm2niix \ -o /data/rawdata/sub-01/func \ -f sub-01_task-rest_run-1_%p \ -z y \ -b y \ -v n \ /data/sourcedata/sub-01/参数含义拆开说-o指定输出目录-f指定文件名模式%p表示把序列描述插入文件名这样转换出来的文件会像sub-01_task-rest_run-1_ep2d_bold.nii.gz后面你可以手动把_ep2d_bold改成_bold或者继续保留也可以只要后续脚本能对上-z y表示压缩输出为nii.gz省磁盘-b y是生成BIDS sidecar JSON文件这一步非常重要-v n让输出信息简单一些避免刷屏。dcm2niix在转换时会自动把连续采样的slice合并成volume把多个volume合并成4D文件同时生成对应的JSON。你可能会在输出目录里看到不止一组NIfTI因为一个文件夹下通常还有T1、localizer等序列它们会被各自转换出来。此时需要根据文件名把T1挪到anat/目录把BOLD留在func/目录。还有一个小技巧如果你的扫描协议里同一个run被不小心拆成了两个DICOM系列dcm2niix可能会分别输出两组4D文件。这时候先不要急着拼接先检查它们的SeriesNumber和采集时间用-m y这样的合并参数有时可以让dcm2niix自动合并同一个扫描下的多个series但请记得合并前务必确认采集顺序连续、头动情况没有断裂。3.3 转换后必须做的元数据体检转换完成不是终点必须做一次细致的“体检”。第一步是打开JSON文件核对几个关键字段。用Python比较方便import json meta json.load(open(sub-01_task-rest_run-1_bold.json)) print(meta.get(RepetitionTime)) # 单位通常为秒 print(meta.get(EchoTime)) # 单位通常为秒 print(meta.get(PhaseEncodingDirection)) # i, j, k等 print(meta.get(SliceTiming)) # slice timing顺序 print(meta.get(ImageType))同时用nibabel检查nii.gz的shape和体素大小import nibabel as nib img nib.load(sub-01_task-rest_run-1_bold.nii.gz) print(img.shape) # 应该是 (x, y, z, n_volumes) print(img.header.get_zooms())这几个字段里RepetitionTimeTR和SliceTiming尤其关键。TR直接决定时间层校正和统计模型里的采样间隔SliceTiming是slice timing校正里最核心的输入之一。如果这两个值缺失或明显异常后面处理前必须找到原因。还有一个容易被忽略的点确认4D数据的volume数量是否正确。比如你的实验设定了200个TR那n_volumes应该是200。如果得到的是199、201或者体积突然少了一半说明DICOM文件里可能混入了其他序列或者有文件缺失一定要回到原始数据里排查。3.4 Slice Timing操作以及最容易错的slice order时间层校正slice timing解决的是这样一个问题BOLD序列不是一次性采集整个脑的而是一个slice接一个slice地扫描所以同一个volume内不同slice的采集时刻并不相同。如果不对这个时间差做校正后续对BOLD时间序列的建模就会出现系统性偏差。这一步在SPM里位于预处理流程的第一步在fMRIPrep里也会自动执行。Slice timing最需要你去确认的是slice order。不同厂商、不同序列的采集顺序不一样常见的有按顺序逐层采集sequential、隔层跳采interleaved以及西门子还有的方向交错采集。获取slice order最可靠的办法是看dcm2niix生成的JSON里的SliceTiming字段。如果这个字段存在你可以根据数值直接推导采集顺序如果字段缺失或者数据来自multiband序列就要格外谨慎。在SPM12里设置slice timing的批处理大致是这个样子matlabbatch{1}.spm.temporal.st.scans { {/data/rawdata/sub-01/func/sub-01_task-rest_run-1_bold.nii.gz,1} }; matlabbatch{1}.spm.temporal.st.nslices 48; matlabbatch{1}.spm.temporal.st.tr 2.0; matlabbatch{1}.spm.temporal.st.ta 2.0 - 2.0/48; matlabbatch{1}.spm.temporal.st.so [1:2:48 2:2:48]; matlabbatch{1}.spm.temporal.st.refslice 48;这里的逻辑要掰开讲一下。nslices是每一卷的slice数tr是重复时间ta指的是volume内部slice采集的时间跨度计算公式是TA TR - TR / nslices。so是slice order上面这个例子假设先采奇数层再采偶数层。refslice是参考层通常选在时间序列的中点附近这样校正后所有slice的信号都对齐到中间时刻避免头尾误差放大。对于multibandSMS加速序列情况比较特殊。由于它同时激发多个sliceSliceTiming里会出现多个slice共享同一采集时刻的情况这时候传统SPM slice timing并不适合。如果TR非常短比如低于1.5秒很多研究组会直接跳过slice timing步骤因为时间差异已经小到不影响结果。遇到这种序列建议在论文里明确写出处理方法方便审稿人理解你的思路。3.5 配准、分割与空间标准化Slice timing和头动校正做完后数据仍然处于原始空间。为了让不同被试、不同session的数据可以放在一起做组分析需要把它们配准到一个标准空间其中最常用的是MNI空间。这一步里就包含了我前面提到的第三层“分割”——组织分割。SPM的做法是先用T1结构像做统一分割Segment生成灰质、白质、脑脊液的概率图同时得到一个从个体空间到MNI空间的形变场然后用这个形变场把功能像从个体空间标准化到MNI空间。这样处理的好处是分割和配准共用一步的结果减少了误差累积。在操作上SPM12的流程通常是先coregister将功能像配准到T1像再segment分割T1最后normalise把分割得到的参数应用到所有功能像和T1像。中间每一步的结果最好都可视化检查一下功能像和T1像是否对齐分割结果是否覆盖了脑组织MNI空间下的大脑是否看起来正常。fMRIPrep对这个过程的处理是自动的它默认用ANTs的模板做标准化并在报告里展示配准质量。即便你用fMRIPrep也建议抽出报告里的T1w预处理页看一眼分割和配准是否成功。毕竟自动工具也会在个别数据上失败及早发现总比最后输出一堆无意义激活强。3.6 质量控制头动报告怎么看头动校正是fMRI预处理里的标配步骤但校正完不等于万事大吉你必须检查头动参数到底大不大。SPM里realign之后会生成rp_*.txt文件每行6个数值对应每个volume在x、y、z方向上的平移和旋转。用Python快速分析一下import numpy as np rp np.loadtxt(rp_sub-01_task-rest_run-1_bold.txt) trans np.abs(rp[:, :3]).max(axis1) rot_deg np.abs(rp[:, 3:]).max(axis1) * 180 / np.pi print(f最大平移: {trans.max():.2f} mm) print(f最大旋转: {rot_deg.max():.2f} deg) print(f平均平移: {trans.mean():.2f} mm)经验阈值方面单次run内最大平移超过3毫米、最大旋转超过3度我会比较警惕考虑是否把该run标记为质量不佳。更严格的研究可以放宽到1毫米和1度但日常工作中3毫米是常见分界线。除了最大值还要看时间序列上有没有突然的尖峰也就是某个单帧位移特别大。计算framewise displacementFD的标准做法是Power等人2012年提出的方法将平移和旋转参数做加权后计算相邻帧之间的位移量。FD超过0.5毫米的volume在统计建模时通常需要用scrubbing方法剔除或加回归元。如果头动过大的volume数量达到总卷数的20%到30%我会考虑直接把该被试的该run排除掉因为靠回归已经很难挽回信息损失了。这一步的判断标准最好在项目开始前写清楚避免事后纠结。4. 常见问题与排查技巧实录4.1 DICOM序列太多fMRI到底藏在哪个文件夹这是初学者最常问的问题。不同厂商的序列命名差异很大但也有一些规律可循厂商DICOM文件常见组织方式BOLD序列常见命名备注西门子按SeriesNumber建子目录如001/、002/ep2d_bold、epfid2d、cmrr_mbep2d_bold出现cmrr通常表示multiband序列GE常见一堆I.开头文件BOLD、EPI、BASIC名称可能不直观需要看SeriesDescription飞利浦.dcm后缀或无后缀文件fMRI、BOLD、EPI新版用.dcm后缀的相对多拿到一包不认识的文件最快的方法是直接读DICOM头信息。用pydicom可以写一个小脚本批量打印关键字段import pydicom from pathlib import Path path Path(/data/sourcedata/sub-01) for f in sorted(path.glob(*))[:20]: ds pydicom.dcmread(f, stop_before_pixelsTrue) print(ds.SeriesNumber, ds.SeriesDescription, ds.ProtocolName, ds.ImageType)看到ImageType为[ORIGINAL, PRIMARY, M, ND, MOSAIC]这类包含MOSAIC的通常就是西门子的BOLD序列。GE和飞利浦的BOLD序列ImageType表现略有不同但结合SeriesDescription和EchoTime基本都能判断出来。批量脚本可以一次把序列信息全部列出来帮你快速定位fMRI数据所在的series。4.2 转换完方向不对坐标轴像被拧过dcm2niix转换后偶尔会出现方向问题比如NIfTI的qform和sform不一致导致图像在软件里显示朝左或朝上翻转。这个问题出现时FSLeyes看起来可能一切正常但后续配准到MNI空间后坐标却是错的组分析结果自然不对。排查方法很简单把转换后的BOLD和T1放到同一个软件里检查冠状位、轴状位、矢状位三个方向上的左右关系是否一致。如果发现方向不对先用FSL的fslreorient2std做一次重定向fslreorient2std input.nii.gz output.nii.gz这个命令会根据NIfTI头部信息把图像重新排列到标准方向。如果重定向后依然有问题那就要回到原始DICOM检查是否在转换时丢失了位置信息必要时重建一遍转换流程。要注意的是重定向之后还需要同时更新对应的JSON和头动参数文件否则后续处理会对不上。还有一个实战经验不要轻易手动画任意方向翻转。看似只改了一个轴但坐标和体素顺序的联动关系很容易弄错结果比原来更糟。宁可多花几分钟重新转换也别进行不确定的几何变换。4.3 时间点数对不上TR明显不对转换完发现volume数不对这是排查最花时间的一类问题。我遇到过几种情况一是原始DICOM文件夹里混入了localizer的多个layer导致dcm2niix把不该合并的序列并了进来二是在复制文件时漏掉了部分sliceDICOM文件本身不完整三是同一run被扫描员拆成了两个SeriesNumberdcm2niix生成了两组4D文件需要手动合并。解决思路很朴素先数文件。用脚本统计DICOM文件总数和预期的理论数量对比。比如一个48层、200 TR的run应该包含9600个DICOM文件不考虑重复。如果对不上优先检查这个run的文件夹里是否有localizer或fieldmap的slice混入。如果文件数量是对的但volume数不对多半是dcm2niix把连续文件识别成了两个series这时要检查SeriesNumber和AcquisitionTime判断是否需要合并。TR不对的问题通常是因为JSON里的RepetitionTime单位或者数值写错了或者原始DICOM头里的TR就不是你实验设计里写的那个值。建议以dcm2niix生成的JSON为准不要凭记忆填写TR因为操作员改过参数的情况时有发生。4.4 头动过大怎么办头动问题是fMRI里最现实的问题每个实操过的人都躲不开。发现某个run的最大平移超过3毫米或FD超过阈值后先不要急着排除被试而是按这个顺序处理先确认头动是不是集中在某个时间段比如某一段任务特别难、被试忍不住动了一下。如果只是局部尖峰可以用scrubbing来解决在统计建模时把FD超过阈值的volume单独加回归元或者直接去掉这些volume。如果头动在一整段里都很大而且FD超过0.5毫米的volume占到20%以上那就别硬救了。这个run的数据质量已经很难通过统计手段挽回尽早标记为排除更明智。比较尴尬的情况是只有一个run但头动很大同时又没有备用数据这时候只能如实报告并考虑用ICA-AROMA一种基于ICA的噪声去除工具来尝试分离头动伪影但结果解释要谨慎不能把它当成灵丹妙药。整个处理过程要留痕哪些被试、哪个run因为什么原因被排除阈值是多少用了什么软件版本。科研结果的可信度很大程度上取决于这些决策的透明度别等到审稿人问了才回去翻记录。4.5 多session / 多run的批量处理多session或多run的批量处理主思路是写循环脚本但有几个地方特别容易出错。第一每个session的元数据要逐一检查同一名被试在不同session间的TR、slice order、相位编码方向必须一致不一致的话后续合并就是一锅粥。第二文件命名要保证排序稳定比如run-10的字符串排序会排在run-2前面所以命名时最好用零填充如run-01、run-02这样。第三批处理脚本要有断点续跑的能力。fMRIPrep这类工具会缓存中间结果重新运行时会跳过已完成的模块这很方便。但自己写SPM批处理时不会自动断点所以建议把每一步的输入输出都落盘保存并记录日志。如果中途挂了检查日志修改问题后从失败的那一步接着跑而不是从头再来一遍。批量处理做完最后再补一道人工抽检随机挑几个被试用MRIQC或肉眼检查配准、分割、平滑后的图像是否正常。自动流程省时间但绝对不能省掉最后的质检环节。我个人的经验里fMRI原始数据分割这个阶段最忌讳急躁。每一次批量处理前把输入数据、输出路径、软件版本写好处理中保留日志处理后检查报告这些看起来重复繁琐的步骤恰恰是让整个项目不发散、结果可复现的关键。你后面做统计建模时跑出来的每一个激活簇都建立在最初那一步是否把原始数据分好、体检做扎实之上。至少在我经手的项目里提前在原始数据阶段多花的一小时永远比最后返工的一整天划算。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。