python dicom 器官分割_图像识别 | 使用Python对医学Dicom文件的预处理(含代码)
前沿
在處理醫(yī)學(xué)圖像時,常常會遇到以Dicom格式保存的醫(yī)學(xué)圖像,如CT、MRI等。Dicom文件是需要專門的軟件或者通過編程,應(yīng)用相應(yīng)的庫進(jìn)行處理。為了能夠更好地服務(wù)下游任務(wù),例如分割或檢測腹腔CT圖像中某個病灶組織,需要先將Dicom圖像進(jìn)行讀取,脫敏,調(diào)窗等步驟,以便于后續(xù)的編輯。本文使用python對醫(yī)學(xué)Dicom文件進(jìn)行相應(yīng)的處理,相比于封裝好的軟件,筆者認(rèn)為自己動手的可操作性更強。
目錄
1 導(dǎo)入相應(yīng)的包
2 讀取Dicom圖像數(shù)據(jù)
3 設(shè)置CT圖像的窗寬和窗位
4 獲取Dicom圖像的tag信息
5 結(jié)果保存及可視化
導(dǎo)入相應(yīng)的包
# load necessary packages
import matplotlib.pyplot as plt
import pydicom.uid
import sys
from PyQt5 import QtGui
import os
import pydicom
import glob
from PIL import *
import matplotlib.pyplot as plt
from pylab import *
from tkinter.filedialog import *
import PIL.Image as Image
其核心是使用了python中的pydicom庫來處理dicom文件。
讀取Dicom圖像數(shù)據(jù)
have_numpy = True
try:
import numpy
except ImportError:
have_numpy = False
raise
sys_is_little_endian = (sys.byteorder == 'little')
NumpySupportedTransferSyntaxes = [
pydicom.uid.ExplicitVRLittleEndian,
pydicom.uid.ImplicitVRLittleEndian,
pydicom.uid.DeflatedExplicitVRLittleEndian,
pydicom.uid.ExplicitVRBigEndian,
]
# 支持"傳輸"語法
def supports_transfer_syntax(dicom_dataset):
return (dicom_dataset.file_meta.TransferSyntaxUID in
NumpySupportedTransferSyntaxes)
def needs_to_convert_to_RGB(dicom_dataset):
return False
def should_change_PhotometricInterpretation_to_RGB(dicom_dataset):
return False
# 加載 Dicom圖像
def get_pixeldata(dicom_dataset):
"""If NumPy is available, return an ndarray of the Pixel Data.
Raises
------
TypeError
If there is no Pixel Data or not a supported data type.
ImportError
If NumPy isn't found
NotImplementedError
if the transfer syntax is not supported
AttributeError
if the decoded amount of data does not match the expected amount
Returns
-------
numpy.ndarray
The contents of the Pixel Data element (7FE0,0010) as an ndarray.
"""
if (dicom_dataset.file_meta.TransferSyntaxUID not in
NumpySupportedTransferSyntaxes):
raise NotImplementedError("Pixel Data is compressed in a "
"format pydicom does not yet handle. "
"Cannot return array. Pydicom might "
"be able to convert the pixel data "
"using GDCM if it is installed.")
if not have_numpy:
msg = ("The Numpy package is required to use pixel_array, and "
"numpy could not be imported.")
raise ImportError(msg)
if 'PixelData' not in dicom_dataset:
raise TypeError("No pixel data found in this dataset.")
# Make NumPy format code, e.g. "uint16", "int32" etc
# from two pieces of info:
# dicom_dataset.PixelRepresentation -- 0 for unsigned, 1 for signed;
# dicom_dataset.BitsAllocated -- 8, 16, or 32
if dicom_dataset.BitsAllocated == 1:
# single bits are used for representation of binary data
format_str = 'uint8'
elif dicom_dataset.PixelRepresentation == 0:
format_str = 'uint{}'.format(dicom_dataset.BitsAllocated)
elif dicom_dataset.PixelRepresentation == 1:
format_str = 'int{}'.format(dicom_dataset.BitsAllocated)
else:
format_str = 'bad_pixel_representation'
try:
numpy_dtype = numpy.dtype(format_str)
except TypeError:
msg = ("Data type not understood by NumPy: "
"format='{}', PixelRepresentation={}, "
"BitsAllocated={}".format(
format_str,
dicom_dataset.PixelRepresentation,
dicom_dataset.BitsAllocated))
raise TypeError(msg)
if dicom_dataset.is_little_endian != sys_is_little_endian:
numpy_dtype = numpy_dtype.newbyteorder('S')
pixel_bytearray = dicom_dataset.PixelData
if dicom_dataset.BitsAllocated == 1:
# if single bits are used for binary representation, a uint8 array
# has to be converted to a binary-valued array (that is 8 times bigger)
try:
pixel_array = numpy.unpackbits(
numpy.frombuffer(pixel_bytearray, dtype='uint8'))
except NotImplementedError:
# PyPy2 does not implement numpy.unpackbits
raise NotImplementedError(
'Cannot handle BitsAllocated == 1 on this platform')
else:
pixel_array = numpy.frombuffer(pixel_bytearray, dtype=numpy_dtype)
length_of_pixel_array = pixel_array.nbytes
expected_length = dicom_dataset.Rows * dicom_dataset.Columns
if ('NumberOfFrames' in dicom_dataset and
dicom_dataset.NumberOfFrames > 1):
expected_length *= dicom_dataset.NumberOfFrames
if ('SamplesPerPixel' in dicom_dataset and
dicom_dataset.SamplesPerPixel > 1):
expected_length *= dicom_dataset.SamplesPerPixel
if dicom_dataset.BitsAllocated > 8:
expected_length *= (dicom_dataset.BitsAllocated // 8)
padded_length = expected_length
if expected_length & 1:
padded_length += 1
if length_of_pixel_array != padded_length:
raise AttributeError(
"Amount of pixel data %d does not "
"match the expected data %d" %
(length_of_pixel_array, padded_length))
if expected_length != padded_length:
pixel_array = pixel_array[:expected_length]
if should_change_PhotometricInterpretation_to_RGB(dicom_dataset):
dicom_dataset.PhotometricInterpretation = "RGB"
if dicom_dataset.Modality.lower().find('ct') >= 0: # CT圖像需要得到其CT值圖像
pixel_array = pixel_array * dicom_dataset.RescaleSlope + dicom_dataset.RescaleIntercept # 獲得圖像的CT值
pixel_array = pixel_array.reshape(dicom_dataset.Rows, dicom_dataset.Columns*dicom_dataset.SamplesPerPixel)
return pixel_array, dicom_dataset.Rows, dicom_dataset.Columns
讀取到dicom文件中的數(shù)據(jù)后,實質(zhì)上是幾個圖像矩陣,這個過程同時也處理了“脫敏”問題。
設(shè)置CT圖像的窗寬和窗位
def setDicomWinWidthWinCenter(img_data, winwidth, wincenter, rows, cols):
img_temp = img_data
img_temp.flags.writeable = True
min = (2 * wincenter - winwidth) / 2.0 + 0.5
max = (2 * wincenter + winwidth) / 2.0 + 0.5
dFactor = 255.0 / (max - min)
for i in numpy.arange(rows):
for j in numpy.arange(cols):
img_temp[i, j] = int((img_temp[i, j]-min)*dFactor)
min_index = img_temp < min
img_temp[min_index] = 0
max_index = img_temp > max
img_temp[max_index] = 255
return img_temp
該函數(shù)的輸入變量winwidth和wincenter即為需要設(shè)置的窗寬和窗位,這兩個值根據(jù)研究的問題(不同的組織器官對應(yīng)不同的窗寬和窗位,有時候也要根據(jù)圖像效果進(jìn)行一定的調(diào)整)調(diào)整不同的值。網(wǎng)上有很多關(guān)于相關(guān)的窗位和窗寬對應(yīng)值,這里給出一些參考資料,如果遇到不確定的,最好借鑒查閱相應(yīng)領(lǐng)域的論文。Windowing (CT) | Radiology Reference Article | Radiopaedia.org?radiopaedia.org
獲取Dicom圖像的tag信息
def loadFileInformation(filename):
information = {}
ds = pydicom.read_file(filename)
information['PatientID'] = ds.PatientID
information['PatientName'] = ds.PatientName
information['PatientBirthDate'] = ds.PatientBirthDate
information['PatientSex'] = ds.PatientSex
information['StudyID'] = ds.StudyID
information['StudyDate'] = ds.StudyDate
information['StudyTime'] = ds.StudyTime
information['InstitutionName'] = ds.InstitutionName
information['Manufacturer'] = ds.Manufacturer
print(dir(ds))
print(type(information))
return information
這個步驟視需求而定,如果不需要查看dicom文件的具體tag信息,此步驟可以跳過。
結(jié)果保存及可視化
可以單張保存,或者批量處理。
讀取單張dicom文件
def main_single():
dcm = dicom.read_file('81228816') # load dicom_file
# 得到 CT 值,圖像的 長, 寬
pixel_array, dcm.Rows, dcm.Columns = get_pixeldata(dcm)
# 調(diào)整窗位、窗寬
img_data = pixel_array
winwidth = 500
wincenter = 50
rows = dcm.Rows
cols = dcm.Columns
dcm_temp = setDicomWinWidthWinCenter(img_data, winwidth, wincenter, rows, cols)
# 可視化
dcm_img = Image.fromarray(dcm_temp) # 將Numpy轉(zhuǎn)換為PIL.Image
dcm_img = dcm_img.convert('L')
# plt.imshow(img, cmap=plt.cm.bone)
# 保存為jpg文件,用作后面的生成label用
dcm_img.save('../output/temp.jpg')
# 顯示圖像
dcm_img.show()
同時讀取一個文件夾中的 dicom 文件,并處理保存 (寫成循環(huán)即可)
def main_mulit(path):
names = os.listdir(path) # 讀取文件夾中的所有文件名
for i in range(len(names)):
dicom_name = path+names[i]
dcm = pydicom.read_file(dicom_name) # 讀取 dicom 文件
pixel_array, dcm.Rows, dcm.Columns = get_pixeldata(dcm) # 得到 dicom文件的 CT 值
img_data = pixel_array
winwidth = 500
wincenter = 50
rows = dcm.Rows
cols = dcm.Columns
dcm_temp = setDicomWinWidthWinCenter(img_data, winwidth, wincenter, rows, cols) # 調(diào)整窗位、窗寬
# 可視化
dcm_img = Image.fromarray(dcm_temp) # 將 Numpy轉(zhuǎn)換為 PIL.Image
dcm_img = dcm_img.convert('L')
# 批量保存
dcm_img.save("C:/output/%s_%s.png" % (path1, names[i]))
注意,以上都寫成函數(shù)的形式,運行時需要調(diào)用,并注意文件路徑的修改。
單張dicom文件處理
main_single()
批量處理
main_mulit(path)
公眾號文章鏈接:圖像識別 | 使用Python對醫(yī)學(xué)Dicom文件的預(yù)處理(含代碼)?mp.weixin.qq.com
覺得有收獲的話,麻煩點個贊再走唄~
總結(jié)
以上是生活随笔為你收集整理的python dicom 器官分割_图像识别 | 使用Python对医学Dicom文件的预处理(含代码)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 优盘启动工具怎么用 如何使用U盘制作启动
- 下一篇: python的datetime举例_Py