主要参考此教程完成的实验
官方网站
肺图像数据库协会的图像收集(LIDC-IDRI)包括诊断和带有病变注释标记的肺癌筛查胸部CT。这是一个网络公开的国际资源,用于肺癌检测和诊断的计算机辅助诊断(CAD)方法的开发、培训和评估。
数据集包含1018个病例,每个病例包括来自临床胸部CT扫描的图像和一个相关的XML文件,该文件记录了由四名经验丰富的胸部放射科医生进行的两阶段图像注释过程的结果。在最初的盲读阶段,每个放射科医生独立审查每个CT扫描,并标记出属于三种类型之一的病变(“结节>= 3mm”,“结节<3mm”,“非结节>= 3mm”)。在随后的非盲读阶段,每个放射科医生独立地回顾自己的评分以及其他三位放射科医生的匿名评分,以给出最终意见。
病例中有的有结节注释,有的没有结节注释,没有的保存在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
通过MakeDataSet类的prepare_dataset方法创建数据集和标注信息文件,MakeDataSet实例的属性包括:
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()
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) # 保存数据信息
首先获得当前病例名字:
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:
# 处理不包含结节注释的病例
使用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:如果提供了整数,则边界框将按此整数数量均匀填充。其他形式的参数可以参考文档。
返回值:
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'
结节像素太小的跳过,有些遮罩尺寸太小。这些可能会阻碍训练:
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)
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数组:
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文件:
print("Saved Meta data")
self.meta.to_csv(self.meta_path+'meta_info.csv',index=False)