HCP-MMP1.0图谱脑区掩码提取:从标注图到二值掩码的完整指南

📅 发布时间:2026/10/12 17:28:24
HCP-MMP1.0图谱脑区掩码提取:从标注图到二值掩码的完整指南
简介这份资源是一套面向fMRI影像分析与RSA研究的MATLAB脚本工具专门解决从HCP-MMP1.0分区图谱中提取特定脑区二进制掩码的问题。使用者只需在mmp.csv查找表中定位感兴趣的ROI并记录其编号调用create_HCP_mask()函数即可按左侧、右侧或双侧提取目标区域并支持将多个ROI分别保存或合并为整体还可选择是否对掩码进行膨胀处理。由于图谱位于MNI空间生成的掩码同样保持MNI坐标便于后续配准与统计分析。资源包共7个文件约834KB包含MATLAB函数脚本、ROI查找表CSV、示例NIfTI数据、HCP图谱压缩包及许可证与说明文档结构紧凑、开箱即用。目前已有545人学习下载适合具备一定MATLAB与神经影像基础、希望快速构建ROI掩码的科研人员与学生参考使用。1. 从 HCP-MMP1.0 图谱里抠出你要的那块脑区掩码做 fMRI 预处理的同行大概率都遇到过这个场景手头有一份 HCP-MMP1.0 分区图谱180 个皮层区域按左右半球各 180 个标签编号排布你想做的却只是把其中某一个或某几个区域单独拎出来做成一张二值掩码喂给后续的 ROI 分析、功能连接计算或者种子点时间序列提取。听起来简单但真动手时你会发现图谱本身是一张带标签值的标注图不是掩码直接拿阈值卡一下很容易把相邻区域一起框进来或者因为左右半球编号规则没搞清把对侧同号区域也带进去。这个标题讲的脚本解决的就是这件事从 HCP-MMP1.0 拆分中按你指定的区域编号稳定地提取出干净的二进制掩码文件。它适合正在做 HCP 数据 ROI 分析、需要批量生成掩码的从业者也适合刚接触分区图谱、想先把单个区域跑通的新手。核心诉求就一个给定区域编号输出一张只包含该区域的 0/1 掩码且空间对齐、方向正确、可直接进后续流程。2. 先搞清楚 HCP-MMP1.0 的标签结构和掩码生成逻辑2.1 为什么不能直接对标注图做阈值HCP-MMP1.0 的皮层分区文件通常以标注图形式存在每个体素或顶点携带一个整数值代表它属于哪个区域。左右半球各有一套编号常见做法是左半球用 1 到 180右半球用 181 到 360或者用另一套约定。如果你直接对这张图做“大于某个值”的阈值操作得到的是所有编号大于该值的区域集合而不是单一区域。更麻烦的是标注图里 0 通常表示背景或未标记区域如果你把 0 也当成一个有效标签去处理掩码里会混入大量非脑区体素。所以第一步必须明确你要的是“等于某个标签值”的二值化而不是“大于某个阈值”的分割。另一个容易翻车的点是空间方向。fMRI 数据经过预处理后仿射矩阵和原始图谱可能不一致。如果你在提取掩码时不检查仿射矩阵生成的掩码和功能像在空间上对不齐后续提取的时间序列就是错的而且这种错误往往不会报错只会让结果悄悄偏掉。我一般会在提取前后各做一次仿射矩阵比对确认方向、原点、体素尺寸一致。2.2 提取掩码的最小操作链路整个链路可以拆成四步读取标注图、按标签值二值化、写入掩码文件、校验空间对齐。用 Python 和 nibabel 实现的话核心代码不长但每一步都有参数要盯。import nibabel as nib import numpy as np # 输入HCP-MMP1.0 标注图路径以及目标区域标签值 label_path HCPMMP1_combined.nii.gz target_label 1 # 例如左半球某区域 # 读取标注图同时拿到数据数组和仿射矩阵 img nib.load(label_path) data img.get_fdata().astype(np.int32) affine img.affine # 关键用等于目标标签值做二值化而不是阈值 mask_data (data target_label).astype(np.uint8) # 用原图的仿射矩阵写入掩码保证空间对齐 mask_img nib.Nifti1Image(mask_data, affine) nib.save(mask_img, fmask_label_{target_label}.nii.gz) # 校验打印掩码体素数和仿射矩阵 print(mask voxels:, mask_data.sum()) print(affine:\n, affine)这段代码里data target_label是核心它保证只保留精确等于该标签的体素。astype(np.uint8)把布尔数组转成 0/1方便后续工具读取。写入时复用原图的affine这是避免空间错位的关键。最后打印体素数和仿射矩阵体素数可以用来判断是否提取到了合理大小的区域仿射矩阵用来和功能像比对。参数方面target_label必须和你的图谱编号约定一致。如果你不确定编号规则可以先跑一段统计代码看看标注图里出现了哪些唯一值以及每个值对应的体素数。import numpy as np import nibabel as nib img nib.load(HCPMMP1_combined.nii.gz) data img.get_fdata().astype(np.int32) # 统计所有非零标签及其体素数 labels, counts np.unique(data, return_countsTrue) for lab, cnt in zip(labels, counts): if lab ! 0: print(flabel {lab}: {cnt} voxels)这段统计能帮你快速确认图谱里实际使用的编号范围避免凭记忆写错标签值。如果发现某个标签体素数异常小比如只有几个体素那可能是图谱边缘的噪声或分割残留提取前要心里有数。2.3 左右半球编号的常见约定与核对方法不同来源的 HCP-MMP1.0 文件左右半球编号方式可能不同。常见的有两种一种是左半球 1-180、右半球 181-360另一种是左右半球各自独立编号靠文件或维度区分。如果你拿到的文件没有说明文档最稳妥的办法是结合解剖位置判断。比如提取一个你熟悉的区域把掩码叠加到标准模板上看一眼确认它在正确的半球和大致位置。这一步虽然看起来笨但能省掉后面大量排查时间。我一般会写一个简单的叠加检查脚本把掩码和模板一起读进来输出几个关键切片的叠加图。不需要复杂的可视化库用 matplotlib 画几层就行。import nibabel as nib import numpy as np import matplotlib.pyplot as plt mask nib.load(mask_label_1.nii.gz).get_fdata() template nib.load(MNI152_T1_2mm.nii.gz).get_fdata() # 取三个正交方向的中间层做叠加 z_mid mask.shape[2] // 2 y_mid mask.shape[1] // 2 x_mid mask.shape[0] // 2 fig, axes plt.subplots(1, 3, figsize(12, 4)) axes[0].imshow(template[x_mid, :, :].T, cmapgray) axes[0].imshow(mask[x_mid, :, :].T, alpha0.4, cmapjet) axes[1].imshow(template[:, y_mid, :].T, cmapgray) axes[1].imshow(mask[:, y_mid, :].T, alpha0.4, cmapjet) axes[2].imshow(template[:, :, z_mid].T, cmapgray) axes[2].imshow(mask[:, :, z_mid].T, alpha0.4, cmapjet) plt.savefig(mask_overlay_check.png, dpi150)叠加图能直观看出掩码是否落在预期位置。如果发现掩码跑到了对侧或者位置整体偏移那就要回头检查标签编号和仿射矩阵。这一步花几分钟比后面发现结果不对再返工划算得多。3. 把脚本用起来批量提取与文件命名规范3.1 批量提取多个区域的循环写法实际项目里很少只提取一个区域通常需要一次性生成几十个掩码。这时候把单区域逻辑包成函数外面套一层循环是最直接的做法。import nibabel as nib import numpy as np import os def extract_mask(label_path, target_label, out_dir): img nib.load(label_path) data img.get_fdata().astype(np.int32) mask_data (data target_label).astype(np.uint8) mask_img nib.Nifti1Image(mask_data, img.affine) out_path os.path.join(out_dir, fmask_label_{target_label:03d}.nii.gz) nib.save(mask_img, out_path) return mask_data.sum() label_path HCPMMP1_combined.nii.gz out_dir masks os.makedirs(out_dir, exist_okTrue) # 假设你要提取 1 到 20 号区域 for lab in range(1, 21): n extract_mask(label_path, lab, out_dir) print(flabel {lab}: {n} voxels saved)这里用fmask_label_{target_label:03d}.nii.gz做命名三位数补零保证文件按名称排序时顺序正确。体素数打印出来方便快速扫一眼有没有异常小的区域。如果某个标签体素数为 0说明该标签在图谱里不存在或者你的编号范围写错了需要回头核对。3.2 输出目录和命名约定为什么重要批量生成掩码时文件命名混乱是后期最大的坑之一。我见过有人用mask1.nii.gz、mask_new.nii.gz、mask_final.nii.gz这种命名过两周自己都分不清哪个对应哪个区域。建议统一用mask_label_XXX.nii.gz格式XXX 是标签值补零到三位。这样在后续脚本里可以直接用标签值拼出文件名不需要额外维护映射表。输出目录也建议单独建一个不要和原始图谱、功能像混在一起。目录结构清晰后面做 ROI 提取时遍历掩码文件就能直接跑减少手动指定路径的错误。3.3 提取后的快速校验体素数、仿射矩阵、与功能像对齐生成掩码后至少做三项校验。第一体素数是否在合理范围。不同区域大小差异很大但突然出现个位数或异常大的值都值得怀疑。第二仿射矩阵是否和功能像一致。可以用 nibabel 分别读取掩码和功能像比较affine矩阵或者更直接地用重采样工具把掩码重采样到功能像空间看是否报错。第三叠加检查。抽几个区域做叠加图确认位置正确。import nibabel as nib import numpy as np mask_img nib.load(masks/mask_label_001.nii.gz) func_img nib.load(sub-01_task-rest_bold.nii.gz) # 比较仿射矩阵 print(mask affine:\n, mask_img.affine) print(func affine:\n, func_img.affine) # 比较体素尺寸 print(mask zooms:, mask_img.header.get_zooms()) print(func zooms:, func_img.header.get_zooms())如果仿射矩阵或体素尺寸不一致后续提取时间序列前必须做重采样。常见做法是用nilearn的resample_to_img或flirt把掩码对齐到功能像空间。这一步不做提取出来的时间序列就是错的而且不会报错属于典型的“静默翻车”。4. 避坑与排查提取 HCP-MMP1.0 掩码时最容易翻车的五个点4.1 现象掩码体素数为零或异常小原因通常有三种标签值写错、图谱里该标签不存在、或者数据被意外转成了浮点导致相等判断失效。解决方法是先用np.unique统计所有标签值确认目标标签确实存在然后检查数据类型确保用astype(np.int32)后再做相等比较。如果标签存在但体素数仍然很小可能是该区域在个体图谱中被分割得很碎需要结合具体区域判断是否正常。4.2 现象掩码位置跑到对侧半球这通常是左右半球编号约定没搞清。解决方法是提取一个已知区域做叠加图确认位置。如果确实跑到了对侧检查你的标签值是否应该加上 180 或减去 180。另一种可能是仿射矩阵的符号方向问题导致左右翻转。这时候需要对比图谱和功能像的仿射矩阵确认方向一致性。4.3 现象掩码和功能像叠加时整体偏移原因一般是仿射矩阵不一致或者体素尺寸不同但没有重采样。解决方法是先比较两者的affine和get_zooms()如果不一致用重采样工具把掩码对齐到功能像空间。注意重采样时选择最近邻插值避免掩码出现非 0/1 的中间值。4.4 现象批量提取时部分文件缺失或覆盖原因可能是循环范围写错或者命名冲突导致后一个覆盖前一个。解决方法是检查循环的起始和结束值确保覆盖所有目标标签命名时用标签值补零避免不同标签生成相同文件名。另外写入前可以先检查文件是否已存在必要时加个跳过逻辑防止重复跑时浪费时间。4.5 现象掩码文件能被读取但后续工具报错这通常是数据类型或头信息问题。比如掩码被保存为浮点型某些工具会认为它不是二值掩码。解决方法是保存前确保mask_data是uint8类型并且头信息里的datatype正确。如果工具仍然报错可以用nib.Nifti1Image重新包装一次显式指定数据类型。5. 进阶把掩码提取嵌进预处理流水线的一个实用技巧如果你已经在用某种预处理流水线比如基于 Python 的 nipype 或者自己写的脚本链把掩码提取做成一个可复用的节点会比每次手动跑脚本更稳。我一般会把它写成一个函数输入标注图路径、标签列表、输出目录返回一个掩码路径列表然后直接传给后续的 ROI 提取节点。这样整个流程从掩码生成到时间序列提取一气呵成减少中间手动操作带来的路径错误。另一个实用技巧是给掩码文件加上元数据。NIfTI 头信息里有一些扩展字段虽然不常用但你可以把标签值、区域名称、生成时间写进去方便后期追溯。不过更简单的做法是额外生成一个 CSV 文件记录每个掩码对应的标签值、体素数、仿射矩阵摘要。这个 CSV 在写论文或做质控时特别有用能快速回答“这个掩码是怎么来的”。import csv import nibabel as nib import numpy as np def extract_and_log(label_path, labels, out_dir, log_csv): img nib.load(label_path) data img.get_fdata().astype(np.int32) rows [] for lab in labels: mask_data (data lab).astype(np.uint8) mask_img nib.Nifti1Image(mask_data, img.affine) out_path f{out_dir}/mask_label_{lab:03d}.nii.gz nib.save(mask_img, out_path) rows.append({ label: lab, voxels: int(mask_data.sum()), out_path: out_path }) with open(log_csv, w, newline) as f: writer csv.DictWriter(f, fieldnames[label, voxels, out_path]) writer.writeheader() writer.writerows(rows) extract_and_log(HCPMMP1_combined.nii.gz, range(1, 21), masks, mask_log.csv)这个 CSV 日志看起来不起眼但当你需要复现某个结果或者合作者问你某个掩码怎么生成的时候它能省掉大量回忆和翻找的时间。我自己的习惯是任何批量生成的中间文件都配一份这样的日志哪怕当时觉得没必要。希望帮到你。本文还有配套的精品资源点击获取