医学影像论文充斥着熟悉的术语:归一化、标签、验证、标注和预处理。
如果你来自通用机器学习领域,你可能认为你已经知道这些词的意思。有时你确实知道。
但医学影像领域有一些特殊之处。有些术语含义不同,有些术语根据上下文会有多种用法。
本文将跟随一张胸部 X 光片从获取的那一刻,到模型做出预测的全过程。在此过程中,我们将审视医学影像论文中常见的术语及其实际含义。
一个伴随的 笔记本 让你可以亲自运行这些步骤中的大部分,而不仅仅是阅读它们。
每个医学影像流水线阶段的名称以及每个阶段实际发生的事情
匿名化、去识别化和假名化之间的区别
为什么“标注”和“归一化”在不同语境下可能有不同的含义
分类、检测和分割在同一张图像上有何不同
调和能解决什么问题,以及为什么验证设计有时比模型选择更重要
获取是指图像的创建过程。它包括成像设备、其设置以及患者的定位方式。
同一患者的两张胸部 X 光片如果使用不同的设备拍摄,可能会呈现不同的外观。制造商、探测器、曝光设置以及图像处理软件都可能影响最终图像。
患者的体位也很重要。例如,站姿的 PA(后前位) 胸部 X 光片可能与患者躺在床上时拍摄的便携式 AP(前后位) X 光片截然不同。
一个重要的区别是心脏的视在大小。这可能会影响模型学习的内容。
这些信息中的大部分存储在 DICOM 文件中。
DICOM(医学数字成像与通信)是存储和传输医学图像的标准格式。DICOM 文件不仅包含像素,还可以包含患者、扫描仪、研究、图像方向、像素间距以及其他细节的信息。
本教程中使用的图像最初是以 JPEG 形式到达的,因此原始的临床 DICOM 元数据不可用。为了演示,笔记本将图像包装在一个新的 DICOM 文件中,并添加了一些有用的字段。
ds = pydicom.dcmread("synthetic_cxr.dcm")
for tag in ["Modality", "BodyPartExamined", "ViewPosition",
"Manufacturer", "ManufacturerModelName", "KVP", "PixelSpacing"]:
print(f"{tag:24s} {ds[tag].value}")
值得关注的一个字段是 PixelSpacing。它表示每个像素所代表的物理尺寸,通常以毫米为单位。两张图片都可以是 512 × 512 像素,但覆盖的物理区域不同。
因此,如果你想以毫米为单位测量某物,不能仅仅计算像素。你还需要知道像素间距。
这三个术语经常被互换使用,但它们的含义不同。
去标识化会删除或修改可能用来识别个人的信息。
例如:
Patient name
Medical record number
Date of birth
Phone numbers
Other identifying information
目标是降低数据被追溯到个人的可能性。
假名化用代码替换标识符。
例如:
Jane Doe → SUBJ_0041
重要的区别在于,即使有独立的密钥,仍然可以将 SUBJ_0041 关联回 Jane Doe。
医院可能需要这样做,因为他们有时需要再次找到原始患者。
匿名化的目标是使重新识别在合理范围内不可能实现。
与假名化不同,匿名化没有保留的密钥可用于将数据重新关联到个人。
一个简单的问题可以帮助:
此数据是否仍能通过额外信息关联回个人?
如果答案是肯定的,因为存在一个独立的密钥,那么通常您面对的是假名化而非真正的匿名化。
确切的法律含义取决于国家和法规,因此研究者在使用这些术语时应格外谨慎。
从 DICOM 头部删除信息仅是工作的一部分。
有时文本实际上会被绘制到图像中。
例如:
Patient name
Date
Hospital name
R
PORTABLE
这被称为烧录注释。
删除 DICOM 元数据对这段文字没有任何影响。你必须在像素中找到文字并将其删除。
这比听起来要难。像“在图像顶部附近寻找亮像素”这样的简单规则会找到不仅仅是文字的内容。锁骨和肋骨等骨骼也很亮。
这意味着一个真正的去标识化管道通常会结合几种技术:
Text detection
OCR
Image-region rules
DICOM metadata removal
Validation
目标不仅仅是移除文字,而是要证明文字确实已被移除。
例如:
IDENTIFIERS = [
"PatientName",
"PatientID",
"PatientBirthDate",
"PatientSex",
"InstitutionName",
"ReferringPhysicianName",
"StudyDate",
"StudyTime",
"AccessionNumber"
]
for tag in IDENTIFIERS:
if tag in ds:
ds[tag].value = ""
# Records that an identity-removal process was performed.
# This field alone does not make the dataset de-identified.
ds.PatientIdentityRemoved = "YES"
像素级部分是简单演示与真实生产流水线之间差异最大的地方。
如果您处理美国医疗数据,经常会看到两个 HIPAA 术语:安全港 和 专家认定。
它们是根据 HIPAA 判断受保护健康信息是否已去标识的两种方法。
安全港是一份清单。它要求删除 18 类标识符,包括以下内容:
Names
Geographic information smaller than a state
Certain dates
Phone numbers
Medical record numbers
Device identifiers
Full-face photographs
它相当直接,因为你可以按照明确的列表操作。
专家确定采用不同的方法。合格的专家评估再识别的风险,并记录剩余风险非常小的原因。这可以让研究人员保留安全港要求删除的信息。
例如,精确的日期在研究疾病随时间的变化时非常有用。
因此存在权衡:
安全港更简单且更具限制性
专家确定更具灵活性,但需要有文档记录的风险评估
这些是美国 HIPAA 的概念。其他国家有不同的隐私法律和定义。
例如,欧盟的 GDPR 和印度的 DPDP 法案对匿名和假名数据有各自的处理方式。
预处理是指在将图像输入模型之前对图像进行的操作。
常见的预处理步骤包括:
调整大小
窗口化
更改方向
归一化像素值
模型通常期望输入固定尺寸的图像。医学图像可能有多种不同的尺寸,因此经常需要调整大小。
但调整大小会改变像素与物理空间之间的关系。
例如,如果将图像从 1024 × 1024 调整到 512 × 512,每个像素现在代表不同的物理面积。
因此,如果需要毫米级的物理测量(如距离或面积),则需要考虑原始或重采样后的像素间距。
窗口化选择一组强度值的范围,并将该范围映射到显示范围。超出范围的值会被裁剪。
放射科医生在观察 CT 图像时常使用不同的窗口设置,因为不同组织在不同窗口下更易于观察。
窗口化改变了图像的表示或显示方式。这并不意味着原始图像数据已被更改。
笔记本使用第 2 和第 98 百分位数演示了窗口化:
lo, hi = np.percentile(img, [2, 98])
windowed = np.clip(
(img.astype(float) - lo) / (hi - lo),
0,
1
)
医学图像也包含方向信息。如果弄错了,左右会被互换,对胸部 X 光片而言,这是一个严重的错误。
这是第一个具有双重含义的词。
在机器学习中,归一化通常指将数值转换为更一致的尺度或分布,以使训练行为更具可预测性。常见的两种方式:
f = img.astype(np.float32)
minmax = (f - f.min()) / (f.max() - f.min())
Z 分数: 减去均值并除以标准差,得到均值为 0,标准差为 1。
zscore = (f - f.mean()) / f.std()
最小-最大归一化有一个弱点:它直接依赖于最小值和最大值。一个非常亮的像素(例如由设备或噪声引起的像素)会改变范围,导致图像的大部分被压缩到尺度的较小部分。
Z-score 归一化对最小值和最大值的依赖较小,尽管极端值仍可能影响均值和标准差。
在医学影像中,归一化也可以在图像或数据集之间保持一致地应用,但使不同扫描仪或机构的图像可比较则更具体地称为 协调化。当你需要处理多个站点时,这一区别变得尤为重要,如第 10 节所述。
两者都描述与图像相关的信息,但它们所代表的信息内容不同。
标签通常用于描述整个图像。
例如:
Image → Pneumonia
一个 注释 通常会告诉你图像中某个物体的位置。
它可以是:
点
边界框
多边形
轮廓
像素级掩码
例如:
Image → Lung mask
关键区别在于位置。
标签告诉你什么存在,而注释既能告诉你什么存在,还能告诉你在哪里。
创建注释也需要付出更多的努力。
一个大型数据集可能从放射科报告中提取出数百万个图像级标签,但只有其中的一小部分可能拥有临床医生创建的详细掩码。
这种捷径有代价。报告可能会说“肺炎”,但这并不一定能告诉你确切是哪些像素显示肺炎。这可能导致噪声标签。
在医学影像研究中,最重要的决定之一就是如何划分数据。
划分通常应在患者层面进行,而不是图像层面。
假设一位患者有五张胸部X光片。如果将三张图像放入训练集,两张放入测试集,那么模型在训练过程中已经看到该患者的图像。患者特有的特征可能同时出现在两个集合中。这属于数据泄漏。
同样的原则也适用于以下情况:
同一患者的多次扫描
同一研究的多张图像
源自同一原始扫描的图像
目标很简单:测试集应包含模型在训练过程中未见过的患者。
因此,不应这样做:
先划分图像再分配患者
应这样做:
先划分患者再分配其图像
这是一个细节,其对结果的影响可能甚至超过更改模型架构。
使用同一张胸部X光片,提出三个不同的问题。
是否存在肺炎?
模型返回一个标签或概率。
Pneumonia: 0.92
它告诉你什么在图像中。它不会告诉你位置。
异常在哪里?模型返回一个边界框。
[x, y, width, height]
它大致告诉你发现的位置,但不会描述其确切的形状。
哪些像素属于异常?
模型返回像素级掩码。这能给出发现的形状和位置。
一个简单的记忆方法:
分类 = 什么?
检测 = 在哪里?
分割 = 哪些像素?
从分类到检测再到分割,标注工作量通常会增加。
因此,选择能回答你问题的最简单任务。如果你只需要知道扫描是否异常,可能不需要分割。
数据增强通过转换现有图像来创建新的训练样本。
常见的转换包括:
旋转
平移
缩放
亮度变化
当医学数据集较小时,这可能很有用。
但医学图像有一个重要的限制:转换后的图像仍应看起来像可能真实发生在患者身上的东西。
例如,水平翻转在自然图像机器学习中很常见。
翻转一张猫的照片,你仍然得到一只猫。
但翻转胸部X光片后,心脏会移到另一侧。你可能刚刚创建了一张看起来像dextrocardia的图像,其中心脏位于右侧。
同样的问题也会出现在标记上。右侧的标记可能突然出现在左侧。字母本身也会被镜像。模型不会自动理解这在解剖学上是错误的。
其他增强方式也可能引起问题。大的亮度变化可能掩盖重要发现。激进的裁剪可能会切除部分肺部。
因此,在使用增强之前,请自问:此图像是否可能真实来自真实的扫描仪和患者?
如果不是,则不要使用它。
# Reasonable: small rotation
M = cv2.getRotationMatrix2D((cx, cy), 7, 1.0)
rotated = cv2.warpAffine(
img,
M,
(w, h),
borderMode=cv2.BORDER_REPLICATE
)
# Potentially problematic for a chest X-ray:
flipped = img[:, ::-1]
后处理在模型产生输出之后进行。
分割模型通常会为每个像素输出一个概率。在应用阈值后,掩码可能包含一些小的不需要的区域或空洞。
例如,笔记本的原始掩码包含:
两个大的肺部区域
十四个小的不需要的区域
一种常见的清理步骤是仅保留最大的连通分量。
由于我们预期有两个肺,我们可以保留两个最大的区域。我们也可以填充小的空洞。
labelled, n = ndimage.label(mask > 0)
sizes = ndimage.sum(
mask > 0,
labelled,
range(1, n + 1)
)
keep = np.isin(
labelled,
np.argsort(sizes)[-2:] + 1
)
clean = ndimage.binary_fill_holes(keep)
在这个例子中,清理操作将连通分量的数量从 16 减少到 2,同时改变的像素不到 5%。
那说明了评估中的重要点:像素重叠指标可能几乎没有变化,尽管预测的结构已经发生了显著变化。
对于某些应用,连通区域的数量和形状比像素重叠的微小变化更重要。因此,选择与你实际关心的错误相匹配的指标。
此外,要小心后处理。如果你的模型在结果看起来好之前需要大量的清理,那么清理可能掩盖了模型中的问题。
也要始终查看原始输出。
想象两家医院使用不同的扫描仪。
图像可能具有以下不同:
亮度
对比度
噪声
分辨率
图像处理特性
在医院 A 上主要训练的模型可能学习到这些差异中的一些,而不是学习与疾病相关的特征。它在医院 A 上可能表现良好,但在医院 B 上可能失败。
这就是调和可以提供帮助的地方。
调和尝试在保留重要信息的同时,使不同来源的数据更具可比性。
一个简单的例子是直方图匹配。
它调整一幅图像的像素值分布,使其看起来更像参考图像。
from skimage.exposure import match_histograms
harmonized = match_histograms(
image_from_hospital_b,
reference_from_hospital_a
)
这有助于解决亮度和对比度的差异,但它不能解决所有问题。
如果两台扫描仪在分辨率、噪声特性或图像处理流程上有所不同,仅靠直方图匹配是不够的。
并且如果在图像获取或裁剪过程中信息丢失,协调也无法神奇地恢复它。
一个实用的经验法则是:
归一化使数值更一致。
协调解决了数据来源之间的系统性差异。
本节讨论研究设计。
回顾性研究使用已经存在的数据。
例如:
我们从医院档案中收集了4000张胸部X光片,并在它们上测试了我们的模型。
在医疗AI中这很常见,因为它相对快速且成本低廉。
但它也会带来偏倚的机会。研究者可能会决定包括哪些患者,排除哪些扫描,使用哪家医院,以及选择哪个阈值。
这些决定可能会无意中使结果看起来更好。
在前瞻性研究中,你首先定义研究计划,然后向前收集数据。
例如:
我们在收集图像之前定义患者人群、评估标准和成功指标。
这可以减少一些偏倚来源,因为重要决策是在看到结果之前做出的。
您还会看到术语 外部验证。
这意味着在与开发模型所使用的数据独立的数据上测试模型。
例如:
Hospital A → Training
Hospital A → Internal test
Hospital B → External validation
一个更强的测试可能是:
Hospital A + B → Development
Hospital C → External validation
在本院数据上表现良好,但在另一家医院表现不佳的模型,可能学到了特定于该站点的模式,而不是普遍的疾病特征。
因此,外部验证往往比仅在同一数据集上再做一次随机划分更具信息量。
现在假设一篇医学影像论文中有这句话:
研究者从两家机构回顾性收集了 4,120 份去标识的前景胸部 X 光片。图像被重采样至 512 × 512,进行强度归一化,并通过直方图匹配在各站点之间进行协调。两位放射科医生手动标注了肺野;分割输出经过最大连通分量选择进行后处理。该模型在第三站点的 890 份研究上进行了外部验证。
这段文字信息量很大,但现在你可以解读它:
回顾性收集:研究者使用了已存在的图像。
去标识:移除或更改了身份信息。
两家机构:数据集来自多个来源。
重采样至 512 × 512:图像被转换为统一的尺寸。
强度归一化:像素值被转换为更一致的数值范围。
协调:研究者试图减少两个站点之间的系统性差异。
两位放射科医生手动标注:专家创建了显示肺野位置的空间信息。
后处理:模型的原始分割输出被清理。
外部验证:模型在另一站点的独立数据上进行了测试。
这一点尤为重要。仅在开发数据上表现良好的模型,无法充分反映其在真实世界中的表现。
一篇医学影像论文可以用几句话描述完整的数据流程。
掌握这些术语后,你就能以不同的方式阅读这些句子。
与其仅关注模型及其准确率,不如提出以下问题:
图像来自哪里?
使用了什么扫描仪?
在训练和测试之间,患者是否被分开?
身份信息是如何被移除的?
是否可能存在烧录的文本?
谁创建了标签或注释?
进行了哪些预处理?
像素值是如何归一化的?
模型是否经过外部验证?
测试数据是否真正独立?
预测后,模型的输出发生了什么?
模型只是医学影像流水线的一部分。更重要的是,许多关键问题往往在 模型看到图像之前 就已经出现。
——
一个热爱技术的程序员,喜欢分享前沿AI知识和开发经验。