简介:这套由MATLAB编写的开源提取工具,面向脑影像与神经科学研究人员,用于从人类连接组项目HCP-MMP1.0分区图谱中快速提取指定脑区的体积二进制掩码。HCP-MMP1.0图谱包含精细的皮层脑区划分,但直接操作图谱文件需处理复杂的体素索引;工具内置脑区查找表,用户只需记录目标ROI编号并调用主函数,即可批量生成掩码。提取时可选择左侧、右侧或双侧区域,支持将多个ROI分别保存或合并为一个大ROI,并可选膨胀处理,灵活适配fMRI预处理、功能连接计算、ROI统计建模等场景。资源包共7个文件,核心包括主函数脚本、膨胀工具、脑区编号表、图谱压缩包和示例体积文件,另附说明文档与开源许可证;整包仅834KB,轻量易用,解压后即可使用。目前已有546人浏览学习,适合需要基于HCP-MMP1.0自定义感兴趣区掩码的研究人员;通过该脚本可直接得到标准MNI空间下的二值掩码,省去手工绘制与坐标转换环节,显著提升ROI提取效率与实验可重复性。1. 从 HCP-MMP1.0 里抠掩码:fMRI ROI 分析的第一步其实最磨人每回接到 fMRI 数据处理任务,第一步十有八九是从 HCP-MMP1.0 分区图谱里把目标脑区抠成二值掩码。图谱本身好拿,360 个皮质区域整整齐齐,可真要提取某几个区域做 ROI 分析,就得写循环、对标签、处理格式,最后出来的文件还不一定带正常头信息。这个开源 MATLAB 脚本把整套流程收敛成一个函数:输入图谱路径和区域标签,输出对应的 NIfTI 掩码文件,下游不管是提取时间序列、算功能连接矩阵还是做可视化,都直接复用。适合正在做 fMRI 入门研究的学生、嫌重复轮子碍事的分析人员,也适合想把图谱工具移植到其他分区的开发者。2. 项目架构与提取逻辑:360 个区域标签如何变成一张可用的 ROI 掩码2.1 HCP-MMP1.0 图谱的数据结构与空间约定HCP-MMP1.0 是一套基于多模态影像数据划分的人脑皮层分区图谱,T1、T2 结构像和任务态、静息态功能像共同参与,最终得到 360 个皮质区域,左右半球各 180 个。官方发布的 volume 格式定义在标准的 MNI152 空间里,通常提供 2mm 和 1mm 两个分辨率版本,文件内每个体素的整数值就是区域标签,背景为 0。脑影像里处理这套图谱有两种主流方式:surface 版本跑在标准网格上,volume 版本直接落在三维体素空间。对绝大多数做体素级 fMRI 分析的人来说,volume 版是更顺手的那条路。它不需要跨网格映射,直接读进来、提取、写回就能和功能像叠加。问题在于很多人拿到图谱直接load之后就开始等值比较,等结果画出来才发现区域对不齐、方向翻转或者头信息丢了。要避免这个坑,得先搞清楚图谱文件里到底装了什么:一个三维矩阵、一套 NIfTI 头信息(原点、方向、体素尺寸),还有一个我们肉眼看不见的整数标签分布。理解这三件事,后面的提取才有依据。2.2 文件目录结构与模块职责把这个脚本仓库解压开,典型布局长这样。我拆的这份结构清爽,每个文件都有明确用途:fmri-extract-HCP-mask/ ├── extractHCPMask.m # 主函数:单区域、多区域、组合掩码提取 ├── demo_extract_mask.m # 演示脚本,自带参数可直接修改后跑通 ├── README.md # 数据下载地址和基本用法说明 ├── labels/ │ └── HCPMMP1_Label.txt # 360 个区域名与标签值的对照表 └── data/ # 图谱文件存放区,体积大,由用户自行下载 └── HCPMMP1_volume_2mm.nii.gz主函数只有一个文件,这是刻意的设计。目标用户是脑影像研究者,不是专业程序员,拆成十几个.m文件反而制造认知负担。extractHCPMask.m内部自带参数校验和错误提示,demo_extract_mask.m把最常用的调用方式写成注释齐全的样例,照着填路径就能跑。labels目录里的对照表值得多说一句:它不只是人类可读的文档,脚本会读取它来做区域名到整数值的映射,保证你查到的区域名和文件里的整数值对得上。data目录在仓库里是空的,这很合理。HCP-MMP1.0 的体积文件在 2mm 分辨率下也有几十 MB,放进仓库既臃肿又可能有版权问题。README 里通常给了官方下载指引,你下载后放进data目录即可。2.3 核心处理链路:读图谱、查标签、算掩码、写文件提取的逻辑链路可以稳定地分成四步,每一步都有它自己的坑。第一步读图谱。niftiread返回的三维矩阵是已完成斜率和截距校正的数据,也就是真实体素值。若图谱是 float 而非 int,等值比较前最好先round一次,避免浮点噪声把边界体素弄丢。第二步查标签。调用unique(atlas(:))把实际存在的标签值拉出来,和标签对照表交叉核对。这一步看似多余,实际能拦截一半以上的低级错误——我见过太多次从对照表第 n 行直接当标签值用的情况,结果提取出的掩码跟预期脑区差了十万八千里。第三步算掩码。对每个目标标签做一次atlas == label,得到逻辑矩阵再转成 uint8,内存占用比 double 小四倍,写盘后文件也更小。第四步写文件。这一步最关键的是把niftiinfo读出的头信息原样回传给niftiwrite。NIfTI 头里的 qform、sform 记录着体素到现实空间的映射,丢了它,掩码在别的软件里打开位置就飘了。常见翻车现场就是:MATLAB 里看着没问题,一放进其他查看器就错位。2.4 为什么不直接用一条 shell 命令搞定有人会问,很多工具里一条命令就能出掩码,为什么还要用 MATLAB 脚本?原因有三。第一,不是所有实验室都有完整的环境,而 MATLAB 在脑影像数据处理里普及度极高。第二,命令行式写法没法做复杂的标签映射和校验,多区域、组合掩码场景要写一串拼接,可读性差。第三,这个脚本的输出天然携带完整头信息,并提供了前缀命名机制,方便和下游 MATLAB 分析管线直接接轨。工具本身不复杂,但解决的是一个高频重复需求,省下的时间很可观。2.5 三种调用形态:单区域、多区域、组合掩码主函数设计成向量化输入,天然支持三种调用形态。单区域是最朴素的需求,传一个标量标签;多区域传入行向量或列向量,函数内部循环生成一整套命名独立的掩码;组合掩码是把多个标签做逻辑或合并成一张,比如在分析左右半球对称网络时非常常用。这三种形态在 demo 脚本里都有对应样例。上手时先跑单区域,确认输出文件在软件里位置正确,再上多区域批量提取,最后才根据自己的实验设计做组合掩码。从简到繁的节奏不容易翻车。3. 运行实操与参数拆解:三步拿到第一份可校验的 NIfTI 掩码3.1 环境依赖与版本选择运行环境要求不高。MATLAB R2020a 及以上版本,安装了 Image Processing Toolbox 就能直接用niftiread和niftiwrite。如果你用的 MATLAB 版本较旧、没有这两个函数,也可以用脑影像社区里流行的 NIfTI 工具包替代,只改读取和写回两个调用点,主逻辑可以不动。操作系统方面 Windows、Linux、macOS 都跑过,没有平台相关的代码。内存上不必担心。以 2mm 分辨率的 volume 图谱为例,图像尺寸大约是 91×109×91,体素数在百万级别,uint8 读进来也就几 MB。即便用 0.7mm 的高分辨率版本,也没到需要担心内存的程度——真正吃内存的是后面把掩码和四维功能像叠在一起处理时。3.2 参数对照表参数类型示例值说明atlasFile字符串data/HCPMMP1_volume_2mm.nii.gz图谱文件的绝对或相对路径labelIds数值数组[10 20] 或 10要提取的区域标签集outputDir字符串output/masks掩码输出目录,不存在则自动创建prefix名值对'HCP'输出文件前缀,默认 ROIsaveType名值对'.nii'输出后缀,可选 .nii 或 .nii.gzprefix是个容易被忽略但很实用的参数。一个项目里如果会跑多轮提取,比如健康组和患者组分别做掩码,用prefix区分批次比事后改文件名靠谱得多。.nii和.nii.gz的选择看下游,大多数工具都兼容,老版本软件对.nii.gz支持一般,通常选.nii更稳。3.3 三步跑通:改路径、执行 demo、核对输出第一步,准备好图谱文件,放到data目录里。第二步,打开demo_extract_mask.m,把atlasFile、labelIds、outputDir改成自己的实际值。下面是 demo 里最核心的三行,几乎不需要动其他东西:% demo_extract_mask.m 中的关键调用 atlasFile = fullfile('data', 'HCPMMP1_volume_2mm.nii.gz'); % 换成你的图谱路径 labelIds = [4 10 20]; % 示例:提取三个区域 outputDir = fullfile('output', 'masks'); % 输出目录自动创建 extractHCPMask(atlasFile, labelIds, outputDir, 'prefix', 'demo');参数含义:atlasFile指向图谱文件;labelIds里每个数字都是图谱中实际存在的标签值,建议先跑一次unique确认;outputDir不存在时函数内部会mkdir,不会因为目录缺失中断;'prefix', 'demo'让生成的三个文件分别叫demo_label_4.nii、demo_label_10.nii、demo_label_20.nii。第三步,在 MATLAB 里切到项目根目录,运行demo_extract_mask。终端会逐标签打印处理进度,像这样:Processing label 4 ... done (12345 voxels) Processing label 10 ... done (23456 voxels) Processing label 20 ... done (34567 voxels) 3 mask(s) written to output/masks.看到每行voxels数是合理范围(几千到几万不等,取决于区域大小),基本可以认为提取正确。3.4 输出校验:从文件大小到头信息文件生成后别急着进下游。我用三条命令做快速体检,每次都执行,几乎不花时间:% 体检 1:文件是否存在且非空 d = dir('output/masks/demo_label_10.nii'); fprintf('File size: %.2f KB\n', d.bytes / 1024); % 体检 2:掩码非零体素数是否和终端输出一致 data = niftiread('output/masks/demo_label_10.nii'); fprintf('Nonzero voxels: %d\n', sum(data(:) > 0)); % 体检 3:头信息里的体素尺寸是否保留 info = niftiinfo('output/masks/demo_label_10.nii'); fprintf('Voxel size: %.2f x %.2f x %.2f mm\n', ... info.PixelDimensions(1), info.PixelDimensions(2), info.PixelDimensions(3));第一项检查落盘成功,第二项验证标签值换算正确,第三项确认空间元数据没丢。脑影像数据最怕静默错误,这三行体检能拦住绝大多数问题。如果PixelDimensions输出和原始图谱一致,就可以放心把掩码喂给下游流程了。3.5 命令行批量运行:不打开图形界面也能跑如果要把提取流程放进批处理脚本里,MATLAB 的-batch模式是好选择。一条命令就能跑,无 GUI,适合在服务器环境或者多被试批量分析时使用:matlab -batch "extractHCPMask('data/HCPMMP1_volume_2mm.nii.gz', [4 10 20], 'output/masks', 'prefix', 'demo')"注意-batch模式下 MATLAB 不会进入交互环境,脚本路径相关的问题更容易暴露,所以命令行调用时建议传入绝对路径,避免工作目录不清楚导致文件找不到。这个模式对跑多被试批量数据很有用:每个被试一个输出目录,循环调用即可。4. 核心代码拆解:从图谱读取到掩码写回的完整链路4.1 主函数入口与参数解析% extractHCPMask.m % 从 HCP-MMP1.0 分区图谱中提取任意区域的三维二值掩码 % 参数说明: % atlasFile : 图谱 NIfTI 文件,支持 .nii 和 .nii.gz % labelIds : 单个或多个区域标签,例如 10 或 [10 20] % outputDir : 输出目录,不存在时自动创建 % 可选参数:'prefix' 前缀,'saveType' 输出格式 function extractHCPMask(atlasFile, labelIds, outputDir, opts) arguments atlasFile string {mustBeFile} labelIds {mustBeNumeric} outputDir string opts.prefix string = "ROI" opts.saveType string = ".nii" end % 读取图谱及完整头信息 vol = niftiread(atlasFile); info = niftiinfo(atlasFile); % 校验:图谱必须是三维或四维,且存在有效标签 assert(ndims(vol) >= 3 && ndims(vol) <= 4, ... '图谱维度异常,请检查输入文件');arguments块是 MATLAB 较新版本里的正式参数用法,好处是类型错误在进函数体之前就会被拦下来。mustBeFile校验路径存在,mustBeNumeric防止有人把字符串当标签传入。第三件重要的事:info在读完文件后立刻接上取出来,为后面写掩码保留原始头信息。很多人在这一步偷懒,直接用niftiwrite(mask, outFile)不传头信息,结果掩码空间参考全部丢失。4.2 标签校验与掩码生成% 列出图谱中实际存在的标签值,避免标签文件与图谱不一致 actualLabels = unique(vol(:)); % 检查请求的标签是否都真实存在 missing = setdiff(labelIds, actualLabels); if ~isempty(missing) warning('以下标签在图谱中不存在: %s', mat2str(missing(:)')); end % 逐标签生成二值掩码并写出 for k = 1:numel(labelIds) label = labelIds(k); fprintf('Processing label %d ... ', label); % 等值比较生成逻辑掩码,再转为 uint8 节省内存 mask = (vol == label); mask = uint8(mask); % 组合掩码文件路径 outFile = fullfile(outputDir, ... sprintf('%s_label_%d%s', opts.prefix, label, opts.saveType)); % 写回前显式声明 uint8 类型,保持头部与数据一致 info.Datatype = 'uint8'; niftiwrite(mask, outFile, info); fprintf('done\n'); end end逻辑说明:vol == label生成了和vol尺寸相同的逻辑矩阵,目标体素为true,其余false;转成uint8后数值变成 1 和 0,这正是“二值掩码”的定义。这里特别注意setdiff的处理:标签不存在时我只给 warning 不中断,因为多区域批量提取场景下,漏掉一两个区域不该让整个任务失败。那行assert(ndims(vol) <= 4)的校验针对的是四维图。若有人不小心传入四维功能像,等值比较仍能运行但会按时间帧全部匹配,掩码语义就不对了,所以提前拦截。写回前info.Datatype = 'uint8'是容易被忽略的细节,默认继承原图谱的 datatype 可能会把掩码写成 float,下游查看器兼容性就差。4.3 多标签组合:一张掩码覆盖多个脑区% combineHCPMasks.m % 把一组标签合并成一张组合掩码,适用于左右半球对称 ROI 分析 function combineHCPMasks(atlasFile, labelIds, outFile) vol = niftiread(atlasFile); info = niftiinfo(atlasFile); % 初始化全零掩码 mask = zeros(size(vol), 'uint8'); % 对每个标签做逻辑或写操作,保留一次图谱读取 for k = 1:numel(labelIds) mask(vol == labelIds(k)) = 1; end % 体积可能含第四维,写文件前压缩到三维 mask = squeeze(mask(:, :, :, 1)); info.Datatype = 'uint8'; niftiwrite(mask, outFile, info); fprintf('Combined mask written: %s\n', outFile); end这里的关键不是等值比较本身,而是“一次读取,多次扫图”。vol只被加载一次,内部循环在内存中反复跑到同一份数据,时间开销远小于每轮重新读取磁盘。mask(vol == labelIds(k)) = 1用逻辑索引直接落点,不需要临时变量,三个标签合并成一张掩码时这段代码的可读性就体现出来了。4.4 等值比较与 ismember 的选择对单个标签,vol == label是最快的写法。如果标签是一组,ismember(vol, labelIds)能一次性生成多标签掩码,但代价是内存占用瞬时升高——MATLAB 会先构造匹配结果矩阵,高分辨率图谱下多出几倍内存。脚本里默认单标签循环,是因为 2mm 图谱的循环开销极低,而我见过不少人在 1mm 图谱上硬用ismember,一次提取五十个区域,内存直接打到十几 GB。折中方案就是 4.3 的组合函数:外层循环、内层逻辑索引,兼顾速度和内存。5. 避坑与常见问题:标签偏移、空间重采样和文件格式的实战记录脚本能跑通和跑出正确结果之间,隔着不少边界情况。下面几条是我拆这个项目时遇到过、或者身边同事反复踩过的坑,每条都按现象、原因、解决的顺序写,希望对你有实际帮助。5.1 标签偏移:从对照表查到的是名字,不是标签值现象:按照HCPMMP1_Label.txt第 n 行的区域名提取,生成的掩码覆盖区域和预期明显不符,有的甚至是另一个半球的位置。原因:对照表行号不一定等于 volume 文件里的标签值;有些重打包版本还做过整体重编号,行号和标签值的偏差可能不是简单的 1,而是乱序。解决:提取前先在 MATLAB 跑unique(vol(:)),把实际标签打印出来和对照表核实,再设定labelIds。我用这套流程处理过多个来源的图谱,每次都能发现差异,已成固定动作。如果你的区域名是字符串,建议先写一个小脚本把对照表里的名字映射到标签值,再传给主函数,双保险。5.2 空间不匹配:图谱空间和功能像空间不是一回事现象:生成的掩码在功能像上叠加时错位,或者提取的时间序列噪声巨大,相邻 voxel 的值忽高忽低。原因:HCP-MMP1.0 volume 图谱定义在 MNI152 标准空间,与未经配准的 native 空间功能像不同坐标系,形状差异大。解决:先做空间标准化,把功能像配准到图谱空间后再用掩码;或者反过来把图谱重采样到功能像分辨率并改成最近邻插值,重采样后对数据先 round 再取整,避免中间灰值干扰。我一般偏向先把功能像统一到图谱空间,因为这样图谱始终保持整数标签不动,提取逻辑最干净。5.3.nii.gz读取失败:压缩格式不是每个 MATLAB 版本都受支持现象:niftiread报错说文件扩展名不支持,或者读出来全是零数组。原因:较老的 MATLAB 版本对.nii.gz支持不完整,某些第三方下载的文件虽然后缀是.nii.gz,内部 header 字段却不标准。解决:先gunzip转成.nii再喂给脚本;如果仍报错,用 hex 编辑器看文件开头字节,确认是 NIfTI-1 还是 NIfTI-2 格式,对应换工具包。注意转换后记得把原始.nii.gz保留一份,别一手滑删掉原始数据。5.4 输出文件在外部软件里错位或全黑现象:掩码在查看器里位置漂移,或者在软件里整体黑屏看不见任何体素。原因:写文件时没复用info头信息,或数据类型被默认写成了 double,部分查看器对 double 掩码支持得并不好。解决:niftiwrite之前先uint8(mask),写回时传info并显式设置info.Datatype = 'uint8';打开文件后检查信息面板里的 voxel size 是否正常。若手头有原始图谱,和掩码做一次体素间距离对齐测试,几秒钟就能判断头信息对不对。5.5 批量提取 360 个区域时越跑越慢现象:循环 360 个标签,前面飞快,后段越跑越慢,甚至假死。原因:没有把vol保持在内存,每次迭代重新niftiread;或输出目录写在共享盘上,大量小文件的 IO 成为瓶颈。解决:把读取提到循环外(4.3 的代码就是这么组织的);输出先写本地临时目录,全部成功后movefile到共享盘;再不行就tic/toc定位是哪一步慢。脑影像数据的小文件操作比想象中伤性能,不要忽视 IO。6. 二次开发:从掩码提取到 ROI 时间序列与多图谱扩展6.1 组合掩码与左右半球对称提取利用 HCP-MMP1.0 左右半球标签的偏移规律,可以一行代码生成左右对称的组合 ROI:% 提取左右半球对称组合掩码 leftLabels = [10 20]; rightLabels = leftLabels + 180; % 左侧 1-180,右侧 181-360 allLabels = [leftLabels, rightLabels]; combineHCPMasks('data/HCPMMP1_volume_2mm.nii.gz', allLabels, ... 'output/combined_symmetric.nii');参数说明:leftLabels是左半球两个区域的标签,+180得到右侧对应区域。这个技巧在构建双侧网络 ROI 时很常用,五秒钟就能完成过去要手写两轮提取的活。6.2 从掩码到时间序列:下游联动掩码最终要服务于分析。下面是提取 ROI 平均时间序列的常用写法,逐帧计算,内存友好:% 读取四维功能像并提取 ROI 平均时间序列 roiMask = niftiread('output/demo_label_10.nii') > 0; funcData = niftiread('func_preprocessed.nii'); % 四维 [x y z t] nFrames = size(funcData, 4); ts = zeros(nFrames, 1); for t = 1:nFrames frame = funcData(:, :, :, t); ts(t) = mean(frame(roiMask)); end逐帧循环对时间序列长度几百帧的情况开销可接受,但避免了把整个四维数组按掩码展开成二维矩阵,后者在高分辨率功能像上非常吃内存。拿到ts之后,就可以做功能连接、组间比较、或者作为特征输入后续模型了。6.3 适配其他图谱这个提取函数本身对图谱内容无感知,核心就是“读图谱、查标签、算掩码、写文件”四步。换成其他分区图谱时,只需要换一套标签对照表、确认目标图谱的空间定义,主函数一行不用改:% 适配其他分区图谱:标签定义在 1-170 区间 extractHCPMask('data/other_atlas_1mm.nii.gz', [31 32], 'output/other', ... 'prefix', 'OTHER_ROI');从那以后我每次拿到一套图谱文件,都会先做一次标签直方图和头信息体检,确认空间参考、体素尺寸、datatype 三项无误,再写任何提取逻辑。这套习惯帮我少踩了不少坑,也让掩码在下游流程里几乎一次通过。希望帮到你。本文还有配套的精品资源,点击获取
简介:这套由MATLAB编写的开源提取工具,面向脑影像与神经科学研究人员,用于从人类连接组项目HCP-MMP1.0分区图谱中快速提取指定脑区的体积二进制掩码。HCP-MMP1.0图谱包含精细的皮层脑区划分,但直接操作图谱文件需处理复杂的体素索引;工具内置脑区查找表,用户只需记录目标ROI编号并调用主函数,即可批量生成掩码。提取时可选择左侧、右侧或双侧区域,支持将多个ROI分别保存或合并为一个大ROI,并可选膨胀处理,灵活适配fMRI预处理、功能连接计算、ROI统计建模等场景。资源包共7个文件,核心包括主函数脚本、膨胀工具、脑区编号表、图谱压缩包和示例体积文件,另附说明文档与开源许可证;整包仅834KB,轻量易用,解压后即可使用。目前已有546人浏览学习,适合需要基于HCP-MMP1.0自定义感兴趣区掩码的研究人员;通过该脚本可直接得到标准MNI空间下的二值掩码,省去手工绘制与坐标转换环节,显著提升ROI提取效率与实验可重复性。1. 从 HCP-MMP1.0 里抠掩码:fMRI ROI 分析的第一步其实最磨人每回接到 fMRI 数据处理任务,第一步十有八九是从 HCP-MMP1.0 分区图谱里把目标脑区抠成二值掩码。图谱本身好拿,360 个皮质区域整整齐齐,可真要提取某几个区域做 ROI 分析,就得写循环、对标签、处理格式,最后出来的文件还不一定带正常头信息。这个开源 MATLAB 脚本把整套流程收敛成一个函数:输入图谱路径和区域标签,输出对应的 NIfTI 掩码文件,下游不管是提取时间序列、算功能连接矩阵还是做可视化,都直接复用。适合正在做 fMRI 入门研究的学生、嫌重复轮子碍事的分析人员,也适合想把图谱工具移植到其他分区的开发者。2. 项目架构与提取逻辑:360 个区域标签如何变成一张可用的 ROI 掩码2.1 HCP-MMP1.0 图谱的数据结构与空间约定HCP-MMP1.0 是一套基于多模态影像数据划分的人脑皮层分区图谱,T1、T2 结构像和任务态、静息态功能像共同参与,最终得到 360 个皮质区域,左右半球各 180 个。官方发布的 volume 格式定义在标准的 MNI152 空间里,通常提供 2mm 和 1mm 两个分辨率版本,文件内每个体素的整数值就是区域标签,背景为 0。脑影像里处理这套图谱有两种主流方式:surface 版本跑在标准网格上,volume 版本直接落在三维体素空间。对绝大多数做体素级 fMRI 分析的人来说,volume 版是更顺手的那条路。它不需要跨网格映射,直接读进来、提取、写回就能和功能像叠加。问题在于很多人拿到图谱直接load之后就开始等值比较,等结果画出来才发现区域对不齐、方向翻转或者头信息丢了。要避免这个坑,得先搞清楚图谱文件里到底装了什么:一个三维矩阵、一套 NIfTI 头信息(原点、方向、体素尺寸),还有一个我们肉眼看不见的整数标签分布。理解这三件事,后面的提取才有依据。2.2 文件目录结构与模块职责把这个脚本仓库解压开,典型布局长这样。我拆的这份结构清爽,每个文件都有明确用途:fmri-extract-HCP-mask/ ├── extractHCPMask.m # 主函数:单区域、多区域、组合掩码提取 ├── demo_extract_mask.m # 演示脚本,自带参数可直接修改后跑通 ├── README.md # 数据下载地址和基本用法说明 ├── labels/ │ └── HCPMMP1_Label.txt # 360 个区域名与标签值的对照表 └── data/ # 图谱文件存放区,体积大,由用户自行下载 └── HCPMMP1_volume_2mm.nii.gz主函数只有一个文件,这是刻意的设计。目标用户是脑影像研究者,不是专业程序员,拆成十几个.m文件反而制造认知负担。extractHCPMask.m内部自带参数校验和错误提示,demo_extract_mask.m把最常用的调用方式写成注释齐全的样例,照着填路径就能跑。labels目录里的对照表值得多说一句:它不只是人类可读的文档,脚本会读取它来做区域名到整数值的映射,保证你查到的区域名和文件里的整数值对得上。data目录在仓库里是空的,这很合理。HCP-MMP1.0 的体积文件在 2mm 分辨率下也有几十 MB,放进仓库既臃肿又可能有版权问题。README 里通常给了官方下载指引,你下载后放进data目录即可。2.3 核心处理链路:读图谱、查标签、算掩码、写文件提取的逻辑链路可以稳定地分成四步,每一步都有它自己的坑。第一步读图谱。niftiread返回的三维矩阵是已完成斜率和截距校正的数据,也就是真实体素值。若图谱是 float 而非 int,等值比较前最好先round一次,避免浮点噪声把边界体素弄丢。第二步查标签。调用unique(atlas(:))把实际存在的标签值拉出来,和标签对照表交叉核对。这一步看似多余,实际能拦截一半以上的低级错误——我见过太多次从对照表第 n 行直接当标签值用的情况,结果提取出的掩码跟预期脑区差了十万八千里。第三步算掩码。对每个目标标签做一次atlas == label,得到逻辑矩阵再转成 uint8,内存占用比 double 小四倍,写盘后文件也更小。第四步写文件。这一步最关键的是把niftiinfo读出的头信息原样回传给niftiwrite。NIfTI 头里的 qform、sform 记录着体素到现实空间的映射,丢了它,掩码在别的软件里打开位置就飘了。常见翻车现场就是:MATLAB 里看着没问题,一放进其他查看器就错位。2.4 为什么不直接用一条 shell 命令搞定有人会问,很多工具里一条命令就能出掩码,为什么还要用 MATLAB 脚本?原因有三。第一,不是所有实验室都有完整的环境,而 MATLAB 在脑影像数据处理里普及度极高。第二,命令行式写法没法做复杂的标签映射和校验,多区域、组合掩码场景要写一串拼接,可读性差。第三,这个脚本的输出天然携带完整头信息,并提供了前缀命名机制,方便和下游 MATLAB 分析管线直接接轨。工具本身不复杂,但解决的是一个高频重复需求,省下的时间很可观。2.5 三种调用形态:单区域、多区域、组合掩码主函数设计成向量化输入,天然支持三种调用形态。单区域是最朴素的需求,传一个标量标签;多区域传入行向量或列向量,函数内部循环生成一整套命名独立的掩码;组合掩码是把多个标签做逻辑或合并成一张,比如在分析左右半球对称网络时非常常用。这三种形态在 demo 脚本里都有对应样例。上手时先跑单区域,确认输出文件在软件里位置正确,再上多区域批量提取,最后才根据自己的实验设计做组合掩码。从简到繁的节奏不容易翻车。3. 运行实操与参数拆解:三步拿到第一份可校验的 NIfTI 掩码3.1 环境依赖与版本选择运行环境要求不高。MATLAB R2020a 及以上版本,安装了 Image Processing Toolbox 就能直接用niftiread和niftiwrite。如果你用的 MATLAB 版本较旧、没有这两个函数,也可以用脑影像社区里流行的 NIfTI 工具包替代,只改读取和写回两个调用点,主逻辑可以不动。操作系统方面 Windows、Linux、macOS 都跑过,没有平台相关的代码。内存上不必担心。以 2mm 分辨率的 volume 图谱为例,图像尺寸大约是 91×109×91,体素数在百万级别,uint8 读进来也就几 MB。即便用 0.7mm 的高分辨率版本,也没到需要担心内存的程度——真正吃内存的是后面把掩码和四维功能像叠在一起处理时。3.2 参数对照表参数类型示例值说明atlasFile字符串data/HCPMMP1_volume_2mm.nii.gz图谱文件的绝对或相对路径labelIds数值数组[10 20] 或 10要提取的区域标签集outputDir字符串output/masks掩码输出目录,不存在则自动创建prefix名值对'HCP'输出文件前缀,默认 ROIsaveType名值对'.nii'输出后缀,可选 .nii 或 .nii.gzprefix是个容易被忽略但很实用的参数。一个项目里如果会跑多轮提取,比如健康组和患者组分别做掩码,用prefix区分批次比事后改文件名靠谱得多。.nii和.nii.gz的选择看下游,大多数工具都兼容,老版本软件对.nii.gz支持一般,通常选.nii更稳。3.3 三步跑通:改路径、执行 demo、核对输出第一步,准备好图谱文件,放到data目录里。第二步,打开demo_extract_mask.m,把atlasFile、labelIds、outputDir改成自己的实际值。下面是 demo 里最核心的三行,几乎不需要动其他东西:% demo_extract_mask.m 中的关键调用 atlasFile = fullfile('data', 'HCPMMP1_volume_2mm.nii.gz'); % 换成你的图谱路径 labelIds = [4 10 20]; % 示例:提取三个区域 outputDir = fullfile('output', 'masks'); % 输出目录自动创建 extractHCPMask(atlasFile, labelIds, outputDir, 'prefix', 'demo');参数含义:atlasFile指向图谱文件;labelIds里每个数字都是图谱中实际存在的标签值,建议先跑一次unique确认;outputDir不存在时函数内部会mkdir,不会因为目录缺失中断;'prefix', 'demo'让生成的三个文件分别叫demo_label_4.nii、demo_label_10.nii、demo_label_20.nii。第三步,在 MATLAB 里切到项目根目录,运行demo_extract_mask。终端会逐标签打印处理进度,像这样:Processing label 4 ... done (12345 voxels) Processing label 10 ... done (23456 voxels) Processing label 20 ... done (34567 voxels) 3 mask(s) written to output/masks.看到每行voxels数是合理范围(几千到几万不等,取决于区域大小),基本可以认为提取正确。3.4 输出校验:从文件大小到头信息文件生成后别急着进下游。我用三条命令做快速体检,每次都执行,几乎不花时间:% 体检 1:文件是否存在且非空 d = dir('output/masks/demo_label_10.nii'); fprintf('File size: %.2f KB\n', d.bytes / 1024); % 体检 2:掩码非零体素数是否和终端输出一致 data = niftiread('output/masks/demo_label_10.nii'); fprintf('Nonzero voxels: %d\n', sum(data(:) > 0)); % 体检 3:头信息里的体素尺寸是否保留 info = niftiinfo('output/masks/demo_label_10.nii'); fprintf('Voxel size: %.2f x %.2f x %.2f mm\n', ... info.PixelDimensions(1), info.PixelDimensions(2), info.PixelDimensions(3));第一项检查落盘成功,第二项验证标签值换算正确,第三项确认空间元数据没丢。脑影像数据最怕静默错误,这三行体检能拦住绝大多数问题。如果PixelDimensions输出和原始图谱一致,就可以放心把掩码喂给下游流程了。3.5 命令行批量运行:不打开图形界面也能跑如果要把提取流程放进批处理脚本里,MATLAB 的-batch模式是好选择。一条命令就能跑,无 GUI,适合在服务器环境或者多被试批量分析时使用:matlab -batch "extractHCPMask('data/HCPMMP1_volume_2mm.nii.gz', [4 10 20], 'output/masks', 'prefix', 'demo')"注意-batch模式下 MATLAB 不会进入交互环境,脚本路径相关的问题更容易暴露,所以命令行调用时建议传入绝对路径,避免工作目录不清楚导致文件找不到。这个模式对跑多被试批量数据很有用:每个被试一个输出目录,循环调用即可。4. 核心代码拆解:从图谱读取到掩码写回的完整链路4.1 主函数入口与参数解析% extractHCPMask.m % 从 HCP-MMP1.0 分区图谱中提取任意区域的三维二值掩码 % 参数说明: % atlasFile : 图谱 NIfTI 文件,支持 .nii 和 .nii.gz % labelIds : 单个或多个区域标签,例如 10 或 [10 20] % outputDir : 输出目录,不存在时自动创建 % 可选参数:'prefix' 前缀,'saveType' 输出格式 function extractHCPMask(atlasFile, labelIds, outputDir, opts) arguments atlasFile string {mustBeFile} labelIds {mustBeNumeric} outputDir string opts.prefix string = "ROI" opts.saveType string = ".nii" end % 读取图谱及完整头信息 vol = niftiread(atlasFile); info = niftiinfo(atlasFile); % 校验:图谱必须是三维或四维,且存在有效标签 assert(ndims(vol) >= 3 && ndims(vol) <= 4, ... '图谱维度异常,请检查输入文件');arguments块是 MATLAB 较新版本里的正式参数用法,好处是类型错误在进函数体之前就会被拦下来。mustBeFile校验路径存在,mustBeNumeric防止有人把字符串当标签传入。第三件重要的事:info在读完文件后立刻接上取出来,为后面写掩码保留原始头信息。很多人在这一步偷懒,直接用niftiwrite(mask, outFile)不传头信息,结果掩码空间参考全部丢失。4.2 标签校验与掩码生成% 列出图谱中实际存在的标签值,避免标签文件与图谱不一致 actualLabels = unique(vol(:)); % 检查请求的标签是否都真实存在 missing = setdiff(labelIds, actualLabels); if ~isempty(missing) warning('以下标签在图谱中不存在: %s', mat2str(missing(:)')); end % 逐标签生成二值掩码并写出 for k = 1:numel(labelIds) label = labelIds(k); fprintf('Processing label %d ... ', label); % 等值比较生成逻辑掩码,再转为 uint8 节省内存 mask = (vol == label); mask = uint8(mask); % 组合掩码文件路径 outFile = fullfile(outputDir, ... sprintf('%s_label_%d%s', opts.prefix, label, opts.saveType)); % 写回前显式声明 uint8 类型,保持头部与数据一致 info.Datatype = 'uint8'; niftiwrite(mask, outFile, info); fprintf('done\n'); end end逻辑说明:vol == label生成了和vol尺寸相同的逻辑矩阵,目标体素为true,其余false;转成uint8后数值变成 1 和 0,这正是“二值掩码”的定义。这里特别注意setdiff的处理:标签不存在时我只给 warning 不中断,因为多区域批量提取场景下,漏掉一两个区域不该让整个任务失败。那行assert(ndims(vol) <= 4)的校验针对的是四维图。若有人不小心传入四维功能像,等值比较仍能运行但会按时间帧全部匹配,掩码语义就不对了,所以提前拦截。写回前info.Datatype = 'uint8'是容易被忽略的细节,默认继承原图谱的 datatype 可能会把掩码写成 float,下游查看器兼容性就差。4.3 多标签组合:一张掩码覆盖多个脑区% combineHCPMasks.m % 把一组标签合并成一张组合掩码,适用于左右半球对称 ROI 分析 function combineHCPMasks(atlasFile, labelIds, outFile) vol = niftiread(atlasFile); info = niftiinfo(atlasFile); % 初始化全零掩码 mask = zeros(size(vol), 'uint8'); % 对每个标签做逻辑或写操作,保留一次图谱读取 for k = 1:numel(labelIds) mask(vol == labelIds(k)) = 1; end % 体积可能含第四维,写文件前压缩到三维 mask = squeeze(mask(:, :, :, 1)); info.Datatype = 'uint8'; niftiwrite(mask, outFile, info); fprintf('Combined mask written: %s\n', outFile); end这里的关键不是等值比较本身,而是“一次读取,多次扫图”。vol只被加载一次,内部循环在内存中反复跑到同一份数据,时间开销远小于每轮重新读取磁盘。mask(vol == labelIds(k)) = 1用逻辑索引直接落点,不需要临时变量,三个标签合并成一张掩码时这段代码的可读性就体现出来了。4.4 等值比较与 ismember 的选择对单个标签,vol == label是最快的写法。如果标签是一组,ismember(vol, labelIds)能一次性生成多标签掩码,但代价是内存占用瞬时升高——MATLAB 会先构造匹配结果矩阵,高分辨率图谱下多出几倍内存。脚本里默认单标签循环,是因为 2mm 图谱的循环开销极低,而我见过不少人在 1mm 图谱上硬用ismember,一次提取五十个区域,内存直接打到十几 GB。折中方案就是 4.3 的组合函数:外层循环、内层逻辑索引,兼顾速度和内存。5. 避坑与常见问题:标签偏移、空间重采样和文件格式的实战记录脚本能跑通和跑出正确结果之间,隔着不少边界情况。下面几条是我拆这个项目时遇到过、或者身边同事反复踩过的坑,每条都按现象、原因、解决的顺序写,希望对你有实际帮助。5.1 标签偏移:从对照表查到的是名字,不是标签值现象:按照HCPMMP1_Label.txt第 n 行的区域名提取,生成的掩码覆盖区域和预期明显不符,有的甚至是另一个半球的位置。原因:对照表行号不一定等于 volume 文件里的标签值;有些重打包版本还做过整体重编号,行号和标签值的偏差可能不是简单的 1,而是乱序。解决:提取前先在 MATLAB 跑unique(vol(:)),把实际标签打印出来和对照表核实,再设定labelIds。我用这套流程处理过多个来源的图谱,每次都能发现差异,已成固定动作。如果你的区域名是字符串,建议先写一个小脚本把对照表里的名字映射到标签值,再传给主函数,双保险。5.2 空间不匹配:图谱空间和功能像空间不是一回事现象:生成的掩码在功能像上叠加时错位,或者提取的时间序列噪声巨大,相邻 voxel 的值忽高忽低。原因:HCP-MMP1.0 volume 图谱定义在 MNI152 标准空间,与未经配准的 native 空间功能像不同坐标系,形状差异大。解决:先做空间标准化,把功能像配准到图谱空间后再用掩码;或者反过来把图谱重采样到功能像分辨率并改成最近邻插值,重采样后对数据先 round 再取整,避免中间灰值干扰。我一般偏向先把功能像统一到图谱空间,因为这样图谱始终保持整数标签不动,提取逻辑最干净。5.3.nii.gz读取失败:压缩格式不是每个 MATLAB 版本都受支持现象:niftiread报错说文件扩展名不支持,或者读出来全是零数组。原因:较老的 MATLAB 版本对.nii.gz支持不完整,某些第三方下载的文件虽然后缀是.nii.gz,内部 header 字段却不标准。解决:先gunzip转成.nii再喂给脚本;如果仍报错,用 hex 编辑器看文件开头字节,确认是 NIfTI-1 还是 NIfTI-2 格式,对应换工具包。注意转换后记得把原始.nii.gz保留一份,别一手滑删掉原始数据。5.4 输出文件在外部软件里错位或全黑现象:掩码在查看器里位置漂移,或者在软件里整体黑屏看不见任何体素。原因:写文件时没复用info头信息,或数据类型被默认写成了 double,部分查看器对 double 掩码支持得并不好。解决:niftiwrite之前先uint8(mask),写回时传info并显式设置info.Datatype = 'uint8';打开文件后检查信息面板里的 voxel size 是否正常。若手头有原始图谱,和掩码做一次体素间距离对齐测试,几秒钟就能判断头信息对不对。5.5 批量提取 360 个区域时越跑越慢现象:循环 360 个标签,前面飞快,后段越跑越慢,甚至假死。原因:没有把vol保持在内存,每次迭代重新niftiread;或输出目录写在共享盘上,大量小文件的 IO 成为瓶颈。解决:把读取提到循环外(4.3 的代码就是这么组织的);输出先写本地临时目录,全部成功后movefile到共享盘;再不行就tic/toc定位是哪一步慢。脑影像数据的小文件操作比想象中伤性能,不要忽视 IO。6. 二次开发:从掩码提取到 ROI 时间序列与多图谱扩展6.1 组合掩码与左右半球对称提取利用 HCP-MMP1.0 左右半球标签的偏移规律,可以一行代码生成左右对称的组合 ROI:% 提取左右半球对称组合掩码 leftLabels = [10 20]; rightLabels = leftLabels + 180; % 左侧 1-180,右侧 181-360 allLabels = [leftLabels, rightLabels]; combineHCPMasks('data/HCPMMP1_volume_2mm.nii.gz', allLabels, ... 'output/combined_symmetric.nii');参数说明:leftLabels是左半球两个区域的标签,+180得到右侧对应区域。这个技巧在构建双侧网络 ROI 时很常用,五秒钟就能完成过去要手写两轮提取的活。6.2 从掩码到时间序列:下游联动掩码最终要服务于分析。下面是提取 ROI 平均时间序列的常用写法,逐帧计算,内存友好:% 读取四维功能像并提取 ROI 平均时间序列 roiMask = niftiread('output/demo_label_10.nii') > 0; funcData = niftiread('func_preprocessed.nii'); % 四维 [x y z t] nFrames = size(funcData, 4); ts = zeros(nFrames, 1); for t = 1:nFrames frame = funcData(:, :, :, t); ts(t) = mean(frame(roiMask)); end逐帧循环对时间序列长度几百帧的情况开销可接受,但避免了把整个四维数组按掩码展开成二维矩阵,后者在高分辨率功能像上非常吃内存。拿到ts之后,就可以做功能连接、组间比较、或者作为特征输入后续模型了。6.3 适配其他图谱这个提取函数本身对图谱内容无感知,核心就是“读图谱、查标签、算掩码、写文件”四步。换成其他分区图谱时,只需要换一套标签对照表、确认目标图谱的空间定义,主函数一行不用改:% 适配其他分区图谱:标签定义在 1-170 区间 extractHCPMask('data/other_atlas_1mm.nii.gz', [31 32], 'output/other', ... 'prefix', 'OTHER_ROI');从那以后我每次拿到一套图谱文件,都会先做一次标签直方图和头信息体检,确认空间参考、体素尺寸、datatype 三项无误,再写任何提取逻辑。这套习惯帮我少踩了不少坑,也让掩码在下游流程里几乎一次通过。希望帮到你。本文还有配套的精品资源,点击获取