2. 医学影像数据集的标准化组织与元数据管理

在医学影像实验中,数据往往来自不同扫描仪、不同序列、不同被试和不同时间点。如果只把文件按“日期+姓名”堆在硬盘里,后续复现实验时几乎无法回答三个关键问题:这份数据属于谁、它是怎样采集的、它经过了哪些处理。标准化组织与元数据管理要解决的正是这三个问题——用统一的格式承载像素与头信息,用统一的目录结构承载被试与实验条件,用机器可读的元数据文件承载扫描参数与处理历史。

一、三种主流格式的元数据提取

DICOM 是临床采集的原始格式,元数据以“标签(Tag)”形式存在,例如 (0010,0010) 为患者姓名、(0018,0080) 为重复时间 TR、(0018,0081) 为回波时间 TE、(0020,0032) 为图像位置。一个被试的一次扫描通常包含成百上千个 DICOM 文件,每个文件都带一份头信息,因此提取时需要先按 SeriesInstanceUID 分组,再逐组读取代表性标签。Python 中常用 pydicom:

import pydicom, glob
files = glob.glob("sub-01/anat/*.dcm")
ds = pydicom.dcmread(files[0])
meta = {
    "PatientID": ds.PatientID,
    "StudyDate": ds.StudyDate,
    "Manufacturer": ds.Manufacturer,
    "TR": float(ds.RepetitionTime),
    "TE": float(ds.EchoTime),
    "SliceThickness": float(ds.SliceThickness),
    "SeriesDescription": ds.SeriesDescription,
}

NIfTI 是分析阶段最常用的格式,像素数据与头信息分离在 .nii 或 .nii.gz 中,头信息包含 pixdim(体素尺寸)、qform/sform(空间变换矩阵)、datatype 等。用 nibabel 可读取:

import nibabel as nib
img = nib.load("sub-01_T1w.nii.gz")
hdr = img.header
print(hdr.get_zooms())      # 体素尺寸 (1.0, 1.0, 1.0)
print(img.affine)           # 4x4 仿射矩阵

NRRD 常见于扩散与显微影像,头信息以键值对文本保存,如 space directions、kinds、sizes。三种格式的元数据字段并不一一对应,因此标准化提取的核心是建立一张“字段映射表”,把不同来源的字段统一映射到同一套命名,例如把 DICOM 的 RepetitionTime、NIfTI 的 pixdim[4]、NRRD 的 space directions 分别映射到统一的 RepetitionTime 与 VoxelSize。

DICOM / NIfTI / NRRD 元数据提取与字段映射 DICOM (0010,0010) PatientName (0018,0080) TR (0018,0081) TE (0020,0032) ImagePosition NIfTI pixdim → VoxelSize qform/sform → Affine datatype → DataType descrip → Description NRRD space directions kinds sizes spacings 统一元数据字段(JSON/YAML) SubjectID · SessionID · Modality · TR · TE · VoxelSize Affine · Manufacturer · ScanDate · TaskName · Condition ProcessingHistory · SourceFormat · Checksum

二、BIDS 目录结构设计

BIDS(Brain Imaging Data Structure)用一套约定俗成的命名规则把“谁、什么时候、做了什么、采集了什么”编码进路径。一个符合 BIDS 的最小数据集如下:

ds-memory/
├── dataset_description.json
├── participants.tsv
├── participants.json
├── sub-01/
│   ├── anat/
│   │   ├── sub-01_T1w.nii.gz
│   │   └── sub-01_T1w.json
│   └── func/
│       ├── sub-01_task-nback_bold.nii.gz
│       ├── sub-01_task-nback_bold.json
│       └── sub-01_task-nback_events.tsv
└── sub-02/
    └── ...

命名中的 sub-、ses-、task-、run-、acq- 等键值对称为实体(entity),顺序固定,不能随意调换。每个影像文件旁必须有一个同名 JSON 侧车文件(sidecar),记录该序列的采集参数;participants.tsv 记录被试级信息,如年龄、性别、组别;dataset_description.json 记录数据集名称、BIDS 版本、许可证。这样,任何工具只要按 BIDS 规则解析路径,就能自动定位数据与参数,无需人工询问。

三、用 JSON/YAML 记录扫描参数与实验条件

侧车 JSON 是 BIDS 的核心。以 N-back 任务为例,sub-01_task-nback_bold.json 可写为:

{
  "TaskName": "nback",
  "RepetitionTime": 2.0,
  "EchoTime": 0.03,
  "FlipAngle": 90,
  "SliceTiming": [0.0, 0.5, 1.0, 1.5],
  "Manufacturer": "Siemens",
  "ManufacturersModelName": "Prisma",
  "MagneticFieldStrength": 3.0,
  "Instructions": "判断当前刺激是否与2个试次前相同"
}

实验条件则记录在 events.tsv 中,每行一个试次,列包括 onset、duration、trial_type、response_time、correct。例如:

onset	duration	trial_type	response_time	correct
0.0	1.5	1-back	0.82	1
3.0	1.5	2-back	1.05	0
6.0	1.5	1-back	0.76	1

YAML 更适合人工编辑的配置,例如扫描协议模板:

protocol: T1w_MPRAGE
parameters:
  TR: 2300
  TE: 2.98
  TI: 900
  flip_angle: 9
  voxel_size: [1.0, 1.0, 1.0]
  slices: 176

四、标准化流程与校验

把原始数据整理成 BIDS 通常分四步:第一,用 dcm2niix 把 DICOM 转成 NIfTI 并生成初始 JSON;第二,按 BIDS 实体重命名文件并放入对应目录;第三,补全侧车 JSON 中缺失的字段,如 TaskName、SliceTiming;第四,用 bids-validator 校验。校验命令与典型输出如下:

$ bids-validator ds-memory/
[WARN] sub-01/func/sub-01_task-nback_bold.json
       SliceTiming 长度与切片数不一致
[ERROR] sub-02/anat/ 缺少 T1w 侧车 JSON

校验通过后,还应生成一份 dataset_description.json,并计算每个文件的校验和写入 checksums.tsv,以便复现时确认数据未被改动。元数据管理不是一次性工作:每次新增被试、修改参数或重新处理,都应同步更新侧车文件与处理历史,使数据集始终处于“可被他人直接理解并复现”的状态。

实践建议:先用 dcm2niix -b y 批量转换并保留 BIDS 侧车,再用 pybids 的 BIDSLayout 读取元数据,可避免手工整理路径与字段。