2. 超声图像数据获取与工程化读取

超声图像进入算法流水线之前,第一道工程关口不是滤波或分割,而是“把机器里的数据完整、可追溯地读进来”。超声设备输出的数据形态比 CT/MRI 更杂:既有标准 DICOM,也有厂商私有格式;既有单帧静态图,也有动态回放的多帧序列;既有灰度二维图,也有彩色多普勒、脉冲波频谱、组织多普勒等不同模态。本节围绕“读得对、组织得清、元数据不丢、多模态能对齐”四个目标展开。

一、先分清三类数据来源

工程上通常把超声数据来源分为三类。第一类是标准 DICOM 文件,扩展名多为 .dcm,遵循 DICOM PS3 标准,可用 pydicom、dcmtk、GDCM 等库直接解析。第二类是厂商私有格式,例如 GE 的 .vol/.raw、Philips 的 .xml+.raw、Esaote 的 .eco 等,它们往往把像素数据与参数分离存放,需要厂商 SDK 或逆向文档才能正确读取。第三类是导出后的通用图像/视频,如 .png、.jpg、.avi、.mp4,这类数据丢失了大部分采集参数,只能作为“已渲染结果”使用,不能反推物理量。

判断一个文件是否值得进入定量分析流水线,关键看它是否保留了像素数据与采集参数的对应关系。若只有渲染后的位图,则无法做基于射频或包络的定量测量,只能做图像层面的形态分析。

二、DICOM 解析的关键标签

用 pydicom 读取一个超声 DICOM 文件时,最需要关注的标签集中在以下几组:

  • 图像标识:SOPInstanceUID(0008,0018)、SeriesInstanceUID(0020,000E)、StudyInstanceUID(0020,000D),用于唯一标识与去重。
  • 几何与像素:Rows(0028,0010)、Columns(0028,0011)、BitsAllocated(0028,0100)、PixelRepresentation(0028,0103)、PhotometricInterpretation(0028,0004)。
  • 超声专用:SequenceOfUltrasoundRegions(0018,6011),内含 RegionLocationMinX0/MaxX0、RegionLocationMinY0/MaxY0、PhysicalUnitsXDirection/YDirection、PhysicalDeltaX/Y,是把像素坐标换算为物理坐标的核心。
  • 多帧与时间:NumberOfFrames(0028,0008)、FrameTime(0018,1063) 或 FrameTimeVector(0018,1065),用于帧序列的时间轴重建。
  • 模态与参数:Modality(0008,0060) 通常为 US,TransducerFrequency(0018,6030)、Depth、Gain 等影响后续定量解释。

一个常见的坑是:PixelData 可能是压缩的(如 JPEG Baseline、JPEG2000、RLE),直接按原始字节读取会得到错误结果。此时应先检查 TransferSyntaxUID(0002,0010),再决定是否调用解码器。另一个坑是 PhotometricInterpretation 为 YBR_FULL_422 的彩色多普勒图,需要先做色彩空间转换再分离速度分量。

三、帧序列的组织方式

超声动态回放通常以多帧形式存储在一个 DICOM 文件中,NumberOfFrames 可能达到数百甚至上千。工程化读取时,建议把帧序列组织为三维数组 [frame, row, col] 或 [frame, row, col, channel],并同时维护一条时间轴。时间轴可由 FrameTime 累加得到,也可由 FrameTimeVector 直接给出每帧相对时间。

对于跨文件的多帧序列(例如每个心动周期存一个文件),需要按 AcquisitionTime(0008,0032) 或 TriggerTime(0018,1060) 排序后再拼接。若设备未写 TriggerTime,可用帧内时间戳或文件名中的序号作为兜底,但必须在元数据中标注“时间来源为推断”,避免后续误用。

四、元数据提取与多模态管理

元数据提取不是把所有标签都存下来,而是按用途分层。建议至少保留三层:标识层(患者/检查/序列 UID,用于索引与去重)、几何层(区域位置、物理步长、深度、帧时间,用于坐标换算与时间对齐)、采集层(探头频率、增益、模式,用于解释信号强度与伪影)。

