#pydicom 安装与使用
安装
conda install -c conda-forge pydicom
使用
使用示例
import pydicom
filename = 'path_to_dicom'
ds = pydicom.dcmread(filename)
ds.dir() # 查看病人所有信息字典keys
print(ds.PatientName) # 查看病人名字
print(ds) # 查看病人所有信息字典, 如果出现某key对应值编码错误,先暂时跳过该key
############ 查看dicom对应图片值 #####################
print(ds.pixel_array.shape)
print(ds.pixel_array)
##读取显示图片
pylab.imshow(ds.pixel_array, cmap=pylab.cm.bone)
pylab.show()
##修改图片中的元素,不能直接使用data_array,需要转换成PixelData
for n,val in enumerate(ds.pixel_array.flat): # example: zero anything < 300
if val < 300:
ds.pixel_array.flat[n]=0
ds.PixelData = ds.pixel_array.tostring()
ds.save_as('newfilename.dcm')
详细使用教程:pydicom
环境
ubuntu 16.04
conda 4.3.30
# coding : utf-8
# DICOM文件读取及JPG格式图片展示
# @Author : huang
# @Time : 2020.08.18
"""
import pydicom
import matplotlib.pyplot as plt
import numpy as np
import os
import multiprocessing
import threading
import time
def extractDicomFileInfo(filename):
"""
提取Dicom文件的tag信息
input :文件名
output:相关信息
"""
info = {}
dcm = pydicom.read_file(filename)
info["PatientID"] = dcm.PatientID # 患者ID
info["PatientName"] = dcm.PatientName # 患者姓名
info["PatientBirthData"] = dcm.PatientBirthData # 患者出生日期
info["PatientAge"] = dcm.PatientAge # 患者年龄
info['PatientSex'] = dcm.PatientSex # 患者性别
info['StudyID'] = dcm.StudyID # 检查ID
info['StudyDate'] = dcm.StudyDate # 检查日期
info['StudyTime'] = dcm.StudyTime # 检查时间
info['InstitutionName'] = dcm.InstitutionName # 机构名称
info['Manufacturer'] = dcm.Manufacturer # 设备制造商
info['StudyDescription']=dcm.StudyDescription # 检查项目描述
return info
def saveAsJPGImage(img_array,jpg_path,uid,lens):
"""
格式转换函数
input : 像素矩阵,图片保存路径,uid,lens
output:
"""
plt.figure(figsize=(12,6),dpi=80) # 绘制画板
arr_temp = np.reshape(img_array,(lens,))
max_val = max(arr_temp) # 获取像素极大值
min_val = min(arr_temp) # 获取像素极小值
img_array = (img_array-min_val)/(max_val-min_val) # 像素值归一化
plt.imshow(img_array,cmap=plt.cm.bone)
plt.title("UID:{}".format(uid))
# print("saving image:{}".format(jpg_path.split('JPG图像/')[1]))
plt.savefig(jpg_path)
plt.close()
def getDicomFile(dicom_dir,jpg_dir):
"""
获取dicom文件的参数信息
input : dicom_dir,jpg_dir
output: args_list
"""
# 定位绝对路径
all_file_list = os.listdir(dicom_dir)
# 初始化args_list
args_list = []
for f in all_file_list:
dicom_file_path = os.path.join(dicom_dir,f)
# dicom_info = extractDicomFileInfo(dicom_file_path) # 提取tag信息备用
jpg_name = jpg_dir+f.split('.d')[0]+'.jpg' # 新建jpg文件名
# print(jpg_name)
dcm = pydicom.read_file(dicom_file_path) # 读取dicom文件
img_array = dcm.pixel_array # 获取图像像素矩阵
lens = img_array.shape[0]*img_array.shape[1] # 获取像素矩阵大小
uid = dcm.SOPInstanceUID # 获取图像uid
args_tuple = (img_array,jpg_name,uid,lens) # 添加参数tuple
args_list.append(args_tuple)
return args_list
def main():
"""
main function
"""
# 存放DICOM文件的文件夹
dicom_dir = "../dicom数据集/DICOM/"
# 存放转换后的JPG图片的文件夹
jpg_dir = "../dicom数据集/JPG图像/"
begin_time = time.time() # 开始时间
pool = multiprocessing.Pool(processes=5) # 创建进程池
args_list = getDicomFile(dicom_dir,jpg_dir) # 获取参数list
pool.starmap(saveAsJPGImage,args_list) # 多进程保存jpg文件
pool.close() # 关闭进程
end_time = time.time() # 结束时间
print("耗时:{}s".format(round(end_time-begin_time,4))) # 打印耗时
if __name__ == '__main__':
main()
##################################################################################
dicom文件预处理
dicom文件处理的一些方式
导入需要的模块
import os
import SimpleITK
import dicom
import numpy as np
import cv2
import glob
from tqdm import tqdm
首先需要导入我们需要的处理的dicom文件,dicom文件是一组连续的图片,我们根据图片中的位置信息对每张图片进行间隔计算,然后把结果存到一个列表中,然后将图片中的像素信息进行提取,缩放到1mm1mm1mm的尺度,get_cube_from_img这个函数是从图像中根据坐标找到目标的中心,并且切一个包含目标的矩阵,然后把这个三维的矩阵平铺开成一个64个2维的矩阵并保存。归一化的目的是为了加快模型收敛的速度,如果要保存成灰度图,需要像素值乘以255.
def is_dicom_file(filename):
'''
判断某文件是否是dicom格式的文件
:param filename: dicom文件的路径
:return:
'''
file_stream = open(filename, 'rb')
file_stream.seek(128)
data = file_stream.read(4)
file_stream.close()
if data == b'DICM':
return True
return False
def load_patient(src_dir):
'''
读取某文件夹内的所有dicom文件
:param src_dir: dicom文件夹路径
:return: dicom list
'''
files = os.listdir(src_dir)
slices = []
for s in files:
if is_dicom_file(src_dir + '/' + s):
instance = dicom.read_file(src_dir + '/' + s)
slices.append(instance)
slices.sort(key=lambda x: int(x.InstanceNumber))
try:
slice_thickness = np.abs(slices[0].ImagePositionPatient[2] - slices[1].ImagePositionPatient[2])
except:
slice_thickness = np.abs(slices[0].SliceLocation - slices[1].SliceLocation)
for s in slices:
s.SliceThickness = slice_thickness
return slices
def get_pixels_hu_by_simpleitk(dicom_dir):
'''
读取某文件夹内的所有dicom文件,并提取像素值(-4000 ~ 4000)
:param src_dir: dicom文件夹路径
:return: image array
'''
reader = SimpleITK.ImageSeriesReader()
dicom_names = reader.GetGDCMSeriesFileNames(dicom_dir)
reader.SetFileNames(dicom_names)
image = reader.Execute()
img_array = SimpleITK.GetArrayFromImage(image)
img_array[img_array == -2000] = 0
return img_array
def rescale_patient_images(images_zyx, org_spacing_xyz, target_voxel_mm, is_mask_image=False):
'''
将dicom图像缩放到1mm:1mm:1mm的尺度
:param images_zyx: 缩放前的图像(3维)
:return: 缩放后的图像(3维)
'''
print("Spacing: ", org_spacing_xyz)
print("Shape: ", images_zyx.shape)
# print "Resizing dim z"
resize_x = 1.0
resize_y = float(org_spacing_xyz[2]) / float(target_voxel_mm)
interpolation = cv2.INTER_NEAREST if is_mask_image else cv2.INTER_LINEAR
res = cv2.resize(images_zyx, dsize=None, fx=resize_x, fy=resize_y, interpolation=interpolation)
# print "Shape is now : ", res.shape
res = res.swapaxes(0, 2)
res = res.swapaxes(0, 1)
# print "Shape: ", res.shape
resize_x = float(org_spacing_xyz[0]) / float(target_voxel_mm)
resize_y = float(org_spacing_xyz[1]) / float(target_voxel_mm)
# cv2 can handle max 512 channels..
if res.shape[2] > 512:
res = res.swapaxes(0, 2)
res1 = res[:256]
res2 = res[256:]
res1 = res1.swapaxes(0, 2)
res2 = res2.swapaxes(0, 2)
res1 = cv2.resize(res1, dsize=None, fx=resize_x, fy=resize_y, interpolation=interpolation)
res2 = cv2.resize(res2, dsize=None, fx=resize_x, fy=resize_y, interpolation=interpolation)
res1 = res1.swapaxes(0, 2)
res2 = res2.swapaxes(0, 2)
res = np.vstack([res1, res2])
res = res.swapaxes(0, 2)
else:
res = cv2.resize(res, dsize=None, fx=resize_x, fy=resize_y, interpolation=interpolation)
res = res.swapaxes(0, 2)
res = res.swapaxes(2, 1)
print("Shape after: ", res.shape)
return res
def get_cube_from_img(img3d, center_x, center_y, center_z, block_size):
start_x = max(center_x - block_size / 2, 0)
if start_x + block_size > img3d.shape[2]:
start_x = img3d.shape[2] - block_size
start_y = max(center_y - block_size / 2, 0)
start_z = max(center_z - block_size / 2, 0)
if start_z + block_size > img3d.shape[0]:
start_z = img3d.shape[0] - block_size
start_z = int(start_z)
start_y = int(start_y)
start_x = int(start_x)
res = img3d[start_z:start_z + block_size, start_y:start_y + block_size, start_x:start_x + block_size]
return res
def normalize_hu(image):
'''
将输入图像的像素值(-4000 ~ 4000)归一化到0~1之间
:param image 输入的图像数组
:return: 归一化处理后的图像数组
'''
MIN_BOUND = -1000.0
MAX_BOUND = 400.0
image = (image - MIN_BOUND) / (MAX_BOUND - MIN_BOUND)
image[image > 1] = 1.
image[image < 0] = 0.
return image
def load_patient_images(src_dir, wildcard="*.*", exclude_wildcards=[]):
'''
读取一个病例的所有png图像,返回值为一个三维图像数组
:param image 输入的一系列png图像
:return: 三维图像数组
'''
src_img_paths = glob.glob(src_dir + wildcard)
for exclude_wildcard in exclude_wildcards:
exclude_img_paths = glob.glob(src_dir + exclude_wildcard)
src_img_paths = [im for im in src_img_paths if im not in exclude_img_paths]
src_img_paths.sort()
images = [cv2.imread(img_path, cv2.IMREAD_GRAYSCALE) for img_path in src_img_paths]
images = [im.reshape((1,) + im.shape) for im in images]
res = np.vstack(images)
return res
def save_cube_img(target_path, cube_img, rows, cols):
'''
将3维cube图像保存为2维图像,方便勘误检查
:param 二维图像保存路径, 三维输入图像
:return: 二维图像
'''
assert rows * cols == cube_img.shape[0]
img_height = cube_img.shape[1]
img_width = cube_img.shape[1]
res_img = np.zeros((rows * img_height, cols * img_width), dtype=np.uint8)
for row in range(rows):
for col in range(cols):
target_y = row * img_height
target_x = col * img_width
res_img[target_y:target_y + img_height, target_x:target_x + img_width] = cube_img[row * cols + col]
cv2.imwrite(target_path, res_img)
if __name__ == '__main__':
dicom_dir = './data/dicom_demo/'
# 读取dicom文件的元数据(dicom tags)
slices = load_patient(dicom_dir)
# 获取dicom的spacing值
pixel_spacing = slices[0].PixelSpacing
pixel_spacing.append(slices[0].SliceThickness)
print('The dicom spacing : ', pixel_spacing)
# 提取dicom文件中的像素值
image = get_pixels_hu_by_simpleitk(dicom_dir)
# 标准化不同规格的图像尺寸, 统一将dicom图像缩放到1mm:1mm:1mm的尺度
image = rescale_patient_images(image, pixel_spacing, 1.00)
for i in tqdm(range(image.shape[0])):
img_path = "./temp_dir/dcm_2_png/img_" + str(i).rjust(4, '0') + "_i.png"
# 将像素值归一化到[0,1]区间
org_img = normalize_hu(image[i])
# 保存图像数组为灰度图(.png)
cv2.imwrite(img_path, org_img * 255)
# 加载上一步生成的png图像
pngs = load_patient_images("./temp_dir/dcm_2_png/", "*_i.png")
# 输入人工标记的结节位置: coord_x, coord_y, coord_z
cube_img = get_cube_from_img(pngs, 272, 200, 134, 64)
print(cube_img)
save_cube_img('./temp_dir/chapter3_3dcnn_img_X.png', cube_img, 8, 8)
##################################################################################
1、引言
1.1、背景介绍
由于导师推荐入门的研究项目涉及医疗图像识别,这就离不开DICOM文件的基础知识了,因此在这里同大家分享DICOM学习心得,其中包括使用pydicom库将dicom文件转为jpg图片的操作。
1.2、文章结构
引言
1.1 背景介绍
1.2 文章结构
医学影像
2.1 医学影像
2.2 医学仪器
DICOM
3.1 DICOM简介
3.2 DICOM文件格式详解
DICOM文件转为JPG图片
4.1 处理DICOM文件的现成库
4.2 pydicom的使用
2、医学影像
2.1、医学影像
医学影像(Medical Imaging),是指利用某种介质(例如X射线、电磁、超声波等等)与人体相互作用,从而以影像方式将人体内部组织器官的结构和密度表现出来,然后提供给医生进行判断并对人体健康状况得出结论的一门科学。
2.2、医学仪器
医学影像仪器主要包含:
1. X光影像仪器
2. CT(Computerized Tomography Computed Tomography)
3. 超声(分B超、彩色多普勒超声、心脏彩超、三维彩超)
4. 核磁共振成像(MRI)
3、DICOM
3.1、DICOM简介
DICOM(医学数字成像和通讯),英文全称Digital Imaging and Communications in Medicine,是ACR(美国放射协会)和NAMA(美国国家电子制造商协会)联合开发医学数字成像和通讯的一个通用标准。随着X线断层造影技术和其他数字诊断模式的产生,以及计算机的使用在临床应用中的增长,为实现在不同制造商生产的设备之间传输图像和联合信息的标准方法—DICOM标准,在1985年应运而生并不断的改版升级,现在主要是3.0版本1。
对于医学专业的同学如要了解更多的相关背景知识,可以参考Dicom官方文档。由于笔者是非医学专业,在此只简述我们所需要的知识。DICOM顾名思义,关键点在于D-igital I-maging和C-ommunications,标准主要由三个部分组成:
1. a file format for images and reports
2. a set of defined services
3. a network protocol
即通过网络协议使用服务进行创建、存储和交换文件的若干标准2。
3.2、DICOM文件格式详解
前面已经大概了解了DICOM标准,接下来需要知道DICOM标准的使用,其通常通过DICOM格式文件进行体现。DICOM文件是标记的图像文件,包含图像和有关图像的数据集合,其能够在两个以DICOM格式接收患者数据和图像的实体之间进行交流3。
通俗的理解就是:患者的医学图像以DICOM文件格式进行存储,其中包含了图像信息以及患者的PHI(protected health information,即姓名、性别、年龄等),以及产生图像的设备的相关信息。如下图所示,以dcm后缀结尾的文件即DICOM文件,其存储的信息为二进制格式。
DICOM文件的内容一般由一个DICOM文件头和一个DICOM数据集组成,结构图如下所示:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0bZ9QAKx-1593698384628)(初识DICOM_002.png)]
1、DICOM文件头包含了标识数据集合的相关信息,每个DICOM文件都必须包含文件头,主要信息:
文件导言:由128个字节组成,描述文件的相关导言信息。
DICOM前缀:由4个字节组成,用于判断其字符串值是否为"DICOM"从而判断是否为DICOM文件,可以理解为一个标志信息。
文件元信息元素
2、 DICOM数据集是DICOM文件的主要组成部分,其由DICOM数据元素按照指定的顺序排列。数据元素最基本的数据单元是数据元,按照TAG从小到大顺序排列,即一个数据元表示一个TAG。数据元主要由4个部分组成:
TAG号:由4个字节组成,包括2字节的组号和2字节的元素号(例如:0010 0040 表示患者性别,其中的组号:0002描述设备通讯信息、0008描述特征参数、0010描述患者信息、0028描述图像信息参数)。我们后期所需要的DICOM文件相关数据时,就是根据TAG来获取。
值表示(VR,value representation):由两个字节的字符组成,存储描述该项数据元信息的数据类型,包含例如:LO(Long String,长字符串)、IS(Interger String,整型字符串),DA(data,日期)等等共27种数据类型。
值长度(value length):存储描述该项信息的数据长度
值域(value):存储描述该项信息的数据值
3、 其中数据元信息可以根据信息的不同,分为4类:
Patient
Study
Series
Image
可以理解为一个患者(patient)可以做多次检查(study),一次检查包含多个检查部位(series),而每个检查部位都有一张或多张相应的影像图像(image)。常见的TAG如下4:
Patient Tag
在这里插入图片描述
Study Tag
在这里插入图片描述
Series Tag
在这里插入图片描述
Image Tag
在这里插入图片描述
在这里插入图片描述
4、Dicom文件转为jpg图片
4.1 处理DICOM文件的现成库
现在有许多现成的解析DICOM标准的第三方库,在工程中引入它们能够高效的进行项目开发,而不需要使用者了解过多底层的解析操作。常见的库如下:
C++:DCMTK
Java:dcm4che
Python:pydicom
本文主要讲解基于python中的pydicom库处理dicom文件的一些常规操作。
4.2 pydicom的使用
pydicom简介:pydicom是一个python中的第三方库,用于DICOM文件,主要为了以一种简单的"python式"方式检查和修改dicom数据而设计,可以提供给使用者轻松的修改,读写文件并转换成显式图像图片。
1、由于pydicom是第三方库,所以使用之前需要下载,这里推荐python的pip指令进行下载,在cmd中输入如下指令进行下载:
pip install pydicom
1
如果下载速度慢,可以添加清华镜像源:
pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pydicom
2、文件的读取以及数据元的获取:
import pydicom
import os
info = {}
# 读取dicom文件
dcm = pydicom.read_file("test.dcm")
# 通过字典关键字来获取图像的数据元信息(当然也可以根据TAG号)
# 这里获取几种常用信息
info["PatientID"] = dcm.PatientID # 患者ID
info["PatientName"] = dcm.PatientName # 患者姓名
info["PatientBirthData"] = dcm.PatientBirthData # 患者出生日期
info["PatientAge"] = dcm.PatientAge # 患者年龄
info['PatientSex'] = dcm.PatientSex # 患者性别
info['StudyID'] = dcm.StudyID # 检查ID
info['StudyDate'] = dcm.StudyDate # 检查日期
info['StudyTime'] = dcm.StudyTime # 检查时间
info['InstitutionName'] = dcm.InstitutionName # 机构名称
info['Manufacturer'] = dcm.Manufacturer # 设备制造商
info['StudyDescription']=dcm.StudyDescription # 检查项目描述
print(info)
3、获取图像Tag中的像素矩阵并保存为JPG图片(其中使用matplotlib库进行绘图以及numpy进行矩阵操作,同理需要pip安装):
import pydicom
import matplotlib.pyplot as plt
import numpy as np
import os
filename = "test.dcm"
jpgname = "test.jpg"
# 读取dicom文件
dcm = pydicom.read_file(filename)
# 获取图像唯一标识符UID
uid = dcm.SOPInstanceUID
# 获取像素矩阵
img_arr = dcm.pixel_array
# 打印矩阵大小
print(img_arr.shape)
# 获取像素点个数
lens = img_arr.shape[0]*img_arr.shape[1]
# 获取像素点的最大值和最小值
arr_temp = np.reshape(img_arr,(lens,))
max_val = max(arr_temp)
min_val = min(arr_temp)
# 图像归一化
img_arr = (img_arr-min_val)/(max_val-min_val)
# 绘制图像并保存
plt.figure(figsize=(12,12),dpi=250) # 图像大小可以根据文件不同进行设置
plt.imshow(img_arr,cmap=plt.cm.bone)
plt.title("UID:{}".format(uid))
plt.savefig(jpgname)
plt.close()
效果如图所示:
在这里插入图片描述
4、批量将DICOM文件转换为JPG图像(多进程、高效率):
# coding : utf-8
# DICOM文件读取及JPG格式图片展示
# @Author : Labyrinthine Leo
# @Time : 2020.06.16
"""
import pydicom
import matplotlib.pyplot as plt
import numpy as np
import os
import multiprocessing
import threading
import time
def extractDicomFileInfo(filename):
"""
提取Dicom文件的tag信息
input :文件名
output:相关信息
"""
info = {}
dcm = pydicom.read_file(filename)
info["PatientID"] = dcm.PatientID # 患者ID
info["PatientName"] = dcm.PatientName # 患者姓名
info["PatientBirthData"] = dcm.PatientBirthData # 患者出生日期
info["PatientAge"] = dcm.PatientAge # 患者年龄
info['PatientSex'] = dcm.PatientSex # 患者性别
info['StudyID'] = dcm.StudyID # 检查ID
info['StudyDate'] = dcm.StudyDate # 检查日期
info['StudyTime'] = dcm.StudyTime # 检查时间
info['InstitutionName'] = dcm.InstitutionName # 机构名称
info['Manufacturer'] = dcm.Manufacturer # 设备制造商
info['StudyDescription']=dcm.StudyDescription # 检查项目描述
return info
def saveAsJPGImage(img_array,jpg_path,uid,lens):
"""
格式转换函数
input : 像素矩阵,图片保存路径,uid,lens
output:
"""
plt.figure(figsize=(12,6),dpi=80) # 绘制画板
arr_temp = np.reshape(img_array,(lens,))
max_val = max(arr_temp) # 获取像素极大值
min_val = min(arr_temp) # 获取像素极小值
img_array = (img_array-min_val)/(max_val-min_val) # 像素值归一化
plt.imshow(img_array,cmap=plt.cm.bone)
plt.title("UID:{}".format(uid))
# print("saving image:{}".format(jpg_path.split('JPG图像/')[1]))
plt.savefig(jpg_path)
plt.close()
def getDicomFile(dicom_dir,jpg_dir):
"""
获取dicom文件的参数信息
input : dicom_dir,jpg_dir
output: args_list
"""
# 定位绝对路径
all_file_list = os.listdir(dicom_dir)
# 初始化args_list
args_list = []
for f in all_file_list:
dicom_file_path = os.path.join(dicom_dir,f)
# dicom_info = extractDicomFileInfo(dicom_file_path) # 提取tag信息备用
jpg_name = jpg_dir+f.split('.d')[0]+'.jpg' # 新建jpg文件名
# print(jpg_name)
dcm = pydicom.read_file(dicom_file_path) # 读取dicom文件
img_array = dcm.pixel_array # 获取图像像素矩阵
lens = img_array.shape[0]*img_array.shape[1] # 获取像素矩阵大小
uid = dcm.SOPInstanceUID # 获取图像uid
args_tuple = (img_array,jpg_name,uid,lens) # 添加参数tuple
args_list.append(args_tuple)
return args_list
def main():
"""
main function
"""
# 存放DICOM文件的文件夹
dicom_dir = "../dicom数据集/DICOM/"
# 存放转换后的JPG图片的文件夹
jpg_dir = "../dicom数据集/JPG图像/"
begin_time = time.time() # 开始时间
pool = multiprocessing.Pool(processes=5) # 创建进程池
args_list = getDicomFile(dicom_dir,jpg_dir) # 获取参数list
pool.starmap(saveAsJPGImage,args_list) # 多进程保存jpg文件
pool.close() # 关闭进程
end_time = time.time() # 结束时间
print("耗时:{}s".format(round(end_time-begin_time,4))) # 打印耗时
if __name__ == '__main__':
main()
##################################################################################