Python JPG文件或DCM文件Mask掩膜ROI手动圈画轮廓

DCM文件是一种医学影像文件,除了文件信息,还包含影像数据,即图像信息,只要能够提取到这个图像信息,就可以将其转换为如JPG等图片格式。
在医学影像处理中,常用到MASK掩膜,即勾画病灶区域,以进行病理分析预测,将DCM文件转换为JPG格式后,就可以利用OpenCV2等进行病灶勾画。

关于DCM文件如何转化为JPG文件,请参考此博客:Python DCM转JPG转NRRD转NII方法
将DCM转JPG需用到pydicom库,若未安装,可使用控制台输入pip install pydicom进行安装
在此也给出核心代码

import pydicom
import scipy.misc
import os
import imageio
import shutil

di=r"C:\Users\Miao\Desktop\Sample\01\T1\\DICOM\\"
#↑↑↑↑  01号病人的T1类型的DCM数据所在文件夹  ↑↑↑↑
d=r"C:\Users\Miao\Desktop\Sample\01"
#↑↑↑↑  01号病人文件夹  ↑↑↑↑

def DCMtoJPG(file_path):
	global di
	global d
    c = []#医学影像常使用:多个患者→多种区域或时期DCM数据→多个DCM数据
    #的文件夹存储格式,所以使用c存储多个DCM数据,一次性转换所有DCM文件
    names = os.listdir(file_path)#file_path为患者数据所在文件夹
    for name in names:
        index = name.rfind('.')
        name = name[:index]
        c.append(name)

    for files in c:
        DCMpath = d+'\T1\DICOM\\'+files+".dcm" #DCM所在文件夹
        JPGpath = d+"\T1jpg\\"+files+".jpg" #将JPG保存到此文件夹
        data = pydicom.read_file(DCMpath)  #使用pydicom读取DCM文件
        img = data.pixel_array  #使用pydicom读取图像数据
        imageio.imsave(JPGpath,img)  #保存JPG图像到out_path

得到JPG文件后,便可以利用OpenCV2进行图像勾画了,但是图像勾画并不是想象中那么简单。

首先尝试实现一张JPG图片的勾画(请先将DCM文件按上述的方法转化为JPG格式)
注:opencv2键鼠操作可参考本博主的文章:python-opencv控制鼠标操作

import numpy as np
import cv2 as cv
from matplotlib import pylab as plt

def drawing(event, x, y, flags, param):
    global img #声明img为全局变量以在drawing函数内部使用img
    if event == 0 and flags == 1: #发现鼠标移动且左键按下
        print(x,y,"\n") #x y为此时鼠标坐标
        cv.circle(img, (x, y), 1, (71,0, 255), -1) #img图像为底 在(x,y)处画一个大小为1的点,其颜色为(71,0,255)(RGB)
        cv.imshow('img1', img) #显示新的img图像与窗口
       
        

img = cv.imread(r'C:\Users\Miao\Desktop\T1jpg\IMG-0003-00001.jpg')#读取jpg文件
cv.namedWindow("img1")#命名窗口为img1
cv.moveWindow('img1',100,100)#将名为img1的窗口移动到屏幕100,100的位置
cv.setMouseCallback('img1', drawing)#将名为img1的窗口与IO操作函数drawing关联
cv.imshow('img1', img)#显示名为img1的窗口,窗口内图片为刚读取的JPG图像:img
cv.waitKey(0)#等待操作
cv.destroyAllWindows()#关闭窗口

运行可以得到如下图窗口 按下鼠标左键并移动即可画图
注意:若移动过快则会画出许多点,所以请勿勾画过快,医学影像病灶勾画中应该并不会出现勾画过快的现象
在这里插入图片描述
目前我们只实现了一张图像的勾画,怎样才能实现多张图像的快速勾画呢?
比如使用鼠标滚轮快速切换图像
听起来很简单,但也是需要做不少修改的,需要我们理解opencv2窗口、图像、IO函数的机制,以五张图像为例,代码如下(为方便大家理解,未采用循环操作,请大家在自己代码中自行更改):

import numpy as np
import cv2 as cv
from matplotlib import pylab as plt

now=0 #从第一张图片开始 意为当前为第几张图片
im=[] #用来存储所有图片
imgs='image'+str(now) #当前窗口名 根据now进行修改以实现窗口切换