多模态数据管理的关键是“同一时空基准”。例如同一患者的一次检查可能同时包含 B 模式、彩色多普勒和脉冲波频谱。B 模式与彩色多普勒通常共享同一帧时间轴,可直接按帧对齐;脉冲波频谱是独立的时间序列,需要按 TriggerTime 或 ECG 门控与图像帧对齐。若设备未提供 ECG,则只能按采集时间做粗对齐,并在报告中注明对齐精度。

工程建议:为每个序列生成一个 sidecar JSON,记录源文件路径、SOPInstanceUID、帧数、时间轴来源、物理坐标换算参数、是否压缩、解码方式。这样即使原始 DICOM 被移动或重命名,也能通过 sidecar 快速重建索引。

五、一个可复用的读取流程

下面给出一个最小可用的读取与组织流程,适用于标准 DICOM 多帧超声:

import pydicom, numpy as np

ds = pydicom.dcmread(path)
arr = ds.pixel_array  # 形状 [frames, rows, cols] 或 [rows, cols]

# 物理坐标换算(以第一个超声区域为例)
region = ds.SequenceOfUltrasoundRegions[0]
dx = float(region.PhysicalDeltaX)  # cm/px
dy = float(region.PhysicalDeltaY)
x0 = int(region.RegionLocationMinX0)
y0 = int(region.RegionLocationMinY0)

# 时间轴
if 'FrameTime' in ds:
    dt_ms = float(ds.FrameTime)
    t = np.arange(arr.shape[0]) * dt_ms
else:
    t = None  # 需从 FrameTimeVector 或外部推断

meta = {
    'sop_uid': ds.SOPInstanceUID,
    'frames': int(ds.NumberOfFrames) if 'NumberOfFrames' in ds else 1,
    'dx_cm': dx, 'dy_cm': dy,
    'origin_px': (x0, y0),
    'time_source': 'FrameTime' if t is not None else 'unknown'
}

这段代码的重点不在语法,而在于它把“像素—物理—时间”三者的对应关系显式记录下来。后续做面积测量、速度计算或时间强度曲线时,都依赖这三个量。

超声数据获取与工程化读取链路 数据来源 标准 DICOM (.dcm) 厂商私有格式 导出图像/视频 解析与解码 TransferSyntax 判断 PixelData 解压 色彩空间转换 帧序列组织 [frame,row,col] 时间轴重建 跨文件排序拼接 元数据分层 标识层 几何层 采集层 关键 DICOM 标签与用途 标识 SOPInstanceUID SeriesInstanceUID 几何 PhysicalDeltaX/Y RegionLocationMin 时间 NumberOfFrames FrameTime / Vector 模态 Modality = US TransducerFrequency 多模态时空对齐 B 模式帧序列 共享帧时间轴 彩色多普勒 按帧对齐 脉冲波频谱 TriggerTime / ECG 门控

六、工程化读取的检查清单

在把数据交给下游算法之前,建议逐项确认:像素数据是否已正确解码并与 NumberOfFrames 一致;物理坐标换算参数是否完整;时间轴来源是否明确;多模态序列是否已按同一时间基准对齐;sidecar 元数据是否已生成。任何一项缺失,都应在数据质量报告中标记,而不是默认“应该没问题”。

常见错误:把压缩的 PixelData 直接当作原始像素读取,得到看似正常但数值错误的图像;或忽略 PhysicalDeltaX/Y,导致面积和速度测量出现系统性偏差。这两类错误在视觉上往往不易察觉,必须通过元数据校验来拦截。

完成本节的数据获取与组织后,后续的滤波、分割、定量分析才有可靠输入。工程化读取的价值不在于“能打开文件”,而在于把像素、物理量和时间三者的对应关系完整保留下来,使每一步处理都可追溯、可复现。