胸部X光模型

在本教程中,我们将构建一个分类器,用于区分患有气胸的胸部 X 光片和未患气胸的胸部 X 光片。图像数据直接从 DICOM 源文件加载,因此无需事先进行 DICOM 数据处理。本教程还将介绍什么是 DICOM 图像,并高层次回顾如何评估分类器的结果。

要使用 fastai.medical.imaging,您需要

conda install pyarrow
pip install pydicom kornia opencv-python scikit-image

要在 Google Colab 上运行本教程,您需要取消注释以下两行并运行单元格

#!conda install pyarrow
#!pip install pydicom kornia opencv-python scikit-image nbdev
from fastai.basics import *
from fastai.callback.all import *
from fastai.vision.all import *
from fastai.medical.imaging import *

import pydicom

import pandas as pd

下载并导入 X 光 DICOM 文件

首先,我们将使用 untar_data 函数下载包含 SIIM-ACR 气胸分割 [1] 数据集子集(250 个 DICOM 文件,约 30MB)的 siim_small 文件夹。下载的 siim_small 文件夹将存储在您的 ~/.fastai/data/ 目录下。一旦下载完成,变量 pneumothorax-source 将存储 siim_small 文件夹的绝对路径。

pneumothorax_source = untar_data(URLs.SIIM_SMALL)

siim_small 文件夹具有以下目录/文件结构

siim_folder_structure.jpg

什么是 DICOM?

DICOMDigital Imaging and COmmunications in Medicine,医学数字成像和通信)是事实上的标准,它建立了规则,允许来自不同供应商的成像设备、计算机和医院之间交换医学图像(X 射线、MRI、CT)及相关信息。DICOM 格式提供了一种适当的方式,符合健康信息交换 (HIE) 标准,用于在设施之间传输健康相关数据,并符合 HL7 标准,后者是使临床应用程序能够交换数据的消息传递标准

DICOM 文件通常具有 .dcm 扩展名,并提供了一种将数据存储在单独的“标签”中的方式,例如患者信息以及图像/像素数据。一个 DICOM 文件包含一个头文件和图像数据集,打包在一个文件中。通过从这些标签中提取数据,可以访问关于患者人口统计信息、研究参数等重要信息。

16 位 DICOM 图像的值范围为 -3276832768,而 8 位灰度图像存储 0255 的值。DICOM 图像中的值范围非常有用,因为它们与 Hounsfield Scale(亨氏单位)相关,亨氏单位是描述放射密度的定量标尺

绘制 DICOM 数据

为了分析我们的数据集,我们使用 get_dicom_files 函数加载 DICOM 文件的路径。调用函数时,我们将 train/ 附加到 pneumothorax_source 路径,以选择 DICOM 文件所在的文件夹。我们将每个 DICOM 文件的路径存储在 items 列表中。

items = get_dicom_files(pneumothorax_source/f"train/")

接下来,我们使用 RandomSplitter 函数将 items 列表分割为训练集 trn 和验证集 val 列表

trn,val = RandomSplitter()(items)

Pydicom 是一个用于解析 DICOM 文件的 Python 包,它使得访问 DICOM 的 header 以及将原始 pixel_data 转换为便于操作的 Python 结构变得更容易。fastai.medical.imaging 使用 pydicom.dcmread 来加载 DICOM 文件。

要绘制 X 射线图像,我们可以选择 items 列表中的一个条目,并使用 dcmread 加载 DICOM 文件。

patient = 7
xray_sample = items[patient].dcmread()

要查看 header

