Python医学影像处理实战:跨越五种格式的互操作指南
如果你刚开始接触医学影像处理,面对一堆后缀名各异的文件——.nii、.nii.gz、.nrrd、.dcm,甚至还有需要可视化的.jpg——感到无从下手,那你来对地方了。在临床研究和算法开发中,我们常常被困在格式的迷宫里:医院PACS系统只认DICOM,而你精心调校的深度学习模型却只接受NIfTI输入;好不容易拿到了标注好的.nrrd分割文件,却需要叠加到原始的DICOM序列上进行可视化验证。这种格式间的壁垒,不仅消耗时间,更可能引入难以察觉的数据错误。
这篇文章就是为你准备的实战手册。我不会仅仅给你几段孤立的代码,而是带你深入理解这五种核心格式(NII, NII.GZ, NRRD, DICOM, JPG)的本质差异、适用场景,并构建一套可复用、可扩展的Python代码工具箱。我们将使用SimpleITK和nibabel这两个业界公认的强力组合,解决从DICOM到NIfTI的批量转换、NRRD与NIfTI的互操作、以及最终生成可用于报告和展示的JPG图像等一系列实际问题。无论你是放射科的研究员、医学影像算法的工程师,还是生物信息学的学生,掌握这套流程,都能让你在数据预处理环节游刃有余,把精力真正聚焦在核心的分析与创新上。
1. 理解战场:五种医学影像格式深度解析
在开始写代码之前,我们必须先弄清楚要对付的“敌人”是谁。每种医学影像格式都有其诞生的背景、设计的哲学和擅长的领域,盲目转换就像用螺丝刀去敲钉子,事倍功半。
NIfTI (.nii) 可以说是学术研究的“通用语”。它的全称是Neuroimaging Informatics Technology Initiative,最初为脑成像设计,现已广泛应用于各类医学影像。NIfTI格式最大的优点是结构简单清晰。一个.nii文件通常包含两部分:一个描述图像数据本身(像素值、维度、数据类型)的头文件(header),以及实际的图像数据体(data)。它将三维(甚至四维)的体数据(volume)存储在一个文件里,管理起来非常方便。其头文件中包含了关键的空间信息,如体素尺寸(voxel spacing)、图像方向(orientation)和原点坐标(origin),这对于保证图像在空间中的几何正确性至关重要。
注意:NIfTI格式默认不包含任何患者标识信息(Patient Health Information, PHI),这使其在分享研究数据时更符合隐私规范,但也意味着从DICOM转换时会丢失大量的元数据。
NIfTI with Gzip压缩 (.nii.gz) 就是上述.nii文件的压缩版本。医学影像动辄数百MB甚至上GB,使用gzip压缩可以显著减少存储空间和网络传输时间,通常能压缩到原大小的1/3到1/2。nibabel库在读写时能自动处理压缩和解压,对用户几乎是透明的。但需要留意,在频繁读写超大文件时,解压缩会带来额外的I/O开销。
NRRD (.nrrd),即“Nearly Raw Raster Data”,由美国犹他大学科学计算与成像研究所(SCI)开发。它比NIfTI更灵活,可以支持更复杂的数据类型、非规则网格、多分量向量场(如扩散张量成像DTI)等。NRRD文件采用清晰的文本头文件加独立数据段的格式,可读性更好。许多先进的图像处理工具和放射组学(Radiomics)库(如PyRadiomics)原生支持NRRD格式。在需要保留复杂元数据或处理特殊数据类型时,NRRD是比NIfTI更好的选择。
DICOM (.dcm) 是临床环境的“统治者”。它不仅仅是一个图像格式,更是一个庞大的信息标准体系。每个DICOM文件(一个切片)都包含一个极其丰富的头文件,里面塞满了成千上万个标签(tags),涵盖患者信息(姓名、ID、生日)、检查参数(扫描序列、设备型号)、图像几何信息以及像素数据。DICOM是面向对象的,一系列相关的切片组成一个“系列”(Series),进而组成“研究”(Study)。它的强大在于其无与伦比的元数据完整性和标准化,但复杂性也令人望而生畏。
JPG/PNG 是最终的展示格式。三维的体数据需要被“切片”成二维图像才能被人眼观察。通常我们会提取轴状位(axial)、冠状位(coronal)和矢状位(sagittal)的视图。将医学影像转为JPG时,有一个关键概念:窗宽窗位。由于医学图像(如CT)的原始像素值范围(例如,CT值在-1000到+3000 HU)远超普通显示器能显示的0-255范围,需要通过窗宽(Window Width)和窗位(Window Center)来映射感兴趣的区域,就像调节显微镜的焦距和亮度一样。
为了更直观地对比,我将这五种格式的核心特性总结如下表:
| 特性维度 | NIfTI (.nii) | NIfTI Gzip (.nii.gz) | NRRD (.nrrd) | DICOM (.dcm) | JPG/PNG |
|---|---|---|---|---|---|
| 主要场景 | 学术研究、算法开发 | 研究数据存储与传输 | 高级图像处理、放射组学 | 临床诊断、医院PACS | 可视化、报告、展示 |
| 数据结构 | 单文件体数据 | 压缩的单文件体数据 | 文本头+数据,灵活 | 单切片文件,含丰富元数据 | 二维光栅图像 |
| 元数据 | 基础几何信息 | 同.nii | 灵活,可扩展 | 极其丰富(患者、设备、扫描参数) | 无(或仅图像参数) |
| 隐私考虑 | 通常匿名 | 同.nii | 通常匿名 | 包含敏感PHI | 可匿名化 |
| 文件大小 | 大 | 较小(压缩后) | 中等 | 极大(众多小文件) | 小 |
| 处理复杂度 | 低 | 低(自动解压) | 中等 | 高 | 很低 |
理解了这些差异,我们就能在格式转换时做出明智决策:从DICOM到NIfTI是为了喂给算法;从NIfTI到NRRD可能是为了使用特定分析工具;而从任何体数据格式到JPG,都是为了让人能看得懂。
2. 搭建你的Python处理环境
工欲善其事,必先利其器。一个稳定、隔离的Python环境是避免依赖冲突的基石。我强烈推荐使用conda或venv创建虚拟环境。
# 使用conda创建并激活环境(推荐)
conda create -n medimg python=3.9
conda activate medimg
# 或者使用venv
python -m venv medimg_venv
# 在Windows上激活:medimg_venv\Scripts\activate
# 在Linux/Mac上激活:source medimg_venv/bin/activate
接下来,安装核心的“四大金刚”库。我建议使用国内镜像源以加速下载。
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple SimpleITK nibabel opencv-python-headless numpy pydicom
简单解释一下每个库的职责:
- SimpleITK: 功能极其强大的医学影像处理库,是ITK的简化Python接口。它在DICOM读写、图像滤波、分割、配准等方面是首选,尤其擅长处理DICOM系列和复杂的空间变换。
- nibabel: 专门为读写NIfTI格式而生,轻量、高效,是处理
.nii和.nii.gz文件的事实标准。 - opencv-python (cv2): 计算机视觉的瑞士军刀,我们主要用它来读写JPG/PNG图像,以及进行一些简单的图像叠加操作。
- numpy: 底层数组操作的基石,所有图像数据在内存中最终都以numpy数组的形式存在。
- pydicom: 一个纯Python的DICOM文件解析器,当你需要精细地读取或修改DICOM头文件中的某个特定标签时,它会非常有用。
安装完成后,可以通过一个简单的脚本验证环境是否就绪:
import SimpleITK as sitk
import nibabel as nib
import cv2
import numpy as np
import pydicom
print(f"SimpleITK version: {sitk.Version_VersionString()}")
print(f"NiBabel version: {nib.__version__}")
print(f"OpenCV version: {cv2.__version__}")
# 如果所有导入都没有报错,恭喜你,环境搭建成功!
3. 核心转换实战:从DICOM到NIfTI/NRRD
这是临床研究中最常见的需求:把从医院PACS系统导出的成百上千个DICOM文件,转换成一个方便算法处理的NIfTI或NRRD体数据文件。
3.1 DICOM转NIfTI:保留空间信息的关键
DICOM转换的难点在于,一个三维扫描通常由几十甚至几百个独立的.dcm文件(每个文件代表一个切片)组成。SimpleITK的ImageSeriesReader能智能地识别并排序同一个系列下的所有DICOM文件。
下面这个函数是我在多个项目中打磨出来的,它不仅能转换,还显式地保留了至关重要的空间信息(原点、间距、方向),这是很多初学者容易忽略,导致后续配准或分析出错的地方。
import os
import SimpleITK as sitk
def dcm_series_to_nifti(dicom_dir, output_path):
"""
将包含一个DICOM系列的文件夹转换为单个NIfTI文件。
参数:
dicom_dir (str): 包含.dcm文件的文件夹路径。
output_path (str): 输出的.nii或.nii.gz文件路径。
"""
# 1. 创建阅读器并获取系列内所有文件
reader = sitk.ImageSeriesReader()
# 获取系列ID,通常一个文件夹只有一个系列
series_ids = reader.GetGDCMSeriesIDs(dicom_dir)
if not series_ids:
raise ValueError(f"在目录 {dicom_dir} 中未找到DICOM系列。")
# 使用第一个找到的系列(对于单一系列文件夹)
series_file_names = reader.GetGDCMSeriesFileNames(dicom_dir, series_ids[0])
# 2. 设置文件名并执行读取
reader.SetFileNames(series_file_names)
image = reader.Execute() # 此时image是一个SimpleITK图像对象
# 3. 关键:获取并打印空间信息,便于调试
origin = image.GetOrigin()
spacing = image.GetSpacing()
direction = image.GetDirection()
print(f"[信息] 图像尺寸: {image.GetSize()}")
print(f"[信息] 原点: {origin}")
print(f"[信息] 体素间距 (mm): {spacing}")
print(f"[信息] 方向矩阵: {direction}")
# 4. 写入NIfTI文件
sitk.WriteImage(image, output_path)
print(f"[成功] 已保存至: {output_path}")
return image
如何使用它?
假设你的DICOM数据放在./data/patient01/CT文件夹下,想输出为./output/patient01_ct.nii.gz,只需调用:
dcm_series_to_nifti('./data/patient01/CT', './output/patient01_ct.nii.gz')
SimpleITK会根据输出路径的后缀名自动决定是否进行gzip压缩。
提示:有时DICOM文件夹可能包含多个扫描序列(如T1、T2加权)。
GetGDCMSeriesIDs会返回所有系列的ID。你可以遍历它们,或根据series_file_names的数量来判断,确保你转换的是正确的序列。
3.2 DICOM转NRRD:为高级分析做准备
转换到NRRD的流程与NIfTI几乎相同,因为SimpleITK的WriteImage函数会根据输出文件后缀自动选择格式。NRRD格式有时能更好地保留一些元数据。
def dcm_series_to_nrrd(dicom_dir, output_path):
"""
将DICOM系列转换为NRRD格式。
参数与dcm_series_to_nifti类似。
"""
# ... (前面的读取步骤与dcm_series_to_nifti完全相同)
reader = sitk.ImageSeriesReader()
series_ids = reader.GetGDCMSeriesIDs(dicom_dir)
series_file_names = reader.GetGDCMSeriesFileNames(dicom_dir, series_ids[0])
reader.SetFileNames(series_file_names)
image = reader.Execute()
# 确保输出路径以.nrrd结尾
if not output_path.lower().endswith('.nrrd'):
output_path += '.nrrd'
sitk.WriteImage(image, output_path)
print(f"[成功] DICOM系列已转换为NRRD: {output_path}")
return image
3.3 使用dcm2niix工具(备选方案)
除了SimpleITK,社区还有一个广受赞誉的专用工具——dcm2niix。它本身是用C++编写的命令行工具,但有Python封装(pydcm2niix)。它在处理多模态、多B值扩散加权成像(DWI)等复杂DICOM数据时非常稳健,并能自动生成BIDS格式的JSON侧文件。
# 安装Python封装
pip install pydcm2niix
在Python中调用:
import pydcm2niix
# 基本转换
converter = pydcm2niix.DCM2NIIX()
# 这会在后台调用dcm2niix二进制文件
# 具体参数设置请参考pydcm2niix文档,它提供了更精细的控制
对于绝大多数标准CT/MR转换,SimpleITK已经足够。但如果你遇到特别棘手的DICOM数据,或者需要BIDS输出,dcm2niix是你的备用王牌。
4. NIfTI、NRRD、JPG之间的自由穿梭
有了体数据文件后,我们经常需要在不同研究格式间转换,并最终生成可视化的图片。
4.1 NIfTI与NRRD互转:一招通用的“格式翻译器”
由于SimpleITK的读写功能与格式无关,我们可以写出一个非常通用的转换函数。
def convert_medical_image(input_path, output_path):
"""
通用医学图像格式转换器。
支持 .nii, .nii.gz, .nrrd 之间的互相转换。
参数:
input_path (str): 输入文件路径。
output_path (str): 输出文件路径。
"""
# 读取图像(SimpleITK自动识别格式)
image = sitk.ReadImage(input_path)
# 写入图像(根据output_path后缀决定格式)
sitk.WriteImage(image, output_path)
# 验证转换
print(f"[转换完成] {input_path} -> {output_path}")
print(f" 输出格式: {output_path.split('.')[-1]}")
print(f" 图像尺寸: {image.GetSize()}")
return image
# 示例:将.nii.gz转为.nrrd
convert_medical_image('input_image.nii.gz', 'output_image.nrrd')
# 示例:将.nrrd转为.nii
convert_medical_image('segmentation.nrrd', 'segmentation.nii')
这个函数的强大之处在于其通用性。无论输入是什么格式(只要SimpleITK支持),它都能正确读取,并按照你指定的输出后缀进行转换。空间信息在转换过程中会被完整保留。
4.2 体数据转JPG:窗宽窗位的艺术
将三维体数据保存为一系列二维JPG切片,是可视化、制作幻灯片和报告的基础。这里的关键操作是窗宽窗位调整和切片方向选择。
首先,我们实现一个核心的窗宽窗位变换函数:
import numpy as np
def apply_window_level(image_array, window_center, window_width):
"""
对医学图像数组应用窗宽窗位变换,将其映射到0-255范围。
参数:
image_array (np.ndarray): 原始图像数据,通常是3D数组的一个切片。
window_center (float): 窗位(C)。
window_width (float): 窗宽(W)。
返回:
np.ndarray: 映射到[0, 255]范围的uint8数组。
"""
# 计算窗口下限和上限
window_min = window_center - 0.5 * window_width
window_max = window_center + 0.5 * window_width
# 线性映射:将[window_min, window_max]映射到[0, 1]
normalized = (image_array - window_min) / window_width
# 将超出窗口的值截断到[0, 1]区间
normalized = np.clip(normalized, 0, 1)
# 映射到0-255并转换为8位整数
output = (normalized * 255).astype(np.uint8)
return output
现在,我们可以利用这个函数,将整个体数据保存为JPG切片。下面的函数提供了保存轴状位(axial)切片的选项,这是最常用的视图。
import os
import nibabel as nib
import cv2
def save_volume_slices_to_jpg(volume_path, output_dir,
axis=2, start_slice=None, end_slice=None,
window_center=None, window_width=None):
"""
将3D医学体数据保存为一系列JPG切片。
参数:
volume_path (str): .nii, .nii.gz 或 .nrrd 文件路径。
output_dir (str): 保存JPG的文件夹。
axis (int): 沿哪个轴切片。0=sagittal, 1=coronal, 2=axial (默认)。
start_slice (int): 起始切片索引。
end_slice (int): 结束切片索引。
window_center (float): 窗位。若为None,则使用数据的最小最大值进行归一化。
window_width (float): 窗宽。若为None,则使用数据范围。
"""
# 1. 加载数据
# 使用nibabel加载,因为它能直接返回numpy数组
img = nib.load(volume_path)
data = img.get_fdata().astype(np.float32)
# 2. 准备输出目录
os.makedirs(output_dir, exist_ok=True)
# 3. 确定切片范围
if start_slice is None:
start_slice = 0
if end_slice is None:
end_slice = data.shape[axis]
# 4. 确定窗宽窗位(如果未提供,则进行简单归一化)
if window_center is None or window_width is None:
# 简单归一化到0-255
data_min, data_max = data.min(), data.max()
window_center = (data_max + data_min) / 2
window_width = data_max - data_min
print(f"[提示] 未提供窗宽窗位,使用全范围归一化: C={window_center:.1f}, W={window_width:.1f}")
# 5. 遍历切片并保存
for i in range(start_slice, min(end_slice, data.shape[axis])):
# 沿指定轴提取切片
if axis == 0:
slice_data = data[i, :, :]
elif axis == 1:
slice_data = data[:, i, :]
else: # axis == 2
slice_data = data[:, :, i]
# 应用窗宽窗位变换
slice_8bit = apply_window_level(slice_data, window_center, window_width)
# 有时需要旋转/翻转以符合放射科观看习惯(取决于数据采集方向)
# 这是一个常见调整,你可能需要根据具体数据修改
slice_8bit = np.rot90(slice_8bit, k=-1) # 逆时针旋转90度
slice_8bit = np.flipud(slice_8bit) # 上下翻转
# 保存为JPG
output_path = os.path.join(output_dir, f'slice_{i:04d}.jpg')
cv2.imwrite(output_path, slice_8bit)
print(f"[完成] 已保存 {end_slice - start_slice} 张切片至 {output_dir}")
实际应用示例(CT图像): 对于CT图像,典型的软组织窗位是40 HU,窗宽是400 HU。肺窗则可能是-600 HU和1500 HU。
# 使用软组织窗保存CT图像的中间100个轴向切片
save_volume_slices_to_jpg(
volume_path='ct_scan.nii.gz',
output_dir='./jpg_output/soft_tissue',
axis=2,
start_slice=100,
end_slice=200,
window_center=40, # 窗位
window_width=400 # 窗宽
)
4.3 处理分割标签(勾画文件)
分割结果(如肿瘤勾画)通常也保存为NIfTI或NRRD格式,但像素值是离散的标签(如0代表背景,1代表肿瘤)。将其转为JPG时需要不同的处理——通常直接乘以255来获得高对比度的二值或伪彩图像。
def save_segmentation_slices_to_jpg(seg_path, output_dir, axis=2):
"""
将分割标签体数据保存为JPG。标签值将直接乘以255。
适用于二值或整数型标签。
"""
img = nib.load(seg_path)
data = img.get_fdata()
# 确保是整数类型
data = data.astype(np.uint8)
os.makedirs(output_dir, exist_ok=True)
for i in range(data.shape[axis]):
if axis == 2:
slice_data = data[:, :, i]
# ... 其他轴同理
# 标签乘以255以获得可视化的高亮
slice_vis = slice_data * 255
# 同样的旋转翻转调整(需与原始图像保持一致)
slice_vis = np.rot90(slice_vis, k=-1)
slice_vis = np.flipud(slice_vis)
output_path = os.path.join(output_dir, f'seg_slice_{i:04d}.jpg')
cv2.imwrite(output_path, slice_vis)
print(f"分割标签切片已保存至 {output_dir}")
4.4 图像融合:将分割结果叠加到原始图像上
这是生成最终演示材料的最后一步——将分割的轮廓或区域半透明地叠加到原始的灰度图像上,直观地展示分析结果。
def overlay_segmentation(original_jpg_dir, segmentation_jpg_dir, output_dir, alpha=0.3):
"""
将分割JPG图像以半透明方式叠加到原始JPG图像上。
参数:
original_jpg_dir (str): 原始图像JPG文件夹。
segmentation_jpg_dir (str): 分割图像JPG文件夹。
output_dir (str): 融合结果输出文件夹。
alpha (float): 分割图像的叠加透明度(0-1之间)。
"""
os.makedirs(output_dir, exist_ok=True)
# 获取文件名并排序,确保一一对应
orig_files = sorted([f for f in os.listdir(original_jpg_dir) if f.endswith('.jpg')])
seg_files = sorted([f for f in os.listdir(segmentation_jpg_dir) if f.endswith('.jpg')])
if len(orig_files) != len(seg_files):
print(f"[警告] 文件数量不匹配: 原始图像{len(orig_files)}张, 分割图{len(seg_files)}张")
# 可以改为按最小数量处理
min_len = min(len(orig_files), len(seg_files))
orig_files = orig_files[:min_len]
seg_files = seg_files[:min_len]
for orig_name, seg_name in zip(orig_files, seg_files):
orig_path = os.path.join(original_jpg_dir, orig_name)
seg_path = os.path.join(segmentation_jpg_dir, seg_name)
img_orig = cv2.imread(orig_path)
img_seg = cv2.imread(seg_path)
if img_orig is None or img_seg is None:
print(f"[跳过] 无法读取图像: {orig_name} 或 {seg_name}")
continue
# 确保两张图像尺寸相同
if img_orig.shape != img_seg.shape:
img_seg = cv2.resize(img_seg, (img_orig.shape[1], img_orig.shape[0]))
# 核心叠加操作:cv2.addWeighted
# 公式: dst = src1 * (1 - alpha) + src2 * alpha
# 这里让分割图(src2)以alpha透明度叠加到原图(src1)上
overlayed = cv2.addWeighted(img_orig, 1 - alpha, img_seg, alpha, 0)
# 保存结果
output_path = os.path.join(output_dir, orig_name)
cv2.imwrite(output_path, overlayed)
print(f"[完成] 图像融合完成,结果保存在 {output_dir}")
使用这个函数,你可以轻松生成用于组会汇报或论文插图的专业级融合图像。调整alpha参数可以控制分割区域的显眼程度。
5. 逆向工程:从NIfTI回到DICOM
这是一个更具挑战性但有时必不可少的需求:将处理后的NIfTI数据(例如,分割结果或经过算法增强的图像)导回DICOM格式,以便将其上传回医院的PACS系统进行临床查看或与原始研究对比。
为什么这很难? 因为DICOM不仅仅包含像素数据,它要求一套完整、合规的元数据。创建一个“合法”的DICOM文件需要填充大量的必要标签。
社区项目 nii2dcm 专门为解决这个问题而生。它试图简化从NIfTI到DICOM的创建过程。
# 安装nii2dcm
pip install nii2dcm
基本使用方式:
# 在命令行中,指定NIfTI文件和输出目录,以及DICOM类型(如MR)
nii2dcm input.nii.gz ./dicom_output/ --dicom-type MR
在Python脚本中,你也可以调用其功能。但请注意,逆向转换通常需要一份原始的DICOM文件作为元数据参考(使用-r参数),以填充患者ID、研究日期等必需信息。
# 假设使用nii2dcm的命令行接口(通过subprocess调用)
import subprocess
def nifti_to_dicom_series(nifti_path, output_dir, dicom_type='MR', reference_dicom=None):
"""
使用nii2dcm工具将NIfTI转换为DICOM系列。
这是一个高级功能,需要仔细处理元数据。
"""
cmd = ['nii2dcm', nifti_path, output_dir, '-d', dicom_type]
if reference_dicom:
cmd.extend(['-r', reference_dicom])
try:
result = subprocess.run(cmd, capture_output=True, text=True, check=True)
print("转换成功:", result.stdout)
except subprocess.CalledProcessError as e:
print("转换失败:", e.stderr)
raise
重要提醒:将任何处理后的数据导回临床PACS系统前,必须与医院的医学工程或IT部门充分沟通,确保符合所有数据安全和合规性要求。错误的元数据可能导致严重的临床混淆。
6. 构建可复用的处理流水线
掌握了各个模块后,我们可以将它们串联起来,形成一个完整的、可配置的处理流水线。这能极大提升批量数据处理的效率。
下面是一个示例流水线类,它封装了从DICOM到最终可视化JPG的完整流程:
import os
from pathlib import Path
class MedicalImagePipeline:
"""
医学影像处理流水线。
"""
def __init__(self, base_output_dir="./pipeline_output"):
self.base_output_dir = Path(base_output_dir)
self.base_output_dir.mkdir(exist_ok=True)
def run_full_pipeline(self, dicom_dir, patient_id,
window_center=40, window_width=400):
"""
执行完整流水线:DICOM -> NIfTI -> JPG切片。
参数:
dicom_dir: 输入DICOM文件夹。
patient_id: 患者/研究标识符,用于组织输出。
window_center: CT窗位。
window_width: CT窗宽。
"""
print(f"\n=== 开始处理流水线: {patient_id} ===")
# 1. 定义输出路径
nifti_path = self.base_output_dir / f"{patient_id}.nii.gz"
jpg_dir = self.base_output_dir / f"{patient_id}_jpg_slices"
# 2. DICOM 转 NIfTI
print("步骤1: DICOM -> NIfTI...")
try:
import SimpleITK as sitk
reader = sitk.ImageSeriesReader()
series_ids = reader.GetGDCMSeriesIDs(str(dicom_dir))
series_files = reader.GetGDCMSeriesFileNames(str(dicom_dir), series_ids[0])
reader.SetFileNames(series_files)
image = reader.Execute()
sitk.WriteImage(image, str(nifti_path))
print(f" 已保存: {nifti_path}")
except Exception as e:
print(f" DICOM转换失败: {e}")
return
# 3. NIfTI 转 JPG 切片
print("步骤2: NIfTI -> JPG切片...")
try:
save_volume_slices_to_jpg(
volume_path=str(nifti_path),
output_dir=str(jpg_dir),
window_center=window_center,
window_width=window_width
)
except Exception as e:
print(f" JPG切片生成失败: {e}")
return
print(f"=== 流水线完成。结果位于: {self.base_output_dir} ===\n")
def batch_process(self, data_dict):
"""
批量处理多个患者/研究。
data_dict: {patient_id: {'dicom_dir': path, 'window_center': 40, ...}}
"""
for pid, config in data_dict.items():
self.run_full_pipeline(
dicom_dir=config['dicom_dir'],
patient_id=pid,
window_center=config.get('window_center', 40),
window_width=config.get('window_width', 400)
)
# 使用示例
if __name__ == "__main__":
pipeline = MedicalImagePipeline("./my_study_output")
# 单个处理
pipeline.run_full_pipeline(
dicom_dir="/医院数据/患者A/CT扫描",
patient_id="PatientA_CT",
window_center=40,
window_width=400
)
# 批量处理
batch_config = {
"Study_01": {"dicom_dir": "./data/study1", "window_center": 50, "window_width": 350},
"Study_02": {"dicom_dir": "./data/study2", "window_center": 40, "window_width": 400},
}
pipeline.batch_process(batch_config)
这个流水线框架可以根据你的具体需求轻松扩展,例如加入质量检查步骤、自动分割、或生成PDF报告等功能。
医学影像格式处理看似繁琐,但一旦建立起清晰的概念体系和一套属于自己的代码工具集,它就会变成一项高效且充满成就感的工作。记住,关键在于理解数据流动的脉络:从临床的DICOM,到研究的NIfTI/NRRD,再到人可读的JPG,每一步转换都服务于特定的目的。多动手实验,用你自己的数据跑通整个流程,遇到报错时仔细阅读空间信息、数据类型和文件路径,这些经验远比死记硬背代码更有价值。
560


&spm=1001.2101.3001.11974&articleId=153805777&d=1&t=3&u=4fb837f5c4124bf8ba56341dcb900293)

被折叠的 条评论
为什么被折叠?



