医学图像预处理——3D CT肝脏dicom图像预处理学习记录(3D-IRCADB)

学习资料:
UNet-肝脏肿瘤图像语义分割
医学图像预处理(三)——windowing(ct对比增强)
Python CT图像预处理——nii格式读取、重采样、窗宽窗位设置
医学图像处理步骤为:
1、数据加载
2、CT图像增强
3、直方图均衡化增强
4、获取对应CT、掩膜灰度图并保存

DICOM数据读取:使用pydicom库中的dcmread函数进行读取

image_slices = [pydicom.dcmread(os.path.join(data_path, file_name)) for file_name in os.listdir(data_path)]

上述代码等同于

image_slices_test = []
for file_name_test in os.listdir(data_path):
    image_slices_test.append(pydicom.dcmread(os.path.join(data_path, file_name_test)))

作用是读取data_path中的文件,放入到image_slices中,得到一个129个大小的list,每一个list元素表示一个切片的全部信息。

排序:按照InstanceNumber(扫描的EPI时间序列顺序)顺序

img_slices.sort(key=lambda x:x.InstanceNumber)

提取像素值:

img_array = np.array([i.pixel_array for i in img_slices])

对于dicom、nii格式的图片,只要是ct图,就都可以选择将储存的原始数据转化为Hu值,Hu值代表了物体真正的密度。

对于nii格式的图片,在使用nibabel、simpleitk常用的api接口,都会自动的进行上述转化构成,即取出的值已是hu。若是想要得到原始数据:需要使用nib.load('xx').dataobj.get_unscaled()或者itk.ReadImage('xx').GetPixel(x,y,z)

对于dcm格式图片,simpleitk,pydicom常用的api接口都不会将原始数据转化为Hu!(itk snap软件读入dcm或nii都不会对数据进行scale操作)

HU值和像素值的转化,以dicm数据为例
HU = pixel_val * slope + intercept(如果Slope为1,Intercept为0,则不需要转化的问题,具体Slope和Intercept的值在头文件中都会有说明的)
dicom转化为Hu的代码如下:

def get_pixels_hu(scans):
    # np.stack()
    # pixel_array 是一个正常的像素矩阵,512x512
    image = np.stack([s.pixel_array for s in scans])
    image = image.astype(np.int16)
    image[image == -2000] = 0
    # 缩放斜率  RescaleIntercept
    intercept = scans[0].RescaleIntercept
    # 截距 RescaleSlope
    slope = scans[0].RescaleSlope
    if slope != 1:
        image = slope * image.astype(np.float64)
        image = image.astype(np.int16)
    image += np.int16(intercept)
    return np.array(image, dtype=np.int16)

根据Hu(CT)值来筛选我们想要的部位的图片,其他部位全部抹黑或者抹白,目的是为了增加图像对比度。使用windowing方法,观察的CT值范围:窗宽。观察的中心CT值即为窗位,然后对肿瘤部分进行二值化处理。

def windowing(img, window_width, window_center):
    # params:需要增强的图片, 窗口宽度, 窗中心   通过窗口最小值来线性移动窗口增强
    min_windows = float(window_center)-0.5*float(window_width)
    new_img = (img-min_windows)/float(window_width)
    new_img[new_img<0] = 0			# 二值化处理 抹白
    new_img[new_img>1] = 1			# 抹黑
    return (new_img * 255).astype('uint8')  # 把数据整理成标准图像格式

直方图均衡化
将整个图像分成许多小块,对每个小块进行均衡化。Opencv中将这种方法为:cv2.createCLAHE()

def clahe_equalized(imgs):
    # 输入imgs的形状必须是3维
    assert (len(imgs.shape) == 3)
    # 定义均衡化函数
    clahe = cv2.createCLAHE(clipLimit=2.0, tileGridSize=(8, 8))
    # 新数组存放均衡化后的数据
    img_res = np.zeros_like(imgs)
    for i in range(len(imgs)):
        img_res[i,:,:] = clahe.apply(np.array(imgs[i,:,:], dtype=np.uint8))
    return img_res/255

nii格式CT数据读取

一、读取:使用nibabel包进行读写查看等操作
nibabel读取会将图像旋转九十度,也就是各轴的对应关系为[z, x, y]