xray_sample
Dataset.file_meta -------------------------------
(0002, 0000) File Meta Information Group Length  UL: 200
(0002, 0001) File Meta Information Version       OB: b'\x00\x01'
(0002, 0002) Media Storage SOP Class UID         UI: Secondary Capture Image Storage
(0002, 0003) Media Storage SOP Instance UID      UI: 1.2.276.0.7230010.3.1.4.8323329.3297.1517875177.149805
(0002, 0010) Transfer Syntax UID                 UI: JPEG Baseline (Process 1)
(0002, 0012) Implementation Class UID            UI: 1.2.276.0.7230010.3.0.3.6.0
(0002, 0013) Implementation Version Name         SH: 'OFFIS_DCMTK_360'
-------------------------------------------------
(0008, 0005) Specific Character Set              CS: 'ISO_IR 100'
(0008, 0016) SOP Class UID                       UI: Secondary Capture Image Storage
(0008, 0018) SOP Instance UID                    UI: 1.2.276.0.7230010.3.1.4.8323329.3297.1517875177.149805
(0008, 0020) Study Date                          DA: '19010101'
(0008, 0030) Study Time                          TM: '000000.00'
(0008, 0050) Accession Number                    SH: ''
(0008, 0060) Modality                            CS: 'CR'
(0008, 0064) Conversion Type                     CS: 'WSD'
(0008, 0090) Referring Physician's Name          PN: ''
(0008, 103e) Series Description                  LO: 'view: PA'
(0010, 0010) Patient's Name                      PN: '6633c659-9249-443e-9851-b83782d1b111'
(0010, 0020) Patient ID                          LO: '6633c659-9249-443e-9851-b83782d1b111'
(0010, 0030) Patient's Birth Date                DA: ''
(0010, 0040) Patient's Sex                       CS: 'M'
(0010, 1010) Patient's Age                       AS: '21'
(0018, 0015) Body Part Examined                  CS: 'CHEST'
(0018, 5101) View Position                       CS: 'PA'
(0020, 000d) Study Instance UID                  UI: 1.2.276.0.7230010.3.1.2.8323329.3297.1517875177.149804
(0020, 000e) Series Instance UID                 UI: 1.2.276.0.7230010.3.1.3.8323329.3297.1517875177.149803
(0020, 0010) Study ID                            SH: ''
(0020, 0011) Series Number                       IS: "1"
(0020, 0013) Instance Number                     IS: "1"
(0020, 0020) Patient Orientation                 CS: ''
(0028, 0002) Samples per Pixel                   US: 1
(0028, 0004) Photometric Interpretation          CS: 'MONOCHROME2'
(0028, 0010) Rows                                US: 1024
(0028, 0011) Columns                             US: 1024
(0028, 0030) Pixel Spacing                       DS: [0.14300000000000002, 0.14300000000000002]
(0028, 0100) Bits Allocated                      US: 8
(0028, 0101) Bits Stored                         US: 8
(0028, 0102) High Bit                            US: 7
(0028, 0103) Pixel Representation                US: 0
(0028, 2110) Lossy Image Compression             CS: '01'
(0028, 2114) Lossy Image Compression Method      CS: 'ISO_10918_1'
(7fe0, 0010) Pixel Data                          OB: Array of 161452 elements

解释每个元素超出了本教程的范围,但 这个 网站包含关于每个条目的一些优秀信息

关于上述标签信息的一些关键点

  • 像素数据 (7fe0 0010) - 这是存储原始像素数据的地方。每个图像平面的像素编码顺序是自左向右,自上向下,即左上角像素(标记为 1,1)首先编码
  • 光度解释 (0028, 0004) - 也称为色彩空间。在这种情况下是 MONOCHROME2,其中像素数据表示为单个单色图像平面,低值=暗,高值=亮。如果色彩空间是 MONOCHROME,则低值=亮,高值=暗。
  • 每像素样本数 (0028, 0002) - 这应该为 1,因为此图像是单色的。如果色彩空间是 RGB,则此值将为 3
  • 存储位数 (0028 0101) - 每个像素样本存储的位数。典型的 8 位图像像素范围在 0255 之间
  • 像素表示 (0028 0103) - 可以是无符号 (0) 或有符号 (1)
  • 有损图像压缩 (0028 2110) - 00 图像未经过有损压缩。01 图像经过有损压缩。
  • 有损图像压缩方法 (0028 2114) - 指明使用的有损压缩类型(在此例中 ISO_10918_1 表示 JPEG 有损压缩)
  • 像素数据 (7fe0, 0010) - 包含 161452 个元素的数组,代表图像像素数据,pydicom 使用它将像素数据转换为图像。

PixelData 看起来像什么?

