前言
最近在学习Unet等家族做肝脏肿瘤分割,肝脏肿瘤公开数据集有出名的(全球人都在研究的)LITS数据集,从网上下载下来的时候,格式是nii文件。
因为我是学习的2D模型训练,所以我需要对nii格式文件进行切片,经过研究学习网上几份代码,发现其中还有有点讲究的,所以想写篇文章分享介绍一下。
(如果有对训练更有利的预处理方法,欢迎评论区讨论)
目标:
- 对nii文件切片成为npy格式
- 数据集转换成png格式,方便自己查看或者验证
- 对肝脏和肿瘤数据集进行分离,方便单独进行训练
先介绍一下代码的主处理逻辑:
- 遍历CT图像文件,读取CT图像和相应的掩膜。
- 对CT图像进行阈值处理,限制像素值在[-200, 200]之间,并进行归一化。
- 找到肝脏区域的起始和结束切片,并进行扩展。
- 裁剪CT图像和掩膜到指定大小(448x448)。
- 对每个切片进行处理,检查掩膜中是否有病灶(即是否有非零值),如果有,则保存CT图像和掩膜为NumPy数组。
代码
代码的主要功能是从CT图像和相应的分割掩膜中提取训练集,并将处理后的图像和掩膜保存为NumPy数组格式。
'''
Func:制作训练集
'''
import os
import numpy as np
import SimpleITK as sitk
from tqdm import tqdm
import cv2
ct_name = ".nii"
mask_name = ".nii"
ct_path = 'F:\datesate\datasetnii\image'
seg_path = 'F:\datesate\datasetnii\label'
png_path = './png/'
outputImg_path = "E:/seg_example/data/trainImage_k1_1217"
outputMask_path = "E:/seg_example/data/trainMask_k1_1217"
if not os.path.exists(outputImg_path):
os.mkdir(outputImg_path)
if not os.path.exists(outputMask_path):
os.mkdir(outputMask_path)
#遍历给定目录,根据参数返回子目录列表或文件列表。
def file_name_path(file_dir, dir=True, file=False):
"""
get root path,sub_dirs,all_sub_files
:param file_dir:
:return: dir or file
"""
for root, dirs, files in os.walk(file_dir):
if len(dirs) and dir:
print("sub_dirs:", dirs)
return dirs
if len(files) and file:
print("files:", files)
return files
#从图像中心裁剪出指定大小的区域。
def crop_ceter(img, croph, cropw):
#for n_slice in range(img.shape[0]):
height, width = img[0].shape
starth = height//2 - (croph//2)
startw = width//2 - (cropw//2)
return img[:, starth:starth+croph, startw:startw+cropw]
if __name__ == "__main__":
for index, file in enumerate(tqdm(os.listdir(ct_path))):
# 获取CT图像及Mask数据 指定了读取 CT 图像时使用 16 位有符号整数类型来存储像素值。
ct_src = sitk.ReadImage(os.path.join(ct_path, file), sitk.sitkInt16)
mask = sitk.ReadImage(os.path.join(seg_path, file.replace('volume', 'segmentation')), sitk.sitkUInt8)
# GetArrayFromImage()可用于将SimpleITK对象转换为ndarray
ct_array = sitk.GetArrayFromImage(ct_src)
mask_array = sitk.GetArrayFromImage(mask)
# mask_array[mask_array == 1] = 0 # 肿瘤
# mask_array[mask_array == 2] = 1
# 阈值截取
ct_array[ct_array > 200] = 200
ct_array[ct_array < -200] = -200
ct_array = ct_array.astype(np.float32)
ct_array = ct_array / 200
# 找到肝脏区域开始和结束的slice,并各向外扩张slice
z = np.any(mask_array, axis=(1, 2))
start_slice, end_slice = np.where(z)[0][[0, -1]]
print('cs',start_slice,end_slice,file)
start_slice = max(0, start_slice - 1)
end_slice = min(mask_array.shape[0] - 1, end_slice + 2)
ct_crop = ct_array[start_slice:end_slice, :, :]
mask_crop = mask_array[start_slice+1:end_slice-1, :, :]
#裁剪(偶数才行) 448*448
ct_crop = ct_crop[:,32:480,32:480]
mask_crop = mask_crop[:,32:480,32:480]
print('ct_crop.shape',ct_crop.shape)
# 切片处理,并去掉没有病灶的切片
if int(np.sum(mask_crop))!=0:
for n_slice in range(mask_crop.shape[0]):
maskImg = mask_crop[n_slice, :, :]
ctImageArray = np.zeros((ct_crop.shape[1], ct_crop.shape[2], 3), np.float32)
ctImageArray[:, :, 0] = ct_crop[n_slice , :, :]
ctImageArray[:, :, 1] = ct_crop[n_slice + 1, :, :]
ctImageArray[:, :, 2] = ct_crop[n_slice + 2, :, :]
imagepath = outputImg_path + "/" + str(index+1) + "_" + str(n_slice) + ".npy"
maskpath = outputMask_path + "/" + str(index+1) + "_" + str(n_slice) + ".npy"
np.save(imagepath, ctImageArray) # (448,448,3) np.float dtype('float64')
np.save(maskpath, maskImg) # (448,448) dtype('uint8') 值为0 1 2
else:
continue
print("Done!")
你可能有一些疑问
-
是数据集是怎么区分肝脏和肿瘤的
-
读取CT图像和相应的掩膜 如何找到对应的掩膜的
-
对CT图像进行阈值处理,限制像素值在[-200, 200]之间,并进行归一化。 必须要归一化吗
-
找到肝脏区域的起始和结束切片,并进行扩展。 这个是有必要的吗 是如何做到找到这个肝脏区域的
-
对每个切片进行处理,检查掩膜中是否有病灶(即是否有非零值),如果有,则保存CT图像和掩膜为NumPy数组。 这个病灶是指肿瘤吗?那没有病灶的图像就不保存了吗 如果保存 对最后训练的数据集准确度会不会有提升?
-
如果我每个切片都输出固定的图片 不去掉没有病灶区域的图片 这样处理效率会不会高一点(对纯黑色图进行训练,我认为没有什么意义,不利于训练效率)
我的理解和 AI的回答:
- 数据集是怎么区分肝脏和肿瘤的:
- 在LiTS数据集中,CT图像的分割掩膜(mask)通常是由医学专家手动标注的。掩膜中每个像素的值代表不同的结构或区域,例如肝脏、肿瘤或背景。通常,肝脏区域会被标记为1,肿瘤区域会被标记为2,背景则为0。通过这种方式,模型可以学习到肝脏和肿瘤的特征。
- 如何找到对应的掩膜:
- 在代码中,掩膜是通过将CT图像文件名中的“volume”替换为“segmentation”来找到的。例如,如果CT图像的文件名是
volume_01.nii
,那么对应的掩膜文件名将是segmentation_01.nii
。这样可以确保CT图像和掩膜之间的对应关系。
- 在代码中,掩膜是通过将CT图像文件名中的“volume”替换为“segmentation”来找到的。例如,如果CT图像的文件名是
- 阈值处理和归一化的必要性:
- 阈值处理是为了去除CT图像中不必要的噪声和极端值,确保图像的像素值在一个合理的范围内。归一化是将像素值缩放到[0, 1]的范围,这样可以提高模型训练的稳定性和收敛速度。虽然不是绝对必要,但通常是一个良好的实践,尤其是在深度学习中。
- 找到肝脏区域的起始和结束切片的必要性:
- 找到肝脏区域的起始和结束切片是为了确保只处理包含肝脏的切片,避免处理无关的背景切片。这可以减少计算量并提高模型的训练效率。通过检查掩膜数组中是否有非零值,可以确定肝脏区域的起始和结束切片。
- 病灶的定义和保存策略:
- 在这里,病灶通常指的是肿瘤。如果掩膜中有非零值(即存在肿瘤或肝脏区域),则认为该切片是有病灶的。代码中只保存有病灶的切片是为了确保训练数据集中包含有用的信息。保存没有病灶的图像可能会导致模型学习到无关的特征,从而降低模型的准确性。因此,通常建议只保留有病灶的切片,以提高训练数据集的质量和模型的准确度。
到这里npy格式的切片文件已经完成,可以拿去训练了。
剩下两个目标,点赞后面会更新
-
肝脏肿瘤分离
-
npy2png格式转换