冯嘉骏,毕业于广州医科大学医学影像技术专业,就职于广州市第一人民医院南沙医院。
项目背景
放射科医生下诊断之前,都需要花费不少时间在医学影像图像上,测量各种数据指标,从而更好地判断患者的病情。这篇文章主要介绍PaddleSeg 2.0在X线胸片上的应用,即如何使用PaddleSeg 2.0快速测量心胸比。
心胸比指的是X线胸片上心脏的横径(左、右心缘至体中线的最大距离之和)与胸廓横径(通过右膈肌顶水平胸廓内径)的比例(如图1),这是确定心脏是否有增大的最简方法。正常值女性约为0.45,男性约为0.43,大于0.5即被认为有心脏扩大的可能。
图1
目前,心胸比需要通过医生手动测量和计算得出。但是人们可以使用飞桨PaddleSeg 2.0开发工具,实现自动快速测量心胸比。
PaddleSeg是飞桨图像分割套件。2020年12月18日,PaddleSeg发布了2.0.0rc的动态图版本。通过模块化的设计,其提供了配置化驱动和API调用等两种方式,可以更便捷地完成训练到部署的开发流程。
此项目使用PaddleSeg封装的Unet网络结构,分割胸片上的肺部和心脏。最终,肺部和心脏分割结果分别达到了0.978和0.955的准确率。然后,我们将通过OpenCV分别获取肺部和心脏mask外界矩,并进行计算得到心胸比。
项目在AI Studio上已经公开,并提供了数据集在内的完整路径。Fork之后可以直接运行,相关链接为:
https://aistudio.baidu.com/aistudio/projectdetail/1438719
https://github.com/paddlepaddle/paddleseg
最终效果如图2:
图2
网络结构介绍
此项目使用PaddleSeg 的API接口,进行数据增强、Loss、和训练和推理,本文主要针对以上步骤进行介绍。
现在,我们将简单介绍项目中用到的网络结构Unet(如图3)。由于人体内部结构相对固定,生成的医学图像内容分布相对规律,加上医学图像边界模糊,且数据集一般都比较小,对分割结果还要求准确,使用网络结构Unet是个不错的选择。
网络结构Unet是结合了低分辨率和高分辨率信息的分割网络,在医学图像分割中表现出很好的性能。PaddleSeg 2.0已经封装Unet和高质量预训练模型,提供API接口(paddleseg.models),调用并设置类别数量即可。
图3
from paddleseg.models import UNet
pretrained = '/home/aistudio/work/model.pdparams'
model = UNet(num_classes=3,pretrained=pretrained)
数据处理和增强
我们从公开SIIM气胸数据集中选择101张胸片,图片格式为“jpg”,作为此次项目的数据集。我们需要对图像进行再次标注肺部和心脏,因为标注的结果文件是nii格式文件,ct值0代表背景,2值代表是肺部,3值代表是心脏。使用nibabel读取并转换成“jpg”格式。由于数据集数据不多,容易过拟合。针对这个此问题,我们将采用数据增强策略。
结合实际情况,疾病可能会导致获取的胸片肺部部分出现白色致密度影,同时,拍摄角度旋转、对比度和明亮度的差异,也会影响胸片呈现。因此,我们采用随机水平翻转、小角度随机旋转、随机模糊、随机对比度、明亮度等策略。通过PaddleSeg的数据增强API(paddleseg.transform),可以快速实现。
import paddleseg.transforms as Ttrain_transforms = [
T.RandomHorizontalFlip(),
T.RandomDistort(),
T.RandomRotation(max_rotation = 10,im_padding_value =(0,0,0),label_padding_value = 0),
T.RandomBlur(),
T.RandomScaleAspect(min_scale = 0.8, aspect_ratio = 0.5),
T.Resize(target_size=(512, 512)),
T.Normalize()
]
图4
训练之前,需要通过API(paddleseg.datasets)快速构建可迭代的训练数据装载器和验证集/测试集数据装载器。只需要传入包含origin和label文件路径的train.txt文件、数据增强策略、分类类别数量和数据装载器类型,即可构建高效的数据装载器。
from paddleseg.datasets import Dataset
train_dataset = Dataset(transforms = train_transforms,
dataset_root = dataset_root,
num_classes = num_classes,
train_path = train_path,
mode = 'train')
开始训练前,还需要定义损失函数,PaddleSeg提供多种损失函数的选择。只需要通过API(paddleseg.models.losses)调用就能实现。在这里,我们简单对比了单独使用交叉熵和使用“交叉熵+Dice”的效果,发现使用“交叉熵+Dice”时,IOU提升约1到2点。
from paddleseg.models.losses import CrossEntropyLoss,DiceLoss
losses = {}
losses['types'] = [CrossEntropyLoss(),DiceLoss()]
losses['coef'] = [1,1]
模型训练
PaddleSeg动态图版本开始训练模型只需要一行代码,调用API(paddleseg.core.train)即可开始训练,训练期间还可以通过VisualDL可视化训练期间的Loss、ACC、IOU等结果。比起静态图组网,此种方式大幅度降低了代码量。
train(
model=model,
train_dataset=train_dataset,
val_dataset=val_dataset,
optimizer=optimizer,
save_dir='output',
iters=10000,
batch_size=4,
save_interval=200,
log_iters=10,
num_workers=0,
losses=losses,
use_vdl=True)
而后,我们通过VisualDL进行可视化,得到图5:
图5
推理预测并计算心胸比
PaddleSeg动态图版本和PaddlePaddle动态图版本,能无缝连接。我们可以调用PaddleSeg的API接口(paddleseg.core.predict)进行推理。同样地,我们也可以自定义推理接口,只需要导入PaddleSeg模型,加载最优模型训练结果,设置模型为eval模式,传入需要预测的胸片数据,即可进行推理。
def predict(model, model_path, im_path):
#预测
para_state_dict = paddle.load(model_path)
model.set_dict(para_state_dict)
model.eval()
im = cv2.imread(im_path)
im, _ = transforms(im)
im = im[np.newaxis, ...]
im = paddle.to_tensor(im)
output = model(im)[0]
output = output.numpy()
output = np.argmax(output,axis=1)
output = output.transpose(1,2,0)
return output
当我们得到模型的推理结果后,通过OpenCV分别获取肺部和心脏的mask结果的最左点、最右点、外边界矩的Width,再找出右膈肌顶的位置,即可计算出此胸部的心胸比,具体实现代码这里不做详细展示,读者可以访问AI Studio项目查看。
#画出肺部的最大横径 和心脏的横径
cv2.line(image,left_heart,(left_heart[0] + int(WidthHeart/2),left_heart[1]),(0,255,0),2)
cv2.line(image,right_heart,(right_heart[0] - int(WidthHeart/2),right_heart[1]),(0,255,0),2)
cv2.line(image,left_chest,(left_chest[0] + WidthChest,left_chest[1]),(0,0,255),2)
总结
通过使用对比改版前的PaddleSeg和如今PaddleSeg动态图版本,大家可以明显感觉代码量的减少,且飞桨越来越Pythonic了。这样的改变可以让开发者更多关注数据本身和开发项目的实际用途。比如,在医疗临床方面上,各种繁琐和重复的工作占用了医生的大部分时间。
欢迎扫码关注: