【3D 图像分类】基于 Pytorch 的 3D 立体图像分类4(多人标注的结节立体框合并和特征等级投票)

本文主要是介绍【3D 图像分类】基于 Pytorch 的 3D 立体图像分类4(多人标注的结节立体框合并和特征等级投票),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

LIDC-IDRI的数据集中,对于同一个案例,存在多个医生标注的结果。这就导致下面几种情况的出现:

  1. A医生标注的结节区域,B医生并不一定会标;
  2. B医生标注的结节,C医生也标注了,但是范围大小存在着交集关系;
  3. 同时标记,给的特征等级也不一定相同。

此时,就需要对一个案例标注的结节进行处理。可以根据标注次数进行选择,也可以简单粗暴的直接取并集。本文就直接取并集,比较的简单。如果要考虑标记次数,可以参考这篇文章:【3D 图像分割】基于 Pytorch 的 3D 图像分割6(数据预处理之LIDC-IDRI 标签 xml 标签转储及标记次数统计 )。

本文的目标,就是在上一节处理得到的PKL文件的基础上,获取一个个结节坐标信息,和对应需要处理的某个特征的信息,比如良恶性,比如钙化程度。最终得到一个合并后汇总版的坐标,和对应的等级,具体步骤如下:

  1. 获取一个个结节信息,包括坐标和等级;
  2. 对这些结节,根据IOU,分成一个个小堆,等着合并用;
  3. 一个堆,一个堆的合并在一起,等级是根据投票少数服从多数;
  4. 最后把合并后的坐标和等级存储下来,供后续裁剪使用。

一、具体实施 ❤️

实施上述内容的主调用函数如下,定义了pkl文件的路径、存储的路径、取什么特征。这样,在main里面就实施上述步骤,也会在本节中,分小节一一展开介绍。

def main(pkl_dir, mainKey, save_dir):pkl_list = os.listdir(pkl_dir)for pkl in pkl_list:name = pkl.split('_')[0]pkl_path = os.path.join(pkl_dir, pkl)print('pkl_path:', pkl_path)boxes, characteres = getPkl_info(pkl_path, mainKey=mainKey)   # 读取pkl文件,获取标注的信息print('boxes:', boxes, characteres)if boxes:cluster_l = make_one_cluster(boxes, iout=0.1)boxes_combine, level_combine = combine_bbox_level(cluster_l, boxes, characteres)print(boxes_combine)print(level_combine)print()np.save(os.path.join(save_dir, '%s_boxes.npy' % (name)), boxes_combine)np.save(os.path.join(save_dir, '%s_level.npy' % (name)), level_combine)if __name__=='__main__':pkl_dir = r'./pkl_file'save_dir = r'./nodule_cls_info'mainKey = 'nodule_calcification'#       实性            部分实性          磨玻璃影cls = ['solidNodule', 'solidNodulePart', 'GGO']main(pkl_dir, mainKey, save_dir)

1.1、读取PKL文件,获取信息

PKL文件的读取,在之前的文章中都有简单的介绍,感兴趣的可以去看看本专栏之前的文章,会对pkl文件的读取和存储留下更深的印象。

pkl文件中:

  • 一个结节一层的记录信息如下所示,
  • 一个结节不同层的信息,会组成一个列表
  • 不同结节,会组成一个更大的列表

对于下面一个结节一层的记录信息,就不一逐一展开介绍了,这块介绍参考上一篇文章:【3D 图像分类】基于 Pytorch 的 3D 立体图像分类3(LIDC-IDRI 肺结节 XML 特征标签 PKL 转储)

{'pixels': [[355, 278],[354, 279],[354, 280],[354, 281],[354, 282],[354, 283],[354, 284],[354, 285],[355, 286],[356, 286],[357, 286],[358, 285],[359, 285],[360, 284],[361, 284],[362, 283],[363, 282],[363, 281],[362, 280],[361, 279],[360, 279],[359, 278],[358, 278],[357, 278],[356, 278],[355, 278]],'sop_uid': '1.3.6.1.4.1.14519.5.2.1.6279.6001.265463834573905158752543199468','sop_Instance_num': 57,'nodule_id': 'Nodule 001','nodule_malignancy': 3,'nodule_subtlety': 4,'nodule_internal_struct': 1,'nodule_calcification': 6,'nodule_sphericity': 4,'nodule_margin': 5,'nodule_lobulation': 1,'nodule_spiculation': 1,'nodule_texture': 5},

获取pkl文件信息的完整定义代码如下,这里使用了一个外接立体框,表示一个结节的坐标,包括了zmin, ymin, xmin, zmax, ymax, xmax,去除掉了就只标记一层的结节。(这里你也可以不去掉,那最后就会留下这个,因为算IOU时候,它与其他的框的值比较低)

