医学数据处理及多目标分割处理汇总

正文

持续更新,各种有关医学影像处理但工具代码
先给一波引用包

1
2
3
4
5
6
7
8
9
10
11
from collections import Counter
import nibabel as nib
import numpy as np
import cv2
import random
import shutil
import os
import numpy as np
import json
import base64

一、nii 3维数组,转2维图像显示、保存

在医学图像处理中,我们经常使用一种NIFTI(Neuroimaging Informatics Technology Initiative,神经影像信息技术倡议)格式图像(.nii文件)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
def read_data(image_path,label_path):
# load data
image_data = nib.load(image_path)
label_data = nib.load(label_path)

# Convert them to numpy format,
image = image_data.get_fdata()
label = label_data.get_fdata()

## only image
# clip the images within [-125, 275]
data_clipped = np.clip(image, -125, 275)
# normalize each 3D image to [0, 1], and
data_normalised = (data_clipped - (-125)) / (275 - (-125))

images = []
labels = []

# extract 2D slices from 3D volume for training cases while
for i in range(data_clipped.shape[0]): # note some dataset slice 0 channels, some dataset slice -1 channels
# slice
img_slice = data_normalised[i, :, :] * 255
label_slice = label[i, :, :] # 0:background 1:kidney 2:carcinoma
if len(Counter(label_slice.flatten()))>1:
images.append(img_slice)
labels.append(label_slice)
return images, labels

文件夹创建

用于创建某个路径文件夹
因为python必须有父文件夹才能创建子文件夹否则报错,所以写了个回掉函数来创建文件夹

1
2
3
4
5
def create_dir(path):
if not os.path.exists(path):
subpath = '/'.join(path.split('/')[:-1])
create_dir(subpath)
os.makedirs(path)

多类别分割结果可视化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def show(img_path,label_path, save_path,n_classes=2): # n_classes 为标签数目
# alpha 为第一张图片的透明度
alpha = 1
# beta 为第二张图片的透明度
gamma = 0
# 颜色
colors = [[255,0,0],[0,255,0],[0,0,255]] # 预设的颜色,如果标签太多可以自己加一些颜色上去
image = cv2.imread(img_path)
label = cv2.imread(label_path)
if image.ndim == 2:
image = cv2.cvtColor(image,cv2.COLOR_GRAY2BGR) # 原图转三通道
if label.ndim == 3:
label = cv2.cvtColor(label,cv2.COLOR_BGR2GRAY) # 标签转单通道
print(Counter(label.flatten()))
lab_num = list(Counter(label.flatten()).keys()) # 所有标签种类 例如[0,1,2]
for i in range(len(lab_num)):
if lab_num[i] == 0: # 0是背景不画
continue
mask = np.copy(label)
mask[mask!=lab_num[i]] = 0 # 把非当前标签的区域都设置为0
mask[mask==lab_num[i]] = 1 # 当前标签区域设置为1
# 给mask上色
mask_BGR = cv2.merge([mask*colors[i][0], mask*colors[i][1], mask*colors[i][2]]) # 三通道mask
image = cv2.addWeighted(image, alpha, mask_BGR, 0.5, gamma) # 融合到原图上
cv2.imwrite(save_path,image)

数据集随机分割

将图像数据集按比例随机分成训练集和测试集

1
2
3
4
5
6
7
8
9
10
11
12
def cut_train_test_data(path,target):
create_dir(os.path.join(target,'images'))
create_dir(os.path.join(target,'labels'))

imgs = os.listdir(path+'/images') # 获取图像列表

imgs = random.sample(imgs,int(len(imgs)*0.1)) # 按比例随机抽出一部分作为测试集


for img in imgs:
shutil.move('%s/images/%s'%(path,img),'%s/images/%s'%(target,img))
shutil.move('%s/labels/%s'%(path,img),'%s/labels/%s'%(target,img))

labelme生成json文件转mask图像标签

目前在做医学影像分割,采用labelme进行数据标注,标注后的数据为json格式,包含了大量标注点数据信息。
现通过代码形式批量将json数据转换为mask图像数据。labelme自带的转换工具不知道为啥只会根据点集形成连接到线条而不会生成mask图,所以干脆自己写了一个。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
def json_to_mask():
# json转mask 图
labels = [''] # 用于统计多标签
json_file = "" # 标签文件夹, 文件夹中同时存在json标签与原图,且通过文件名对应。
target = "" # 保存文件夹路径
if not os.path.exists(target):
os.mkdir(target)
if not os.path.exists(os.path.join(target+'/images/')): # 原图放在images文件夹
os.mkdir(os.path.join(target+'/images/'))
if not os.path.exists(os.path.join(target+'/labels/')): # mask放在labels文件夹
os.mkdir(os.path.join(target+'/labels/'))
count = [e for e in os.listdir(json_file) if e.endswith('.json')] # 获取文件夹下面所有json
print(len(count)) # 看有多少个json文件
for i in tqdm(range(0, len(count))): # 开始逐个遍历处理json文件
path = os.path.join(json_file, count[i])
name = count[i].split('.')[0]
if os.path.isfile(path):
data = json.load(open(path)) #这里读入json数据

# 读入原图
if data['imageData']: #包含数据 直接读入
imageData = data['imageData']
else: # 不包含数据 按标签名去读图片
imagePath = os.path.join(os.path.dirname(path), data['imagePath'])
with open(imagePath, 'rb') as f:
imageData = f.read()
imageData = base64.b64encode(imageData).decode('utf-8')
img_b64decode = base64.b64decode(imageData) # base64解码
img_array = np.frombuffer(img_b64decode, np.uint8) # 转换np序列
img = cv2.imdecode(img_array, cv2.COLOR_BGR2RGB) # 转换Opencv格式

# 根据原图创建同样维度的mask图,里面的值全是0
mask = np.zeros((img.shape[0], img.shape[1]), dtype=np.uint8)

if len(data["shapes"])<1: # 空标签 则不做处理,继续下一个
print(count[i])
continue

for points in data["shapes"]: # 读标签
if points['label'] not in labels: # 如果此标签之前未遇到 则记录到labels中
labels.append(points['label'])
idx = labels.index(points['label']) # 得到标签序号
ps = points["points"] # 取点集
ps = np.array(ps, dtype=np.int32) # tips: points location must be int32
cv2.fillPoly(mask, [ps], (idx)) #可能会被覆盖类型,所以多标签分割时,先标注大的,再标注小的。

rat = 256/max(img.shape)
img = cv2.resize(img, (int(img.shape[1]*rat),int(img.shape[0]*rat)))
mask = cv2.resize(mask, (int(mask.shape[1]*rat),int(mask.shape[0]*rat)))

h = 256-img.shape[0]
w = 256-img.shape[1]
img = cv2.copyMakeBorder(img, int(h/2), int(h-h/2), int(w/2), int(w-w/2), cv2.BORDER_CONSTANT, value=[127, 127, 127])
mask = cv2.copyMakeBorder(mask, int(h/2), int(h-h/2), int(w/2), int(w-w/2), cv2.BORDER_CONSTANT, value=[0])

cv2.imwrite('%s/images/%s.png' % (target, name), img)
cv2.imwrite('%s/labels/%s.png' % (target, name), mask)