nnUNet V2代码——生成nnUNetPlans.json(三)

前文请见nnUNetv2_plan_and_preprocess命令

阅读nnUNet\nnunetv2\experiment_planning\experiment_planners\default_experiment_planner.py

文件包含一个ExperimentPlanner类和一个_maybe_copy_splits_file函数。

在ExperimentPlanner函数内涉及的其他函数都在文章后半部分说明。

生成nnUNetPlans.json文件共三篇:

nnUNet V2代码——生成nnUNetPlans.json(一)

nnUNet V2代码——生成nnUNetPlans.json(二)

nnUNet V2代码——生成nnUNetPlans.json(三)

plan_experiment函数

参数

无实际参数

过程

本函数主要配置预处理、训练、后处理过程中用到的常量数据、处理函数、网络结构等信息,并将其保存为nnUNetPlans.json文件。

创建一个缓存字典(_tmp变量);再获取前向转置和后向转置(由determine_transpose函数获得);之后获取全分辨率的体素间距(由determine_fullres_target_spacing获得)并前向转置它;代码较为清晰,不做粘贴。

接下来依据dataset_fingerprint.json文件(存储在self.dataset_fingerprint变量)内的spacings和shapes_after_crop以及上文得到的全分辨率体素间距(fullres_spacing)三个变量计算数据集内图像的新形状;计算出新shape后,找到其三个维度的中值,并将其前向转置得到前向转置的、合理优化的、中位数的数据集image和seg的shape,存入new_median_shape_transposed变量,这只是参考值,后续代码有优化。

new_shapes = [compute_new_shape(j, i, fullres_spacing) for i, j in
                zip(self.dataset_fingerprint['spacings'], self.dataset_fingerprint['shapes_after_crop'])]
new_median_shape = np.median(new_shapes, 0)
new_median_shape_transposed = new_median_shape[transpose_forward]

其中,compute_new_shape函数计算方式为 (shapes_after_crop * spacings) / fullres_spacing。

接下来根据刚刚得到的shape(new_median_shape_transposed变量)和训练集样例数量计算(估算)出整个训练集的image体素数量:

approximate_n_voxels_dataset = float(np.prod(new_median_shape_transposed, dtype=np.float64) *
                               self.dataset_json['numTraining'])

前菜准备完毕,开始配置2d3d_fullres3d_lowres

本函数针对2d3d_fullres3d_lowres三种情况提供了相应的训练配置,配置详见get_plans_for_configuration函数。如果只有2d则不配置另外两个3d:

if new_median_shape_transposed[0] != 1:	## 3d情况
	# 省略**3d_fullres**和**3d_lowres**代码 !!!!!
else:
    plan_3d_fullres = None
    plan_3d_lowres = None

首先是3d_fullres3d_lowres,二者是一起的,先配置3d_fullres,后根据3d_fullres配置3d_lowres

此处会涉及到3D级联(3D U-Net Cascade),做个说明,便于理解后面配置3d_lowres,见下图请添加图片描述
📌📌在用户使用3D级联训练网络时,nnUNet V2会生成两个U-Net第一个U-Net输入输出图像是原有数据样例下采样后得到的,根据这个下采样的数据样例(分辨率变低了)大小配置第一个U-Net结构(配置过程详见get_plans_for_configuration函数)。对于第一个U-Net输出的seg图像,nnUNet V2会将其上采样至原有大小,再将上采样后的seg图像按类别(通道)进行one-hot编码后,作为第二个U-Net的数据样例。上图stage 2中的输入输出图像大小、网络结构和stage 1的很像,这并不是因为下采样,而是使用了patch训练的缘故。

说明完毕

3d_fullres的配置简单、直接、一次成型:调用get_plans_for_configuration函数获取对应配置。👍👍:

plan_3d_fullres = self.get_plans_for_configuration(fullres_spacing_transposed,
                                                    new_median_shape_transposed,
                                                    self.generate_data_identifier('3d_fullres'),
                                                    approximate_n_voxels_dataset, _tmp)

配置3d_fullres后,会试着配置3d_lowres,如果不满足要求(即本次数据集不能使用3D级联),就不再配置本数据集的3d_lowres。💔💔

如何判断不能使用3D级联呢?结合上面的3D级联来说明,对于stage 1阶段下采样后的数据样例,如果它的大小 * 2后小于3d_fullres的数据样例大小,就使用,否则,不使用。一句话,二者图像大小差别大就使用,差别小,用了没效果,就不使用。

开始试着配置3d_lowres:获得3d_fullres配置下的patch size,以及上文计算出的shape(new_median_shape_transposed变量,原有数据样例大小的中位数),由这两个变量分别计算一个patch内的体素数量(num_voxels_in_patch变量)和一个样例内的体素数量(median_num_voxels)。接着初始化plan_3d_lowres为None,同时复制一份3d_fullres的体素间距给3d_lowres,再设置间距增加因子(spacing_increase_factor 变量),之前是1.01(见注释),现在是1.03:

patch_size_fullres = plan_3d_fullres['patch_size']
median_num_voxels = np.prod(new_median_shape_transposed, dtype=np.float64)
num_voxels_in_patch = np.prod(patch_size_fullres, dtype=np.float64)