def getPkl_info(pkl_path, mainKey='nodule_malignancy'):boxes, character_l = [], []with open(pkl_path, "rb") as f:pkl_data = pickle.load(f)print(pkl_data)for rad_annotationID in pkl_data['nodules']:z_l, y_l, x_l = [], [], []for one_nodule in rad_annotationID:sop_Instance_num = one_nodule['sop_Instance_num']pixel_array = np.array(one_nodule['pixels'])character = one_nodule[mainKey]y1, x1, y2, x2 = np.min(pixel_array[:, 1]), np.min(pixel_array[:, 0]), np.max(pixel_array[:, 1]), np.max(pixel_array[:, 0])z_l.append(sop_Instance_num)y_l.append(y1), y_l.append(y2)x_l.append(x1), x_l.append(x2)zmin, zmax = min(z_l), max(z_l)ymin, ymax = min(y_l), max(y_l)xmin, xmax = min(x_l), max(x_l)if zmax > zmin:     # 去除掉只标记了一层的boxes.append([zmin, ymin, xmin, zmax, ymax, xmax])character_l.append(character)return boxes, character_l

1.2、根据IOU,分成小堆

在判断同一次检查,不同的结节之间的关系,采用了立体框之间的IOU作为判断标准,其中:

  1. IOU 低于阈值的,两个立体框之间是相离更大的,归为不同的结节
  2. IOU 高于阈值的,两个立体框之间是相交更大的,他们两个需要划到一个堆里面,供后续合并操作
def iou_3d(cubes_a, cubes_b):# cubes_a:[zi.min(), yi.min(), xi.min(),#           zi.max(), yi.max(), xi.max()]cubes_a = np.expand_dims(cubes_a, axis=1)cubes_b = np.expand_dims(cubes_b, axis=0)# np.maximum逐元素比较两个array的大小,取出大的值overlap = np.maximum(0.0,np.minimum(cubes_a[..., 3:], cubes_b[..., 3:]) -   # 大大,求最小np.maximum(cubes_a[..., :3], cubes_b[..., :3])     # 小小,求最大)overlap = np.prod(overlap, axis=-1)     # np.prod:计算数组中所有元素的乘积area_a = np.prod(cubes_a[..., 3:] - cubes_a[..., :3], axis=-1)  # 最大坐标减去最小坐标area_b = np.prod(cubes_b[..., 3:] - cubes_b[..., :3], axis=-1)iou = overlap / (area_a + area_b - overlap+1e-5)return ioudef make_one_cluster(boxes, iout=0.1):iou = iou_3d(boxes, boxes)n, m = iou.shapeiou[np.tril_indices_from(iou)] = 0print(iou, iou.shape, type(iou))# iou_l = iou.tolist()cluster_l = []all_l = []for i in range(n):res = np.where(iou[i] > iout)[0]print(i, res, type(res))match_index = res.tolist()if i not in all_l:match_index.append(i)cluster_l.append(match_index)all_l.extend(match_index)print(cluster_l)print(all_l)return cluster_l

上面代码中,除了计算框与框之间的IOU外,还需要去除对角线及其下三角形(tril)的值,这里都置为0。除此之外,1匹配到3,那就不能再3匹配到1了,所以增加了一个去重的判断,已经纳入了,就不在归入列表了。

1.3、合并在一起

合并在一起就比较的简单了,这里取的是并集,所以对于一个堆的不同医生标注的结节,zyx的最大值,最小的取最小,最大的取最大就可以了。

当然,你也可以取交集。但是,对于最后分类阶段对结节像素区域裁剪一般不构成影响的。因为结节的区域是比较小的,crop后的patch一般会比这个结节大很多的,所以,这点影响不大。

下面是合并的代码。

def combine_bbox_level(cluster_l, boxes, characteres):boxes_combine, level_combine = [], []for oneCluster in cluster_l:level_list, bbox_list = [], []for i in oneCluster:level_list.append(characteres[i])bbox_list.append(boxes[i])print('level_list:', level_list)print('bbox_list:', bbox_list)bbox_array = np.array(bbox_list)zmin, zmax = min(bbox_array[:, 0]), max(bbox_array[:, 3])ymin, ymax = min(bbox_array[:, 1]), max(bbox_array[:, 4])xmin, xmax = min(bbox_array[:, 2]), max(bbox_array[:, 5])characteristicLeval = max(level_list, key=level_list.count)boxes_combine.append([zmin, ymin, xmin, zmax, ymax, xmax])level_combine.append(characteristicLeval)return boxes_combine, level_combine

1.4、存储下来

通过前面一系列的处理,将多人标注的结节进行了汇总,多相应的等级进行了投票处理,得到了立体框的坐标,以及对应特征的等级。此时,将这两个数据临时存储下来,供后续裁剪等操作,提供数据。

存储的方式如下:

			np.save(os.path.join(save_dir, '%s_boxes.npy' % (name)), boxes_combine)np.save(os.path.join(save_dir, '%s_level.npy' % (name)), level_combine)

这样一次检查,就得到了一个_boxes.npy的文件,和一个_level.npy为文件。

打开npy文件查看,_boxes.npy存放的内容如下:

