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,则只能按采集时间做粗对齐,并在报告中注明对齐精度。
五、一个可复用的读取流程
下面给出一个最小可用的读取与组织流程,适用于标准 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'
}
这段代码的重点不在语法,而在于它把“像素—物理—时间”三者的对应关系显式记录下来。后续做面积测量、速度计算或时间强度曲线时,都依赖这三个量。
六、工程化读取的检查清单
在把数据交给下游算法之前,建议逐项确认:像素数据是否已正确解码并与 NumberOfFrames 一致;物理坐标换算参数是否完整;时间轴来源是否明确;多模态序列是否已按同一时间基准对齐;sidecar 元数据是否已生成。任何一项缺失,都应在数据质量报告中标记,而不是默认“应该没问题”。
完成本节的数据获取与组织后,后续的滤波、分割、定量分析才有可靠输入。工程化读取的价值不在于“能打开文件”,而在于把像素、物理量和时间三者的对应关系完整保留下来,使每一步处理都可追溯、可复现。