plan_3d_lowres = None
lowres_spacing = deepcopy(plan_3d_fullres['spacing'])spacing_increase_factor = 1.03  # used to be 1.01 but that is slow with new GPU memory estimation!            

接下来试着增加3d_lowres配置下的spacing(此时3d_lowres的spacing和3d_fullres一样):

创建while循环,循环条件是一个patch中的体素数量与一个样例内的体素数量的比值小于设置的阈值(self.lowres_creation_threshold变量,值为0.25)。结束时说明3D级联的stage 1的U-Net的patch size与它的样例大小保持在阈值之内,不让patch size 太小。既要让stage 1的U-Net的样例大小小于原有样例大小,又要让stage 1的U-Net的patch size不能太小,二者缺一不可,共同决定是否能使用3D级联

while循环内代码先获取当前 3d_lowres体素间距(lowres_spacing变量)的最大值,如果该轴间距与其他间距比值大于2,则让其他轴的间距乘间距增加因子,否则所有轴间距都乘间距增加因子。

再基于最新的3d_lowres体素间距、之间获取的3d_fullres体素间距和样例大小的中位数,计算新的、3d_lowres样例内的体素数量(median_num_voxels变量):

max_spacing = max(lowres_spacing)
if np.any((max_spacing / lowres_spacing) > 2):
    lowres_spacing[(max_spacing / lowres_spacing) > 2] *= spacing_increase_factor
else:
    lowres_spacing *= spacing_increase_factor
median_num_voxels = np.prod(plan_3d_fullres['spacing'] / lowres_spacing * new_median_shape_transposed,
                            dtype=np.float64)

接着通过 self.get_plans_for_configuration 函数获取3d_lowres配置,并根据新的配置计算3d_lowres配置的patch内体素数量(num_voxels_in_patch变量):

plan_3d_lowres = self.get_plans_for_configuration(lowres_spacing,
                                                    tuple([round(i) for i in plan_3d_fullres['spacing'] /
                                                            lowres_spacing * new_median_shape_transposed]),
                                                    self.generate_data_identifier('3d_lowres'),
                                                    float(np.prod(median_num_voxels) *
                                                        self.dataset_json['numTraining']), _tmp)
num_voxels_in_patch = np.prod(plan_3d_lowres['patch_size'], dtype=np.int64)

while循环结束

如果3d_fullres样例内体素数量与3d_lowres样例内体素数量比值小于2,则丢弃3d_lowres配置,并打印相应信息,设置plan_3d_lowres为None,代码较为清晰,不做粘贴。

接着依据3d_lowres配置是否为None,设置训练时损失函数是否使用batch_dice(batch_dice在训练阶段的损失函数处会介绍):

if plan_3d_lowres is not None:
    plan_3d_lowres['batch_dice'] = False
    plan_3d_fullres['batch_dice'] = True
else:
    plan_3d_fullres['batch_dice'] = False

处理完3d_fullres3d_lowres配置后,开始处理2d配置:通过 self.get_plans_for_configuration 函数获取2d配置,并设置batch_dice为True,简单、清晰、一步到位👍👍:

plan_2d = self.get_plans_for_configuration(fullres_spacing_transposed[1:],
                                            new_median_shape_transposed[1:],
                                            self.generate_data_identifier('2d'), approximate_n_voxels_dataset,
                                            _tmp)

接下来获取原始体素间距的中位值以及image前景区域大小的中位值,并对二者前向转置,后续存入nnUNetPlans.json文件中;再将用户设置的dataset.json文件复制到nnUNet_preprocessed对应数据集文件夹下;再将上述获取的所有配置及其他信息存入plan字典中,将其保存为nnUNet_preprocessed对应数据集文件夹下的nnUNetPlans.json。

接下来如果有3d_fullres3d_lowres配置,就在plan字典中加入对应配置:

if plan_3d_lowres is not None:
    plans['configurations']['3d_lowres'] = plan_3d_lowres
    if plan_3d_fullres is not None:
        plans['configurations']['3d_lowres']['next_stage'] = '3d_cascade_fullres'
    print('3D lowres U-Net configuration:')
    print(plan_3d_lowres)
    print()
if plan_3d_fullres is not None:
    plans['configurations']['3d_fullres'] = plan_3d_fullres
    print('3D fullres U-Net configuration:')
    print(plan_3d_fullres)
    print()
    if plan_3d_lowres is not None:
        plans['configurations']['3d_cascade_fullres'] = {
            'inherits_from': '3d_fullres',
            'previous_stage': '3d_lowres'
        }

📌📌 3d_lowres是3D级联所用,必须搭配3d_cascade_fullres一起,3d_cascade_fullres配置在使用时会照搬3d_fullres配置,但nnUNet V2为了区分当前训练是只有3d_fullres还是3D级联下的“3d_fullres”,加入3d_cascade_fullres字段。

继承(inherits)这个设置还能让用户自定义配置,继承自谁,额外注意,这里的继承是父类覆盖子类,与python的子类覆盖父类不一样。

最后,保存plan字典,并返回它:

self.plans = plans
self.save_plans(plans)
return plans
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值