
本文详解如何使用 pydicom 和 OpenCV 安全裁剪单帧或多帧 DICOM 文件的像素区域,并保留原始元数据与符合标准的 Pixel Data 结构,最终生成可被 ITK-SNAP、3D Slicer 等医学图像工具正常读取的合规 .dcm 文件。
本文详解如何使用 pydicom 和 opencv 安全裁剪单帧或多帧 dicom 文件的像素区域,并保留原始元数据与符合标准的 pixel data 结构,最终生成可被 itk-snap、3d slicer 等医学图像工具正常读取的合规 `.dcm` 文件。
DICOM 文件并非普通图像容器,其 Pixel Data(标签 (7FE0,0010))需严格遵循传输语法(Transfer Syntax)、光度解释(Photometric Interpretation)、位深(Bits Stored)等元数据约束。直接对 Dataset 对象切片(如 ds[500:1500, 500:1500])会失败——因为 pydicom 的 __getitem__ 仅支持按 DICOM 标签(Tag)索引,而非 NumPy 式数组切片。
真正的裁剪操作必须作用于 解码后的像素数组(ds.pixel_array),再将处理结果通过 ds.set_pixel_data() 安全写回,并显式维护关键图像属性。以下是经过验证、兼容单帧/多帧、支持常见压缩/非压缩格式(含 YBR_FULL_422)的稳健方案:
✅ 正确流程:加载 → 提取 → 裁剪 → 重置像素 → 保存
import pydicom
import numpy as np
import cv2
def crop_dicom_dcm(input_path: str, output_path: str,
top: int, left: int, height: int, width: int):
"""
裁剪 DICOM 文件中所有帧的指定矩形区域,并保存为新 DICOM 文件。
Args:
input_path: 原始 .dcm 文件路径
output_path: 输出 .dcm 文件路径
top, left: 裁剪起始坐标(y, x)
height, width: 裁剪区域尺寸
"""
ds = pydicom.dcmread(input_path)
# 1. 获取像素数组 —— 自动处理单帧(2D)和多帧(3D/4D)情况
pixel_array = ds.pixel_array
if pixel_array.ndim == 2:
# 单帧:扩展为 (1, H, W) 便于统一处理
pixel_array = np.expand_dims(pixel_array, axis=0)
elif pixel_array.ndim == 3 and pixel_array.shape[-1] in (3, 4):
# RGB/RGBA 帧(如某些内窥镜或彩色超声),保持通道维度在最后
pass
else:
# 多帧(如 CT/MRI 序列):形状为 (N, H, W)
pass
# 2. 执行逐帧裁剪(确保不越界)
cropped_frames = []
for i, frame in enumerate(pixel_array):
h, w = frame.shape[:2]
# 边界校验
actual_top = max(0, min(top, h - 1))
actual_left = max(0, min(left, w - 1))
actual_h = max(1, min(height, h - actual_top))
actual_w = max(1, min(width, w - actual_left))
cropped = frame[actual_top:actual_top + actual_h,
actual_left:actual_left + actual_w]
cropped_frames.append(cropped)
# 3. 重建像素数组(保持原始维度结构)
cropped_array = np.array(cropped_frames)
if len(cropped_frames) == 1:
cropped_array = cropped_array[0] # 恢复为 2D(单帧)
# 4. 关键:安全设置新像素数据 —— 自动适配 PhotometricInterpretation & BitsStored
# 注意:set_pixel_data() 会自动更新 Rows/Columns/NumberOfFrames 等依赖字段
ds.set_pixel_data(
cropped_array,
photometric_interpretation=ds.get("PhotometricInterpretation", "MONOCHROME2"),
bits_stored=ds.get("BitsStored", 16),
# 若原图含压缩(如 JPEG2000),需额外调用 encapsulate() —— 但本例假设非压缩
)
# 5. 【重要】清除可能冲突的旧像素相关私有标签(可选,增强兼容性)
if hasattr(ds, 'PixelData'):
del ds.PixelData
# 6. 保存(pydicom 会自动选择合适传输语法)
ds.save_as(output_path)
print(f"✅ 裁剪完成:{output_path} | 形状 {cropped_array.shape}")
# 使用示例:裁剪从 (500, 500) 开始、1000×1000 像素的区域
crop_dicom_dcm(
input_path=r"D:\praca\GUMED\dicom\mrxs\1_AORTA\AO_1_014_Masson\3_0.dcm",
output_path=r"D:\praca\GUMED\dicom\mrxs\1_AORTA\AO_1_014_Masson\3_0-crop.dcm",
top=500, left=500, height=1000, width=1000
)⚠️ 关键注意事项
-
避免
openslide兼容性问题:YBR_FULL_422报错源于裁剪后未重置PhotometricInterpretation或色彩空间不匹配。上述代码显式传递该值,并优先使用原始值;若目标软件(如 OpenSlide)仅支持MONOCHROME2,可在set_pixel_data()中强制指定:ds.set_pixel_data(cropped_array, photometric_interpretation="MONOCHROME2", ...)
并确保输入图像是灰度(必要时用
cv2.cvtColor(frame, cv2.COLOR_YCrCb2GRAY)转换)。立即学习“Python免费学习笔记(深入)”;
-
压缩格式处理:若原始 DICOM 使用 JPEG2000(
1.2.840.10008.1.2.4.91)等压缩传输语法,set_pixel_data()不会自动封装。此时需手动调用:from pydicom.encaps import encapsulate from pydicom.uid import JPEGBaseline # ... 裁剪后 if ds.file_meta.TransferSyntaxUID.is_compressed: # 需先编码为 JPEG2000 字节流(需安装 openjpeg 或 pillow) # 此处简化为抛出提示 —— 生产环境建议转为无损压缩或显式解压 raise RuntimeError("压缩 DICOM 裁剪需额外编码步骤,建议先转换为隐式 VR 小端序") 元数据一致性:裁剪后
Rows/Columns已由set_pixel_data()自动更新;但ImagePositionPatient、PixelSpacing等空间信息需根据实际物理坐标重新计算(若需精确配准),本例未修改以保持最小侵入。
✅ 验证输出
生成的 .dcm 文件可通过以下方式快速验证:
# 用 pydicom 读取并检查
ds_out = pydicom.dcmread(output_path)
print("Rows:", ds_out.Rows, "Columns:", ds_out.Columns)
print("Pixel Data shape:", ds_out.pixel_array.shape)
print("PhotometricInterpretation:", ds_out.PhotometricInterpretation)
# 用 ITK-SNAP 或 3D Slicer 直接打开确认可视化正常此方法绕过了 cv2.imread() / PIL 等中间格式转换,全程在 DICOM 标准框架内操作,确保输出文件元数据完整、像素结构合规,完美满足多层 DICOM 数据统一分辨率预处理需求。


