def drawing(event, x, y, flags, param):

    global imgs
    global now
    global im
    
    if event == 0 and flags == 1: #检测到鼠标移动且左键按下
        print(x,y,"\n")
        cv.circle(im[now], (x, y), 1, (71,0, 255), -1)
        cv.imshow(imgs, im[now])
    elif event==10: #检测到滚轮滑动
        cv.destroyAllWindows() #先关闭当前窗口
        if(flags<0): #如果是下滑
            now+=1 #图片切换到下一张
            if(now>(num-1)):now=0 #若超过最大图片数则置为第一张循环开始
            imgs='image'+str(now) #窗口名也随之进行修改
            cv.namedWindow(imgs) #建立新的窗口
            cv.moveWindow(imgs,100,100) #移动新的窗口
            cv.setMouseCallback(imgs, drawing) #为新的窗口关联IO函数
            cv.imshow(imgs, im[now]) #显示新的窗口 
        else: #如果是上滑
            now-=1 #切换到上一张
            if(now<0):now=num-1 #若当前为第一张 则切换为最后一张进行循环
            imgs='image'+str(now) #以下同上
            cv.namedWindow(imgs)
            cv.moveWindow(imgs,100,100)
            cv.setMouseCallback(imgs, drawing)  
            cv.imshow(imgs, im[now])
       
        
num = 5 #共五张图片
#依次读取五张图片
img = cv.imread(r'C:\Users\Miao\Desktop\T1jpg\IMG-0003-00001.jpg')
img2 = cv.imread(r'C:\Users\Miao\Desktop\T1jpg\IMG-0003-00002.jpg')
img3 = cv.imread(r'C:\Users\Miao\Desktop\T1jpg\IMG-0003-00003.jpg')
img4 = cv.imread(r'C:\Users\Miao\Desktop\T1jpg\IMG-0003-00004.jpg')
img5 = cv.imread(r'C:\Users\Miao\Desktop\T1jpg\IMG-0003-00005.jpg')
#将图片数据保存在im列表中
im.append(img)
im.append(img2)
im.append(img3)
im.append(img4)
im.append(img5)

#↓↓↓↓↓↓↓↓↓先建立第一张图片的窗口↓↓↓↓↓↓↓↓↓
cv.namedWindow(imgs) 
cv.moveWindow(imgs,100,100)
cv.setMouseCallback(imgs, drawing)
cv.imshow(imgs, im[now])
    
cv.waitKey(0)
cv.destroyAllWindows()

由此一来,便实现了滚轮快速切换勾画图像的需求。

现在只实现了画图而已,MASK掩膜其实是勾画内部全部为1,外部全部为0的图像,与原JPG文件并无关系,所以我们需要记录勾画的坐标然后找到内部的点并将其置为1,其余置为0

先尝试记录下每张图像勾画的坐标

import numpy as np
import cv2 as cv
from matplotlib import pylab as plt

num = 5
now=0
im=[]
XY=[] #XY用来存储五张图片各自勾画的坐标
imgs='image'+str(now)
for i in range(num):
    XY.append([])



def drawing(event, x, y, flags, param):
    global imgs #窗口名
    global now #当前图片
    global im #所有图片
    if event == 0 and flags == 1: #鼠标移动 and 左键按下
    	#注:为了加快运算速度,勾画时不会存储本图片已勾画过的点
        if([x,y] not in XY[now]): #若当前点未记录则添加
            XY[now].append([x,y])
        if([x+1,y+1] not in XY[now]):#将当前点与附近的点都添加进勾画坐标以防止出现间隙
            XY[now].append([x+1,y+1])
        if([x-1,y-1] not in XY[now]):
            XY[now].append([x-1,y-1])
        if([x+1,y-1] not in XY[now]):
            XY[now].append([x+1,y-1])
        if([x-1,y+1] not in XY[now]):
            XY[now].append([x-1,y+1])
        if([x+1,y] not in XY[now]):
            XY[now].append([x+1,y])
        if([x-1,y] not in XY[now]):
            XY[now].append([x-1,y])
        if([x,y+1] not in XY[now]):
            XY[now].append([x,y+1])
        if([x,y-1] not in XY[now]):
            XY[now].append([x,y-1])
        cv.circle(im[now], (x, y), 1, (71,0, 255), -1)
        cv.imshow(imgs, im[now])
    elif event==10: #滚轮
        cv.destroyAllWindows()
        if(flags<0): #向下滚
            now+=1
            if(now>num-1):now=0
            imgs='image'+str(now)
            cv.namedWindow(imgs)
            cv.moveWindow(imgs,100,100)
            cv.setMouseCallback(imgs, drawing)  
            cv.imshow(imgs, im[now])
        else: #向下滚
            now-=1
            if(now<0):now=num-1
            imgs='image'+str(now)
            cv.namedWindow(imgs)
            cv.moveWindow(imgs,100,100)
            cv.setMouseCallback(imgs, drawing)  
            cv.imshow(imgs, im[now])
       
        

