资讯动态

HCP-MMP1.0图谱脑区掩码提取:从拆分文件到二值掩码的完整流程

发布时间:2026/10/15 1:39:48 来源:尧图企业网站定制
简介这份资源是一套面向fMRI影像分析与RSA研究的MATLAB脚本工具用于从HCP-MMP1.0分区图谱中便捷提取特定体积二进制掩码文件。它适合具备一定MATLAB基础、从事脑影像ROI分析的研究生与科研人员可解决手动从大图谱中裁剪目标脑区、生成掩码效率低且易出错的问题。资源包共7个文件约834KB包含MATLAB脚本、mmp.csv ROI查找表、HCP_atlas压缩包、示例nii数据、LICENSE及README说明文档覆盖脚本、数据与使用说明三类内容。目前已有545人学习下载。使用者可通过ROI查找表定位感兴趣脑区代码调用create_HCP_mask()函数按单个或合并方式输出掩码并灵活选择左、右或双侧半球及是否膨胀处理输出掩码保持MNI空间便于后续配准与统计分析为脑区定位与RSA流程提供可直接复用的基础工具。1. 从 HCP-MMP1.0 图谱里抠出你要的那块脑区掩码做静息态或任务态 fMRI 的人几乎都会遇到同一个卡点手头有一份 HCP-MMP1.0 分区图谱180 个皮层区域每个区域左右半球各一份可你只想要其中某一个或某几个区域做成一张二值掩码拿去提时间序列、算功能连接、做 ROI-wise 分析。问题是这份图谱的原始文件动辄几百 MB标签是离散的整数编号直接丢进流程里既浪费内存又容易把编号搞错。标题里说的这个脚本干的就是这件事从 HCP-MMP1.0 的拆分文件里按你指定的区域编号抽出一张干净的二进制掩码。它解决的不是什么高深算法而是把「图谱 → 目标区域 → 可用掩码」这条链路里最容易出错的一步标准化。适合谁适合正在用 HCP 图谱做 ROI 分析、又不想每次手动在可视化软件里描区域的研究生和工程师。下面我按自己实际跑通的顺序把选型、命令、参数和踩过的坑讲清楚。2. 先搞懂 HCP-MMP1.0 的文件结构和编号逻辑2.1 为什么不能直接拿原始图谱当掩码用HCP-MMP1.0 的官方发布形式通常是一个体积不小的 NIfTI 文件体素值就是区域标签。比如左侧某个区域是 1右侧对应区域是 2依次排到 180。这种「一张图装下全部区域」的设计对可视化很友好对批处理却很不友好。你拿它当掩码等于告诉后续流程「所有非零体素都是感兴趣区」那 180 个区域会被一锅端。更麻烦的是很多工具在读取时会把标签当连续灰度处理插值一开边界立刻糊掉编号也跟着漂。所以正确做法是先明确你要哪几个标签值再把它们单独抠成 0/1 掩码。这一步不做后面所有 ROI 信号都是脏的。2.2 拆分文件、标签值和左右半球的关系HCP-MMP1.0 的「拆分」通常指按半球或按区域拆出来的子文件。常见约定是左侧皮层标签为奇数或从 1 开始的一段右侧为对应偏移。不同发布版本编号规则可能不同所以第一件事不是写代码而是把标签表读出来核对。我一般会先用工具打印唯一值确认目标区域到底对应哪个整数。这里有个血泪经验有人直接按文献里的编号去提结果文献用的是另一套重编号方案提出来的是隔壁区域做完一圈分析才发现只能返工。核对标签这一步花五分钟能省你两天。2.3 掩码为什么必须是二值且和功能像对齐掩码的本质是布尔索引。它只有两个状态在区域内、不在区域内。如果你保留原始标签值后续做均值时会把标签值本身当权重等于给不同体素偷偷加了系数结果完全不可解释。另外掩码必须和你的功能像处在同一空间、同一网格。HCP 图谱常见是 32k 表面或体积模板而你的 fMRI 可能是 2mm 或 3mm 体素。空间没对齐就提信号等于拿错地图找路。所以流程里一定要有重采样或配准这一步且重采样时用最近邻插值别用线性否则 0/1 边界会被插出 0.3、0.7 这种中间值。3. 用脚本从拆分文件提取指定区域掩码的完整流程3.1 环境准备与依赖确认这个脚本本质是围绕 NIfTI 读写和数组操作Python 环境下最稳的组合是 nibabel 加 numpy。先确认版本能对上避免读文件时报头信息错误。# 建议在独立虚拟环境里操作避免和现有神经影像工具链冲突 python -m venv env_mask source env_mask/bin/activate # 核心依赖nibabel 负责读写 NIfTInumpy 负责数组运算 pip install nibabel numpy逻辑说明nibabel 是神经影像领域读 NIfTI 的事实标准能保留仿射矩阵和头信息这对后面空间对齐至关重要。numpy 负责把体素数据转成数组做布尔运算。参数上没有太多可调的重点是别在系统 Python 里乱装很多人的环境里已经有一套老版本 nibabel混用会出现仿射矩阵读出来是单位阵的玄学问题。3.2 读取拆分文件并确认目标标签先别急着提掩码把文件读进来看看里面到底有哪些标签值这一步是后悔药。import nibabel as nib import numpy as np # 替换成你的 HCP-MMP1.0 拆分文件路径 atlas_path HCPMMP1_split.nii.gz atlas_img nib.load(atlas_path) atlas_data atlas_img.get_fdata().astype(np.int32) # 打印所有唯一标签值确认目标区域编号 labels np.unique(atlas_data) print(唯一标签值, labels) print(标签总数, len(labels))逻辑说明get_fdata()返回浮点数组这里强制转成整型是因为标签本质是离散编号浮点比较容易出现 1.0000001 不等于 1 的翻车。np.unique把出现过的标签全列出来你对着标签表找目标值。参数上注意如果文件很大get_fdata会占内存可以改用np.asanyarray(atlas_img.dataobj)做惰性读取但多数拆分文件不至于。确认标签后把目标值记下来比如左侧某区是 7右侧是 8。3.3 生成二值掩码并保存确认标签后提取就是一行布尔运算的事但保存时的细节决定后面能不能用。# 假设目标区域标签为 7 和 8 target_labels [7, 8] # 生成二值掩码在目标标签内为 1否则为 0 mask_data np.isin(atlas_data, target_labels).astype(np.uint8) # 用原图谱的仿射矩阵和头信息构建新图像保证空间一致 mask_img nib.Nifti1Image(mask_data, atlas_img.affine, atlas_img.header) mask_img.set_data_dtype(np.uint8) # 保存掩码 nib.save(mask_img, roi_mask.nii.gz) print(掩码体素总数, int(mask_data.sum()))逻辑说明np.isin一次性处理多个标签比循环快且不易错。astype(np.uint8)把布尔转成 0/1节省空间。关键在atlas_img.affine和atlas_img.header仿射矩阵决定体素坐标到世界坐标的映射头信息里的体素尺寸、方向也一并继承这样掩码和原图谱严格对齐。set_data_dtype显式声明数据类型避免保存时被默认转成 float。最后打印体素数是给自己一个 sanity check如果体素数为 0说明标签选错了如果大得离谱说明把整个半球都框进去了。3.4 把掩码对齐到功能像空间掩码和图谱对齐了但你的 fMRI 可能是另一个空间。这一步不能省。# 常见做法是用 FSL 的 flirt 做配准最近邻插值保住 0/1 边界 flirt -in roi_mask.nii.gz \ -ref func_img.nii.gz \ -out roi_mask_func_space.nii.gz \ -applyxfm -usesqform \ -interp nearestneighbour逻辑说明-ref指定功能像作为参考空间-applyxfm -usesqform表示用文件自带的 sform 做变换适合已经配准过的场景。-interp nearestneighbour是重点最近邻插值保证输出只有 0 和 1不会出现中间值。如果你的图谱和功能像还没配准得先做一步仿射或非线性配准再套这个命令。参数上-usesqform和-applyxfm的组合在头信息规范时很省事但头信息乱的文件会翻车这时改用-init指定变换矩阵更稳。4. 提取掩码时最容易翻车的几个地方4.1 标签编号对不上提出来是隔壁区域现象掩码体素数看着正常但提出来的时间序列和预期脑区功能对不上做统计时结果诡异。原因HCP-MMP1.0 存在多套编号方案文献、可视化软件、拆分文件三者可能不一致直接照搬文献编号就会错位。解决永远先用np.unique打印实际标签再对照你手上那份拆分文件自带的标签表核对别信记忆。我一般会把标签表和唯一值并排打印肉眼过一遍再动手。4.2 重采样用了线性插值掩码边界糊掉现象掩码里出现 0.3、0.5 这类值后续做均值时边界体素被算进去信号被稀释。原因线性或样条插值会把 0/1 边界平滑成中间值。解决所有涉及掩码的重采样一律用最近邻插值。FSL 里是-interp nearestneighbour其他工具里找 nearest neighbor 选项。已经糊掉的掩码可以重新二值化但边界已经漂了不如重做。4.3 左右半球标签混用掩码跨了中线现象掩码看起来覆盖了目标区域但体素数比单侧预期多一倍或者形状跨了中线。原因左侧和右侧标签是不同整数如果只填了一个却以为覆盖双侧或者把左右标签都填进去却没意识到就会出问题。解决明确你要单侧还是双侧。单侧只填对应标签双侧把左右两个标签都放进target_labels。做完后把掩码叠到图谱上目视检查一次这一步别偷懒。4.4 头信息丢失导致空间错位现象掩码单独打开看着没问题一叠到功能像上就偏到别处。原因保存时没继承原图谱的仿射矩阵或者中间某步用了默认头信息重建图像。解决构建新图像时始终传入原图的affine和header保存前打印一下仿射矩阵和原图对比。如果已经丢了用原图的仿射矩阵重新赋值再保存。4.5 体素尺寸不一致直接提信号现象掩码和功能像体素尺寸不同提信号时工具自动重采样结果不可控。原因图谱常见 1mm 或表面模板功能像常是 2mm、3mm直接混用会触发隐式重采样。解决显式做一步配准或重采样把掩码变到功能像网格用最近邻插值做完检查两者体素尺寸和方向是否一致。别依赖工具的默认行为默认行为是黑匣子。5. 进阶批量提取多区域与掩码质量自检当你不是提一个区域而是要把 180 个区域全提一遍做全脑连接时逐个手改标签就不现实了。我一般会写一个循环把标签表读进来按区域名和标签的映射批量生成掩码每个掩码单独存文件文件名带上区域名方便后续索引。import nibabel as nib import numpy as np import os atlas_img nib.load(HCPMMP1_split.nii.gz) atlas_data atlas_img.get_fdata().astype(np.int32) # 区域名到标签的映射按你手上的标签表填写 region_map { area_A_left: 1, area_A_right: 2, area_B_left: 3, area_B_right: 4, } out_dir masks os.makedirs(out_dir, exist_okTrue) for name, label in region_map.items(): mask_data (atlas_data label).astype(np.uint8) voxel_count int(mask_data.sum()) # 体素数为 0 说明标签错了直接跳过并报警 if voxel_count 0: print(f警告{name} 标签 {label} 未匹配到体素请核对标签表) continue mask_img nib.Nifti1Image(mask_data, atlas_img.affine, atlas_img.header) mask_img.set_data_dtype(np.uint8) nib.save(mask_img, os.path.join(out_dir, f{name}_mask.nii.gz)) print(f{name}: 标签 {label}, 体素 {voxel_count})逻辑说明循环里对每个标签做等值判断比np.isin更适合单标签场景。体素数为 0 时直接报警并跳过避免生成空掩码污染后续流程。文件名带区域名后续做连接矩阵时可以直接按名字排序不用记编号。参数上region_map建议从标签表自动生成减少手抄错误如果标签表是 CSV用 pandas 读进来转字典即可。批量生成后质量自检不能省。我习惯做三件事一是统计每个掩码的体素数和该区域在文献里的近似体积对比数量级差太多就回头查二是随机抽几个掩码叠到图谱上目视看边界是否贴合三是检查所有掩码的仿射矩阵是否一致不一致的说明中间某步头信息被改了。这三步做完再往流程里灌数据心里才有底。最后说个我自己的习惯每次提完掩码先拿一个已知功能的区域跑一遍种子点连接看看连接模式是否符合预期。如果连出来的图和教科书里的默认网络对不上八成是掩码或空间对齐出了问题。这个自检比任何日志都直观。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