利用 Segment Anything实现医学图像分割

前言

Meta 前不久发布了图像分割通用模型Segment Anything, 标志者图像分割领域的chatgpt时刻来临,通用模型在图像处理领域表现出了强大的潜力,业内有人戏称 “Segment Anything 的出现,代表着曾经作为图像处理领域主流的图像分割任务,基本不存在了,分割已经没什么任务需要继续去做了^_^”。 Segment Anything 在自然图像分割领域,性能强大,但是,就医学图像分割 (如靶区勾画等)而言,Segment Anything 的局限性仍然显著存在,不过,Segment Anything 作为一个强大的工具,可以成为医学图像分割任务前处理或后处理的利器(如对分割的目标区域进行约束等)。以下,是一个 Segment Anything实现医学图像分割的简单demo,以供参考,基于此demo,可以发掘更多Segment Anything更为有趣的玩法。

基本流程

  • 图像转化
    医学图像基本都是3D图像,而Segment Anything是基于自然图像训练而成,因此,无法直接对3D图像进行分割,所以,需要将3D 医学图像通过指定窗宽窗位转换为RGB3通道的一系列2D图像。
def transformNsCV(self):
        minWindow = (2 * self.windowsCenter - self.windowsLevel) / 2.0 + 0.5
        maxWindow = (2 * self.windowsCenter + self.windowsLevel) / 2.0 + 0.5
        dFactor = 255.0 / (maxWindow - minWindow)

        transArray = self.imgArray - minWindow
        transArray = np.trunc(transArray * dFactor)
        
        transArray[transArray > 255] = 255
        transArray[transArray < 0] = 0

        return transArray

  • 根据先验种子点实现分割
    Segment Anything 可以根据人为指定的初始种子点进行分割,类似于区域生长算法,实现一键抠图效果 。 种子点是一个 N \times 2 的numpy 数组, 具体如下
predictor = SamPredictor(self.model)
predictor.set_image(targetSlice)
        
seedsArr = np.array([[100,245],[500,345]]))    
labels = [i+1 for i in range(0,len(seedsArr))]
masks, scores, logits = predictor.predict(
 point_coords= seedsArr,
 point_labels= np.array(labels),
multimask_output=True,
        )


  • 全图作为分割
    Segment Anything 也支持对全图做分割,但是速度较慢
def pipelineSegAllImage(self,targetSlice:np.ndarray):
        
        maskSliceRes = np.zeros_like(targetSlice[:,:,0])
        
        mask_generator = SamAutomaticMaskGenerator(self.mdoel)
        targetSlice = targetSlice.astype(np.uint8)
        masks = mask_generator.generate(targetSlice)
        for mask in masks:
            maskArrayBool = mask["segmentation"]
            maskArray = maskArrayBool.astype(np.uint8)
            maskSliceRes = maskSliceRes + maskArray

        return maskSliceRes

其中一张slice的实现效果如下,由于是通过2D实现的分割,所以最终效果可能在z方向上的连续性不够好。


综上,利用Segment Anything实现医学图像分割的全流程代码如下


from segment_anything import sam_model_registry
from segment_anything import SamPredictor,SamAutomaticMaskGenerator
import SimpleITK as sitk
import numpy as np
import torch as t
import cv2
import matplotlib.pyplot as plt
from itertools import product
import random
import sys
from ReadAndWrite import ReadImageBase

class SamMedSegmentation():
    def __init__(self,imgArray:np.ndarray,windowsCenter:int,windowsLevel:int,seedlists=None):
        
        self.imgArray = imgArray
        assert(len(self.imgArray.shape)) == 3
        if seedlists == None:
            self.seedlists = self.generateSeeds(1)

        else:
            self.seedlists = seedlists

        self.windowsCenter = windowsCenter
        self.windowsLevel = windowsLevel
        
        self.model = self.initializeModel()

    def initializeModel(self):
        modelType = "vit_l"
        checkPoint = r"/mnt/e/ChromeDwnLoad/sam_vit_l_0b3195.pth"
        sam = sam_model_registry[modelType](checkpoint=checkPoint)
        sam.to("cuda")

        return sam


    def generateSeeds(self,maxIter):
        z,y,x = self.imgArray.shape
        px = (int(x/8),int(x/7),int(x/6),int(x/4),int(x/3),int(x/2))
        py = (int(y/8),int(y/7),int(y/5),int(y/4),int(y/3),int(y/2))

        points = list(product(py,px))
        pointsNum = list(map(lambda x: np.array(x),points))
        point_xy = list()
        for i in range(0,maxIter):
            points_random = random.choice(pointsNum)
        #print([points_random[0], points_random[1]])
            point_x = points_random[0]
            point_y = points_random[1]
            point_xy.append([point_x,point_y])
        return point_xy
    

    def transformNsCV(self):
        minWindow = (2 * self.windowsCenter - self.windowsLevel) / 2.0 + 0.5
        maxWindow = (2 * self.windowsCenter + self.windowsLevel) / 2.0 + 0.5
        dFactor = 255.0 / (maxWindow - minWindow)

        transArray = self.imgArray - minWindow
        transArray = np.trunc(transArray * dFactor)
        
        transArray[transArray > 255] = 255
        transArray[transArray < 0] = 0

        return transArray




    def convertMedImg2CV(self,targetArray:np.ndarray):
        assert len(targetArray.shape) == 3
        z,y,x = targetArray.shape
        imglist = []
        for z_i in range(0,z):
            targetSlice = targetArray[z_i,:,:]
            targetSliceRGB = cv2.cvtColor(targetSlice,cv2.COLOR_GRAY2RGB)
            #print(type(targetSliceRGB))
            imglist.append(targetSliceRGB)
        return imglist


    def pipelineSegAllImage(self,targetSlice:np.ndarray):
        
        maskSliceRes = np.zeros_like(targetSlice[:,:,0])
        
        mask_generator = SamAutomaticMaskGenerator(self.mdoel)
        targetSlice = targetSlice.astype(np.uint8)
        masks = mask_generator.generate(targetSlice)
        for mask in masks:
            maskArrayBool = mask["segmentation"]
            maskArray = maskArrayBool.astype(np.uint8)
            maskSliceRes = maskSliceRes + maskArray

        return maskSliceRes
    

    def pipelineSegPoint(self,targetSlice:np.ndarray):
        targetSlice  = targetSlice.astype(np.uint8)
        maskSliceRes = np.zeros_like(targetSlice[:,:,0])
        
        predictor = SamPredictor(self.model)
        predictor.set_image(targetSlice)
        
        
        seeds = self.generateSeeds(4)
        seedsArr = np.array(seeds)
        print(seedsArr.shape)    
        #print(seeds)
        labels = [i+1 for i in range(0,len(seedsArr))]
        masks, scores, logits = predictor.predict(
        point_coords= seedsArr,
        point_labels= np.array(labels),
        multimask_output=True,
        )

        for idx,mask in enumerate(masks):
            mask = mask.astype(np.uint8)
            maskSliceRes += mask

        return maskSliceRes


    

    def processPipeline(self,all=False):
        targetArray = self.transformNsCV()
        imgslists = self.convertMedImg2CV(targetArray)
        if False == all:
            maskSlicesRes = list(map(self.pipelineSegPoint,imgslists))
        else:

            maskSlicesRes = list(map(self.pipelineSegAllImage,imgslists))
        maskResults = np.zeros_like(self.imgArray)
        for idx,mask in enumerate(maskSlicesRes):
            maskResults[idx,:,:] = mask

        
        return maskResults
    


if __name__ == '__main__':
    
    imgpath = r"./0522c0149"
    outpath = r"./out"
    reader = ReadImageBase(imgpath,outpath,'.nrrd')
    #fileLen = len(reader)
    imgArray,detail = reader[0]


    seg = SamMedSegmentation(imgArray,50,350)
    maskResults = seg.processPipeline(all=False)
    print(maskResults)
    print(np.max(maskResults))
    maskResults = maskResults.astype(np.uint8)
    reader.writer(maskResults,detail,"segPoint.nrrd",outPath = outpath)


最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 220,295评论 6 512
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,928评论 3 396
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 166,682评论 0 357
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 59,209评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 68,237评论 6 397
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,965评论 1 308
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,586评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,487评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 46,016评论 1 319
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 38,136评论 3 340
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,271评论 1 352
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,948评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,619评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,139评论 0 23
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,252评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,598评论 3 375
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,267评论 2 358

推荐阅读更多精彩内容