(CNS复现)CLAM——Chapter_02

CLAM: A Deep-Learning-based Pipeline for Data Efficient and Weakly Supervised Whole-Slide-level Analysis

文章目录

  • (CNS复现)CLAM——Chapter_02
    • 前言
    • Step-01: imports
    • Step-02:初始化配置信息
    • Step-03:调试代码
      • Step-03.01 初始化配置文件
      • Step-03.02 初始化WSI
      • Step-03.03 初始化分割patch参数
      • Step-03.04 patch 分割步骤解读

前言

(CNS复现)CLAM——Chapter_00

(CNS复现)CLAM——Chapter_01

(CNS复现)CLAM——Chapter_02

(CNS复现)CLAM——Chapter_03

这一部分主要是针对数据预处理的了解 create_patches_fp.py 中获取 patch 的方法和具体流程

该步骤的目的是讲WSI分割成固定大小的patch,将这些patch作为一个WSI的候选特征图。

这样就解决了超大的WSI图片无法训练的问题

Step-01: imports

# imports
# 这一部分都是基础包,不需要讲解
import os
import numpy as np
import time
import pdb
import cv2
import matplotlib.pyplot as plt
plt.rcParams['axes.unicode_minus'] = False
import pandas as pd
import warnings# my imports
from wsi_core.batch_process_utils import initialize_df # 这个包的作用是将输入的参数转换为EXCEL,方便记录
from wsi_core.WholeSlideImage import WholeSlideImage # WSI对象,里面的功能到时候一一讲解
from wsi_core.wsi_utils import StitchCoords # 这个是一个可视化工具# other options
warnings.filterwarnings("ignore")

Step-02:初始化配置信息

这个配置内容基本上和上次的教程完全一样,就不复制了

# 初始化配置信息
# 元路径
save_dir = '/media/yuansh/14THHD/CLAM/Result'
source = '/media/yuansh/14THHD/CLAM/DataSet/LUAD'# 输出文件路径
patch_save_dir = os.path.join(save_dir, 'patches') # patch文件,以.H5形式保存,其中记录了各个patch 的四个坐标的位置
mask_save_dir = os.path.join(save_dir, 'masks') # mask文件,即去除背景色后的jpg文件,可以显著减少image大小
stitch_save_dir = os.path.join(save_dir, 'stitches') # 可视化文件directories = {'source': source,'save_dir': save_dir,'patch_save_dir': patch_save_dir,'mask_save_dir': mask_save_dir,'stitch_save_dir': stitch_save_dir}# 判断目标文件夹是否存在,如果不存在则创建文件夹
for key, val in directories.items():print("{} : {}".format(key, val))if key not in ['source']:os.makedirs(val, exist_ok=True)# 设置基本参数
process_list = None # 一个文本文件,里面存放各个数据的处理参数
patch_size = 256 # 每个patch 的大小
step_size = 256 # 每个patch搜索框的步长,当与patch size相同的时候每个patch没有交集
patch_level = 0 # 下采样水平,水平越高,下采样程度越大,数据大小越小
use_default_params = False # 是否使用初始化参数
seg = True # 是否切割图片
save_mask = True # 保存切割后的数据
stitch = True # 是否对输出的patch数据可视化
patch = False # 是否输出patch文件
auto_skip = False # 是否跳过已经处理的数据# 操作参数
seg_params = {'seg_level': -1, 'sthresh': 8, 'mthresh': 7, 'close': 4, 'use_otsu': False,'keep_ids': 'none', 'exclude_ids': 'none'}
filter_params = {'a_t': 100, 'a_h': 16, 'max_n_holes': 8}
vis_params = {'vis_level': -1, 'line_thickness': 500}
patch_params = {'use_padding': True, 'contour_fn': 'four_pt'}
parameters = {'seg_params': seg_params,'filter_params': filter_params,'patch_params': patch_params,'vis_params': vis_params}
# 如果有参数表,则输入参数表
if process_list:process_list = os.path.join(save_dir, process_list)else:process_list = None
source : /media/yuansh/14THHD/CLAM/DataSet/LUAD
save_dir : /media/yuansh/14THHD/CLAM/Result
patch_save_dir : /media/yuansh/14THHD/CLAM/Result/patches
mask_save_dir : /media/yuansh/14THHD/CLAM/Result/masks
stitch_save_dir : /media/yuansh/14THHD/CLAM/Result/stitches

Step-03:调试代码

这里我们需要参考 WholeSlideImage 这个文件

这一次主要调试的对象是 WSI_object.process_contours 该部分主要由两函数构成:

  1. process_contours

  2. process_contour

