醫學影像處理中的資料讀寫
常見的醫學影像的格式
不管格式如何變化,對於醫學影像而言,最終讀取到內容中的資料就是影像的強度值資訊,就類似自然影像的RGB表示法一樣。這裡叫做強度值,因為不同的醫學影像例如CT、MRI他們可區分的訊號的範圍和一般自然影像不一樣。一般自然影像256個級別就夠用了,而醫學影像可能會有2000個級別。對於我而言,比較熟悉的有幾種常見的資料字尾。
- dicom:常見於臨床上醫學裝置採集的資訊,裡面有許多欄位,我們只關心影像相關的資訊即可。
- mha:是一種體資料的儲存格式,由一個描述資料的頭和資料組成,一般就針對醫學影像使用。
- mhd:和mha類似,只不過是把頭和影像資料分開儲存了。
- nii:NIFTI格式,常用於神經影像,有時候也會見到hdr img這樣的檔案,本質上也是這種格式,只不過是把頭和資料分開了。
- nrrd:也是一樣包含了頭和影像資料。
- raw:一般只有影像資訊,其他資訊需要去找對應的解析或者頭。
- img:一般只有影像資訊,其他資訊需要去找對應的解析或者頭。
本文沒有過多的介紹這些格式,因為大家主要的目的是做醫學影像處理,其實也不關心他們的具體實現,可以認為他們都是一樣的,都儲存了醫學影像(也可能是三維影像)資訊,我們只需要呼叫現成的庫,將他們解析成類似三維陣列的格式,就可以供我們之後的使用了。
在Python中進行讀寫
Python提供了豐富的庫來輔助我們工作,大多數情況下只需要幾行程式碼就可以搞定讀寫。千萬別想不去去用C++之類的語言,只有在需要工程化的時候再考慮去用C++。
DICOM的讀寫
我從這個網站 https://www.dicomlibrary.com/ 下載了DICOM Samples DICOM的資料作為Example。
import SimpleITK as sitk
import sys
import os
import numpy as np
import matplotlib.pyplot as plt
#本文從上述網站下載的dicom是一個序列有300+切片資料
dicomdir = "Dataset/dicom/data1/series-00000"
reader = sitk.ImageSeriesReader()
dicom_names = reader.GetGDCMSeriesFileNames(dicomdir)
reader.SetFileNames(dicom_names)
image = reader.Execute()
size = image.GetSize()
print("Image size:", size[0], size[1], size[2])
rawImg = sitk.GetArrayFromImage(image)
print("Max value:",np.max(rawImg),"Min value:",np.min(rawImg),rawImg.shape)
Image size: 512 512 361
Max value: 2948 Min value: -1000 (361, 512, 512)
#以上程式碼成功讀取了dicom序列檔案,下面視覺化幾個切片看看
imgslicer = []
for i in range(4):
#擴充維度,為了視覺化
s = np.expand_dims(rawImg[i*70,:,:],2)
#增加通道
s = np.repeat(s,3,2)
#這裡進行歸一化,因為dicom這裡有3000個level所以歸一化肯定會丟失精度
#一般的做法是給定一個區間,再歸一化,其他區域就不管了,這個叫開窗
print(s.shape)
s = s.astype(np.float32)
s = (s - np.min(s))/(np.max(s) - np.min(s))
imgslicer.append(s)
# rawImg = rawImg.transpose((1,2,0))
# rawImg = np.repeat(rawImg,3,2)
# print("Max value:",np.max(rawImg),"Min value:",np.min(rawImg),rawImg.shape)
for i in range(4):
plt.subplot(1,4,i+1)
plt.imshow(imgslicer[i])
(512, 512, 3)
(512, 512, 3)
(512, 512, 3)
(512, 512, 3)
如果你需要其他的資訊,可以查閱SimpleITK的文件,另外寫入dicom不在這裡說明了。
mha檔案的讀寫
mha檔案格式更加簡單和緊湊,也是個人用的比較多的一種格式,現在我們將上面使用的dicom檔案轉成mha檔案,來作為我們的Example。注意,有些重複的程式碼這裡就不寫了,包括import庫等,如果後面需要新的庫,會重新import。
mhadir = os.path.join("Dataset","mha")
if not os.path.exists(mhadir):
os.mkdir(mhadir)
sitk.WriteImage(image,os.path.join(mhadir,"data1.mha"))
在得到了mha檔案以後我們就可以讀取mha檔案了,這裡就不進行視覺化,因為轉成numpy以後,後面的操作都是一樣的。
#讀取mha
mhaimg = sitk.ReadImage(os.path.join(mhadir,"data1.mha"))
print(mhaimg.GetSize())
print(mhaimg.GetSpacing())
#轉為numpy
rawmhaimg = sitk.GetArrayFromImage(mhaimg)
print(rawmhaimg.shape)
#儲存mha
sitk.WriteImage(mhaimg,os.path.join(mhadir,"data1_cpy.mha"))
(512, 512, 361)
(0.58984375, 0.58984375, 0.5)
(361, 512, 512)
mhd檔案的讀寫
mhd檔案類似於mha檔案,只不過是將檔案分開進行了儲存,我們繼續將之前的檔案轉為mhd作為Example
mhddir = os.path.join("Dataset","mhd")
if not os.path.exists(mhddir):
os.mkdir(mhddir)
sitk.WriteImage(image,os.path.join(mhddir,"data1.mhd"))
print(os.listdir(mhddir))
['data1.mhd', 'data1.raw']
#讀取mhd
mhdimg = sitk.ReadImage(os.path.join(mhddir,"data1.mhd"))
print(mhdimg.GetSize())
print(mhdimg.GetSpacing())
#轉為numpy
rawmhdimg = sitk.GetArrayFromImage(mhdimg)
print(rawmhdimg.shape)
#儲存mhd
sitk.WriteImage(mhaimg,os.path.join(mhddir,"data1_cpy.mhd"))
(512, 512, 361)
(0.58984375, 0.58984375, 0.5)
(361, 512, 512)
nii檔案的讀寫
繼續使用dicom讀取的資料進行讀寫說明
niidir = os.path.join("Dataset","nii")
if not os.path.exists(niidir):
os.mkdir(niidir)
sitk.WriteImage(image,os.path.join(niidir,"data1.nii.gz"))
print(os.listdir(niidir))
['data1.nii.gz']
#讀取nii
niiimg = sitk.ReadImage(os.path.join(niidir,"data1.nii.gz"))
print(niiimg.GetSize())
print(niiimg.GetSpacing())
#轉為numpy
rawniiimg = sitk.GetArrayFromImage(niiimg)
print(rawniiimg.shape)
#儲存nii
sitk.WriteImage(niiimg,os.path.join(niidir,"data1_cpy.nii.gz"))
(512, 512, 361)
(0.58984375, 0.58984375, 0.5)
(361, 512, 512)
nrrd檔案讀寫
nrrddir = os.path.join("Dataset","nrrd")
if not os.path.exists(nrrddir):
os.mkdir(nrrddir)
sitk.WriteImage(image,os.path.join(nrrddir,"data1.nrrd"))
print(os.listdir(nrrddir))
['data1.nrrd']
#讀取nrrd
nrrdimg = sitk.ReadImage(os.path.join(nrrddir,"data1.nrrd"))
print(nrrdimg.GetSize())
print(nrrdimg.GetSpacing())
#轉為numpy
rawnrrdimg = sitk.GetArrayFromImage(nrrdimg)
print(rawnrrdimg.shape)
#儲存nii
sitk.WriteImage(nrrdimg,os.path.join(nrrddir,"data1_cpy.nrrd"))
(512, 512, 361)
(0.58984375, 0.58984375, 0.5)
(361, 512, 512)
raw/img檔案的讀取
關於raw檔案的讀取可能會相對來說要麻煩一些,因為raw格式只存資料,至於如何解析,需要額外的說明,這個說明可能就是像mhd那樣有一個檔案來說明,或者有些是在網站或者文件裡直接給出的,需要自己簡單處理一下,之間就碰到過,拿到的資料是raw/img的字尾,只有純資料資訊,而關於尺寸、資料型別等資訊都是這個資料的網站上給出的,因此需要自己簡單處理一下。本文可以利用mhd格式中那個raw檔案來說明一下。
#我們來看一下之前那個資料的型別是什麼,
#隨便找一個numpy陣列看一下
print(rawmhaimg.dtype)
int16
#利用np從檔案讀取檔案
rawpath = os.path.join(mhddir,"data1.raw")
mdata = np.fromfile(rawpath,dtype=np.int16)
#reshape資料,這裡要注意讀入numpy的時候,對應是(z,y,x)
mdata = mdata.reshape((361, 512, 512))
mrawimg = sitk.GetImageFromArray(mdata)
#設定pixel spacing
mrawimg.SetSpacing((0.58984375, 0.58984375, 0.5))
#輸出檔案,我們將其儲存為mha
sitk.WriteImage(mrawimg,os.path.join(mhadir,"data1_raw2mha.mha"))
#讀取一下看看
rawtest = sitk.ReadImage(os.path.join(mhadir,"data1_raw2mha.mha"))
print(rawtest.GetSize())
print(rawtest.GetSpacing())
(512, 512, 361)
(0.58984375, 0.58984375, 0.5)