关于 pydicom-medical-imaging
pydicom-medical-imaging 是面向医学影像开发者和研究人员的 Python DICOM 工作流技能,涵盖元数据与像素数据访问、匿名化、格式转换及影像序列处理为 3D 数组。
主要功能
- 01读取和修改 DICOM 文件
- 02提取元数据和像素数组
- 03转换为 PNG、JPEG 或 TIFF
- 04移除可识别个人身份的信息
- 05将影像序列构建为 3D 数组
适用场景
- 01批量提取 DICOM 目录元数据
- 02准备影像以供可视化
- 03对患者文件进行去标识化
- 04将 CT 或 MRI 序列处理为体积
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ jaechang-hits/ sciagent-skills --skill pydicom-medical-imaging安装后在 Claude Code 中输入/pydicom-medical-imaging确认已加载。
附带文件references/dicom_standards.md
详细介绍
Pydicom 医学影像
概述
Pydicom 是一个纯 Python 库,可用于读取、写入和修改 DICOM(医学数字成像和通信)文件。它提供对 DICOM 元数据标签和 NumPy 数组形式像素数据的访问,支持 CT、MRI、X 射线、超声及其他医学影像模态。该库可处理压缩和未压缩的传输语法,并支持通过可选编解码器插件进行解码。
适用场景
- 读取 DICOM 文件并提取元数据(患者信息、检查参数、成像设置)
- 从 DICOM 图像中提取像素数据,以进行分析或可视化
- 将 DICOM 图像转换为标准格式(PNG、JPEG、TIFF)
- 通过移除受保护的健康信息(PHI)对 DICOM 文件进行匿名化处理
- 修改 DICOM 元数据标签,以重新标记或更正数据
- 从头创建 DICOM 文件(例如,将 NumPy 数组封装为 DICOM)
- 将 CT/MRI 序列处理为 3D 体积数组,以进行重建
- 从多帧 DICOM(电影/视频)中提取帧
- 对于全切片病理图像(SVS、NDPI),请改用
histolab-wsi-processing - 对于 NIfTI 神经影像体积(.nii/.nii.gz),请改用
nibabel
先决条件
- Python 包:
pydicom、numpy、pillow - 可选编解码器:
pylibjpeg+pylibjpeg-libjpeg(JPEG)、pylibjpeg-openjpeg(JPEG 2000)、python-gdcm(大多数格式) - 数据格式:符合 NEMA PS3.10 标准的 DICOM 文件(.dcm、.ima 或无扩展名)
pip install pydicom numpy pillow
# Optional: compression codec handlers (install as needed)
pip install pylibjpeg pylibjpeg-libjpeg # JPEG Baseline/Lossless
pip install pylibjpeg-openjpeg # JPEG 2000
pip install python-gdcm # Comprehensive codec support
快速开始
import pydicom
import numpy as np
# Read a DICOM file
ds = pydicom.dcmread("scan.dcm")
# Access metadata
print(f"Patient: {ds.PatientName}, Modality: {ds.Modality}")
print(f"Size: {ds.Rows}x{ds.Columns}, Bits: {ds.BitsAllocated}")
# Extract pixel data as NumPy array
pixels = ds.pixel_array
print(f"Pixel array shape: {pixels.shape}, dtype: {pixels.dtype}")
# Apply windowing for display (CT/MR)
from pydicom.pixel_data_handlers.util import apply_voi_lut
display = apply_voi_lut(pixels, ds)
print(f"Windowed range: [{display.min()}, {display.max()}]")
核心 API
模块 1:读取和访问元数据
使用属性名称或标签表示法读取 DICOM 文件并访问元数据。
import pydicom
# Read DICOM file (defer_size delays loading large elements)
ds = pydicom.dcmread("scan.dcm")
ds_lazy = pydicom.dcmread("large.dcm", defer_size="1 KB")
# Access by attribute name (standard DICOM keywords)
print(f"Patient Name: {ds.PatientName}")
print(f"Study Date: {ds.StudyDate}")
print(f"Modality: {ds.Modality}")
print(f"Image Size: {ds.Rows} x {ds.Columns}")
# Access by tag number (group, element)
print(f"Patient ID: {ds[0x0010, 0x0020].value}")
# Safe access with getattr (avoids AttributeError)
slice_thick = getattr(ds, 'SliceThickness', 'N/A')
print(f"Slice Thickness: {slice_thick}")
# Iterate all elements
for elem in ds:
if elem.VR != 'SQ': # Skip sequences
print(f" {elem.tag} {elem.keyword}: {elem.value}")
# Read DICOM directory (DICOMDIR)
from pydicom.filereader import dcmread
dicomdir = pydicom.dcmread("DICOMDIR")
for record in dicomdir.DirectoryRecordSequence:
if record.DirectoryRecordType == "IMAGE":
ref_file = record.ReferencedFileID
# ref_file is a list of path components
print(f"Image file: {'/'.join(ref_file)}")
模块 2:提取像素数据
将像素数据提取为 NumPy 数组,支持灰度、彩色、窗宽窗位和多帧数据。
import pydicom
import numpy as np
from pydicom.pixel_data_handlers.util import apply_voi_lut, apply_modality_lut
ds = pydicom.dcmread("ct_scan.dcm")
# Basic pixel extraction
pixels = ds.pixel_array # NumPy ndarray
print(f"Shape: {pixels.shape}, dtype: {pixels.dtype}")
# Apply Modality LUT (rescale to Hounsfield Units for CT)
hu_pixels = apply_modality_lut(pixels, ds)
print(f"HU range: [{hu_pixels.min()}, {hu_pixels.max()}]")
# Apply VOI LUT (windowing for display contrast)
display = apply_voi_lut(hu_pixels, ds)
print(f"Display range: [{display.min()}, {display.max()}]")
# Manual windowing (when VOI LUT metadata is absent)
center, width = 40, 400 # Soft tissue window
lower = center - width / 2
upper = center + width / 2
windowed = np.clip(hu_pixels, lower, upper)
print(f"Manual window [{lower}, {upper}]")
# Color images (ultrasound, photos) — handle YBR color space
import pydicom
ds = pydicom.dcmread("ultrasound.dcm")
pixels = ds.pixel_array
print(f"Color shape: {pixels.shape}") # (rows, cols, 3)
# Convert YBR to RGB if needed
photo_interp = ds.PhotometricInterpretation
if "YBR" in photo_interp:
from pydicom.pixel_data_handlers.util import convert_color_space
rgb = convert_color_space(pixels, photo_interp, "RGB")
print(f"Converted {photo_interp} -> RGB")
# Multi-frame (cine/video DICOM)
ds_multi = pydicom.dcmread("cine.dcm")
frames = ds_multi.pixel_array # Shape: (num_frames, rows, cols)
print(f"Frames: {frames.shape[0]}, Frame size: {frames.shape[1:]}")
模块 3:图像转换
将 DICOM 像素数据转换为标准图像格式,以便可视化和导出。
import pydicom
import numpy as np
from PIL import Image
from pydicom.pixel_data_handlers.util import apply_voi_lut
ds = pydicom.dcmread("scan.dcm")
pixels = ds.pixel_array
# Apply windowing
display = apply_voi_lut(pixels, ds)
# Normalize to 8-bit for standard image formats
if display.dtype != np.uint8:
dmin, dmax = display.min(), display.max()
if dmax > dmin:
normalized = ((display - dmin) / (dmax - dmin) * 255).astype(np.uint8)
else:
normalized = np.zeros_like(display, dtype=np.uint8)
else:
normalized = display
# Save as PNG
img = Image.fromarray(normalized)
img.save("output.png")
print(f"Saved output.png ({img.size[0]}x{img.size[1]})")
# Save as JPEG with quality control
img.save("output.jpg", quality=95)
# Batch conversion: directory of DICOM files to PNG
import pydicom
import numpy as np
from PIL import Image
from pathlib import Path
from pydicom.pixel_data_handlers.util import apply_voi_lut
def dicom_to_image(dcm_path, out_path, fmt="PNG"):
"""Convert a single DICOM file to standard image format."""
ds = pydicom.dcmread(str(dcm_path))
pixels = apply_voi_lut(ds.pixel_array, ds)
dmin, dmax = float(pixels.min()), float(pixels.max())
if dmax > dmin:
norm = ((pixels - dmin) / (dmax - dmin) * 255).astype(np.uint8)
else:
norm = np.zeros_like(pixels, dtype=np.uint8)
Image.fromarray(norm).save(str(out_path))
dcm_dir = Path("dicom_files/")
out_dir = Path("images/")
out_dir.mkdir(exist_ok=True)
for dcm_file in sorted(dcm_dir.glob("*.dcm")):
out_file = out_dir / f"{dcm_file.stem}.png"
dicom_to_image(dcm_file, out_file)
print(f"Converted: {dcm_file.name} -> {out_file.name}")
模块 4:修改元数据和匿名化处理
修改 DICOM 属性并移除受保护的健康信息,以进行去标识化处理。
import pydicom
from pydicom.uid import generate_uid
ds = pydicom.dcmread("original.dcm")
# Modify attributes
ds.PatientName = "Anonymous"
ds.PatientID = "ANON001"
ds.InstitutionName = "Research Lab"
# Add new attribute
ds.add_new(0x00081030, 'LO', 'Research Study') # Study Description
# Delete attribute
if 'PatientBirthDate' in ds:
del ds.PatientBirthDate
# Generate new UIDs for de-identification
ds.StudyInstanceUID = generate_uid()
ds.SeriesInstanceUID = generate_uid()
ds.SOPInstanceUID = generate_uid()
# Save modified file (preserves original)
ds.save_as("modified.dcm")
print(f"Saved modified.dcm with new UIDs")
# PHI anonymization: remove patient-identifying tags (DICOM PS3.15 Annex E)
import pydicom
from pydicom.uid import generate_uid
PHI_TAGS = [ # Core set — extend per institutional policy
'PatientName', 'PatientID', 'PatientBirthDate', 'PatientSex',
'PatientAge', 'PatientWeight', 'PatientAddress',
'OtherPatientIDs', 'OtherPatientNames',
'InstitutionName', 'InstitutionAddress',
'ReferringPhysicianName', 'PerformingPhysicianName',
'OperatorsName', 'StudyID', 'AccessionNumber',
]
def anonymize_dicom(ds, prefix="ANON"):
"""Remove PHI tags and assign anonymous identifiers."""
for tag in PHI_TAGS:
if hasattr(ds, tag): delattr(ds, tag)
ds.PatientName, ds.PatientID = f"{prefix}_Patient", f"{prefix}_ID"
ds.StudyInstanceUID = generate_uid()
ds.SeriesInstanceUID = generate_uid()
ds.SOPInstanceUID = generate_uid()
return ds
ds = pydicom.dcmread("patient_scan.dcm")
anonymize_dicom(ds, prefix="STUDY001").save_as("anonymized.dcm")
print("Anonymized: PHI tags removed, UIDs replaced")
模块 5:从头编写 DICOM
使用 NumPy 数组创建具有适当元数据的新 DICOM 文件。
import pydicom, numpy as np, datetime
from pydicom.dataset import FileDataset, FileMetaDataset
from pydicom.uid import ExplicitVRLittleEndian, generate_uid
# File meta header
file_meta = FileMetaDataset()
file_meta.MediaStorageSOPClassUID = '1.2.840.10008.5.1.4.1.1.2' # CT Image Storage
file_meta.MediaStorageSOPInstanceUID = generate_uid()
file_meta.TransferSyntaxUID = ExplicitVRLittleEndian
# Dataset with required attributes
ds = FileDataset("new.dcm", {}, file_meta=file_meta, preamble=b"\x00" * 128)
ds.SOPClassUID = file_meta.MediaStorageSOPClassUID
ds.SOPInstanceUID = file_meta.MediaStorageSOPInstanceUID
ds.StudyInstanceUID, ds.SeriesInstanceUID = generate_uid(), generate_uid()
ds.Modality, ds.Manufacturer = 'CT', 'Research'
ds.is_little_endian, ds.is_implicit_VR = True, False
dt = datetime.datetime.now()
ds.ContentDate, ds.ContentTime = dt.strftime('%Y%m%d'), dt.strftime('%H%M%S.%f')
# Pixel data from NumPy array
pixels = np.random.randint(0, 4096, (512, 512), dtype=np.uint16)
ds.Rows, ds.Columns = pixels.shape
ds.BitsAllocated, ds.BitsStored, ds.HighBit = 16, 12, 11
ds.PixelRepresentation = 0 # Unsigned
ds.SamplesPerPixel, ds.PhotometricInterpretation = 1, 'MONOCHROME2'
ds.PixelData = pixels.tobytes()
ds.save_as("new.dcm")
print(f"Created DICOM: {ds.Rows}x{ds.Columns}, {ds.BitsStored}-bit")
模块 6:序列处理和 3D 体积
加载 DICOM 序列,按空间位置排序,并堆叠成 3D NumPy 数组。
import pydicom
import numpy as np
from pathlib import Path
def load_dicom_series(series_dir):
"""Load and sort a DICOM series by slice position."""
dcm_files = []
for f in Path(series_dir).iterdir():
try:
ds = pydicom.dcmread(str(f))
dcm_files.append(ds)
except Exception:
continue # Skip non-DICOM files
if not dcm_files:
raise ValueError(f"No DICOM files found in {series_dir}")
# Sort by ImagePositionPatient (z-coordinate) or InstanceNumber
try:
dcm_files.sort(key=lambda x: float(x.ImagePositionPatient[2]))
except (AttributeError, IndexError):
dcm_files.sort(key=lambda x: int(x.InstanceNumber))
print(f"Loaded {len(dcm_files)} slices, "
f"Series: {getattr(dcm_files[0], 'SeriesDescription', 'N/A')}")
return dcm_files
def series_to_volume(dcm_files):
"""Stack sorted DICOM slices into a 3D NumPy array."""
from pydicom.pixel_data_handlers.util import apply_modality_lut
slices = []
for ds in dcm_files:
pixels = apply_modality_lut(ds.pixel_array, ds)
slices.append(pixels)
volume = np.stack(slices, axis=0)
# Calculate voxel spacing
pixel_spacing = dcm_files[0].PixelSpacing
if len(dcm_files) > 1:
try:
z0 = float(dcm_files[0].ImagePositionPatient[2])
z1 = float(dcm_files[1].ImagePositionPatient[2])
slice_spacing = abs(z1 - z0)
except (AttributeError, IndexError):
slice_spacing = float(getattr(dcm_files[0], 'SliceThickness', 1.0))
else:
slice_spacing = float(getattr(dcm_files[0], 'SliceThickness', 1.0))
spacing = (slice_spacing, float(pixel_spacing[0]), float(pixel_spacing[1]))
print(f"Volume shape: {volume.shape}, Spacing (z,y,x): {spacing} mm")
return volume, spacing
# Usage
dcm_files = load_dicom_series("ct_series/")
volume, spacing = series_to_volume(dcm_files)
print(f"HU range: [{volume.min()}, {volume.max()}]")
核心概念
DICOM 数据模型
DICOM 以四级层次结构组织医学影像数据:
| 层级 | 关键 UID | 描述 |
|---|---|---|
| 患者 | PatientID | 单个患者 |
| 检查 | StudyInstanceUID | 一次成像检查(可能包含多个模态) |
| 序列 | SeriesInstanceUID | 一组采集序列(例如,T1 加权 MRI) |
| 实例(图像) | SOPInstanceUID | 一张图像/一帧(一个 DICOM 文件) |
每个 DICOM 文件包含一个实例,其元数据标签按组组织。标签使用 (group, element) 表示法(例如,PatientName 对应 (0010,0010))。
传输语法和压缩
传输语法定义了 DICOM 数据的编码方式(字节序、VR 编码、像素压缩):
| 传输语法 | UID | 压缩 | 所需处理器 |
|---|---|---|---|
| 隐式 VR 小端 | 1.2.840.10008.1.2 | 无 | 否 |
| 显式 VR 小端 | 1.2.840.10008.1.2.1 | 无 | 否 |
| 显式 VR 大端 | 1.2.840.10008.1.2.2 | 无 | 否 |
| JPEG 基线 | 1.2.840.10008.1.2.4.50 | 有损 JPEG | pylibjpeg |
| JPEG 无损 | 1.2.840.10008.1.2.4.70 | 无损 JPEG | pylibjpeg |
| JPEG 2000 无损 | 1.2.840.10008.1.2.4.90 | 无损 J2K | pylibjpeg-openjpeg |
| JPEG 2000 | 1.2.840.10008.1.2.4.91 | 有损 J2K | pylibjpeg-openjpeg |
| RLE 无损 | 1.2.840.10008.1.2.5 | RLE | pydicom(内置) |
检查传输语法:ds.file_meta.TransferSyntaxUID。访问压缩文件的 pixel_array 前,请先安装相应的处理器。
常用 DICOM 标签
最常访问的标签(完整目录见 references/dicom_standards.md):
| 标签 | 关键字 | VR | 描述 |
|---|---|---|---|
| (0008,0060) | Modality | CS | CT、MR、US、CR、DX、PT、NM |
| (0010,0010) | PatientName | PN | 患者全名 |
| (0010,0020) | PatientID | LO | 患者标识符 |
| (0008,0020) | StudyDate | DA | 检查日期(YYYYMMDD) |
| (0020,000D) | StudyInstanceUID | UI | 检查唯一标识符 |
| (0020,0013) | InstanceNumber | IS | 序列中的图像编号 |
| (0020,0032) | ImagePositionPatient | DS | x、y、z 位置(mm) |
| (0028,0010) | Rows | US | 图像像素高度 |
| (0028,0011) | Columns | US | 图像像素宽度 |
| (0028,0030) | PixelSpacing | DS | 行、列间距(mm) |
| (0028,1050) | WindowCenter | DS | 显示窗中心 |
| (0028,0004) | PhotometricInterpretation | CS | MONOCHROME1/2、RGB、YBR_FULL |
| (0028,0100) | BitsAllocated | US | 8 或 16 |
值表示(VR)
VR 定义每个元素的数据类型。最常见的类型(完整表格见 references/dicom_standards.md):
| VR | 名称 | Python 类型 | 示例 |
|---|---|---|---|
| CS | 代码字符串 | str | "CT"、"MR" |
| DA | 日期 | str | "20240115" |
| DS | 十进制字符串 | DSfloat | "1.5" |
| IS | 整数字符串 | IS | "42" |
| LO | 长字符串 | str | "Study description" |
| PN | 人名 | PersonName | "Doe^John" |
| SQ | 序列 | Sequence | 嵌套数据集 |
| UI | 唯一标识符 | UID | "1.2.840..." |
| US | 无符号短整数 | int | 512 |
常见工作流
工作流 1:批量提取元数据到 CSV
目标:遍历包含 DICOM 文件的目录,提取关键元数据字段,并导出为 CSV 清单。
import pydicom
import pandas as pd
from pathlib import Path
FIELDS = ['PatientID', 'Modality', 'StudyDate', 'SeriesDescription',
'StudyInstanceUID', 'SeriesInstanceUID', 'InstanceNumber',
'Rows', 'Columns', 'SliceThickness', 'BitsStored']
def extract_metadata(dcm_path):
"""Extract key metadata from a DICOM file."""
try:
ds = pydicom.dcmread(str(dcm_path), stop_before_pixels=True)
except Exception as e:
return {"file": str(dcm_path), "error": str(e)}
rec = {"file": str(dcm_path)}
for f in FIELDS:
rec[f] = str(getattr(ds, f, ''))
return rec
# Scan directory recursively
dicom_dir = Path("dicom_archive/")
records = [extract_metadata(f) for f in sorted(dicom_dir.rglob("*")) if f.is_file()]
df = pd.DataFrame(records)
df.to_csv("dicom_manifest.csv", index=False)
print(f"Extracted metadata from {len(df)} files")
print(f"Modalities: {df['Modality'].value_counts().to_dict()}")
print(f"Unique patients: {df['PatientID'].nunique()}")
工作流 2:将 CT 序列转换为 3D 体数据并可视化
目标:加载 CT 序列,构建以 Hounsfield 单位表示的 3D 体数据,并显示轴向、矢状和冠状视图。
import pydicom, numpy as np, matplotlib.pyplot as plt
from pathlib import Path
from pydicom.pixel_data_handlers.util import apply_modality_lut
# Load and sort series by z-position
dcm_files = []
for f in sorted(Path("ct_series/").glob("*")):
try: dcm_files.append(pydicom.dcmread(str(f)))
except Exception: continue
dcm_files.sort(key=lambda x: float(x.ImagePositionPatient[2]))
# Stack into 3D volume (Hounsfield Units)
volume = np.stack([apply_modality_lut(ds.pixel_array, ds) for ds in dcm_files])
ps = dcm_files[0].PixelSpacing
z_sp = abs(float(dcm_files[1].ImagePositionPatient[2])
- float(dcm_files[0].ImagePositionPatient[2]))
print(f"Volume: {volume.shape}, spacing: {z_sp:.2f}x{float(ps[0]):.2f}x{float(ps[1]):.2f} mm")
# Display orthogonal views (soft tissue window)
vmin, vmax = -160, 240 # center=40, width=400
fig, axes = plt.subplots(1, 3, figsize=(18, 6))
mid = [s // 2 for s in volume.shape]
axes[0].imshow(volume[mid[0]], cmap='gray', vmin=vmin, vmax=vmax)
axes[0].set_title(f"Axial (slice {mid[0]})")
axes[1].imshow(volume[:, mid[1], :], cmap='gray', vmin=vmin, vmax=vmax,
aspect=z_sp/float(ps[1]))
axes[1].set_title(f"Coronal")
axes[2].imshow(volume[:, :, mid[2]], cmap='gray', vmin=vmin, vmax=vmax,
aspect=z_sp/float(ps[0]))
axes[2].set_title(f"Sagittal")
for ax in axes: ax.axis('off')
plt.tight_layout()
plt.savefig("orthogonal_views.png", dpi=150, bbox_inches='tight')
print("Saved orthogonal_views.png")
工作流 3:批量匿名化流程
目标:匿名化目录中的所有 DICOM 文件,并使用新的 UID 保留序列结构。
import pydicom
from pydicom.uid import generate_uid
from pathlib import Path
PHI_TAGS = [
'PatientName', 'PatientID', 'PatientBirthDate', 'PatientSex',
'PatientAge', 'PatientWeight', 'PatientAddress',
'OtherPatientIDs', 'OtherPatientNames',
'InstitutionName', 'InstitutionAddress',
'ReferringPhysicianName', 'PerformingPhysicianName',
'OperatorsName', 'PhysiciansOfRecord', 'StudyID', 'AccessionNumber',
]
uid_map = {} # Preserves study/series relationships across files
def get_mapped_uid(uid):
if uid not in uid_map: uid_map[uid] = generate_uid()
return uid_map[uid]
input_dir, output_dir = Path("original_dicoms/"), Path("anonymized_dicoms/")
output_dir.mkdir(exist_ok=True)
count, errors = 0, 0
for dcm_path in sorted(input_dir.rglob("*")):
if not dcm_path.is_file(): continue
try: ds = pydicom.dcmread(str(dcm_path))
except Exception: errors += 1; continue
for tag in PHI_TAGS:
if hasattr(ds, tag): delattr(ds, tag)
ds.PatientName, ds.PatientID = "ANONYMOUS", "ANON"
ds.StudyInstanceUID = get_mapped_uid(ds.StudyInstanceUID)
ds.SeriesInstanceUID = get_mapped_uid(ds.SeriesInstanceUID)
ds.SOPInstanceUID = generate_uid()
ds.save_as(str(output_dir / f"anon_{count:06d}.dcm"))
count += 1
print(f"Anonymized {count} files, {errors} errors, {len(uid_map)} UIDs mapped")
关键参数
| 参数 | 模块 | 默认值 | 范围 / 选项 | 作用 |
|---|---|---|---|---|
defer_size | dcmread | None | "1 KB"、"1 MB"、整数(字节) | 延迟加载大于指定大小的元素 |
stop_before_pixels | dcmread | False | True/False | 跳过像素数据加载(仅元数据) |
force | dcmread | False | True/False | 即使缺少 DICOM 前导码也强制读取 |
WindowCenter | 窗宽窗位 | 来自文件 | 任意数值 | 显示窗中心(CT 中为 HU) |
WindowWidth | 窗宽窗位 | 来自文件 | > 0 | 显示窗宽度 |
BitsAllocated | 写入 | 16 | 8、16、32 | 每像素分配的位数 |
BitsStored | 写入 | 12 | 1-BitsAllocated | 实际有效位数 |
PixelRepresentation | 写入 | 0 | 0(无符号)、1(有符号) | 像素值的符号属性 |
PhotometricInterpretation | 写入 | "MONOCHROME2" | MONOCHROME1、MONOCHROME2、RGB、YBR_FULL | 色彩空间 |
TransferSyntaxUID | 写入 | Explicit VR LE | 参见传输语法表 | 编码格式 |
最佳实践
-
仅处理元数据时使用
stop_before_pixels=True:避免在仅读取标签时加载大型像素数组。在批量提取元数据时,这能显著加快速度。ds = pydicom.dcmread("scan.dcm", stop_before_pixels=True) -
对于可选标签,始终使用带默认值的
getattr():DICOM 文件中包含哪些标签差异很大。直接访问属性时,如果标签缺失,就会引发AttributeError。# Good thickness = getattr(ds, 'SliceThickness', None) # Bad — will crash on files without SliceThickness thickness = ds.SliceThickness -
处理 CT 数据时,先应用 Modality LUT,再进行窗宽窗位调整:原始像素值是存储值;先应用
apply_modality_lut()将其转换为 Hounsfield 单位,再应用apply_voi_lut()以供显示。 -
访问压缩像素数据前先安装压缩处理器:检查
ds.file_meta.TransferSyntaxUID并安装相应的处理器。如果未安装处理器就尝试访问pixel_array,会引发RuntimeError。 -
每次修改文件后都生成新的 UID:修改后绝不要重复使用原始 SOPInstanceUID。使用
pydicom.uid.generate_uid()确保 UID 全局唯一。 -
使用
save_as(),而不是覆盖原文件:始终保存到新路径,以保留原始数据。DICOM 存档可能会进行完整性检查,若原文件被就地修改,检查可能会失败。 -
进行 3D 重建时,按 ImagePositionPatient 排序序列:
InstanceNumber并不总是可靠。ImagePositionPatient[2](z 坐标)可确保轴位 CT/MR 序列按正确的物理顺序排列。
常用方法
方法:处理压缩与解压缩
适用场景:读取压缩的 DICOM 文件,或压缩未压缩的文件。
import pydicom
ds = pydicom.dcmread("compressed.dcm")
ts = ds.file_meta.TransferSyntaxUID
# Check if compressed
print(f"Transfer Syntax: {ts}")
print(f"Compressed: {ts.is_compressed}")
# Decompress in-place (requires appropriate handler installed)
if ts.is_compressed:
ds.decompress()
print(f"Decompressed to {ds.file_meta.TransferSyntaxUID}")
# Access pixel data (works after decompression)
pixels = ds.pixel_array
print(f"Pixel shape: {pixels.shape}")
方法:处理 DICOM 序列
适用场景:访问嵌套数据结构,例如引用序列、手术代码或协议元素。
import pydicom
ds = pydicom.dcmread("structured.dcm")
# Access sequence elements (SQ VR = list of datasets)
if hasattr(ds, 'ReferencedStudySequence'):
for item in ds.ReferencedStudySequence:
print(f" Referenced Study: {item.ReferencedSOPInstanceUID}")
# Access procedure code sequence
if hasattr(ds, 'ProcedureCodeSequence'):
for code in ds.ProcedureCodeSequence:
print(f" Procedure: {code.CodeMeaning} ({code.CodeValue})")
# Create a sequence when writing DICOM
from pydicom.dataset import Dataset
from pydicom.sequence import Sequence
ref_item = Dataset()
ref_item.ReferencedSOPClassUID = '1.2.840.10008.5.1.4.1.1.2'
ref_item.ReferencedSOPInstanceUID = pydicom.uid.generate_uid()
ds.ReferencedImageSequence = Sequence([ref_item])
方法:提取多帧图像
适用场景:从电影、视频或增强型多帧 DICOM 文件中提取单独的帧。
import pydicom
import numpy as np
from PIL import Image
from pathlib import Path
ds = pydicom.dcmread("multiframe.dcm")
frames = ds.pixel_array # Shape: (num_frames, rows, cols) or (num_frames, rows, cols, 3)
num_frames = frames.shape[0]
print(f"Total frames: {num_frames}, Frame size: {frames.shape[1:]}")
# Extract all frames as images
out_dir = Path("frames/")
out_dir.mkdir(exist_ok=True)
for i in range(num_frames):
frame = frames[i]
# Normalize to uint8
if frame.dtype != np.uint8:
fmin, fmax = frame.min(), frame.max()
if fmax > fmin:
frame = ((frame - fmin) / (fmax - fmin) * 255).astype(np.uint8)
else:
frame = np.zeros_like(frame, dtype=np.uint8)
Image.fromarray(frame).save(out_dir / f"frame_{i:04d}.png")
print(f"Extracted {num_frames} frames to {out_dir}/")
故障排查
| 问题 | 原因 | 解决方案 |
|---|---|---|
RuntimeError: No available image handler | 压缩传输语法未安装编解码器 | 安装相应的处理器:pip install pylibjpeg pylibjpeg-libjpeg(JPEG)、pip install pylibjpeg-openjpeg(JPEG 2000),或 pip install python-gdcm(所有格式) |
AttributeError: 'Dataset' has no attribute 'X' | 文件中不存在可选标签 | 使用 getattr(ds, 'X', default),或在访问前检查 'X' in ds |
InvalidDicomError: File is missing DICOM preamble | 非标准 DICOM 文件或非 DICOM 文件 | 尝试使用 pydicom.dcmread(path, force=True) 跳过前导码检查 |
| 像素值错误(没有负 HU 值) | 未应用 Modality LUT | 在分析前应用 apply_modality_lut(pixels, ds)——原始存储值与实际 HU 值不同 |
| 图像显示反转(明暗颠倒) | MONOCHROME1 光度解释 | 检查 ds.PhotometricInterpretation;对于 MONOCHROME1,使用 np.max(pixels) - pixels 进行反转 |
加载大型序列时出现 MemoryError | 一次性将所有切片加载到内存中 | 分批处理切片;扫描元数据时使用 stop_before_pixels=True;对于大型元素,使用 defer_size |
| 3D 体数据的切片顺序不一致 | 按 InstanceNumber 而非位置排序 | 按 ImagePositionPatient[2] 排序,以确保物理顺序正确 |
| PatientName 中出现乱码 | 字符编码不匹配 | 检查 SpecificCharacterSet 标签;pydicom 会自动解码,但有些文件的字符集声明不正确 |
设置 PixelData 时出现 TypeError | 像素数组的字节格式错误 | 使用 pixel_array.tobytes(),并确保数据类型与 BitsAllocated 匹配(16 位使用 uint16) |
捆绑资源
references/dicom_standards.md
整合的 DICOM 标签目录和传输语法参考。包含患者人口统计信息、检查/序列标识、图像几何信息、像素数据编码、窗宽窗位参数以及模态专用标签(CT 亨氏参数、MR 序列参数、设备标识、时间)的完整标签表。包含压缩类型和处理器安装信息在内的完整传输语法 UID 表。值表示(VR)类型参考。
- 涵盖内容:common_tags.md 中的所有标签类别(患者、检查、序列、图像、像素、窗宽窗位、CT 专用、MR 专用、设备、时间);transfer_syntaxes.md 中的所有传输语法 UID、压缩格式和处理器映射;VR 类型目录
- 迁移至其他部分的内容:核心概念中的关键标签表(最常见的 20 个);核心概念中的传输语法摘要表(最常见的 8 个);核心概念中的 VR 摘要表
- 省略内容:分步处理器安装教程(已在先决条件和故障排除中介绍);对每个标签冗长的文字说明(表格内容清晰,无需额外说明)
原始文件处置情况(5 个文件):
- SKILL.md(434 行)——已迁移:概述、工作流、最佳实践、常见问题经过重组,纳入新的 SKILL.md 格式
- references/common_tags.md(229 行)——(b)已整合:关键的 20 个标签表纳入核心概念;完整目录纳入 references/dicom_standards.md
- references/transfer_syntaxes.md(353 行)——(b)已整合:摘要表(8 种语法)纳入核心概念;完整 UID 表和处理器详情纳入 references/dicom_standards.md
- scripts/anonymize_dicom.py(138 行)——(c)已吸收:PHI 标签列表和匿名化函数纳入核心 API 模块 4;批处理流水线纳入工作流 3
- scripts/dicom_to_image.py(173 行)——(c)已吸收:转换函数纳入核心 API 模块 3(dicom_to_image helper);批量转换纳入模块 3 的第二个代码块
- scripts/extract_metadata.py(174 行)——(c)已吸收:元数据提取函数纳入工作流 1(批量提取到 CSV)
相关技能
- histolab-wsi-processing——全切片病理图像处理(SVS、NDPI);用于数字病理图块提取
- nibabel (planned)——用于脑部 MRI 体积分析的 NIfTI 神经影像格式
- matplotlib-scientific-plotting——用于 DICOM 图像和 3D 体积切片的出版级可视化
参考资料
- Pydicom 文档——官方 API 参考和用户指南
- Pydicom GitHub 仓库——源代码、示例和问题跟踪器
- DICOM 标准浏览器——交互式 DICOM 标签和 IOD 参考
- pylibjpeg 文档——适用于 pydicom 的 JPEG 编解码器插件