def save_as_dicom(image, original_ds, output_path):
ds = pydicom.Dataset()
for elem in original_ds:
if elem.tag not in [0x7FE00010, 0x00080018, 0x00080016]:
ds.add(elem)
if hasattr(original_ds, 'file_meta') and hasattr(original_ds.file_meta, 'TransferSyntaxUID'):
ds.file_meta = original_ds.file_meta.copy()
else:
ds.file_meta = pydicom.dataset.FileMetaDataset()
ds.file_meta.TransferSyntaxUID = ImplicitVRLittleEndian
ds.is_little_endian = ds.file_meta.TransferSyntaxUID.is_little_endian
ds.is_implicit_VR = ds.file_meta.TransferSyntaxUID.is_implicit_VR
ds.SOPInstanceUID = generate_uid()
ds.SeriesInstanceUID = generate_uid()
if original_ds.BitsAllocated == 16:
if original_ds.PixelRepresentation == 0:
pixel_data = image.astype(np.uint16)
else:
pixel_data = image.astype(np.int16)
else:
pixel_data = image.astype(np.uint16)
ds.BitsAllocated = 16
ds.BitsStored = 16
ds.HighBit = 15
ds.PixelRepresentation = 0
ds.PixelData = pixel_data.tobytes()
ds.Rows, ds.Columns = pixel_data.shape
ds.save_as(output_path)
print(f"已保存DICOM文件: {output_path}")
整体说明
这个函数的作用是把处理后的图像(比如添加了伪影的 CT 图像)保存成医学影像专用的 DICOM 格式文件。用大白话一步一步解释:
简述
把 DICOM 文件想象成一个 "带说明书的照片":
- 普通照片只有图像内容
- DICOM 文件除了图像,还包含病人信息、拍摄设备参数、图像尺寸等 "说明书" 信息
这个函数就像:拿一张处理后的新照片,配上原始照片的说明书(只改必要的部分),最后装成新的 "带说明书的照片"。
步骤拆解
- 准备一个新的 "说明书外壳"
ds = pydicom.Dataset() |
→ 先创建一个空的 DICOM 容器(类似一个空白的说明书模板)。
- 复制原始说明书的大部分内容
for elem in original_ds: if elem.tag not in [0x7FE00010, 0x00080018, 0x00080016]: ds.add(elem) |
→ 把原始 DICOM 文件里的信息(病人信息、设备参数等)复制过来,但跳过 3 个特殊标签:
-
- 0x7FE00010:原始图像的像素数据(我们要用新的处理后图像)
- 0x00080018、0x00080016:文件的唯一标识(需要换新的)
- 处理文件格式信息
if hasattr(original_ds, 'file_meta'): ds.file_meta = original_ds.file_meta.copy() # 复制原始格式信息 else: ds.file_meta = ... # 用默认格式信息 |
→ 保证新文件的格式和原始文件一致(比如编码方式、字节顺序),否则其他软件可能打不开。
- 生成新的唯一标识
ds.SOPInstanceUID = generate_uid() ds.SeriesInstanceUID = generate_uid() |
→ 给新文件生成新的 "身份证号"(每个 DICOM 文件必须有唯一标识,不能和原始文件重复)。
- 处理图像数据格式
if original_ds.BitsAllocated == 16: pixel_data = image.astype(...) # 按原始格式转换新图像 else: pixel_data = ... # 用默认格式转换 |
→ 把处理后的图像数据转换成 DICOM 要求的格式(比如 16 位像素值),保证和原始文件的 "图像规格" 一致。
- 放入新图像并保存
ds.PixelData = pixel_data.tobytes() # 把新图像数据放进DICOM容器 ds.Rows, ds.Columns = pixel_data.shape # 记录图像尺寸 ds.save_as(output_path) # 保存成文件 |
→ 最后把处理后的图像数据放进 DICOM 容器,记录好尺寸,保存成新的 DICOM 文件。
总结
这个函数的核心就是:"复用原始 DICOM 的大部分信息,只替换图像数据和唯一标识,最后生成一个符合标准的新 DICOM 文件"。这样保存的文件既能被医学影像软件识别,又包含了我们处理后的图像内容。
复制 DICOM 文件格式信息:确保新文件能正常打开
# 关键修复:设置字节序和VR编码方式
if hasattr(original_ds, 'file_meta') and hasattr(original_ds.file_meta, 'TransferSyntaxUID'):
ds.file_meta = original_ds.file_meta.copy()
else:
# 手动创建文件元数据并设置默认传输语法
ds.file_meta = pydicom.dataset.FileMetaDataset()
ds.file_meta.TransferSyntaxUID = ImplicitVRLittleEndian # 默认小端隐式VR
这句话的作用是 **“复制原始 DICOM 文件的格式信息”**,保证新生成的 DICOM 文件能被正常打开和识别。用大白话拆解:
说明
把 DICOM 文件比作 “加密的 Word 文档”:
- file_meta 就像文档的 “加密方式说明”(比如用 AES 加密还是 DES 加密)
- TransferSyntaxUID 是说明里的 “核心密钥”(没有它就打不开文档)
这段代码就像: “先检查原始文档有没有‘加密说明’和‘核心密钥’,如果有,就把这两样东西复制到新文档里,让新文档和原始文档用一样的方式加密”。
“先检查原始文档有没有‘加密说明’和‘核心密钥’,如果有,就把这两样东西复制到新文档里,让新文档和原始文档用一样的方式加密”。
逐句说明
- if hasattr(original_ds, 'file_meta') and hasattr(original_ds.file_meta, 'TransferSyntaxUID')
→ 先确认两件事:
-
- 原始 DICOM 文件(original_ds)里有file_meta这个 “格式信息包”
- 这个包里确实包含TransferSyntaxUID这个 “文件读取规则”
- ds.file_meta = original_ds.file_meta.copy()
→ 如果上面两个条件都满足,就把原始文件的file_meta(格式信息包)完整复制到新文件(ds)里。
why?
DICOM 文件有严格的格式标准,不同设备生成的文件可能用不同的 “编码规则”(比如数据存储顺序、压缩方式)。如果新文件的格式规则和原始文件不一致,医学影像软件可能会报错 “无法打开”。
通过复制原始文件的file_meta,可以确保新文件和原始文件 “用同样的规则编码”,避免出现格式不兼容的问题。
这就像你抄作业时,不仅要抄答案,还要抄对 “格式要求”(比如老师要求用 A4 纸、黑色笔):
这两行代码保证了新文件的 “底层读写规则” 和它的 “格式说明” 完全一致,避免出现数据混乱。
确保 DICOM 图像数据格式兼容与通用
这段代码是在处理图像的 “像素格式”,确保新生成的 DICOM 文件里的图像数据格式和原始文件兼容(或者符合通用标准)。用大白话解释:
先理解两个关键概念:
- BitsAllocated:每个像素分配了多少位(bit)的存储空间。比如 16 位(相当于每个像素用 16 个 “0/1” 二进制数表示),能记录的明暗细节更多。
- PixelRepresentation:像素值是 “无符号” 还是 “有符号”。
- 0 = 无符号(uint16):像素值只能是 0 及以上的正数(比如 0-65535)。
- 1 = 有符号(int16):像素值可以是负数(比如 - 32768 到 32767),医学图像中有时用来表示不同组织的相对密度。
代码逻辑拆解:“跟着原始文件的格式走,没有就用通用格式”
- 先看原始文件是不是 16 位格式
if original_ds.BitsAllocated == 16: |
→ 检查原始 DICOM 文件的像素是不是用 16 位存储的(这是医学图像最常见的格式)。
- 如果是 16 位,再看像素值是正还是可负
- 若PixelRepresentation == 0(无符号):
pixel_data = image.astype(np.uint16) |
→ 把处理后的图像转换成 16 位无符号整数格式(只能存正数),和原始文件保持一致。
-
- 若PixelRepresentation != 0(有符号):
pixel_data = image.astype(np.int16) |
→ 转换成 16 位有符号整数格式(可以存负数),匹配原始文件。
- 如果原始文件不是 16 位(比如 8 位)
else: pixel_data = image.astype(np.uint16) # 强制转换成16位无符号格式 # 同时给新文件设置16位格式的参数 ds.BitsAllocated = 16 # 分配16位空间 ds.BitsStored = 16 # 实际存储16位数据 ds.HighBit = 15 # 最高位是第15位(0开始计数,15=16-1) ds.PixelRepresentation = 0 # 无符号(只存正数) |
→ 统一转换成 16 位无符号格式(这是 DICOM 的通用标准),并手动设置新文件的格式参数,确保软件能正确识别。
why?
图像的像素格式就像 “数据的语言”:
- 如果原始文件用 16 位无符号格式,新文件也必须用同样的格式,否则软件会把像素值读错(比如把亮的地方显示成暗的)。
- 如果原始文件格式特殊(比如 8 位),就转换成通用的 16 位格式,保证大部分医学软件都能正常显示。
简单说,这段代码是为了让新文件的图像数据 “说和原始文件一样的语言”(或者说通用语言),避免出现图像显示错乱。