pair标注后保存的文件,是一个压缩文件。解压后会包含一个json文件和一个nii文件。本文就将对nii文件进行解析。完成各个类别的分拆,验证是否存在问题

官方Pair标注结果读取说明可以通过软件获取。标记后解析nii文件后的mask图例,如下右侧图。

(因为是发现了问题,才尝试了去解决问题。下文采用倒叙方式,由处理好的结果,一步步的探究由来)。

SAM语义分割方法_深度学习

就发现直接从nii文件读取保存后的标注文件,与在pair软件上的位置不太对。经过仔细的对比才发现,是方位发生了变化。下面就将pair标记好的mask,与原始图进行匹配。

1.标注mask匹配

经过了两次变换,分别是旋转90度和水平翻转,就发现转出来的结果就可以和pair软件匹配上了,匹配的结果如下所示:

(这块没有来得及和官方人员进行沟通,没有明白其中的思路,为什么保存的nii文件,还需要两步操作才能和pair软件显示的对应上)

# 旋转90度
mask = cv2.rotate(mask, cv2.ROTATE_90_CLOCKWISE)
# 水平翻转
right_mask = cv2.flip(mask, 1)

SAM语义分割方法_pair_02


上述变换和图像展示的完整代码也分享出来,如下这样:

import cv2
import os
import numpy as np

mask_Dir = r'F:\Pair\data\label-all'
raw_Dir = r'F:\Pair\data\image'
mask_list = os.listdir(mask_Dir)
raw_list = os.listdir(raw_Dir)
for i in range(len(mask_list)):
    maskfile_name = mask_list[i]
    rawfile_name = raw_list[i]
    mask_path = os.path.join(mask_Dir, maskfile_name)
    raw_path = os.path.join(raw_Dir, rawfile_name)

    raw_img = cv2.imread(raw_path, 1)

    mask = cv2.imread(mask_path, 0)
    # 旋转90度
    mask = cv2.rotate(mask, cv2.ROTATE_90_CLOCKWISE)
    # 水平翻转
    mask = cv2.flip(mask, 1)

    cv2.imshow(maskfile_name+'mask', mask)
    # cv2.waitKey(1000)

    contours, _ = cv2.findContours(mask, cv2.RETR_TREE, cv2.CHAIN_APPROX_SIMPLE)
    for contour in contours:
        x, y, w, h = cv2.boundingRect(contour)
        xmin, ymin, xmax, ymax = x, y, x + w, y + h
        print(xmin, ymin, xmax, ymax)

        cv2.drawContours(raw_img, [contour], 0, (0, 0, 255), 1)
        cv2.rectangle(raw_img, (int(xmin), int(ymin)), (int(xmax), int(ymax)), (0, 255, 0), 1)

    cv2.imshow(maskfile_name+'img', raw_img)
    cv2.waitKey(1000)

其中image和label文件夹内容如下所示:

SAM语义分割方法_计算机视觉_03


到这里就要问,那label是如何获取的呢?获取方式如下:

  • pair标注生成标注压缩文件
  • 解析压缩文件产生json和nii.gz文件
  • nii.gz文件转储为上述label文件这样的图片形式

具体的产生代码如下(这里稍微会复杂一点点,因为我想要引出pair的多标签标注的解析):

import numpy as np
import nibabel as nib
import os
from PIL import Image

def nii_2img(select=False):
    label = nib.load(r"./data\17\1_2_840_113704_1_1762661706_15468_1049307570_11_1_3_2_0_62_0_62_dcm_Label.nii.gz")

    #Convert them to numpy format,
    label_data = label.get_fdata()
    print("=======label shape=======")
    print(label_data.shape)
    print("=======label value=======")
    print(label_data[label_data != 0])

    # 挑选类别
    if select:
        print('change after ---')
        label_data[label_data != 20.0] = 0

    #extract 2D slices from 3D volume for training cases while
    # e.g. slice 000
    for i in range(label_data.shape[2]):
        label_one = label_data[:, :, i]
        label_slice000 = label_one * 255
				
				# 旋转和翻转也可以直接放到这里,就不存在开篇对不上的问题
				# 旋转90度
        mask = cv2.rotate(label_slice000, cv2.ROTATE_90_CLOCKWISE)
        # 水平翻转
        label_slice000 = cv2.flip(mask, 1)

        nozero_list = label_one[label_one != 0]
        if 21.0 in nozero_list:
            print("=======label shape=======")
            print(label_one.shape)
            print("=======label value=======")
            print(label_one[label_one != 0])

            np.savetxt(r"./data/txt/" + str(i) + ".txt", label_one, delimiter=',', fmt='%5s')

        label = Image.fromarray(label_slice000)
        label = label.convert("L")
        label.save(r"./data/label/" + str(i) + "_label.png")

if __name__ == "__main__":
    nii_2img(select=False)

可以看到select这个选项,这是干神马的呢?简单点就是多标签的类别解析

SAM语义分割方法_计算机视觉_04

2.各类别拆分和mask转储

上图可以看到,此case的标注类别有两个,分别是类别20和类别21,此时的类别就变得丰富了起来,不再只是单类问题了。通过上述代码中:

np.savetxt(r"./data/txt/" + str(i) + ".txt", label_one, delimiter=',', fmt='%5s')

查看下保存的TXT文件内容,就知晓了。

同时,也为选择某一类别,提供了依据,有时标记了很多类,但是在使用阶段,可能并不需要那么多类,这就是select存在的意义。

SAM语义分割方法_人工智能_05

修改下面内容,即可实现对对应类别的筛选。20就是标记序号为20多类,如果这个类的标号是31,那此事想要拿到这个类的位置信息,就需要将这里设定为21的保留下来,其余的都设为0:

# 挑选类别
if select:
    print('change after ---')
    label_data[label_data != 20.0] = 0

那就展示下各个类别分开后的样子,如下:

SAM语义分割方法_pair_06


至此,大功告成,完成了pair软件标注文件的后处理。有了这个各个类别的mask图,想干点其他的事情,都方便了很多。例如转labelme的json文件等等,都可以通用了。

PS:如果你的标记是3个类别,但是你只想取其中的两个类为目标,其他类别为0展示,对select部分补充如下:

# 挑选类别
    if select:
        print('change after ---')
        print(type(label_data))

        # method select one class
        # label_data[label_data != 19.0] = 0

        # method select one or mult class
        index = np.ones(label_data.shape)
        for class_i in TB_ClassList:
            index_i = label_data != class_i
            index = index_i * np.array(index, dtype=bool)

        print(index, type(index))
        label_data[index] = 0

这样就实现了我们的想法,展示结果如下:

SAM语义分割方法_深度学习_07

上述内容不仅对于pair软件试用,对于大多数语义分割的数据标注集的处理同样适用