怕篇幅占的太多,具体的函数内容就不展示了

Step-03.01 初始化配置文件

上次讲过,这次不讲了

注意,有些 False 的代码块我直接删掉,太占篇幅

# 初始化配置文件 & 函数
slides = sorted(os.listdir(source))
slides = [slide for slide in slides if os.path.isfile(os.path.join(source, slide))]
if process_list is None:df = initialize_df(slides, seg_params, filter_params,vis_params, patch_params)
else:df = pd.read_csv(process_list)df = initialize_df(df, seg_params, filter_params,vis_params, patch_params)mask = df['process'] == 1
process_stack = df[mask]total = len(process_stack)def segment(WSI_object, seg_params, filter_params):# Start Seg Timerstart_time = time.time()# SegmentWSI_object.segmentTissue(**seg_params, filter_params=filter_params)# Stop Seg Timersseg_time_elapsed = time.time() - start_timereturn WSI_object, seg_time_elapseddef patching(WSI_object, **kwargs):# Start Patch Timerstart_time = time.time()# Patchfile_path = WSI_object.process_contours(**kwargs)# Stop Patch Timerpatch_time_elapsed = time.time() - start_timereturn file_path, patch_time_elapsed

Step-03.02 初始化WSI

注意,有些 False 的代码块我直接删掉,太占篇幅

# 初始化WSI对象
i = 50 # 输入索引
idx = process_stack.index[i] # 获取索引
slide = process_stack.loc[idx, 'slide_id'] # 获取slide id
print("\n\nprogress: {:.2f}, {}/{}".format(i/total, i, total))
print('processing {}'.format(slide))df.loc[idx, 'process'] = 0 # 正在处理第 i 个文件
slide_id, _ = os.path.splitext(slide) # 获取id前缀# 跳过已经处理过的数据,我这里点False
if auto_skip and os.path.isfile(os.path.join(patch_save_dir, slide_id + '.h5')):print('{} already exist in destination location, skipped'.format(slide_id))df.loc[idx, 'status'] = 'already_exist'
#    continue# 初始化WSI对象
# 基本属性包括:
#         1. 样本名
#         2. wsi对象
#         3. level_dim 下采样对应维度
#         4. 可下采样水平
full_path = os.path.join(source, slide)
WSI_object = WholeSlideImage(full_path)
legacy_support = Falseif use_default_params:current_vis_params = vis_params.copy()current_filter_params = filter_params.copy()current_seg_params = seg_params.copy()current_patch_params = patch_params.copy()else:current_vis_params = {}current_filter_params = {}current_seg_params = {}current_patch_params = {}for key in vis_params.keys():if legacy_support and key == 'vis_level':df.loc[idx, key] = -1current_vis_params.update({key: df.loc[idx, key]})for key in filter_params.keys():if legacy_support and key == 'a_t':old_area = df.loc[idx, 'a']seg_level = df.loc[idx, 'seg_level']scale = WSI_object.level_downsamples[seg_level]adjusted_area = int(old_area * (scale[0] * scale[1]) / (512 * 512))current_filter_params.update({key: adjusted_area})df.loc[idx, key] = adjusted_areacurrent_filter_params.update({key: df.loc[idx, key]})for key in seg_params.keys():if legacy_support and key == 'seg_level':df.loc[idx, key] = -1current_seg_params.update({key: df.loc[idx, key]})for key in patch_params.keys():current_patch_params.update({key: df.loc[idx, key]})if current_vis_params['vis_level'] < 0:if len(WSI_object.level_dim) == 1:current_vis_params['vis_level'] = 0else:wsi = WSI_object.getOpenSlide()best_level = wsi.get_best_level_for_downsample(64)current_vis_params['vis_level'] = best_levelif current_seg_params['seg_level'] < 0:if len(WSI_object.level_dim) == 1:current_seg_params['seg_level'] = 0else:wsi = WSI_object.getOpenSlide()best_level = wsi.get_best_level_for_downsample(64)current_seg_params['seg_level'] = best_level  keep_ids = str(current_seg_params['keep_ids'])
if keep_ids != 'none' and len(keep_ids) > 0:str_ids = current_seg_params['keep_ids']current_seg_params['keep_ids'] = np.array(str_ids.split(',')).astype(int)
else:current_seg_params['keep_ids'] = []exclude_ids = str(current_seg_params['exclude_ids'])
if exclude_ids != 'none' and len(exclude_ids) > 0:str_ids = current_seg_params['exclude_ids']current_seg_params['exclude_ids'] = np.array(str_ids.split(',')).astype(int)
else:current_seg_params['exclude_ids'] = []w, h = WSI_object.level_dim[current_seg_params['seg_level']]
if w * h > 1e8:print('level_dim {} x {} is likely too large for successful segmentation, aborting'.format(w, h))df.loc[idx, 'status'] = 'failed_seg'
#    continuedf.loc[idx, 'vis_level'] = current_vis_params['vis_level']
df.loc[idx, 'seg_level'] = current_seg_params['seg_level']
WSI_object.level_dim
WSI_object.level_downsamples
use_default_params
legacy_supportcurrent_seg_params
current_filter_params

