延迟重采样#
引言#
延迟重采样是 MONAI 1.2 中引入的新特性。此特性目前仍处于实验阶段,其行为和 API 在未来版本中可能会发生变化。
延迟重采样重构了预处理的执行方式。它改进了标准的预处理流程,与传统预处理相比,可以带来显著的益处。它可以改善:* 流程执行时间 * CPU 或 GPU 中的流程内存使用 * 通过减少重采样引起的偶然噪声和伪影来提高图像和分割质量
其实现方式是借鉴计算机图形学流程中使用的技术,即通过组合一系列“齐次矩阵”来修改场景中物体的变换。
每个变换不再孤立执行(这可能需要对数据进行重采样以创建新的张量),而是将操作可以用齐次变换描述的变换延迟执行。相反,它们会创建一个“待定操作”,添加到操作列表中,这些操作将在需要时被融合在一起并执行。
延迟重采样如何改变预处理#
为了理解传统流程和延迟流程之间的区别,最好通过一个示例流程及其执行策略的差异来进行说明。
传统执行#
在使用传统的重采样(在 MONAI 和许多其他预处理库中均有发现)时,您通常会定义一系列变换,并将它们传递给一个 Compose
对象,例如 monai.transforms.compose.Compose
。
示例
transforms = [
Spacingd(keys=["img", "seg"], ...),
Orientationd(keys=["img", "seg"], ...),
RandSpatialCropd(keys=["img", "seg"], ...),
RandRotate90d(keys=["img", "seg"], ...),
RandRotated(keys=["img", "seg"], ...),
RandZoomd(keys=["img", "seg"], ...),
RandGaussianNoised(keys="img", ...),
]
pipeline = Compose(transforms)
# elsewhere this will be called many times (such as in a Dataset instance)
outputs = pipeline(inputs)
当我们调用 pipeline(inputs)
时,会发生以下情况:
调用
Spacingd
并对数据样本进行插值调用
Orientationd
对数据样本进行置换,以重新组织其空间维度调用
RandSpatialCropd
裁剪数据样本的随机块,在此过程中丢弃其余数据调用
RandRotate90d
可能执行基于张量的数据样本旋转调用
RandRotated
可能对数据样本进行完全重采样调用
RandZoomd
可能对数据样本进行插值调用
RandGaussianNoised
可能向img
添加噪声
图示传统流程执行。张量(图像主体中的方框)通过流程,并在每一步显示其 applied_operations 属性的状态。带有粗红色边框的张量在该阶段经历了一些重采样操作。#
总体而言,数据通过空间变换(Spacingd
、RandRotated
和 RandZoomd
)进行插值或重采样的情况最多有三次。此外,发生的裁剪意味着输出数据样本可能包含具有数据但显示填充值的像素,因为数据已被 RandSpatialCrop
丢弃。
这些操作中的每一个都会消耗时间和内存,而且,正如我们在上面的示例中看到的,还会产生重采样伪影,甚至会破坏结果数据样本中的数据。
延迟执行#
延迟重采样的工作方式非常不同。当您使用 lazy=True 执行相同的流程时,会发生以下情况
Spacingd
被延迟执行。它将其要执行的操作的描述放入待定操作列表Orientationd
被延迟执行。它将自身操作的描述添加到待定操作列表,现在共有 2 个待定操作RandSpatialCropd
被延迟执行。它将自身操作的描述添加到待定操作列表,现在共有 3 个待定操作RandRotate90d
被延迟执行。它将自身操作的描述添加到待定操作列表,现在共有 4 个待定操作RandRotated
被延迟执行。它将自身操作的描述添加到待定操作列表,现在共有 5 个待定操作RandZoomd
被延迟执行。它将自身操作的描述添加到待定操作列表,现在共有 6 个待定操作[Spacingd, Orientationd, RandSpatialCropd, RandRotate90d, RandRotated, RandZoomd] 都已在待定操作列表中,但尚未对数据执行
RandGaussianNoised
不是一个延迟变换。现在是评估待定操作的时候了。它们的描述通过数学方法组合在一起,以确定所有操作执行后产生的最终操作。然后在一个单一的重采样操作中应用此最终操作。完成此操作后,RandGaussianNoised 对结果数据进行操作
图示延迟流程执行。我们显示张量在流程处理过程中 pending_operations 和 applied_operations 属性的状态。粗红色边框表示在该步骤发生了一些重采样操作。延迟重采样的此类操作次数要少得多。#
单一的重采样操作引入的噪声较少,因为它在此流程中只发生一次,而在传统流程中发生三次。更重要的是,虽然裁剪描述了只保留数据样本子集的操作,但裁剪直到空间变换完成后才执行,这意味着所有在界限内的数据样本都得到了保留,并成为结果输出的一部分。
组合齐次矩阵#
虽然对齐次矩阵的完整论述超出了本文档的范围,但对其进行简要概述有助于理解延迟重采样的机制。齐次矩阵在计算机图形学中用于以统一(齐次)的方式描述笛卡尔空间中的操作。旋转、缩放、平移和剪切是其中可以执行的操作。齐次矩阵具有可以将它们组合在一起的有趣特性,从而描述一系列操作的结果。请注意,顺序很重要;缩放 -> 旋转 -> 平移 与 平移 -> 旋转 -> 缩放 的结果非常不同。
将齐次矩阵组合在一起的能力使得可以将一系列操作作为单一操作执行,这是延迟重采样发挥作用的关键机制。
API 变更#
现有属性中添加了一些新参数,我们将在此详细介绍。特别是,我们将重点介绍 Compose<monai.transforms.compose.Compose
> 以及 LazyTrait
/LazyTransform
以及它们之间的交互方式。
Compose#
Compose
增加了一些可用于控制重采样行为的新参数。每个参数都在其各自的章节中介绍
lazy#
lazy
控制执行是否以延迟方式进行。它可以取以下三个值:
lazy=False 强制流程以标准方式执行,每个变换立即应用
lazy=True 强制流程延迟执行。每个实现
LazyTrait
(或继承LazyTransform
)的变换都将延迟执行lazy=None 意味着流程可以延迟执行,但仅限于那些自身 lazy 属性设置为 True 的变换。
overrides#
overrides
允许用户指定在延迟执行时可用于覆盖变换的某些参数。此参数主要用于允许您在无需修改 mode
和 padding_mode
等字段的情况下运行流程。执行基于字典的变换时,您提供一个包含每个键的覆盖项的字典,如下所示。不需要覆盖的键可以省略
{
"image": {"mode": "bilinear"},
"label": {"padding_mode": "zeros"}
}
log_stats#
如果您想准确了解您的流程如何执行,则提供了变换执行日志功能。它可以取 bool
或 str
值,默认为 False,表示禁用日志。否则,您可以通过传入您希望使用的日志记录器的名称来启用它(注意,您无需事先构建日志记录器)。
LazyTrait / LazyTransform#
许多变换现在实现 LazyTrait<monai.transforms.traits.LazyTrait> 或 LazyTransform<monai.transforms.transform.Transform>。这样做会标记变换为延迟执行。延迟变换有以下共同点:
__init__
有一个 lazy
参数#
lazy
是一个 bool
值,可以在实例化延迟变换时传递给初始化器。这指示变换是应该延迟执行还是非延迟执行。请注意,此值可以通过将 lazy
传递给 __init__
来覆盖。lazy
默认为 False
__call__
有一个 lazy
参数#
lazy
是一个可选的 bool
值,可以在调用时传递,以覆盖初始化期间定义的行为。其默认值为 None
。如果它不是 None
,则使用此值代替 self.lazy
。这允许调用的 Compose
实例覆盖默认值,而无需在每个延迟变换上设置(除非用户将 Compose.lazy
设置为 None
)。
lazy 属性#
lazy 属性允许您在构建延迟变换后获取或设置其延迟状态。
requires_current_data 属性 (仅获取)#
requires_current_data
属性表示变换在执行过程中使用了传递给它的一个或多个张量中的数据。此类变换要求张量必须是最新的,即使变换本身是延迟执行的。对于 CropForeground[d]
、RandCropByPosNegLabel[d]
和 RandCropByLabelClasses[d]` 等变换,这是必需的。此属性在
LazyTransform
上实现时返回 False
,并且必须由执行时检查数据值的变换覆盖以返回 True
。
控制延迟性#
用户可以通过两种方式对延迟性进行更精细的控制。一种是在初始化或调用 Compose
实例时使用 lazy=None。另一种是使用 ApplyPending[d]
变换。这些技术可以自由组合和搭配使用。
使用 lazy=None
#
Lazy=None
告诉 Compose
遵循在每个延迟变换上设置的 lazy 标志。这些标志默认为 False,因此用户必须在他们仍然希望延迟执行的变换上将 lazy 设置为 True。
lazy=None
示例:#
图示当 Compose
使用 lazy=None
执行时,使用 lazy=False
的效果。请注意,由于 RandRotate90d
以非延迟方式执行而发生的额外重采样。#
使用 ApplyPending[d]
#
ApplyPending[d]
会导致所有待定变换在下一个变换之前执行,无论下一个变换是否是延迟变换,或者是否配置为延迟执行。
ApplyPending
示例:#
图示使用 ApplyPendingd
导致在一系列延迟变换中间发生重采样。#