医学图像处理——实战篇(一)

在实战篇中,我主要想跟大家一起从几个简单的项目学起,通过我们对深度学习的学习,简单的完成几个模型的训练、预测,并应用在实际场景中,加深知识在脑海中的印象。

项目都是我从公司的项目中扣出来的功能模块,有些涉密的东西我会用其他的表达方式代替。

 正文开始

背景

在入门篇中我们讲了一个最最最基本的深度学习框架,不管是目标检测、图像分割、图像分类,都可以往上套,只需要更换模型文件即可。

当然,大家如果是从网上克隆下来的开源代码,里面有训练脚本,改改路径什么的也能直接用,但是对里面的函数调用就要一层层的扒着去看了。这个没有硬性要求,看自己更喜欢哪种方式了,(我一般是用照这开源代码修改自己的训练脚本,这样代码比较熟悉)。

这一篇文章,我们就从图像分类入手,利用自己已有的数据训练一个二分类模型。我从项目中抽出了一个细胞分类的功能模块,并简化成二分类,跟大家分享。

医学专业知识

在做细胞分类之前,我们应该先简单的了解一下我们的目标细胞,不用100%全认识,只求知道个大概。

以下内容是公司前辈整理的专业知识,望珍惜。

我们的目标细胞是造血细胞中的红系和粒系 两类细胞 

我们研究的小白鼠骨髓腔中的造血细胞主要由红系、粒系、巨核系组成,(骨髓腔内的造血细胞远不止上述三系细胞,且每系细胞还分为早中晚阶段,但是在HE染色中无法100%区分各系细胞或细胞各阶段,只能大致归为三类(红系/粒系/巨核系)

红系大多呈现岛状或灶状聚集分布特征

粒系大多呈现片状分布特征。

在显微镜40X下,红箭头所指为红系。红系细胞的特征是细胞核染色相对周围细胞较深,核形大多为圆形或多边形,细胞核不存在分叶或甜甜圈形态;细胞胞浆可有可无,可嗜酸性或嗜碱性。(图中为示例,未标记所有红系)

红箭头所指为红系。注意黄圈内的细胞,这些细胞核尽管染色深,但是细胞核存在明显的分叶,属于粒细胞。因此,判断红系/粒系一方面要看深色这一特征,还需要依靠核形进行判断,核形权重大于深色(图中为示例,未标记所有红系)

 数据集

 数据集是公司内部的数据,不能公开。

大家如果没有现成的数据的话,可以从网上下载猫狗二分类的数据集。

 模型

 模型选型 是inceptionV3

优点:此处省略一万字。

当然也可以使用其他的,我只是比较懒,随便拿了一个手头上顺手的。

模型测试结果

总结:实验结果 总体还算符合预期,毕竟有的细胞人都分不清。

缺点inceptionV3需要的输入图像大小为320,而一个细胞的大小在20左右,不但会增加冗余数据,还增大计算量 

后续有时间了再想其他的办法。

代码

model.py 

# 模型文件
# 可以选择是否加载预训练模型
# 将模型的最后一层分类层改成2分类

import torch
import torch.nn as nn
import torchvision.models as models

class INCEPTIONV3(nn.Module):
    def __init__(self, out_class, path=None):
        super(INCEPTIONV3, self).__init__()

        self.model = models.inception_v3(pretrained=False)
        if path is not None:
            self.model.load_state_dict(torch.load(path))


        in_features = self.model.fc.in_features
        self.model.fc = nn.Linear(in_features, out_class)

    def forward(self, x):
        x = self.model(x)

        return x

mydataset.py

# 自定义dataset
# 可以根据自己的需求对数据进行预处理

import cv2
import os
import torch
from torch.utils.data import Dataset
import torchvision.transforms as transforms

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize(mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))
])


class Dataset_hongli(Dataset):
    def __init__(self, data, path_positive, path_negative, img_size):
        self.data = data
        self.path_positive = path_positive
        self.path_negative = path_negative
        self.img_size = img_size

    def __len__(self):
        return len(self.data)

    def __getitem__(self, item):
        _data, _label = self.data[item]
        if len(_data) > 500:
            _data = cv2.imread(_data)
        else:
            if _label == 0:
                _data = cv2.imread(os.path.join(self.path_negative, _data))
            if _label == 1:
                _data = cv2.imread(os.path.join(self.path_positive, _data))
        _data = transform(cv2.resize(_data, (self.img_size, self.img_size)))
        _label = torch.tensor(_label, dtype=torch.long)

        return _data, _label

train_inception.py

# 训练inception v3

import os

os.environ["CUDA_VISIBLE_DEVICES"] = "0"
import torch
import torch.nn as nn
import torch.optim as optim
from torch.autograd import Variable
from torch.utils.data import DataLoader
from sklearn.model_selection import train_test_split
from sklearn.metrics import accuracy_score, recall_score
from dataset_hongli import Dataset_hongli
import random
import argparse
import yaml
from model import INCEPTIONV3
import matplotlib.pyplot as plt
import numpy as np
import time
from tqdm import tqdm
import torchvision.transforms as transforms

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
transform = transforms.Compose([
    transforms.ToTensor()]
)
random.seed(16)


def shuffle_data(data, label):
    """
    打乱数据和标签
    :param data:
    :param label:
    :return:
    """
    _dataset = list(zip(data, label))
    random.shuffle(_dataset)

    return _dataset


if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument('--config', default='config.yaml', type=str)
    args = parser.parse_args()
    # print(args.config)

    # 获取参数
    with open(args.config, errors='ignore') as f:
        config = yaml.safe_load(f)

    path_hong = config.get('path_imgs_positive')
    path_li = config.get('path_imgs_negative')
    batch_size = config.get('batch_size')
    epochs = config.get('epochs')
    lr = config.get('lr')
    out_channel = config.get('out_channel')
    image_size = config.get('image_size')
    best_recall = config.get('best_recall')
    checkpoint = config.get('checkpoint')
    warmup_step = config.get('warmup_step')
    cha = (lr - 0.000001) / warmup_step

    hong_list = os.listdir(path_hong)
    li_list = os.listdir(path_li)

    data_list = shuffle_data(hong_list+li_list, [1] * len(hong_list)+[0] * len(li_list)) # 0 lixi  1 hongxi

    # 划分数据集
    train_data, test_data = train_test_split(data_list, test_size=0.2, random_state=16)

    train_dataset = Dataset_hongli(train_data, path_hong, path_li, image_size)
    test_dataset = Dataset_hongli(test_data, path_hong, path_li, image_size)
    train_dataloader = DataLoader(train_dataset, batch_size, shuffle=True, num_workers=0)
    test_dataloader = DataLoader(test_dataset, batch_size, shuffle=False, num_workers=0)

    # 声明模型
    cls_model = INCEPTIONV3(out_channel,'inception_v3_google-1a9a5a14.pth').to(device)
    # cls_model.load_state_dict(torch.load(model_path))

    output_params = list(map(id, cls_model.model.fc.parameters()))
    feature_params = filter(lambda p: id(p) not in output_params, cls_model.model.parameters())

    optimizer = optim.Adam([{'params': feature_params},
     {'params': cls_model.model.fc.parameters(), 'lr': lr * 10}], lr=lr)

    # optimizer = optim.Adam(cls_model.parameters(), lr=lr)

    criterion = nn.CrossEntropyLoss().to(device)

    train_acc, train_recall, train_loss = [], [], []
    test_acc, test_recall, test_loss = [], [], []
    lossing = []

    for epoch in range(epochs):
        start_time = time.time()
        # 训练
        cls_model.train()
        run_loss, acc, recall = 0.0, 0.0, 0.0
        # 手动调整学习率
        # if epoch <= warmup_step:
        #     _lr = 0.000001 + epoch * cha
        #     for param_group in optimizer.param_groups:
        #         param_group['lr'] = _lr

        for i, (data, label) in enumerate(train_dataloader):
            data = data.to(device)
            label = label.to(device)
            optimizer.zero_grad()
            pred,_ = cls_model(data)
            loss = criterion(pred, label)
            loss.backward()
            optimizer.step()
            run_loss += loss.item()
            lossing.append(loss.item())

            _, pred = torch.max(pred, 1)
            acc += accuracy_score(label.cpu(), pred.cpu())
            recall += recall_score(label.cpu(), pred.cpu(), average='micro')

            if i % 30 == 29:
                print('train: {epoch:%d}: loss=%3.3f, correct=%3.3f, recall=%3.3f'
                      % (epoch, run_loss / (i + 1), acc / (i + 1), recall / (i + 1)))

        train_loss.append(run_loss / len(train_dataloader))
        train_acc.append(acc / len(train_dataloader))
        train_recall.append(recall / len(train_dataloader))

        # 测试
        cls_model.eval()
        run_loss, acc, recall = 0.0, 0.0, 0.0
        for i, (data, label) in enumerate(test_dataloader):
            data = data.to(device)
            label = label.to(device)
            pred = cls_model(data)
            loss = criterion(pred, label)
            run_loss += loss.item()

            _, pred = torch.max(pred, 1)
            acc += accuracy_score(label.cpu(), pred.cpu())
            recall += recall_score(label.cpu(), pred.cpu(), average='micro')

            if i % 30 == 29:
                print('test: {epoch:%d}: loss=%3.3f, correct=%3.3f, recall=%3.3f'
                      % (epoch, run_loss / (i + 1), acc / (i + 1), recall / (i + 1)))

        test_loss.append(run_loss / len(test_dataloader))
        test_acc.append(acc / len(test_dataloader))
        test_recall.append(recall / len(test_dataloader))

        # 保存模型
        if test_recall[-1] > best_recall:
            torch.save(cls_model.state_dict(), r'inception_v3_hong_li.pth')
            best_recall = test_recall[-1]

        end_time = time.time()
        print("one epoch used time:", end_time - start_time)

        if epoch % 20 == 19:
            print('best recall:', best_recall)
            x = np.arange(epoch + 1)
            p1 = plt.subplot(131)
            plt.title('loss')
            plt.plot(x, train_loss, 'b')
            plt.plot(x, test_loss, 'r')
            p2 = plt.subplot(132)
            plt.title('acc')
            plt.plot(x, train_acc, 'b')
            plt.plot(x, test_acc, 'r')
            p3 = plt.subplot(133)
            plt.title('recall')
            plt.plot(x, train_recall, 'b')
            plt.plot(x, test_recall, 'r')
            plt.savefig('result.png')

    print('best recall:', best_recall)
    x = np.arange(epochs)
    p1 = plt.subplot(131)
    plt.title('loss')
    plt.plot(x, train_loss, 'b')
    plt.plot(x, test_loss, 'r')
    p2 = plt.subplot(132)
    plt.title('acc')
    plt.plot(x, train_acc, 'b')
    plt.plot(x, test_acc, 'r')
    p3 = plt.subplot(133)
    plt.title('recall')
    plt.plot(x, train_recall, 'b')
    plt.plot(x, test_recall, 'r')
    plt.savefig('result.png')
    # plt.show()