import nibabel as  nb
img = nb.load(xxx.nii.gz) #读取nii格式文件
img_affine = img.affine
data = img.get_data()

存储nii格式文件,保存时要联通affine矩阵一起保存

import nibabel as  nb
nb.Nifti1Image(data,img_affine).to_filename(xxx.nii.gz)

查看

from nibabel.viewers import OrthoSlicer3D
OrthoSlicer3D(data.transpose(1,2,0)).show()

重采样
CT图像的重采样视为了使体数据中大小不同的体素变得大小相同。由于CT不同轴的体素尺寸、粗细粒度不同,直接使用不利于进行深度模型的训练和推理
重采样步骤:
1、获取先从nii文件的头文件中获取各轴单位体素对应的空间距离

header = img.header
print(header)

打印出来可见如下信息:

print(header)
<class 'nibabel.nifti1.Nifti1Header'> object, endian='<'
sizeof_hdr      : 348
data_type       : b''
db_name         : b''
extents         : 0
session_error   : 0
regular         : b'r'
dim_info        : 0
dim             : [  3 512 512  81   1   1   1   1]
intent_p1       : 0.0
intent_p2       : 0.0
intent_p3       : 0.0
intent_code     : none
datatype        : int16
bitpix          : 16
slice_start     : 0
pixdim          : [-1.     0.881  0.881  5.     0.     0.     0.     0.   ]
vox_offset      : 0.0
scl_slope       : nan
scl_inter       : nan
slice_end       : 0
slice_code      : unknown
xyzt_units      : 10
cal_max         : 0.0
cal_min         : 0.0
slice_duration  : 0.0
toffset         : 0.0
glmax           : 0
glmin           : 0
descrip         : b'Time=151745.150'
aux_file        : b'Venous_Phase'
qform_code      : scanner
sform_code      : scanner
quatern_b       : 0.0
quatern_c       : 1.0
quatern_d       : 0.0
qoffset_x       : 217.3328
qoffset_y       : -225.04568
qoffset_z       : 1390.0
srow_x          : [ -0.881   -0.       0.     217.3328]
srow_y          : [   0.         0.881      0.      -225.04568]
srow_z          : [   0.    0.    5. 1390.]
intent_name     : b''
magic           : b'n+1'

关键字段含义如下:
sizeof_hdr:是保存文件的头文件大小,NIFTI-1或者ANALYZE格式的文件sizeof_hdr=348
dim_info:dim_info字段存储着频率编码方向(1, 2, 3),相位编码方向(1, 2, 3)和采集期间层选择方向(1, 2, 3),对于径向采集来讲,频率编码和相位编码都设置为0。
dim:short dim[8]保存着前面提到的图像的维度信息。如果第0维不是(1-7)之间的数字,那么这个数据具有相反的字节顺序,所以应该进行字节交换。
datatype中存储的是数据的类型。
bitpix字段必须与datatype中的代码所对应的bit(s)/pix的大小相等。
slice切片信息:
slice_start,slice_end,slice_code,slice_furation
slice_duration是存储功能磁共振成像采集的时间相关信息,需要与dim_info字段一起使用。
pixdim体素维度:每个体素维度信息都保存在pixdim中,各自对应dim,但pixdm[0]有特殊意义,其值只能是-1或1。前四个维度将在xyzt_units字段中指定。pixdim[1]对应x轴,pixdim[2]对应y轴,pixdim[3]对应z轴。
vox_offset体素偏移量:vox_offset指单个文件(.nii)图像数据的字节偏移量。
在重采样中主要需要使用到pixdim这一个字段的数据。

pixdim          : [-1.     0.881  0.881  5.     0.     0.     0.     0.   ]

此外,还需要知道各轴的体素总数:

width, height, channel = img.dataobj.shape
print(width, height, channel)

结果如下:x轴数据的空间大小为512 * pixdim[1] = 451.07199mm,y轴数据的空间大小为512 * pixdim[2] = 451.07199mm,z轴数据的空间大小为81 * pixdim[3] = 405mm。

512 512 81

根据各轴实际对应的空间大小,可以根据重采样后期望各体素在某个轴上单位体素对应的空间大小,算各种重采样后的体素总数。此处以各轴重采样为1mm spacing为例。使用到的是skimage中的transform函数,调用resize使重采样后的数据具有指定的shape。