xray_sample.PixelData[:200]
b'\xfe\xff\x00\xe0\x00\x00\x00\x00\xfe\xff\x00\xe0\x9cv\x02\x00\xff\xd8\xff\xdb\x00C\x00\x03\x02\x02\x02\x02\x02\x03\x02\x02\x02\x03\x03\x03\x03\x04\x06\x04\x04\x04\x04\x04\x08\x06\x06\x05\x06\t\x08\n\n\t\x08\t\t\n\x0c\x0f\x0c\n\x0b\x0e\x0b\t\t\n\x11\n\x0e\x0f\x10\x10\x11\x10\n\x0c\x12\x13\x12\x10\x13\x0f\x10\x10\x10\xff\xc0\x00\x0b\x08\x04\x00\x04\x00\x01\x01\x11\x00\xff\xc4\x00\x1d\x00\x00\x02\x03\x01\x01\x01\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x04\x05\x02\x03\x06\x00\x01\x07\x08\t\xff\xc4\x00R\x10\x00\x02\x01\x03\x03\x02\x04\x03\x06\x05\x04\x00\x04\x01\x02\x17\x01\x02\x11\x00\x03!\x04\x121\x05A\x13"Qa\x06q\x81\x142\x91\xa1\xb1\xf0#B\xc1\xd1\xe1\x07\x15R\xf1\x16$3br\x08%4C&cs\x82\x92\xa2'

由于解释 PixelData 的复杂性,pydicom 提供了一种方便的方式以便捷的形式获取它:pixel_array,它返回一个包含像素数据的 numpy.ndarray

xray_sample.pixel_array, xray_sample.pixel_array.shape
(array([[ 0,  0,  0, ..., 13, 13,  5],
        [ 0,  0,  0, ..., 13, 13,  5],
        [ 0,  0,  0, ..., 13, 12,  5],
        ...,
        [ 0,  0,  0, ...,  5,  3,  0],
        [ 0,  0,  0, ...,  6,  4,  0],
        [ 0,  0,  0, ...,  8,  5,  0]], dtype=uint8),
 (1024, 1024))

然后可以使用 show 函数查看图像

xray_sample.show()

您还可以使用 from_dicoms 方便地为数据集中的所有图像创建一个包含所有 tag 信息作为列的数据框

dicom_dataframe = pd.DataFrame.from_dicoms(items)
dicom_dataframe[:5]
特定字符集 SOPClassUID SOPInstanceUID 研究日期 研究时间 入院号 模式 转换类型 转诊医师姓名 序列描述 ... 有损图像压缩 有损图像压缩方法 文件名 多像素间距 像素间距1 图像最小值 图像最大值 图像均值 图像标准差 图像窗口百分比
0 ISO_IR 100 1.2.840.10008.5.1.4.1.1.7 1.2.276.0.7230010.3.1.4.8323329.6904.1517875201.850819 19010101 000000.00 CR WSD 视图:PA ... 01 ISO_10918_1 C:\Users\tijme\.fastai\data\siim_small\train\No Pneumothorax\000000.dcm 1 0.168 0 254 160.398039 53.854885 0.087029
1 ISO_IR 100 1.2.840.10008.5.1.4.1.1.7 1.2.276.0.7230010.3.1.4.8323329.11028.1517875229.983789 19010101 000000.00 CR WSD 视图:PA ... 01 ISO_10918_1 C:\Users\tijme\.fastai\data\siim_small\train\No Pneumothorax\000002.dcm 1 0.143 0 250 114.524713 70.752315 0.326269
2 ISO_IR 100 1.2.840.10008.5.1.4.1.1.7 1.2.276.0.7230010.3.1.4.8323329.11444.1517875232.977506 19010101 000000.00 CR WSD 视图:PA ... 01 ISO_10918_1 C:\Users\tijme\.fastai\data\siim_small\train\No Pneumothorax\000005.dcm 1 0.143 0 246 132.218334 73.023531 0.266901
3 ISO_IR 100 1.2.840.10008.5.1.4.1.1.7 1.2.276.0.7230010.3.1.4.8323329.32219.1517875159.70802 19010101 000000.00 CR WSD 视图:PA ... 01 ISO_10918_1 C:\Users\tijme\.fastai\data\siim_small\train\No Pneumothorax\000006.dcm 1 0.171 0 255 153.405355 59.543063 0.144505
4 ISO_IR 100 1.2.840.10008.5.1.4.1.1.7 1.2.276.0.7230010.3.1.4.8323329.32395.1517875160.396775 19010101 000000.00 CR WSD 视图:PA ... 01 ISO_10918_1 C:\Users\tijme\.fastai\data\siim_small\train\No Pneumothorax\000007.dcm 1 0.171 0 250 166.198407 50.008985 0.053009