pred_inception.py

# 预测inceptionv3 输出混淆矩阵


from torch.utils.data import DataLoader
from dataset_hongli import Dataset_hongli
from model import INCEPTIONV3
import torch
import matplotlib.pyplot as plt
import os
from tqdm import tqdm

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")


#  返回混淆矩阵
def ConfusionMatrix(numClass, imgPredict, Label):
    mask = (Label >= 0) & (Label < numClass)
    label = numClass * Label[mask] + imgPredict[mask]
    count = torch.bincount(label.int(), minlength=numClass ** 2)
    confusionMatrix = count.reshape(numClass, numClass)
    return confusionMatrix


# 可视化混淆矩阵
def Visual_ConfusionMatrix(confusion, classes, save_path):
    n = confusion.shape[0]
    plt.figure(figsize=(n+1, n+1))  # 设置图片大小

    # 1.热度图,后面是指定的颜色块,cmap可设置其他的不同颜色
    plt.imshow(confusion, cmap=plt.cm.Blues)
    plt.colorbar()  # 右边的colorbar

    # 2.设置坐标轴显示列表
    indices = range(n)
    # 第一个是迭代对象,表示坐标的显示顺序,第二个参数是坐标轴显示列表
    plt.xticks(indices, classes, rotation=45)  # 设置横坐标方向,rotation=45为45度倾斜
    plt.yticks(indices, classes)

    # 3.设置全局字体
    # 在本例中,坐标轴刻度和图例均用新罗马字体['TimesNewRoman']来表示
    # ['SimSun']宋体;['SimHei']黑体,有很多自己都可以设置
    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.rcParams['axes.unicode_minus'] = False

    # 4.设置坐标轴标题、字体
    plt.xlabel('预测值')
    plt.ylabel('真实值')
    plt.title('混淆矩阵', fontsize=12, fontfamily="SimHei")  # 可设置标题大小、字体

    # 5.显示数据
    normalize = False
    fmt = '.2f' if normalize else 'd'
    thresh = confusion.max() / 2.

    for i in range(n):  # 第几行
        for j in range(len(confusion[i])):  # 第几列
            plt.text(j, i, format(confusion[i][j], fmt),
                     fontsize=16,  # 矩阵字体大小
                     horizontalalignment="center",  # 水平居中。
                     verticalalignment="center",  # 垂直居中。
                     color="white" if confusion[i, j] > thresh else "black")

    plt.savefig(save_path)


