如何快速读取大型DICOM文件像素数组首帧为numpy数组
问题描述
现有一大型DICOM文件,需要尽可能快速地将其像素数组的第一帧读取为numpy数组格式。
初始读取代码如下:
import pydicom directory = # 目标DICOM文件存储路径 dicom = pydicom.dcmread(directory)
参考通用方案读取首帧的代码为:
first_image = dicom.pixel_array[0]
当前手头的DICOM文件像素数组形状为(1691, 555, 800, 3),执行dicom.pixel_array全量读取耗时约12秒。由于需要批量处理大量同类DICOM文件的首帧读取任务,需要效率更高的实现方案。
已尝试的思路
曾尝试直接读取像素数据字节段dicom[0x7fe0,0x0010]._value,计划提取首帧对应字节段后转换为numpy数组,但无法确定首帧在字节段中的准确范围。
参考公开技术资料给出的像素数据长度计算公式:ROWS * COLUMNS * NUMBER_OF_FRAMES * SAMPLES_PER_PIXEL * (BITS_ALLOCATED/8)
在当前场景下计算结果为1332000,该值无法整除实际像素数据长度122320858,无法参考该公式定位首帧字节位置。
长度不匹配的核心原因是:你使用的DICOM文件采用了压缩传输语法(常见如JPEG基线、JPEG2000、RLE无损压缩等),标签(0x7FE0, 0x0010)中存储的不是连续排列的裸像素字节,而是符合DICOM封装格式的压缩帧集合,包含帧偏移表、帧分隔符等额外结构字节,因此裸像素长度公式计算结果必然和实际存储长度不匹配。
不需要手动计算字节偏移,pydicom 2.0及以上版本已经提供了单帧解码的原生优化接口,不会解码所有帧,速度可以提升两个数量级以上:
方案1:最简实现(推荐90%场景使用)
直接调用内置的单帧解码接口,仅解码目标帧,跳过其余1690帧的解码流程:
from pydicom import dcmread from pydicom.pixels import decode_frame # 读取文件元数据,默认不会触发全量像素解码 ds = dcmread("your_dicom_file.dcm") # 仅解码第0帧(即第一帧),返回格式为标准numpy数组 first_frame = decode_frame(ds, frame_number=0)
该方案常规耗时在几十毫秒级别,相比全量读取12秒的耗时提升非常明显,且自动处理传输语法适配、像素位深转换、颜色空间修正等逻辑,返回的numpy数组和全量读取ds.pixel_array[0]的结果完全一致,不需要额外做格式兼容处理。
方案2:极致性能优化(适合超大规模批量处理场景)
如果需要进一步压缩耗时,可以直接提取第一帧的压缩字节段,跳过其他帧的字节读取和解码流程:
from pydicom import dcmread from pydicom.encaps import get_frame_offsets from pydicom.pixels import decode_frame # 可选开启lazy模式,仅加载必要元数据,不读入全量文件内容 ds = dcmread("your_dicom_file.dcm", lazy=True) # 获取所有压缩帧在PixelData中的字节偏移位置 frame_offsets, _ = get_frame_offsets(ds.PixelData) # 切片提取第一帧对应的压缩字节段 first_frame_bytes = ds.PixelData[frame_offsets[0]:frame_offsets[1]] # 仅解码第一帧的压缩数据 first_frame = decode_frame( ds, compressed_data=first_frame_bytes, frame_number=0 )
该方案可以进一步减少内存占用和IO耗时,适合单文件大小超过1GB、批量处理文件量过万的场景。
注意事项
- 不要直接访问
ds.pixel_array属性,该属性会触发所有帧的全量解码,是之前读取耗时过长的核心原因 - 裸像素长度计算公式仅适用于非压缩传输语法(Implicit VR Little Endian、Explicit VR Little Endian、Explicit VR Big Endian三类),所有带压缩的传输语法都不能用这个公式计算帧偏移
- 如果使用的pydicom版本低于2.0,请先升级到最新稳定版,旧版本没有提供单帧解码的优化接口
内容的提问来源于stack exchange,提问作者温泽海

