主要参考此教程完成的实验
一、LIDC-IDRI数据集简介
官方网站
肺图像数据库协会的图像收集(LIDC-IDRI)包括诊断和带有病变注释标记的肺癌筛查胸部CT。这是一个网络公开的国际资源,用于肺癌检测和诊断的计算机辅助诊断(CAD)方法的开发、培训和评估。
数据集包含1018个病例,每个病例包括来自临床胸部CT扫描的图像和一个相关的XML文件,该文件记录了由四名经验丰富的胸部放射科医生进行的两阶段图像注释过程的结果。在最初的盲读阶段,每个放射科医生独立审查每个CT扫描,并标记出属于三种类型之一的病变(“结节>= 3mm”,“结节<3mm”,“非结节>= 3mm”)。在随后的非盲读阶段,每个放射科医生独立地回顾自己的评分以及其他三位放射科医生的匿名评分,以给出最终意见。
二、参数配置lung.conf
病例中有的有结节注释,有的没有结节注释,没有的保存在Clean文件夹:
[prepare_dataset]
lidc_dicom_path = ./LIDC-IDRI
mask_path = ./data/Mask
image_path = ./data/Image
clean_path_image = ./data/Clean/Image
clean_path_mask = ./data/Clean/Mask
meta_path = ./data/Meta/
mask_threshold = 8
[pylidc]
confidence_level = 0.5
padding_size = 512
三、创建数据集prepare_dataset.py:
通过MakeDataSet类的prepare_dataset方法创建数据集和标注信息文件,MakeDataSet实例的属性包括:
- LIRI_list:遍历保存LIDC_IDRI数据的文件夹,得到包含所有病例文件夹名字的列表:
- img_path,mask_path:包含结节的肺部图像和掩码的保存路径
- clean_path_img,clean_path_mask:不包含结节的肺部图像和掩码的保存路径
- meta_path:结节信息保存路径
- mask_threshold:最小的结节阈值
- c_level和padding:生成结节掩码时的输入参数
- meta:结节信息属性名
patient_id | nodule_no | slice_no | original_image | mask_image | maliganancy | is_cancer | is_clean |
---|---|---|---|---|---|---|---|
病例id | 结节编号 | 切片编号 | 图像编号 | 掩码编号 | 恶性程度 | 是否为癌症 | 是否包含结节 |
class MakeDataSet:
def __init__(self, LIDC_Patients_list, IMAGE_DIR, MASK_DIR,CLEAN_DIR_IMAGE,CLEAN_DIR_MASK,META_DIR, mask_threshold, padding, confidence_level=0.5):
self.IDRI_list = LIDC_Patients_list
self.img_path = IMAGE_DIR
self.mask_path = MASK_DIR
self.clean_path_img = CLEAN_DIR_IMAGE
self.clean_path_mask = CLEAN_DIR_MASK
self.meta_path = META_DIR
self.mask_threshold = mask_threshold
self.c_level = confidence_level
self.padding = [(padding,padding),(padding,padding),(0,0)]
self.meta = pd.DataFrame(index=[],columns=['patient_id','nodule_no','slice_no','original_image','mask_image','malignancy','is_cancer','is_clean'])
def calculate_malignancy(self,nodule):
# 计算结节恶性程度
def save_meta(self,meta_list):
# 保存结节信息到csv文件
def prepare_dataset(self):
# 创建数据集
if __name__ == '__main__':
LIDC_IDRI_list= [f for f in os.listdir(DICOM_DIR) if not f.startswith('.')]
LIDC_IDRI_list.sort()
test= MakeDataSet(LIDC_IDRI_list,IMAGE_DIR,MASK_DIR,CLEAN_DIR_IMAGE,CLEAN_DIR_MASK,META_DIR,mask_threshold,padding,confidence_level)
test.prepare_dataset()
1、使用到的库
- pylidc:pylidc不仅可用于分析和查询注释数据(不需要访问DICOM图像数据),也能用于处理与LIDC数据集相关联的DICOM文件。
- pathlib:这个模块提供了用适合不同操作系统的语义表示文件系统路径的类。路径类分为纯路径(提供没有I/O的纯计算操作)和具体路径(继承自纯路径,但也提供I/O操作)。
- Tqdm 是一个快速,可扩展的Python进度条,可以在 Python 长循环中添加一个进度提示信息,用户只需要封装任意的迭代器 tqdm(iterator)。
- median_high:高中位数始终是数据集的成员。当数据点的数量为奇数时,将返回中间值。当它是偶数时,将返回两个中间值中较大的一个。
2、生成数据集函数
def prepare_dataset(self):
# 为image和mask命名
# 0000,0001,0002,...,0999
prefix = [str(x).zfill(3) for x in range(1000)]
# 创建文件夹
if not os.path.exists(self.img_path):
os.makedirs(self.img_path)
if not os.path.exists(self.mask_path):
os.makedirs(self.mask_path)
if not os.path.exists(self.clean_path_img):
os.makedirs(self.clean_path_img)
if not os.path.exists(self.clean_path_mask):
os.makedirs(self.clean_path_mask)
if not os.path.exists(self.meta_path):
os.makedirs(self.meta_path)
IMAGE_DIR = Path(self.img_path)
MASK_DIR = Path(self.mask_path)
CLEAN_DIR_IMAGE = Path(self.clean_path_img)
CLEAN_DIR_MASK = Path(self.clean_path_mask)
for patient in tqdm(self.IDRI_list):
# 处理每个病例
print("Saved Meta data")
self.meta.to_csv(self.meta_path+'meta_info.csv',index=False) # 保存数据信息
3、处理每个病例
首先获得当前病例名字:
pid = patient #LIDC-IDRI-0001~
查找到一个名字相符的scan对象:
import pylidc as pl
scan = pl.query(pl.Scan).filter(pl.Scan.patient_id == pid).first()
scan对象中一个结节可能包含多个注释,可以使用pylidc.Scan.cluster_annotations()方法来确定哪些注释是指同一个结节,它使用距离函数来创建一个邻接图,以确定哪些注释在一次扫描中引用了同一个结节:
nodules_annotation = scan.cluster_annotations()
将扫描图像值转换为NumPy数组,便于进行图像处理:
vol = scan.to_volume()
print("Patient ID: {} Dicom Shape: {} Number of Annotated Nodules: {}".format(pid,vol.shape,len(nodules_annotation)))
输出示例:
Patient ID: LIDC-IDRI-0003 Dicom Shape: (512, 512, 140) Number of Annotated Nodules: 4
表示编号LIDC-IDRL-0003的病例的成像尺寸为512*512*140,有4个标注结节。
对注释进行可视化可以得到:
scan.visualize(annotation_groups=nodules_annotation)
从图中可以看出编号为LIDC-IDRI-0003的病例有四个标注的结节,分别包含不同数量的注释,最多为4个(因为一共有四位医师)。
有的病例中不包含结节注释,应区别处理:
if len(nodules_annotation) > 0:
# 处理包含结节注释的病例
else:
# 处理不包含结节注释的病例
4、处理包含结节注释的病例
使用pylidc.utils.consensus()方法获得结节掩码。
输入参数:
-
nodule:聚类后一个结节的注释列表,长度最大为4,例如:
nodule 1 : [Annotation(id=90,scan_id=14), Annotation(id=93,scan_id=14), Annotation(id=98,scan_id=14),
-
clevel:例如clevel=0.5,那么当各位医师注释的结节分割中有超过50%的结果包含该体素时,体素返回值为1,否则为0。
-
padding:如果提供了整数,则边界框将按此整数数量均匀填充。其他形式的参数可以参考文档。
返回值:
- mask[ndarray]:是根据clevel生成的注释掩码的布尔值数组。
- cbbox[tumple]:是一个3元组,可用于在相应位置对图像进行索引,根据不同的padding值,cddox的大小也不同。
- masks[,list]:是对应于每个Annotation对象的布尔值掩码的列表。掩码列表中的每个掩码具有相同的形状,并在cbbox提供的边界框中进行采样。
for nodule_idx, nodule in enumerate(nodules_annotation): # 遍历每个结节
mask, cbbox, masks = consensus(nodule,self.c_level,self.padding)
lung_np_array = vol[cbbox]
malignancy, cancer_label = self.calculate_malignancy(nodule) # 计算恶性程度
for nodule_slice in range(mask.shape[2]):
# 处理每个CT切片
计算恶性程度
选取所有注释的中高位数:
def calculate_malignancy(self,nodule):
list_of_malignancy =[]
for annotation in nodule:
list_of_malignancy.append(annotation.malignancy)
malignancy = median_high(list_of_malignancy)
if malignancy > 3:
return malignancy,True
elif malignancy < 3:
return malignancy, False
else:
return malignancy, 'Ambiguous'
处理每个CT切片
结节像素太小的跳过,有些遮罩尺寸太小。这些可能会阻碍训练:
if np.sum(mask[:,:,nodule_slice]) <= self.mask_threshold:
continue
对肺实质进行分割(segment_lung函数会在下一篇中单独介绍):
lung_segmented_np_array = segment_lung(lung_np_array[:,:,nodule_slice])
一些值被存储为-0,这可能导致pytorch训练中的数据类型错误:
lung_segmented_np_array[lung_segmented_np_array==-0] =0
每个文件的命名:NI=结节图像,MA=掩码原始:
nodule_name = "{}_NI{}_slice{}".format(pid[-4:],prefix[nodule_idx],prefix[nodule_slice])
mask_name = "{}_MA{}_slice{}".format(pid[-4:],prefix[nodule_idx],prefix[nodule_slice])
meta_list = [pid[-4:],nodule_idx,prefix[nodule_slice],nodule_name,mask_name,malignancy,cancer_label,False]
保存结节信息文件和图像:
self.save_meta(meta_list)
np.save(patient_image_dir / nodule_name,lung_segmented_np_array)
np.save(patient_mask_dir / mask_name,mask[:,:,nodule_slice])
save_meta()
函数将结节信息添加到meta列表:
def save_meta(self,meta_list):
# 将结节信息保存在csv文件中
tmp = pd.Series(meta_list,index=['patient_id','nodule_no','slice_no','original_image','mask_image','malignancy','is_cancer','is_clean'])
self.meta = self.meta.append(tmp,ignore_index=True)
5、处理不包含注释的病例
patient_clean_dir_image = CLEAN_DIR_IMAGE / pid
patient_clean_dir_mask = CLEAN_DIR_MASK / pid
Path(patient_clean_dir_image).mkdir(parents=True, exist_ok=True)
Path(patient_clean_dir_mask).mkdir(parents=True, exist_ok=True)
for slice in range(vol.shape[2]):
if slice >50:
break
# 处理每一个切片
没有结节的切片对应的mask为全是黑色:
lung_segmented_np_array = segment_lung(vol[:,:,slice])
lung_segmented_np_array[lung_segmented_np_array==-0] =0
lung_mask = np.zeros_like(lung_segmented_np_array)
保存图像和对应掩码,将信息添加到meta数组:文章来源:https://www.toymoban.com/news/detail-454447.html
nodule_name = "{}_CN001_slice{}".format(pid[-4:],prefix[slice])
mask_name = "{}_CM001_slice{}".format(pid[-4:],prefix[slice])
meta_list = [pid[-4:],slice,prefix[slice],nodule_name,mask_name,0,False,True]
self.save_meta(meta_list)
np.save(patient_clean_dir_image / nodule_name, lung_segmented_np_array)
np.save(patient_clean_dir_mask / mask_name, lung_mask)
将信息保存到meta.csv文件:文章来源地址https://www.toymoban.com/news/detail-454447.html
print("Saved Meta data")
self.meta.to_csv(self.meta_path+'meta_info.csv',index=False)
到了这里,关于【深度学习】肺结节分割项目实战一:处理数据集的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!