if __name__ == '__main__':
    save_path='confusion_matrix.png'
    path_h = r'D:\gzz\data\hongli\dataset\dataset08_c2\hongxi'
    path_l = r'D:\gzz\data\hongli\dataset\dataset08_c2\lixi'
    # path_h = r'D:\gzz\data\hongli\dataset\testset05_c1\hongxi'
    # path_l = r'D:\gzz\data\hongli\dataset\testset05_c1\lixi'
    out_channel = 2
    image_size = 320
    batch_size=16
    class_name=['粒系','红系']

    hong_list = os.listdir(path_h)
    li_list = os.listdir(path_l)
    _dataset = list(zip(hong_list + li_list, [1] * len(hong_list) + [0] * len(li_list)))  # 0 lixi  1 hongxi
    test_dataset = Dataset_hongli(_dataset, path_h, path_l, image_size)
    test_dataloader = DataLoader(test_dataset, batch_size, shuffle=False, num_workers=0)

    cls_model = INCEPTIONV3(out_channel).to(device)
    model_path = 'inception_v3_hong_li.pth'
    cls_model.load_state_dict(torch.load(model_path))

    all_pred = []
    all_label = []

    cls_model.eval()
    with torch.no_grad():
        for (data,label) in tqdm(test_dataloader):
            data = data.to(device)
            label = label
            pred = cls_model(data)
            _, pred = torch.max(pred, 1)

            all_pred.extend(pred.data.cpu())
            all_label.extend(label)

    # print(all_pred)
    # print(all_label)
    a = ConfusionMatrix(out_channel, torch.tensor(all_pred), torch.tensor(all_label))


    Visual_ConfusionMatrix(a, class_name, save_path)

补更:

后来我又试了其他几种模型,有监督的、无监督的都试了,结果都差不多,但跟预期有些差距。

补更+1:

偶然间的想法,因为我有庞大的细胞数据,可以利用特征匹配、特征检索做,kd树或faiss库。

 

  • 3
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
医学图像处理CSND是指利用计算机科学和技术来处理医学图像的过程。医学图像处理是医学领域的一项重要技术,它可以将医学图像从原始数据转化为可视化的图像,以帮助医生进行疾病诊断和治疗。 CSND指的是计算机技术与网络技术在医学图像处理中的应用。在医学图像处理过程中,计算机科学的方法被用来对医学图像进行数字化和分析,以提取有用的信息。此外,网络技术也被应用于医学图像处理中的图像传输和共享,以实现医疗数据的无缝传输和访问。 医学图像处理CSND的应用非常广泛。例如,它可以用来进行医学影像的增强和滤波,以提高图像质量和减少噪音。此外,它还可以用于医学影像的分割和分类,以帮助医生定位和识别疾病区域。另外,医学图像处理CSND还可以用于三维可视化和虚拟现实,以提供更直观和全面的医学图像信息。 在CSND技术的支持下,医学图像处理取得了显著的进展。它不仅提高了医生的诊断准确性,还缩短了疾病的诊断时间,大大改善了患者的治疗效果。此外,医学图像处理CSND还为医学研究和教育提供了重要的工具和资源。 总之,医学图像处理CSND是一门重要的技术,它将计算机科学和技术与医学相结合,为医疗提供了更准确、高效和安全的图像处理方法。随着技术的不断发展,医学图像处理CSND将进一步拓展其应用领域,为医学和医疗健康领域带来更多的创新和改进。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值