img = cv.imread(r'C:\Users\Miao\Desktop\T1jpg\IMG-0003-00001.jpg')
img2 = cv.imread(r'C:\Users\Miao\Desktop\T1jpg\IMG-0003-00002.jpg')
img3 = cv.imread(r'C:\Users\Miao\Desktop\T1jpg\IMG-0003-00003.jpg')
img4 = cv.imread(r'C:\Users\Miao\Desktop\T1jpg\IMG-0003-00004.jpg')
img5 = cv.imread(r'C:\Users\Miao\Desktop\T1jpg\IMG-0003-00005.jpg')
im.append(img)
im.append(img2)
im.append(img3)
im.append(img4)
im.append(img5)
cv.namedWindow(imgs)
cv.moveWindow(imgs,100,100)
cv.setMouseCallback(imgs, drawing)
cv.imshow(imgs, im[now])
    
cv.waitKey(0)
cv.destroyAllWindows()

XY数据如下图所示 其中图片0 1 2被勾画 3 4未勾画
在这里插入图片描述
现在我们便成功得到了勾画坐标,进行一些处理便可以生成MASK掩膜文件
①找到勾画内部的坐标点
②将圈画及内部置为1,外部置为0
③优化速度

找到内部坐标点最稳定的方法就是检测当前点的上下左右是否含有勾画点,缺点是速度比较慢,但可以通过一些优化大大加快其速度
请注意: 代码中图像存储行列大小与此处分辨率应做调换 详见代码及注释在这里插入图片描述

完整代码如下:

import numpy as np
import cv2 as cv
from matplotlib import pylab as plt

num = 5
now=0
im=[]
XY=[]
imgs='image'+str(now)
for i in range(num):
    XY.append([])


#检测左侧是否有勾画点
def left(ii,jj,l):
    for i in l:
        if(i[0]==ii and i[1]<jj):
            return True
    return False
#检测右侧是否有勾画点
def right(ii,jj,l):
    for i in l:
        if(i[0]==ii and i[1]>jj):
            return True
    return False
#检测上侧是否有勾画点
def up(ii,jj,l):
    for i in l:
        if(i[1]==jj and i[0]<ii):
            return True
    return False
#检测下侧是否有勾画点
def down(ii,jj,l):
    for i in l:
        if(i[1]==jj and i[0]>ii):
            return True
    return False 

def drawing(event, x, y, flags, param):
    global imgs
    global now
    global im
    if event == 0 and flags == 1: #鼠标移动 and 左键按下
        if([x,y] not in XY[now]):
            XY[now].append([x,y])
        if([x+1,y+1] not in XY[now]):
            XY[now].append([x+1,y+1])
        if([x-1,y-1] not in XY[now]):
            XY[now].append([x-1,y-1])
        if([x+1,y-1] not in XY[now]):
            XY[now].append([x+1,y-1])
        if([x-1,y+1] not in XY[now]):
            XY[now].append([x-1,y+1])
        if([x+1,y] not in XY[now]):
            XY[now].append([x+1,y])
        if([x-1,y] not in XY[now]):
            XY[now].append([x-1,y])
        if([x,y+1] not in XY[now]):
            XY[now].append([x,y+1])
        if([x,y-1] not in XY[now]):
            XY[now].append([x,y-1])
        cv.circle(im[now], (x, y), 1, (71,0, 255), -1)
        cv.imshow(imgs, im[now])
    elif event==10: #滚轮
        cv.destroyAllWindows()
        if(flags<0): #向下滚
            now+=1
            if(now>num-1):now=0
            imgs='image'+str(now)
            cv.namedWindow(imgs)
            cv.moveWindow(imgs,100,100)
            cv.setMouseCallback(imgs, drawing)  
            cv.imshow(imgs, im[now])
        else: #向下滚
            now-=1
            if(now<0):now=num-1
            imgs='image'+str(now)
            cv.namedWindow(imgs)
            cv.moveWindow(imgs,100,100)
            cv.setMouseCallback(imgs, drawing)  
            cv.imshow(imgs, im[now])
       
        