### Get original space
width, height, channel = img.dataobj.shape
ori_space = [width,height,channel]
# Resample to have 1.0 spacing in all axes
spacing = [1.0, 1.0, 1.0]
data_resampled = resample(data,ori_space, header, spacing)
OrthoSlicer3D(data_resampled).show()
def resample(data,ori_space, header, spacing):
    ### Calculate new space
    new_width = round(ori_space[0] * header['pixdim'][1] / spacing[0])
    new_height = round(ori_space[1] * header['pixdim'][2] / spacing[1])
    new_channel = round(ori_space[2] * header['pixdim'][3] / spacing[2])
    new_space = [new_width, new_height, new_channel]
    data_resampled = skimage.transform.resize(data,new_space,order=1, mode='reflect', cval=0, clip=True, preserve_range=False, anti_aliasing=True, anti_aliasing_sigma=None)
    return data_resampled

医学图像领域的窗口技术,包括窗宽(window width) 窗位(window center),用于选择感兴趣的CT值范围。窗宽和窗位分别对应图像的对比度与亮度。

note:窗宽窗位是CT图像特有的概念,MRI中并没有;CT图像必须先转换成HU值再做窗宽窗位调整。
**窗宽**是CT图像上显示的CT值范围,在此CT值范围内的组织和病变均以不同的模拟灰度显示。而CT值高于此范围的组织和病变,无论高出程度有多少,均以白影显示,不再有灰度差异; 反之,低于此范围的组织结构,不论低的程度有多少,均以黑影显示,也无灰度差别。增大窗宽,则图像所示CT值范围加大,显示具有不同密度的组织结构增多,但各结构之间的灰度差别减少。减小窗宽,则显示的组织结构减少,然而各结构之间的灰度差别增加。如观察脑质的窗宽常为-15~+85H,即密度在-15 ~+85H范围内的各种结构如脑质和脑脊液间隙均以不同灰度显示。而高于+85H的组织结构如骨质几颅内钙化,其间虽有密度差,但均以白影显示,无灰度差别;而低于-15H组织结构如皮下脂肪及乳突内气体均以黑影显示,其间也无灰度差别。
**窗位**是窗的中心位置,同样的窗宽,由于窗位不同,其所包括的CT值范围的CT值也有差异。例如窗宽同为100H,当窗位为0H时,其CT值范围为-50 ~ +50H ; 如窗位为+35H时,则CT值范围为-15~+85H。通常,欲观察某以组织结构及发生的病变,应以该组织的CT值为窗位。例如脑质CT值约为+35H,则观察脑组织及其病变时,选择窗位以+35H为妥。

窗宽窗位的设置方法:
一、手动设置
根据常用的预设值设置窗宽窗位。实例中使用的为腹部ct,常用的预设值为窗宽 300-500,窗位 30-50。以下示例中使用窗宽400,窗位40。

w_width = 400
w_center = 40
data_adjusted1 = adjustMethod1(data_resampled,w_width,w_center)

def adjustMethod1(data_resampled,w_width,w_center):
    val_min = w_center - (w_width / 2)
    val_max = w_center + (w_width / 2)
    data_adjusted = data_resampled.copy()
    data_adjusted[data_resampled < val_min] = val_min
    data_adjusted[data_resampled > val_max] = val_max
    return data_adjusted

方法二:

根据观察目标的mask,提取对应的目标区域;统计其最大值和最小值,从而计算窗宽窗位。
重采样和窗宽窗位调整参考以下几篇文章:
[https://blog.csdn.net/qq_46511579/article/details/118441519](https://blog.csdn.net/qq_46511579/article/details/118441519)
[https://blog.csdn.net/qq_44289607/article/details/123495983](https://blog.csdn.net/qq_44289607/article/details/123495983)
[https://blog.csdn.net/qq_44289607/article/details/123394110?spm=1001.2014.3001.5502](https://blog.csdn.net/qq_44289607/article/details/123394110?spm=1001.2014.3001.5502)

后续学习:
参考文章:
【医学影像数据处理】nii、npz、npy、dcm、mhd 的数据格式互转,及多目标分割处理汇总
.npy、.mha、.npz

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值