这一部分我们已经复现出来了,所以直接运行就可以

WSI_object, seg_time_elapsed = segment(WSI_object, current_seg_params, current_filter_params)
mask = WSI_object.visWSI(**current_vis_params)
mask

从上图可以看出,这个wsi有三个轮廓(绿色)

Step-03.03 初始化分割patch参数

这里主要涉及到WSI类方法process_contoursprocess_contour

在下面的代码中,我把两部分合并了

还有运用到了一个patch的判定方法 isInContourV3_Easy

# 查看配置信息
current_patch_params.update({'patch_level': patch_level,'patch_size': patch_size,'step_size': step_size,'save_path': patch_save_dir
})
current_patch_params

初始化输入数据,若输入参数在字典中则使用字典中的参数否则使用默认参数

# 现在进行类方法调试
# 初始化输入数据
# 若输入参数在字典中则使用字典中的参数否则使用默认参数
use_padding= True
contour_fn= 'four_pt'
patch_level= 0
patch_size= 256
step_size= 256
save_path= '/media/yuansh/14THHD/CLAM/Result/patches'
bot_right=None
top_left=None
contour_holes = WSI_object.holes_tissue[0]# import
import math
import multiprocessing as mp# my imports
from wsi_core.util_classes import isInContourV3_Easy # 用于判定patchs 是否属于 轮廓内
from wsi_core.wsi_utils import  save_hdf5

Step-03.04 patch 分割步骤解读

着一个步骤,代码实现起来明显比上一个步骤简单,但是讲解的话用文字太过于抽象,所以我弄了个大概的图:

  1. 获取所有的候选轮廓

  2. 获取轮廓的bbox

  3. 由于有些轮廓处在图像的边缘,且bbox本身就应该要比图像边界略大,所以很有可能bbox会超出图像的大小

  4. 这时候的解决方案一般有两种

    • 直接padding image
    • 是直接裁减到image 的边缘
  5. 对bbox内的实例对象进行裁减分割成固定大小的小patch

    最后对patch进行筛选,并不是所有的patch都能使用,判定的表中一共有4种(上一期讲过),比如下面的代码使用的 四角简单判别法 。各个patch的四个角,只要有其中一个角落到轮廓内,则该patch可被使用。如上图:1,2,6 patch 没有被使用,3,4,5这些patch被纳入使用
    具体代码如下

save_path_hdf5 = os.path.join(save_path, str(WSI_object.name) + '.h5')
print("Creating patches for: ", WSI_object.name, "...",)
elapsed = time.time()
n_contours = len(WSI_object.contours_tissue) # 获取轮廓数量
print("Total number of contours to process: ", n_contours)fp_chunk_size = math.ceil(n_contours * 0.05) # 每5% 输出一次进度init = True
for idx, cont in enumerate(WSI_object.contours_tissue):if (idx + 1) % fp_chunk_size == fp_chunk_size:print('Processing contour {}/{}'.format(idx, n_contours))# 获取轮廓的直角坐标# 如果没有轮廓,则使用整张imagestart_x, start_y, w, h = cv2.boundingRect(cont) if cont is not None else (0, 0, WSI_object.level_dim[patch_level][0], WSI_object.level_dim[patch_level][1])# 下采样patch_downsample = (int(WSI_object.level_downsamples[patch_level][0]), int(WSI_object.level_downsamples[patch_level][1]))# patch 的尺寸ref_patch_size = (patch_size*patch_downsample[0], patch_size*patch_downsample[1])# 获取原始Image的大小img_w, img_h = WSI_object.level_dim[0]# 如果对图片进行padding操作的话,则终止点就是bbox的终止点# 这里注意以下,之所以这么做的原因是,如果组织块所处的位置比较边缘,那么bbox可能会超出图像大小# 所以要对bbox进行矫正# 矫正方法有两种# 第一种直接padding image# 第二种 是直接裁减到image 的边缘if use_padding:stop_y = start_y+hstop_x = start_x+welse:stop_y = min(start_y+h, img_h-ref_patch_size[1]+1)stop_x = min(start_x+w, img_w-ref_patch_size[0]+1)print("Bounding Box:", start_x, start_y, w, h)print("Contour Area:", cv2.contourArea(cont))# 如果有给出默认的左上角或者右下角的坐标的话,就对image 进行矫正if bot_right is not None:stop_y = min(bot_right[1], stop_y)stop_x = min(bot_right[0], stop_x)if top_left is not None:start_y = max(top_left[1], start_y)start_x = max(top_left[0], start_x)if bot_right is not None or top_left is not None:w, h = stop_x - start_x, stop_y - start_yif w <= 0 or h <= 0:print("Contour is not in specified ROI, skip")asset_dict = {}attr_dict = {}else:print("Adjusted Bounding Box:", start_x, start_y, w, h)# 根据不同的筛选标准过滤掉轮廓外的patchif isinstance(contour_fn, str):if contour_fn == 'four_pt':cont_check_fn = isInContourV3_Easy(contour=cont, patch_size=ref_patch_size[0], center_shift=0.5)elif contour_fn == 'four_pt_hard':cont_check_fn = isInContourV3_Hard(contour=cont, patch_size=ref_patch_size[0], center_shift=0.5)elif contour_fn == 'center':cont_check_fn = isInContourV2(contour=cont, patch_size=ref_patch_size[0])elif contour_fn == 'basic':cont_check_fn = isInContourV1(contour=cont)else:raise NotImplementedErrorelse:assert isinstance(contour_fn, Contour_Checking_fn)cont_check_fn = contour_fn# 获取bbox的起始位置和终止位置后,根据步长提取patchstep_size_x = step_size * patch_downsample[0]step_size_y = step_size * patch_downsample[1]x_range = np.arange(start_x, stop_x, step=step_size_x)y_range = np.arange(start_y, stop_y, step=step_size_y)# 生成patch的bbox坐标矩阵x_coords, y_coords = np.meshgrid(x_range, y_range, indexing='ij')coord_candidates = np.array([x_coords.flatten(), y_coords.flatten()]).transpose()# 多线程处理 (这里可以根据自己的电脑性能进行获取)num_workers = mp.cpu_count()if num_workers > 4:num_workers = 4pool = mp.Pool(num_workers)iterable = [(coord, contour_holes, ref_patch_size[0], cont_check_fn)for coord in coord_candidates]results = pool.starmap(WholeSlideImage.process_coord_candidate, iterable)pool.close()results = np.array([result for result in results if result is not None])print('Extracted {} coordinates'.format(len(results)))# 最后输出patch 的属性和patch# patch的属性其中包括:# patch 的大小# patch 的下采样水平# 下采样后原始图片的大小# 样本名# 保存路径#  patch 的坐标信息if len(results) > 1:asset_dict = {'coords':          results}attr = {'patch_size':            patch_size,  # To be considered...'patch_level':           patch_level,'downsample':             WSI_object.level_downsamples[patch_level],'downsampled_level_dim': tuple(np.array(WSI_object.level_dim[patch_level])),'level_dim':              WSI_object.level_dim[patch_level],'name':                   WSI_object.name,'save_path':              save_path}attr_dict = {'coords': attr}else:asset_dict = {}attr_dict = {}if len(asset_dict) > 0:if init:save_hdf5(save_path_hdf5, asset_dict, attr_dict, mode='w')init = Falseelse:save_hdf5(save_path_hdf5, asset_dict, mode='a')

最后所有的数据均以 .h5 形式保存。

这里的话想必大家也发现了一个重要的问题,就是由于每一个WSI的大小是不一样的,因此patch的个数也不一样。如果构建的模型要有通用性的情况,那么必须保证每一个WSI的特征图的个数是一样的(卷积操作与通道(特征)个数相同),所以显然要在做一次数据的处理,进一步的提取特征图,在下一章节将对这一部分进行讲解。

如果我的博客您经过深度思考后仍然看不懂,可以根据以下方式联系我:Best Regards,
Yuan.SH
---------------------------------------
School of Basic Medical Sciences,
Fujian Medical University,
Fuzhou, Fujian, China.
please contact with me via the following ways:
(a) e-mail :yuansh3354@163.com