[[ 54 276 351  58 288 364][ 49 360 285  51 370 297][ 40 258 324  43 268 335][ 21 330 316  22 340 328][ 23 232 292  26 243 302][ 15 290 155  16 295 162][ 50 199 109  51 208 116]] 

_level.npy存放的内容如下:

[6 6 6 6 6 6 6]

二、总结 ❤️

本文就没有延伸太多无关的内容,主要就是对本系列一二两个动手实操内容的一个数据处理的补充。通过本文之后,你就得到了一个结节具体的坐标位置,以及对应特征类别的等级。有了这两个信息,无论你是做检测,还是扣成patch进行分类,都非常的简单了。

最后这个扣patch的操作,会在下一节给出,期待。

这篇关于【3D 图像分类】基于 Pytorch 的 3D 立体图像分类4(多人标注的结节立体框合并和特征等级投票)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/420697

相关文章

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

基于人工智能的图像分类系统

目录 引言项目背景环境准备 硬件要求软件安装与配置系统设计 系统架构关键技术代码示例 数据预处理模型训练模型预测应用场景结论 1. 引言 图像分类是计算机视觉中的一个重要任务,目标是自动识别图像中的对象类别。通过卷积神经网络(CNN)等深度学习技术,我们可以构建高效的图像分类系统,广泛应用于自动驾驶、医疗影像诊断、监控分析等领域。本文将介绍如何构建一个基于人工智能的图像分类系统,包括环境

无人叉车3d激光slam多房间建图定位异常处理方案-墙体画线地图切分方案

墙体画线地图切分方案 针对问题:墙体两侧特征混淆误匹配,导致建图和定位偏差,表现为过门跳变、外月台走歪等 ·解决思路:预期的根治方案IGICP需要较长时间完成上线,先使用切分地图的工程化方案,即墙体两侧切分为不同地图,在某一侧只使用该侧地图进行定位 方案思路 切分原理:切分地图基于关键帧位置,而非点云。 理论基础:光照是直线的,一帧点云必定只能照射到墙的一侧,无法同时照到两侧实践考虑:关

认识、理解、分类——acm之搜索

普通搜索方法有两种:1、广度优先搜索;2、深度优先搜索; 更多搜索方法: 3、双向广度优先搜索; 4、启发式搜索(包括A*算法等); 搜索通常会用到的知识点:状态压缩(位压缩,利用hash思想压缩)。

hdu2241(二分+合并数组)

题意:判断是否存在a+b+c = x,a,b,c分别属于集合A,B,C 如果用暴力会超时,所以这里用到了数组合并,将b,c数组合并成d,d数组存的是b,c数组元素的和,然后对d数组进行二分就可以了 代码如下(附注释): #include<iostream>#include<algorithm>#include<cstring>#include<stack>#include<que

烟火目标检测数据集 7800张 烟火检测 带标注 voc yolo

一个包含7800张带标注图像的数据集,专门用于烟火目标检测,是一个非常有价值的资源,尤其对于那些致力于公共安全、事件管理和烟花表演监控等领域的人士而言。下面是对此数据集的一个详细介绍: 数据集名称:烟火目标检测数据集 数据集规模: 图片数量:7800张类别:主要包含烟火类目标,可能还包括其他相关类别,如烟火发射装置、背景等。格式:图像文件通常为JPEG或PNG格式;标注文件可能为X

day-51 合并零之间的节点

思路 直接遍历链表即可,遇到val=0跳过,val非零则加在一起,最后返回即可 解题过程 返回链表可以有头结点,方便插入,返回head.next Code /*** Definition for singly-linked list.* public class ListNode {* int val;* ListNode next;* ListNode() {}*

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟)

【每日一题】LeetCode 2181.合并零之间的节点(链表、模拟) 题目描述 给定一个链表,链表中的每个节点代表一个整数。链表中的整数由 0 分隔开,表示不同的区间。链表的开始和结束节点的值都为 0。任务是将每两个相邻的 0 之间的所有节点合并成一个节点,新节点的值为原区间内所有节点值的和。合并后,需要移除所有的 0,并返回修改后的链表头节点。 思路分析 初始化:创建一个虚拟头节点

MiniGPT-3D, 首个高效的3D点云大语言模型,仅需一张RTX3090显卡,训练一天时间,已开源

项目主页:https://tangyuan96.github.io/minigpt_3d_project_page/ 代码:https://github.com/TangYuan96/MiniGPT-3D 论文:https://arxiv.org/pdf/2405.01413 MiniGPT-3D在多个任务上取得了SoTA,被ACM MM2024接收,只拥有47.8M的可训练参数,在一张RTX

利用matlab bar函数绘制较为复杂的柱状图,并在图中进行适当标注

示例代码和结果如下:小疑问:如何自动选择合适的坐标位置对柱状图的数值大小进行标注?😂 clear; close all;x = 1:3;aa=[28.6321521955954 26.2453660695847 21.69102348512086.93747104431360 6.25442246899816 3.342835958564245.51365061796319 4.87