首先挂一个论文链接:https://doi.org/10.1093/bioinformatics/btac456
代码链接: https://github. com/Docurdt/DEMoS.git
摘要:
动机:癌症基因组图谱(TCGA)倡议提出的基于综合多组学资料的胃癌(腺癌)分子分型为四种主要亚型,代表了患者分层的有效策略。然而,这种方法需要使用多种技术平台,并且执行起来相当昂贵和耗时。一种利用组织病理学图像数据推断分子亚型的计算方法可能是一种实用的、成本和时间效率高的辅助工具,用于预后和临床管理目的。
结果:在这里,我们提出了一种深度学习集成方法(称为DEMoS),能够直接从组织病理学图像中预测四种公认的胃癌分子亚型。DEMoS分别使用独立的测试数据集,在受体-工作特征曲线(AUROC)下实现了0.785、0.668、0.762和0.811的瓷砖水平面积,用于预测这四种亚型的胃癌[即(i) eb病毒感染(EBV), (ii)微卫星不稳定(MSI), (iii)基因组稳定(GS)和(iv)染色体不稳定肿瘤(CIN)]。在患者层面,AUROC值分别为0.897、0.764、0.890和0.898。因此,DEMoS很好地预测了这四种子类型。基准测试实验进一步表明,DEMoS能够提高基于图像的子类型的分类性能,并防止模型过拟合。本研究强调了仅利用组织病理学图像特征,使用基于深度学习集成的方法快速可靠地诊断胃癌亚型(腺癌)的可行性。
可用性和实现:本研究中使用的所有幻灯片图像均来自TCGA数据库。
这项研究建立在我们之前发布的HEAL框架之上,相关文档和教程可在http://heal.erc.monash.edu.au上找到。
文章模型架构:
简单说一下流程:首先拿到svs格式的全切片,用openslide包进行裁剪成patch,然后使用拉普拉斯方差计算模糊度,模糊度小于一个值的patch视为较模糊,丢弃。剩下的可用patch用macenko方法使用一个标准的图片,分离和转换染色成分对所有patch进行染色标准化,使用LuminosityStandardizer类中的standardize方法进行亮度标准化。将数据集在病人级别上分离出独立测试集后,使用十字交叉方法将剩下的数据分为十份。然后训练十个EfficientNet-b1。标签文件csv中有每个patch的路径加图像名称加扩展名,与标签(需要用代码生成)。
以下是遇到和学习到的问题:
一.一些包的认识作用和使用方法
1.spams包:SPAMS是一个开源的优化工具箱,主要用于解决各种稀疏估计问题。它包含了多个子工具箱,可以在MATLAB和Python中使用。
这个包应该是openslide包中用到的,因为我没在代码中看到这个import,但确实报错了。
2.tqdm包
3.tensorboard包:目前没用到这个,应该是tensorflow框架中用到的可视化工具。
4.torch.optim
5.from PIL import lmage
6.scikit-image包
7.报错
这个其实可以用,只是会报warning,把warning在代码中注释掉就可以了。
8.torch.utils.data Dataset DataLoader
9.from torchvision import models, transforms
10.序列化与反序列化
代码:用来将一些参数写入配置文件中,或者将参数从配置文件中提取出来。
def save_variable(var, filename): #序列化
pickle_f = open(filename, 'wb')
pickle.dump(var, pickle_f)
pickle_f.close()
return filename
def load_variable(filename): #反序列化
pickle_f = open(filename, 'rb')
var = pickle.load(pickle_f)
pickle_f.close()
return var
save_variable({'Mode': False, 'Classes': ['1','2','3'], 'Class_number': 3}, "parameter.conf")
#其中的内容是字典,存在parameter.conf配置文件中
conf_dict = load_variable("E:/classify/DEMoS-main/HEAL/new_breastcaner/parameter.conf") ##反序列化,用来从文件中读取保存的变量
_work_mode = conf_dict["Mode"] #从conf_dict字典中获取"Mode"键对应的值,赋值给_work_mode变量
_class_cate = conf_dict["Classes"]
_class_number = conf_dict["Class_number"]
11.多线程至异步调用,使用到multiprocessing包
12.EfficientNet-b1模型需要进行如下操作:from efficientnet_pytorch import EfficientNet
该包需要安装pip install efficientnet_pytorch
13.BCEloss和CrossEntropyLoss的区别
因此模型若能够实现二分类和多分类,代码如下:
if _model_name == "ResNet50":
model = models.resnet50(pretrained=True) #加载预训练的ResNet50模型
num_ftrs = model.fc.in_features #获取模型最后一层全连接层的输入特征数
##如果_mode为真,则将输出层替换为一个线性层和一个Sigmoid层,并使用二值交叉熵作为损失函数
if _mode:
model.fc = nn.Sequential(nn.Linear(num_ftrs, _class_num), nn.Sigmoid())
criterion = nn.BCELoss()
##如果_mode为假,则将输出层替换为一个线性层,并使用交叉熵作为损失函数
else:
model.fc = nn.Linear(num_ftrs, _class_num)
criterion = nn.CrossEntropyLoss()
model = torch.nn.DataParallel(model) #将模型封装为一个数据并行模块
return model, criterion #返回模型和损失函数
elif _model_name == "ResNet18":
model = models.resnet18(pretrained=False) #不预训练
num_ftrs = model.fc.in_features
if _mode:
model.fc = nn.Sequential(nn.Linear(num_ftrs, _class_num), nn.Sigmoid())
criterion = nn.BCELoss()
else:
model.fc = nn.Linear(num_ftrs, _class_num)
criterion = nn.CrossEntropyLoss()
model = torch.nn.DataParallel(model)
return model, criterion
elif _model_name == "Vgg16":
model = models.vgg16(pretrained=False) #创建一个没有预训练权重的Vgg16模型
num_ftrs = model.classifier[6].in_features #获取模型分类器中最后一层的输入特征数
#第七个子模块是最后一个线性层,它的输入特征数就是我们要替换的新线性层的输入特征数。
features = list(model.classifier.children())[:-1] #remove the last layer, 移除最后一层,得到一个特征提取器的列表
#model.classifier.children()是一个迭代器,它可以遍历模型分类器中的所有子模块。list()函数可以将迭代器转换为一个列表。[:-1]表示切片操作,它可以获取列表中除了最后一个元素以外的所有元素。
if _mode:
features.extend([nn.Sequential(nn.Linear(num_ftrs, _class_num), nn.Sigmoid())]) #在特征提取器的列表后面添加一个线性层和一个Sigmoid激活函数,输出类别数为_class_num
model.classifier = nn.Sequential(*features) #将特征提取器的列表转换为一个序列模块,并赋值给模型的分类器属性
criterion = nn.BCELoss()
else:
features.extend([nn.Linear(num_ftrs, _class_num)])
model.classifier = nn.Sequential(*features)
criterion = nn.CrossEntropyLoss()
model = torch.nn.DataParallel(model)
return model, criterion
14.Ir_scheduler.ReduceLROnPlateau是PyTorch中的一个学习率调整方法。它可以监控指标,当指标不再变化时则调整学习率
15.用re模块中的sub方法,替换路径中的某些单词,输出目标路径
16.os.walk()方法遍历目标路径下的所有文件夹与文件
for _root, _dir, _imgs in os.walk(_image_path):
_imgs = [f for f in _imgs if not f[0] == '.'] #用列表推导式,过滤掉_imgs列表中以"."开头的隐藏文件,并重新赋值给_imgs变量
_dir[:] = [d for d in _dir if not d[0] == '.'] #使用列表推导式,过滤掉_dir列表中以"."开头的隐藏目录,并重新赋值给_dir变量。注意这里使用了切片赋值的语法,保持了_dir变量在内存中的地址不变。
for idx in range(len(_imgs)): #遍历_imgs列表中的每个元素。即对每个图像
_img = _imgs[idx] #_img是一个字符串,表示图像文件的名称。
_img_path = os.path.join(_root, _img) #使用os模块中的path.join方法,将_root和_img两个字符串拼接起来,并赋值给_img_path变量。这个字符串表示图像文件的完整路径。
pool.apply_async(blur_color_processing, (_root, _img_path, _img)) #异步执行blur_color_processing
17.保存训练好的模型
18.python中_gray.point函数在 Python 中,gray.point() 函数可以用于转换图像的像素值(例如图像对比度调整)
bw = _gray.point(lambda x: 0 if x < 220 else 1, 'F') ## 对灰度图像进行二值化处理,将灰度值小于220的像素点设为0,其余设为1。也就是说背景为1.
19.元组和列表
二.关于TCGA中数据集的下载方式和查看方式
1.下载方法,我是用的是GDC Data Transfer Tool,在TCGA网站上下载这个应用,在Cart中下载所需要的数据的manifest.txt文件,将这个文件拖进GDC Data Transfer Tool中,即可实现下载。
得到svs格式的全切片。
2.查看SVS格式的全切片,我用的是ImageScope
3.安装openslide包,处理svs全切片,切成小的patch。
(1)安装
安装方法:从官网下载二进制预编译
网址:OpenSlide on Windowshttps://openslide.org/docs/windows/
我放在了E:\classify\DEMoS-main\envs\Lib\site-packages文件夹下。然后执行pip install openslide-python.
然后报错:
这个错误是因为Python找不到OpenSlide DLL。这个DLL是Openslide的动态链接库,它包含Openslide的函数和变量。这个错误通常是由于没有正确安装Openslide或没有正确设置环境变量导致的。如果您使用的是Windows操作系统,您可以尝试将Openslide的bin目录添加到PATH环境变量中。如果您使用的是Mac OS X或Linux操作系统,您可以尝试使用以下命令安装Openslide:brew install openslide。如果您已经安装了Openslide并且仍然遇到这个错误,请确保您已经正确设置了PYTHONPATH环境变量
按照上述方法添加path环境变量:
E:\classify\DEMoS-main\envs\Lib\site-packages\openslide-win64-20221217\bin
E:\classify\DEMoS-main\envs\Lib\site-packages\openslide-win64-20221217\lib
结果还是报错:
就将site-packages\openslide-win64-20221217\openslide-win64-20221217\bin中的内容全部复制到DEMoS-main\envs文件夹下。就好了。
(2)openslide简单的介绍
其中的DeepZoomGenerator对象可以实现切片操作。
全切片有多个图层,获取当前主程序设置的我们需要的放大倍率的那一个图层,然后去切割patch:
_factors = self._slide.level_downsamples #第i层图像相对于第 0 层图像的下采样因子
#_factors:此属性由 OpenSlide Python 定义为幻灯片图像每个级别的下采样因子元组。
#self._slide.level_downsamples中获取一个字典,这个字典的键为图像文件的级别,值为一个浮点数
# 下采样因子是级别 0 图像中对应于当前级别中单个像素的像素数。例如,(1.0、4.0、16.0) 表示级别 0 没有缩减采样,级别 1 具有 4 倍缩减采样,级别 2 具有 16 倍缩减采样。
#level_downsamples 每一个级别K的对应的下采样因子,下采样因子应该对应一个倍率
_objective = float(self._slide.properties[openslide.PROPERTY_NAME_OBJECTIVE_POWER])
print("The slide's OBJECTIVE_POWER is", _objective)
#self._slide.properties[openslide.PROPERTY_NAME_OBJECTIVE_POWER]这个变量是一个字典。在这个字典中,键为openslide.PROPERTY_NAME_OBJECTIVE_POWER,值为一个字符串,表示镜头的倍率。
# 如果这个属性不存在,则返回None。
#这个代码的意思是获取一个图像文件的镜头倍率,并将这个镜头倍率赋值给变量_objective
#这个字符串常量的值是一个固定的字符串,用于表示镜头的倍率。这个常量通常用于从OpenSlide库中读取图像文件的元数据信息。例如,如果你想要获取一个图像文件的镜头倍率,你可以使用这个常量来获取这个信息。
#在数字病理学图像中,目标功率是指显微镜物镜的放大倍数,通常以数字形式表示。例如,20x物镜的目标功率为20。
_available = tuple(_objective / x for x in _factors) ## 计算可用的放大倍数,_objective除以每个层级的下采样因子。为(20,5,20/8)元组取整
#print(_factors)下采样因子
#print(_available) 可用放大倍数
#tuple()是Python内置函数之一,用于将一个可迭代对象转换为元组。例如,将列表转换为元组。
#用于计算图像的缩放比例,在主程序代码中缩放比例必须设为可用的缩放比例的其中之一。
print("The deepzoom level count is",self._dz.level_count) #deepzoom产生的level数量
for level in range(self._dz.level_count-1, -1, -1):
#从self._dz.level_count-1开始,每次减1,直到0为止
#对幻灯片的每一层级
#level_count——幻灯片中的级别数。级别从0(最高分辨率)到level_count - 1(最低分辨率)编号
#DeepZoom level是指图像的缩放级别,每个缩放级别都是由上一个缩放级别的图像分辨率降低一半得到的,因此,每个缩放级别都是上一个缩放级别的四倍.DeepZoom level越高,图像分辨率越低,但是可以显示更大的图像区域。
_thisMag = int(_available[0] / pow(2, self._dz.level_count - (level + 1)))
#20/1,20/2,20/4,20/8然后取整
if self._t_mag != _thisMag: #当前放大倍数是否等于图片计算出来的可用放大倍数。
continue
print("current Mag is", _thisMag)
print("current deepzoom level is ",level)
tile_dir = os.path.join("%s_files" % self._basename, str(_thisMag))
if not os.path.exists(tile_dir):
os.makedirs(tile_dir)
cols, rows = self._dz.level_tiles[level] #获取当前层级的图像块的列数和行数,并分别赋值给cols和rows
#行数和列数包含空白小tile
#dz.level_tiles一个元组,其中包含某分辨率级别的每个维度中的图块数。例如,如果最高分辨率级别中有100个水平瓦片和50个垂直瓦片,则输出将是(100, 50)。
for row in range(rows):
for col in range(cols):
tile_name = os.path.join(tile_dir, '%d_%d.%s' % (col, row, self._format))
if not os.path.exists(tile_name):
self._queue.put((self._associated, level, (col, row), tile_name))
#如果tile_name不存在,则将(self._associated, level, (col, row), tile_name)这个元组放入队列中。其中self._associated表示当前处理的区域是否与上一次处理的区域相同
#这句代码传递进去class TileWorker(Process):去掉那些
#将元组(associated, level, (col, row), tile_name)放入队列中。这个队列是一个线程安全的队列,用于在多线程环境下传递数据
self._tile_done() #调用self._tile_done()方法,表示完成了一个图像块的处理
print("The tile with over 50% white space has been automatically removed by OpenSlide")
(3)openslide自动切割的优点:
4.我想直接切割我的数据集,但不是svs格式,就是jpg,有一个图片保存的质量问题,会有模糊的情况出现 。cv2.imwrite()可以指定图片质量。
5.GPU内存不足,清空也没用,只能减小batchsize从64到16。
另注:d2l包不要随意安装,因为安装它会更改numpy,matplotlib,pytorch等版本,导致与项目代码本身不兼容,此时需要卸载d2l和上述这些包,重新pip才可以!!!!!!!!!!!!!!!!!!!!!!
写太多了,自己都不爱看了。哈哈哈