(CNS复现)CLAM——Chapter_02相关推荐

  1. (CNS复现)CLAM——Chapter_00

    (CNS复现)CLAM--Chapter_00 CLAM: A Deep-Learning-based Pipeline for Data Efficient and Weakly Supervise ...

  2. (CNS复现)CLAM——Chapter_03

    (CNS复现)CLAM--Chapter_03 CLAM: A Deep-Learning-based Pipeline for Data Efficient and Weakly Supervise ...

  3. (CNS复现)CLAM——Chapter_01

    (CNS复现)CLAM--Chapter_01 CLAM: A Deep-Learning-based Pipeline for Data Efficient and Weakly Supervise ...

  4. 记录一下复现CLAM

    说是复现,其实人家代码都有,我只要先跑通,然后看懂代码就行. CLAM是一个帮助切割who slide image的patch和提取特征的工具包,我之后的工作可能会用到这个工具包.并且CLAM的代码比 ...

  5. 纯小白为了实现Camelyon16 数据集的分割和特征提取(基于CLAM的代码和AutoDL服务器)所走的弯路

    此贴纯纯记录一下小半个月来的时间里为了复现 CLAM 走的弯路和心路历程. CLAM 源代码地址:GitHub - mahmoodlab/CLAM: Data-efficient and weakly ...

  6. 国庆特惠 !| CNS图表复现|生信分析|R绘图 资源分享讨论群!

    cover ❝ Q:群里有哪些资源? A:2022.12.31前木舟笔记公众号更新的所有资源.(具体目录详见下方) Q:2022年都快结束了,现在加群不是亏了? A:无论什么时候加群,拿到的资源都是一 ...

  7. FigDraw 14. SCI 文章绘图之和弦图及文章复现(Chord Diagram)

    点击关注,桓峰基因 桓峰基因 生物信息分析,SCI文章撰写及生物信息基础知识学习:R语言学习,perl基础编程,linux系统命令,Python遇见更好的你 128篇原创内容 公众号 桓峰基因公众号推 ...

  8. PHP-JAVA-Python-JavaScript框架介绍CVE-2018-1002015/CNVD-2018-24942/2x-rce/Spring命令执行/CVE-2021_21234漏洞复现

    框架 假如我们要买一台电脑.框架为我们提供了已经装好的电脑,我们只要买回来就能用, 但你必须把整个电脑买回来.这样用户用起来自然轻松许多,但是我们这电脑没有软件其他,会导致很多人用一样的电脑,太死板了 ...

  9. Facebook 发布深度学习工具包 PyTorch Hub,让论文复现变得更容易

    近日,PyTorch 社区发布了一个深度学习工具包 PyTorchHub, 帮助机器学习工作者更快实现重要论文的复现工作.PyTorchHub 由一个预训练模型仓库组成,专门用于提高研究工作的复现性以 ...

最新文章

  1. 盯住未来!揭秘英特尔的AI芯片生意
  2. python手机版怎么弄-手把手教你如何使用Python向手机发送通知
  3. TabLayout+ViewPager更新fragment的ui数据
  4. pythonGB2312乱码问题
  5. Java - 线程安全的 HashMap 实现方法及原理
  6. james-2.3.2中的配置
  7. linux安装mysql 5.6_linux 安装mysql5.6
  8. caffe下matlab、python的配置和faster RCNN的运行
  9. (转)令人无法理解的死锁案例分析
  10. Linux下rz,sz与ssh的配合使用
  11. Umbrella Network与Linear Finance合作,将专业金融数据带入DeFi
  12. 卷积神经网络之迁移学习
  13. cwRsyncServer 从 windows server 2008 同步到 CentOS
  14. windows平台下CMDER的安装和配置
  15. 【音频编码】AAC编码之FDK AAC
  16. 计算机桌面不在正中怎么办,电脑屏幕不在中间怎么处理
  17. 肾囊肿平时要注意什么饮食?
  18. 会声会影浪漫婚礼视频——美到想哭
  19. OpenCV图像处理--设置和获取摄像头参数
  20. 热敏电阻和温度转换公式和程序

热门文章

  1. Android开发之播放音频
  2. 杜克大学计算机科学博士,博士生毕业真有那么难?看看杜克大学的数据
  3. java区分手机号归属地_JAVA手机号码归属地查询
  4. Springboot中上一个定时任务没执行完,是否会影响下一个定时任务执行分析及结论
  5. CultureInfo
  6. word2003如何设置护眼模式_在word中开启保护眼睛模式的详细教程
  7. 文献阅读:Raise a Child in Large Language Model: Towards Effective and Generalizable Fine-tuning
  8. 2022华为开发者大赛 首届·厦门开发者创新应用赛在厦门举办
  9. 知识库的分类梳理原则与实践经验
  10. 为了对n个设备使用总线的请求进行裁决_持久化(1):I/O设备