一、胃肠道癌症图像分割数据分析
比赛地址: www.kaggle.com/competition…
1.比赛简介
2019 年,全球估计有 500 万人被诊断出患有胃肠道癌症。在这些患者中,大约一半有资格接受放射治疗,通常每天进行 10-15 分钟,持续 1-6 周。放射肿瘤学家尝试使用指向肿瘤的 X 射线束进行高剂量辐射,同时避开胃和肠。借助集成磁共振成像和线性加速器系统(也称为 MR-Linacs)等新技术,肿瘤学家能够可视化肿瘤和肠道的每日位置,这些位置每天都在变化。在这些扫描中,放射肿瘤学家必须手动勾勒出胃和肠的位置,以便调整 X 射线束的方向,以增加向肿瘤输送的剂量并避开胃和肠。这是一个耗时且劳动密集型的过程,可以将治疗从每天 15 分钟延长到每天一个小时,这对于患者来说可能难以忍受——除非深度学习可以帮助自动化分割过程。分割胃和肠的方法将使治疗更快,并使更多患者获得更有效的治疗。
UW-Madison Carbone 癌症中心是基于 MR-Linac 放射治疗的先驱,自 2015 年以来一直根据患者的日常解剖结构对患者进行 MRI 引导放射治疗。UW-Madison 已慷慨同意支持该项目,该项目为接受治疗的患者提供匿名 MRI在威斯康星大学麦迪逊卡本癌症中心。威斯康星大学麦迪逊分校是威斯康星州麦迪逊市的一所公立研究型大学。威斯康星大学的理念是大学向国家、国家和世界的承诺,他们的努力将使所有公民受益。
在本次比赛中,您将创建一个模型以在 MRI 扫描中自动分割胃和肠。MRI 扫描来自实际的癌症患者,他们在放射治疗期间的不同日子进行了 1-5 次 MRI 扫描。您将基于这些扫描的数据集制定您的算法,以提出创造性的深度学习解决方案,帮助癌症患者获得更好的护理。
在此图中,肿瘤(粉红色粗线)靠近胃(红色粗线)。高剂量的放射线直接照射到肿瘤上,同时避开胃部。剂量水平由彩虹轮廓表示,较高剂量由红色表示,较低剂量由绿色表示。
癌症需要付出足够的代价。如果成功,您将使放射肿瘤学家能够安全地向肿瘤提供更高剂量的辐射,同时避开胃和肠。这将使癌症患者的日常治疗更快,并让他们获得更有效的治疗,副作用更少,更好地长期控制癌症。
2.数据简介
在本次比赛中,我们在图像中分割器官细胞。训练注释以 RLE 编码掩码的形式提供,图像采用 16 位灰度 PNG 格式。
本次比赛中的每个案例都由多组扫描切片表示(每组由扫描发生的日期标识)。有些案例是按时间拆分的(早期是在训练中,后期是在测试中),而有些案例是按案例拆分的——整个案例都在训练中或测试中。本次比赛的目标是能够推广到部分和完全未见的案例。
请注意,在这种情况下,测试集是完全看不见的。如训练集中所示,大约有 50 个案例,具有不同的天数和切片数。
- train.csv - 所有训练对象的 ID 和掩码。
- sample_submission.csv - 格式正确的示例提交文件。
- train - 案例/日文件夹的文件夹,每个文件夹包含给定日期特定案例的切片图像。
- id- 对象的唯一标识符
- class- 对象的预测类别
- EncodedPixels - 已识别对象的 RLE 编码像素
二、数据处理
1.Imports库
!pip install wandb >log.log
[33mWARNING: You are using pip version 22.0.4; however, version 22.1.2 is available. You should consider upgrading via the '/opt/conda/envs/python35-paddle120-env/bin/python -m pip install --upgrade pip' command.[0m[33m [0m
from pathlib import Path import random import matplotlib.pyplot as plt import numpy as np import pandas as pd from glob import glob from PIL import Image from IPython.display import IFrame import imageio import os
2.数据解压缩
!unzip -qoa data/data149096/uw-madison-gi-tract-image-segmentation.zip -d data
3.数据加载
root_dir = Path('./data/') df_train = pd.read_csv(root_dir / 'train.csv') df_train.head() .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
id | class | segmentation | |
0 | case123_day20_slice_0001 | large_bowel | NaN |
1 | case123_day20_slice_0001 | small_bowel | NaN |
2 | case123_day20_slice_0001 | stomach | NaN |
3 | case123_day20_slice_0002 | large_bowel | NaN |
4 | case123_day20_slice_0002 | small_bowel | NaN |
4.创建分割dataframe
dic = {} for data in df_train.iterrows(): p_key = data[1]['id'] if not dic.get(p_key): dic[p_key] = {} dic[p_key]['large_bowel_segmentation'] = None dic[p_key]['small_bowel_segmentation'] = None dic[p_key]['stomach_segmentation'] = None if data[1]['class'] == 'large_bowel': key = 'large_bowel_segmentation' elif data[1]['class'] == 'small_bowel': key = 'small_bowel_segmentation' elif data[1]['class'] == 'stomach': key = 'stomach_segmentation' value = data[1]['segmentation'] dic[p_key][key] = value df_segmentation = pd.DataFrame.from_dict(dic, orient='index') df_segmentation.head() .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
large_bowel_segmentation | small_bowel_segmentation | stomach_segmentation | |
case123_day20_slice_0001 | NaN | NaN | NaN |
case123_day20_slice_0002 | NaN | NaN | NaN |
case123_day20_slice_0003 | NaN | NaN | NaN |
case123_day20_slice_0004 | NaN | NaN | NaN |
case123_day20_slice_0005 | NaN | NaN | NaN |
5.创建图像基本信息
# 生成图像文件列表 images_list = glob('./data/train/*/*/scans/*.png') # path转dataframe df_images = pd.DataFrame({'Path':images_list}) # 从文件路径分割 path_split = df_images['Path'].str.split('/',n=6,expand=True) # 分割 [4] and [6]数据 df_images['CaseNum_Day'] = path_split[4] df_images['SliceNum'] = path_split[6] # 获取 day, slice, height and width case_split = df_images['CaseNum_Day'].str.split('_',n=2, expand=True) df_images['Case'] = case_split[0].str[4:].astype(int) df_images['Day'] = case_split[1].str[3:].astype(int) # 获取 slice, height and width fileName_split = df_images['SliceNum'].str.split('_',n=6, expand=True) df_images['Slice'] = fileName_split[1].astype(int) df_images['Height'] = fileName_split[2].astype(int) df_images['Width'] = fileName_split[3].astype(int) #'id' in segmentation df_images['id'] = df_images['CaseNum_Day'] + '_slice_' + fileName_split[1] df_images.head() .dataframe tbody tr th:only-of-type { vertical-align: middle; } .dataframe tbody tr th { vertical-align: top; } .dataframe thead th { text-align: right; }
Path | CaseNum_Day | SliceNum | Case | Day | Slice | Height | Width | id | |
0 | ./data/train/case131/case131_day23/scans/slice... | case131_day23 | slice_0044_360_310_1.50_1.50.png | 131 | 23 | 44 | 360 | 310 | case131_day23_slice_0044 |
1 | ./data/train/case131/case131_day23/scans/slice... | case131_day23 | slice_0122_360_310_1.50_1.50.png | 131 | 23 | 122 | 360 | 310 | case131_day23_slice_0122 |
2 | ./data/train/case131/case131_day23/scans/slice... | case131_day23 | slice_0031_360_310_1.50_1.50.png | 131 | 23 | 31 | 360 | 310 | case131_day23_slice_0031 |
3 | ./data/train/case131/case131_day23/scans/slice... | case131_day23 | slice_0127_360_310_1.50_1.50.png | 131 | 23 | 127 | 360 | 310 | case131_day23_slice_0127 |
4 | ./data/train/case131/case131_day23/scans/slice... | case131_day23 | slice_0115_360_310_1.50_1.50.png | 131 | 23 | 115 | 360 | 310 | case131_day23_slice_0115 |
# 数据信息统计 print('Unique case numbers ',len(df_images['Case'].unique())) print('Unique Days ',len(df_images['Day'].unique())) print('Unique Heights ',df_images['Height'].unique()) print('Unique Widths ',df_images['Width'].unique())
Unique case numbers 85 Unique Days 35 Unique Heights [360 276 266 234] Unique Widths [310 276 266 234]
# Unique CaseNum_Day unique_case_day = df_images['CaseNum_Day'].unique() print(len(unique_case_day))
274
6.RLE数据解码
详见: baike.baidu.com/item/rle/36…
RLE全称(run-length encoding),翻译为游程编码,又译行程长度编码,又称变动长度编码法(run coding),在控制论中对于二值图像而言是一种编码方法,对连续的黑、白像素数(游程)以不同的码字进行编码。游程编码是一种简单的非破坏性资料压缩法,其好处是加压缩和解压缩都非常快。其方法是计算连续出现的资料长度压缩之。
- 一种压缩过的位图文件格式,RLE压缩方案是一种极其成熟的压缩方案,特点是无损失压缩,既节省了磁盘空间又不损失任何图像数据。
- 游程编码是一种统计编码,该编码属于无损压缩编码。对于二值图有效。其在对图像数据进行编码时,沿一定方向排列的具有相同灰度值的像素可看成是连续符号,用字串代替这些连续符号,可大幅度减少数据量。
- 行程编码是连续精确的编码,在传输过程中,如果其中一位符号发生错误,即可影响整个编码序列,使行程编码无法还原回原始数据。
- 游程编码所能获得的压缩比有多大,主要取决于图像本身的特点。如果图像中具有相同颜色的图像块越大,图像块数目越少,获得的压缩比就越高。反之,压缩比就越小。
# 获取像素位置的函数 def get_pixel_loc(random_segmentation, img_shape): loc = [] for rle_string in random_segmentation: p_loc = [] if isinstance(rle_string, str): # Handle NaN! rle = [int(i) for i in rle_string.split(' ')] pairs = list(zip(rle[0::2],rle[1::2])) for start, length in pairs: for p_pos in range(start, start + length): p_loc.append([p_pos // img_shape[1], p_pos % img_shape[1], len(loc)]) loc.append(p_loc) return loc # 获取 mask def get_mask(rle_group, img_shape): masks = np.zeros(img_shape) masks = np.repeat(masks[:, :, np.newaxis], 3, axis=2) for rle_single in rle_group: for loc in rle_single: masks[tuple(loc)] = 1 return masks # 应用 mask def apply_mask(image, loc_segm, img_shape): image = image / image.max() masks = get_mask(loc_segm, img_shape) image = np.dstack((image + masks[..., 0], image + masks[..., 1], image + masks[..., 2])) image = image / image.max() return image
7.mask打印
# 从数据集打印随机mask # mask数据获取 mask_encoding = df_segmentation.loc[(df_segmentation['large_bowel_segmentation'].notnull()) | (df_segmentation['small_bowel_segmentation'].notnull()) | (df_segmentation['stomach_segmentation'].notnull())] # 列表id转换 mask_id = list(mask_encoding.index) for _ in range(5): random_id = mask_id[np.random.randint(0,len(mask_id) - 1)] random_segmentation = mask_encoding.loc[random_id] splits = random_id.split('_') x = df_images[(df_images['Case']==int(splits[0][4:])) &(df_images['Day']==int(splits[1][3:])) &(df_images['Slice']==int(splits[3]))] image = np.array(Image.open(x['Path'].values[0])) k = image.shape p_loc = get_pixel_loc(random_segmentation, k) fig, ax = plt.subplots(1,3, figsize=(12,16)) ax[0].set_title('Image') ax[0].imshow(image, cmap='gray') ax[1].set_title('Mask') ax[1].imshow(get_mask(p_loc, k)) ax[2].imshow(apply_mask(image, p_loc, k)) plt.show()
8.mask存储gif动画
def save_gif(random_case_day): gif_name = str(random_case_day + '.gif') filenames = [] df_selected_images = df_images[df_images['CaseNum_Day'] == random_case_day].sort_values('Slice').reset_index(drop=True) for i, row in df_selected_images.iterrows(): image = np.array(Image.open(row['Path'])) k = image.shape segmentation = df_segmentation.loc[row['id']] pixel_loc = get_pixel_loc(segmentation, k) masked_image = apply_mask(image, pixel_loc, k) filename = f'{i}.png' filenames.append(filename) plt.imsave(filename, masked_image) with imageio.get_writer(gif_name, mode='I') as writer: for filename in filenames: image = imageio.imread(filename) writer.append_data(image) for filename in set(filenames): os.remove(filename) shape = (df_selected_images.iloc[0]['Width'], df_selected_images.iloc[0]['Height']) return gif_name, shape
random_case_day = unique_case_day[np.random.randint(0,len(unique_case_day) - 1)] out = save_gif(random_case_day) IFrame(out[0], out[1][0], out[1][1])