nnUNet V2代码——生成dataset_fingerprint.json

前文请见nnUNetv2_plan_and_preprocess命令

阅读nnUNet\nnunetv2\experiment_planning\dataset_fingerprint\fingerprint_extractor.py

文件内只有一个DatasetFingerprintExtractor类,阅读它

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

一. __init__函数

1. 参数

  • dataset_name_or_id:数据集名称或ID
  • num_processes:进程数
  • verbose:是否打印详细信息

2. 过程

定义一些变量,部分如下:

  • dataset_name:数据集名称,例如Dataset001_QQQQ
  • dataset_json:数据集相关信息,是用户创建的,例如:Dataset001_QQQQ/dataset_json
  • dataset:字典类型,{“样例1” : { “images” : {原始图像文件路径列表}, “label” : {分割掩码文件路径列表} }}
  • num_foreground_voxels_for_intensitystats:数据集一共会采样的前景像素数量,用于统计,后续有介绍

二.run函数

1. 参数

2. 过程

获取nnUNet_preprocessed文件夹路径,再创建nnUNet_preprocessed文件夹,最后获取dataset_fingerprint.json文件路径。代码较为清晰,不做粘贴

由上述dataset_fingerprint.json文件路径判断其是否已经存在选择覆写,则载入现有文件并返回。代码较为清晰,不做粘贴

如果fingerprint文件不存在 或 选择覆写,则:

首先获取本次训练使用数据集的读取和写入器(由determine_reader_writer_from_dataset_json函数函数获取,实质是查表,见本文最后)

接下来计算每个样例的前景体素****采样次数,再利用多进程运行analyze_case函数,快速处理数据集多个样例。多进程代码不做粘贴。

从此处继续,results列表存储了每一例数据调用analyze_case函数返回的结果:

results = [i.get()[0] for i in r]

从results列表内提取相关变量,依据这些变量,计算数据集的均值、最值等信息,存入fingerprint变量并写入dataset_fingerprint.json文件

三.analyze_case函数

1. 参数

  • image_files:image
  • segmentation_file:seg
  • reader_writer_class:样例文件读取和写入器
  • num_samples:前景体素采样次数

2. 过程

本函数统计图像大小、体素间距等信息

读取image和seg文件,按照image非零区域对image和seg裁剪,获得以下三个变量:

data_cropped, seg_cropped, bbox = crop_to_nonzero(images, segmentation)

接下来使用collect_foreground_intensities函数收集裁剪后image的统计信息

这些信息包括:

meanmedianminmaxpercentile_99_5percentile_00_5
均值中位数最小值最大值第99.5百分位数第0.5百分位数
foreground_intensities_per_channel, foreground_intensity_stats_per_channel = \
            DatasetFingerprintExtractor.collect_foreground_intensities(seg_cropped, data_cropped,
                                                                       num_samples=num_samples)

获取image体素间距(spacing)、裁剪前后的image大小(shape_before_crop和shape_after_crop)以及裁剪比例(relative_size_after_cropping):

spacing = properties_images['spacing']

shape_before_crop = images.shape[1:]
shape_after_crop = data_cropped.shape[1:]
relative_size_after_cropping = np.prod(shape_after_crop) / np.prod(shape_before_crop)

最后返回上述相关信息:

四.collect_foreground_intensities函数

1. 参数

  • segmentation:seg
  • images:image
  • seed:随机采样种子
  • num_samples:采样数

2. 过程

此函数统计image中seg前景区域内均值、最值等信息

首先进行断言和创建随机数生成器,随机数生成器用于随机采样,代码较为清晰,不做粘贴。

初始化intensities_per_channel和intensity_statistics_per_channel列表,二者分别用于记录随机采取的image前景像素值和image所有采样像素值的均值、最值等信息;去除seg中的空维度,并获取seg中非背景区域,存入foreground_mask变量;定义用于统计百分位数的数组,部分代码没有粘贴:

foreground_mask = segmentation[0] > 0

和去除掩码图的空维度类似,对图像的第一个维度遍历(我使用的数据集是ACDC数据集,此处的image第一个维度和seg第一个维度一样,都是空维度)。for循环开始:

images[i]的shape是channel * height * width;获取images[i]中在seg前景区域内的像素;再利用rs随机采样器对这些像素随机、有放回的采样;

intensities_per_channel.append(
        rs.choice(foreground_pixels, num_samples, replace=True) if num_fg > 0 else [])

由于有些样例的seg并没有前景区域(全是背景,没有前景),所以作者默认均值最值等信息是nan,如果含有前景区域则统计信息并存入intensity_statistics_per_channel变量;

mean, median, mini, maxi, percentile_99_5, percentile_00_5 = np.nan, np.nan, np.nan, np.nan, np.nan, np.nan

其余代码较为清晰,不做粘贴

for循环结束

最后返回上述计算的数据

五.涉及的函数

1. get_filenames_of_train_images_and_targets函数

a. 参数

  • raw_dataset_folder:nnUNet_raw下的数据集文件夹,例如nnunet_raw/Dataset001_QQQQ
  • dataset_json:用户创建的dataset_json

b. 过程

首先是确保成功读取dataset.json文件

如果dataset.json文件中存在dataset键值,则规范化用户定义的文件路径

否则根据raw_dataset_folder文件夹获取正确的数据集内各个样例的文件路径。

最后将数据集各样例标识符(区分样例和样例的)、image文件路径、seg文件路径打包成字典并返回

if 'dataset' in dataset_json.keys():
    dataset = dataset_json['dataset']
    for k in dataset.keys():
        dataset[k]['label'] = os.path.abspath(join(raw_dataset_folder, dataset[k]['label'])) if not os.path.isabs(dataset[k]['label']) else dataset[k]['label']
        dataset[k]['images'] = [os.path.abspath(join(raw_dataset_folder, i)) if not os.path.isabs(i) else i for i in dataset[k]['images']]
else:
    identifiers = get_identifiers_from_splitted_dataset_folder(join(raw_dataset_folder, 'imagesTr'), dataset_json['file_ending'])
    images = create_lists_from_splitted_dataset_folder(join(raw_dataset_folder, 'imagesTr'), dataset_json['file_ending'], identifiers)
    segs = [join(raw_dataset_folder, 'labelsTr', i + dataset_json['file_ending']) for i in identifiers]
    dataset = {i: {'images': im, 'label': se} for i, im, se in zip(identifiers, images, segs)}

2. get_identifiers_from_splitted_dataset_folder函数

a. 参数

  • folder:待读取的文件夹
  • file_ending:样例文件的拓展名

b. 过程

读取文件夹下的所有文件

去除文件名的拓展位和**_XXXX**,例如:QQQQQ001_0000.nii.gz --> QQQQQ001,这样就获取了每个样例的唯一标识名

去重后返回标识名列表

def get_identifiers_from_splitted_dataset_folder(folder: str, file_ending: str):
    files = subfiles(folder, suffix=file_ending, join=False)
    # all files have a 4 digit channel index (_XXXX)
    crop = len(file_ending) + 5
    files = [i[:-crop] for i in files]
    # only unique image ids
    files = np.unique(files)
    return files

3. create_lists_from_splitted_dataset_folder函数

a. 参数

b. 过程

确保identifiers是folder下所有文件的标识名列表

获取folder下所有文件名称,存入files变量

遍历所有标识名,如果符合“标识名 _XXXX.拓展名”的格式,就将其文件路径加入list_of_lists列表

遍历结束返回list_of_lists列表

def create_lists_from_splitted_dataset_folder(folder: str, file_ending: str, identifiers: List[str] = None) -> List[
    List[str]]:
    """
    does not rely on dataset.json
    """
    if identifiers is None:
        identifiers = get_identifiers_from_splitted_dataset_folder(folder, file_ending)
    files = subfiles(folder, suffix=file_ending, join=False, sort=True)
    list_of_lists = []
    for f in identifiers:
        p = re.compile(re.escape(f) + r"_\d\d\d\d" + re.escape(file_ending))
        list_of_lists.append([join(folder, i) for i in files if p.fullmatch(i)])
    return list_of_lists

4. determine_reader_writer_from_dataset_json函数

a. 参数

  • dataset_json_content:内容是dataset.json
  • example_file:数据集的一个数据
  • allow_nonmatching_filename:是否允许不匹配的文件名
  • verbose:是否输出详细信息

b. 过程

判断“overwrite_image_reader_writer”是否在dataset.json文件内设置,就是用户是否指定了文件读写器

如果设置且不为None,则按名称寻找

否则调用determine_reader_writer_from_file_ending函数,按照文件拓展名自动寻找读取器和写入器(实际是查表)

def determine_reader_writer_from_dataset_json(dataset_json_content: dict, example_file: str = None,
                                              allow_nonmatching_filename: bool = False, verbose: bool = True
                                              ) -> Type[BaseReaderWriter]:
    if 'overwrite_image_reader_writer' in dataset_json_content.keys() and \
            dataset_json_content['overwrite_image_reader_writer'] != 'None':
        ioclass_name = dataset_json_content['overwrite_image_reader_writer']
        # trying to find that class in the nnunetv2.imageio module
        try:
            ret = recursive_find_reader_writer_by_name(ioclass_name)
            if verbose: print(f'Using {ret} reader/writer')
            return ret
        except RuntimeError:
            # 略
    return determine_reader_writer_from_file_ending(dataset_json_content['file_ending'], example_file,
                                                    allow_nonmatching_filename, verbose)

5. determine_reader_writer_from_file_ending函数

a. 参数

  • 同determine_reader_writer_from_dataset_json函数

b. 过程

实际是查表,代码不做粘贴

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

开栈

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值