5 行 x 42 列

接下来,我们需要加载数据集的标签。我们使用 pandas 导入 labels.csv 文件并打印前五个条目。file 列显示 .dcm 文件的相对路径,label 列指示胸部 X 射线是否患有气胸。

df = pd.read_csv(pneumothorax_source/f"labels.csv")
df.head()
文件 标签
0 train/No Pneumothorax/000000.dcm 无气胸
1 train/Pneumothorax/000001.dcm 气胸
2 train/No Pneumothorax/000002.dcm 无气胸
3 train/Pneumothorax/000003.dcm 气胸
4 train/Pneumothorax/000004.dcm 气胸

现在,我们使用 DataBlock 类准备 DICOM 数据进行训练。

由于我们处理的是 DICOM 图像,我们需要使用 PILDicom 作为 ImageBlock 类别。这样 DataBlock 就会知道如何打开 DICOM 图像。由于这是一个二分类任务,我们将使用 CategoryBlock

pneumothorax = DataBlock(blocks=(ImageBlock(cls=PILDicom), CategoryBlock),
                   get_x=lambda x:pneumothorax_source/f"{x[0]}",
                   get_y=lambda x:x[1],
                   batch_tfms=[*aug_transforms(size=224),Normalize.from_stats(*imagenet_stats)])

dls = pneumothorax.dataloaders(df.values, num_workers=0)

此外,我们绘制第一个批次并应用指定的变换

dls = pneumothorax.dataloaders(df.values)
dls.show_batch(max_n=16)
Due to IPython and Windows limitation, python multiprocessing isn't available now.
So `number_workers` is changed to 0 to avoid getting stuck

训练

然后我们可以使用 vision_learner 函数并开始训练。

learn = vision_learner(dls, resnet34, metrics=accuracy)

请注意,如果您不选择损失函数或优化器函数,fastai 将尝试为任务选择最佳选项。您可以通过调用 loss_func 查看损失函数

learn.loss_func
FlattenedLoss of CrossEntropyLoss()

您也可以通过调用 opt_func 对优化器执行相同的操作

learn.opt_func
<function fastai.optimizer.Adam(params, lr, mom=0.9, sqr_mom=0.99, eps=1e-05, wd=0.01, decouple_wd=True)>

使用 lr_find 尝试找到最佳学习率

learn.lr_find()
SuggestedLRs(lr_min=0.005754399299621582, lr_steep=0.0063095735386013985)

learn.fit_one_cycle(1)
周期 训练损失 验证损失 准确率 时间
0 1.191782 2.123666 0.320000 00:37
learn.predict(pneumothorax_source/f"train/Pneumothorax/000004.dcm")

当对图像进行预测时,learn.predict 返回一个元组(类别、类别张量和 [每个类别的概率])。在此数据集中,只有 无气胸气胸 两个类别,因此每个概率都有两个值,第一个值是图像属于 类别 0无气胸 的概率,第二个值是图像属于 类别 1气胸 的概率

tta = learn.tta(use_max=True)
learn.show_results(max_n=16)

interp = Interpretation.from_learner(learn)
interp.plot_top_losses(2)

结果评估

医疗模型的影响通常很高,因此了解模型在检测特定疾病方面的性能至关重要。

该模型的准确率为 56%。准确率可以定义为正确预测的数据点数占所有数据点数的比例。然而,在此上下文中,我们可以将准确率定义为模型正确判断患者患病的概率**加上**模型正确判断患者未患病的概率

在评估医疗模型时,还需要用到其他一些关键术语