img = cv.imread(r'C:\Users\Miao\Desktop\T1jpg\IMG-0003-00001.jpg')
img2 = cv.imread(r'C:\Users\Miao\Desktop\T1jpg\IMG-0003-00002.jpg')
img3 = cv.imread(r'C:\Users\Miao\Desktop\T1jpg\IMG-0003-00003.jpg')
img4 = cv.imread(r'C:\Users\Miao\Desktop\T1jpg\IMG-0003-00004.jpg')
img5 = cv.imread(r'C:\Users\Miao\Desktop\T1jpg\IMG-0003-00005.jpg')
im.append(img)
im.append(img2)
im.append(img3)
im.append(img4)
im.append(img5)
cv.namedWindow(imgs)
cv.moveWindow(imgs,100,100)
cv.setMouseCallback(imgs, drawing)
cv.imshow(imgs, im[now])
    
cv.waitKey(0)
cv.destroyAllWindows()

img_size = [260, 320, 3] #根据自己的JPG图像修改前两个数据大小 与分辨率调换行列


for l in XY: #依次处理每张图片的勾画
    if(l):#若本图有勾画信息
        img2 = np.zeros(img_size, np.uint16)#先生成同型的全0数据
        for m in l:           
            img2[m[1]][m[0]]=[1,1,1]#若想可视化,请修改为[255,255,255],但掩膜文件应全为1,方便后续处理
        iii=0
        jjj=0
        start=0 #当前行是否有勾画 
        for i in img2:
            jjj=0
            if(right(iii,jjj,l) or left(iii,jjj,l) or up(iii,jjj,l) or down(iii,jjj,l)):start=1 #若当前行有勾画则处理本行
            if(start):
                for j in i:
                    if(right(iii,jjj,l) and left(iii,jjj,l) and up(iii,jjj,l) and down(iii,jjj,l)):
                        img2[jjj][iii]=[1,1,1] #注:若想可视化请修改为[255,255,255],但制作掩膜需要使用[1,1,1]
                    jjj+=1
            start=0
            iii+=1
        #若需要可视化 请先将代码中[1,1,1]修改为[255,255,255]    
        #plt.figure(figsize=(26, 32))
        #ax = plt.subplot(3,1,1)
        #ax.imshow(img2)
        #ax.axis("off")

    

若将代码中[1,1,1]修改为[255,255,255]并取消最后四行的注释则可进行可视化
在这里插入图片描述
此时我们就可以尝试将掩膜转换为DCM文件以进行后续的病理分析处理
在本博文Python DCM转JPG转NRRD转NII方法中介绍了几种常见的医学影像的转换方法,但是掩膜JPG转换为DCM还需要进一步处理,因为我们使用的pydicom库在处理过程中会把0 1数据转换为0 256 与512

(JPG转换为DCM,需要一个DCM文件,因为机器一般会直接输出DCM文件,为了方便处理(比如勾画MASK掩膜)我们才会将其转换为JPG然后再转换为DCM,所以我们一般会有原DCM文件,只要我们将原DCM文件里存储图像的数据部分修改为新的JPG图像数据再保存即可将JPG转换为DCM文件 代码如下(需要下载opencv2))

picture_path = r'C:\Users\Miao\Desktop\T1jpgalr\\' + files + ".jpg" 
out_path = r'C:\Users\Miao\Desktop\T1jpgalr\T1dcmalr\\' + files + ".dcm"    
img = cv2.imread(picture_path)
img_arr16 = np.array(img, dtype=np.int16)
data_changed = img_arr16[:, :, 0]
pd = data_changed.tobytes()
dcm = pydicom.dcmread(r'C:\Users\Miao\Desktop\T1jpgalr\T1\DICOM\\' + files + ".dcm")
dcm.PixelData = pd
dcm.save_as(out_path)
ds = pydicom.dcmread(out_path)
img = ds.pixel_array
ii=0
jj=0
for i in img:
	for j in i:
		if(j!=0 and(j==256 or j==512)):
			img[ii][jj]=1
		jj+=1
		if(jj==size1[1]):jj=0
	ii+=1
ds.PixelData = img.tobytes()
ds.save_as(out_path)

至此,便生成了掩膜DCM文件,再按照此文章的方法,便可将其转换为NRRD或NII格式,以进行医学影像病理分析。

你可能感兴趣的:(python,opencv,计算机视觉)