写在前面的话
本文是对nnUNet如何在2D数据上进行训练以及预测,是对几篇博文参考之后的总结,由于本人对nnUNet使用也是刚开始不久,所以对于nnUNet的理解肯定还停留在表层,所以原理上的东西就不多介绍了,更侧重于介绍其在2D数据上的使用流程。官方也有nnUNet训练2D数据的例子,一切以官方为准,本文仅作参考。
本人的系统环境为:pytorch为:1.10.2。
一、 nnUNet简介
nnUNet是一种基于2D and 3D 原始U-Nets 自适应框架,该框架能根据给定数据集的属性自动调整所有超参数,整个过程无需人工干预。仅仅依赖于朴素的U-Net结构(就是原始U-Net)和鲁棒的训练方案,nnU-Net在六个得到公认的分割挑战中实现了最先进的性能。
github仓库:https://github.com/MIC-DKFZ/nnUNet
paper:《Automated Design of Deep Learning Methods for Biomedical Image Segmentation》
nnUNet是德国癌症研究中心的工程师编写的框架,迄今为止依旧在维护和更新。虽然整个过程实现自动化,但是对GPU资源要求比较高,虽然官方说For training nnU-Net models the GPU should have at least 10 GB,但是实测后发现,如果按默认配置,需要19G显存。如果显存不足可以考虑降低batch size大小。
二、nnUNet安装
由于本人避免python环境冲突,用conda创建一个虚拟环境nnUNet,并激活虚拟环境
conda create -n nnUNet python=3.8
source activate nnUnet
由于是面向初学者的使用教程,初学者请务必按照我的做法,等你熟练掌握以后再考虑新的姿势(有些文件夹的创建时多余的,但是你还是跟着我这样做最好)。
第一步:在某个目录下(比如说/data)创建nnUNetFrame目录,在nnUNet的终端环境下cd到这个nnUNetFrame目录,然后git clone https://github.com/MIC-DKFZ/nnUNet.git
备注:如果第一步克隆就出问题的,直接去github上下载ZIP压缩包是一样的。
第二步:cd nnUNet
第三步:pip install -e .(兄弟们和集美们别忘了加 . )(备注:安装之后默认会安装pytorch1.11以上的版本,但是我实际操作发现会报与cuda错误的OSError,所以我卸载了后重新安装的1.10.2+cuda)
当你安装完成这些以后,你的每一次对nnUNet的操作,都会在命令行里以nnUNet_开头,代表着你的nnUNet开始工作的指令。
接下来是创建数据保存目录。
进入你之前创建的nnUNetFrame文件夹里面,创建一个名为DATASET的文件夹,现在你的nnUNetFrame文件夹下有两个文件夹,nnUNet是代码源,另一个DATASET就是我们接下来用来放数据的地方;
进入创建好的DATASET文件夹下面,创建下面三个文件夹分别是nnUNet_preprocessed、nnUNet_raw、nnUNet_trained_models
第一个用来存放原始数据预处理之后的数据,第二个用来存放原始的你要训练的数据,第三个用来存放训练的结果。
进入上面第二个文件夹nnUNet_raw,创建下面两个文件夹,分别是nnUNet_cropped_data、nnUNet_cropped_data,右边为原始数据,左边为crop以后的数据。
进入右边文件夹nnUNet_raw_data,创建一个名为Task100_BloodVessel的文件夹(解释:这nnUnet的数据格式是固定的,Task001_BloodVessel由Task+ID+数据名组成,你可以对这个任务的数字ID进行任意的命名,比如你要分割心脏,你可以起名为Task110_Heart,比如你要分割肾脏,你可以起名为Task112_Kidney,前提是必须按照这种格式)
三、 数据准备
3.1 2D数据转3D数据
假定我们的数据长这样,图片和mask分别存放在img和mask的文件夹下,两者同名。
由于这是2D图片数据,要将其转为3D数据,其实就是z轴为1的3维数据。
所以随意新建一个项目,创建一个2DDataProcessTo3D.py脚本
备注:如果你的数据集没有提前划分训练和测试,用以下代码即可:
import os
import random
from tqdm import tqdm
import SimpleITK as sitk
import cv2
import numpy as np
def SplitDataset(img_path, train_percent=0.9):
data = os.listdir(img_path)
train_images = []
test_images = []
num = len(data)
train_num = int(num * train_percent)
indexes = list(range(num))
train = random.sample(indexes, train_num)
for i in indexes:
if i in train:
train_images.append(data[i])
else:
test_images.append(data[i])
return train_images, test_images
def conver(img_path, save_dir, mask_path=None, select_condition=None, mode="trian"):
os.makedirs(save_dir, exist_ok=True)
if mode == "train":
savepath_img = os.path.join(save_dir, 'imagesTr')
savepath_mask = os.path.join(save_dir, 'labelsTr')
elif mode == "test":
savepath_img = os.path.join(save_dir, 'imagesTs')
savepath_mask = os.path.join(save_dir, 'labelsTs')
os.makedirs(savepath_img, exist_ok=True)
if mask_path is not None:
os.makedirs(savepath_mask, exist_ok=True)
ImgList = os.listdir(img_path)
with tqdm(ImgList, desc="conver") as pbar:
for name in pbar:
if select_condition is not None and name not in select_condition:
continue
Img = cv2.imread(os.path.join(img_path, name))
if mask_path is not None:
Mask = cv2.imread(os.path.join(mask_path, name), 0)
Mask = (Mask / 255).astype(np.uint8)
if Img.shape[:2] != Mask.shape:
Mask = cv2.resize(Mask, (Img.shape[1], Img.shape[0]))
Img_Transposed = np.transpose(Img, (2, 0, 1))
Img_0 = Img_Transposed[0].reshape(1, Img_Transposed[0].shape[