假阳性与假阴性

  • 假阳性是一种错误,其中测试结果错误地表明存在某种疾病等疾病状况(结果为阳性),而实际上并不存在

  • 假阴性是一种错误,其中测试结果错误地表明不存在某种疾病等疾病状况(结果为阴性),而实际上存在

敏感性与特异性

  • 敏感性或真阳性率是指模型在患者确实患有疾病的情况下将患者分类为患有疾病。敏感性衡量避免假阴性的能力

示例:一项新测试对 10,000 名患者进行了测试,如果新测试的敏感性为 90%,则测试将正确检测出 9,000 名(真阳性)患者,但会遗漏 1000 名(假阴性)实际患有疾病但被测试为未患病的患者

  • 特异性或真阴性率是指模型在患者确实未患有疾病的情况下将患者分类为未患有疾病。特异性衡量避免假阳性的能力

理解和使用敏感性、特异性和预测值 是一篇很棒的论文,如果您有兴趣了解更多关于理解敏感性、特异性和预测值的知识,可以阅读此文。

PPV 和 NPV

大多数医学测试通过 PPV(阳性预测值)或 NPV(阴性预测值)进行评估。

PPV - 如果模型预测患者患有某种疾病,则患者实际患有该疾病的概率是多少

NPV - 如果模型预测患者未患有某种疾病,则患者实际未患有该疾病的概率是多少

PPV 的理想值(在完美的测试中)为 1(100%),最差可能的值为零

NPV 的理想值(在完美的测试中)为 1(100%),最差可能的值为零

混淆矩阵

混淆矩阵是针对 valid 数据集绘制的

interp = ClassificationInterpretation.from_learner(learn)
losses,idxs = interp.top_losses()
len(dls.valid_ds)==len(losses)==len(idxs)
interp.plot_confusion_matrix(figsize=(7,7))

您也可以像这样重现从 plot_confusion_matrix 中解释的结果

upp, low = interp.confusion_matrix()
tn, fp = upp[0], upp[1]
fn, tp = low[0], low[1]
print(tn, fp, fn, tp)
23 13 12 2

请注意,敏感性 = 真阳性/(真阳性 + 假阴性)

sensitivity = tp/(tp + fn)
sensitivity
0.14285714285714285

在这种情况下,模型的敏感性为 40%,因此只能正确检测出 40% 的真阳性(即患有气胸的人),但会遗漏 60% 的假阴性(实际患有气胸但被告知未患病的患者!这不是一个好的情况)。

这也称为第二类错误

特异性 = 真阴性/(假阳性 + 真阴性)

specificity = tn/(fp + tn)
specificity
0.6388888888888888

该模型的特异性为 63%,因此可以正确检测出 63% 的时间患者未患有气胸,但会错误地将 37% 的患者分类为患有气胸(假阳性),而他们实际上并未患病。

这也称为第一类错误

阳性预测值 (PPV)

ppv = tp/(tp+fp)
ppv
0.13333333333333333

在这种情况下,模型在正确预测气胸患者方面的表现较差

阴性预测值 (NPV)

npv = tn/(tn+fn)
npv
0.6571428571428571

该模型在预测无气胸患者方面表现更好

计算准确率

如前所述,此模型的准确率为 56%,但这如何计算得来?我们可以将准确率视为

准确率 = 敏感性 * 患病率 + 特异性 * (1 - 患病率)

其中患病率是一个统计概念,指特定时间点特定人群中疾病的病例数。在此情况下,患病率是指验证集中患病的患者数与总患者数之比。

要查看验证集中的文件,可以调用 dls.valid_ds.cat

val = dls.valid_ds.cat
#val[0]

验证集中有 15 张气胸图像(总共有 50 张图像,可以通过使用 len(dls.valid_ds) 查看),因此这里的患病率为 15/50 = 0.3

prevalence = 15/50
prevalence
0.3
accuracy = (sensitivity * prevalence) + (specificity * (1 - prevalence))
accuracy
0.490079365079365

引用

[1] Filice R et al. 利用 NIH 胸部 X 光数据集上的机器学习标注进行气胸标注的众包。J Digit Imaging (2019)。https://doi.org/10.1007/s10278-019-00299-9