scipy.ndimage.

vectorized_filter#

scipy.ndimage.vectorized_filter(input, function, *, size=None, footprint=None, output=None, mode='reflect', cval=None, origin=None, axes=None, batch_memory=1073741824)[源]#

使用向量化的 Python 可调用对象作为核来过滤数组

参数:
input类数组

输入数组。

function可调用对象

要应用于以input的每个元素为中心的窗口的核。可调用对象必须具有签名

function(window: ndarray, *, axis: int | tuple) -> scalar

其中 axis 指定了 window 的轴(或多个轴),沿着这些轴评估过滤函数。

size标量或元组,可选

请参阅下面的footprint。如果给定footprint,则忽略此参数。

footprint数组,可选

必须定义sizefootprint之一。size指定从输入数组的每个元素位置获取的形状,以定义滤镜函数的输入。footprint是一个布尔数组,它(隐式地)指定一个形状,并且还指定此形状内哪些元素将传递给滤镜函数。因此,size=(n, m) 等同于 footprint=np.ones((n, m))。我们将size调整为axes指示的维度数量。例如,如果axes(0, 2, 1) 并且为 size 传入 n,则有效的size(n, n, n)

output数组或 dtype,可选

用于放置输出的数组,或返回数组的 dtype。默认情况下,将创建一个与输入具有相同 dtype 的数组。

mode{‘reflect’, ‘constant’, ‘nearest’, ‘mirror’, ‘wrap’},可选

参数mode决定了输入数组如何在其边界之外进行扩展。默认值为 'reflect'。每个有效值的行为如下

‘reflect’(反射) (d c b a | a b c d | d c b a)

通过反射最后一个像素的边缘来扩展输入。此模式有时也称为半样本对称。

‘constant’(常量) (k k k k | a b c d | k k k k)

通过用cval参数定义的相同常量值填充边界之外的所有值来扩展输入。

‘nearest’(最近) (a a a a | a b c d | d d d d)

通过复制最后一个像素来扩展输入。

‘mirror’(镜像) (d c b | a b c d | c b a)

通过反射最后一个像素的中心来扩展输入。此模式有时也称为全样本对称。

‘wrap’(环绕) (a b c d | a b c d | a b c d)

通过环绕到对边来扩展输入。

‘valid’(有效) (| a b c d |)

输入不进行扩展;相反,输出形状会根据窗口大小按以下计算方式减小

window_size = np.asarray(size if size is not None else footprint.shape)
output_shape = np.asarray(input.shape)
output_shape[np.asarray(axes)] -= (window_size - 1)
cval标量,可选

如果mode为 'constant',用于填充输入边界之外的值。默认值为 0.0。

origin整数或序列,可选

控制滤镜在输入数组像素上的放置。值为 0(默认)时,滤镜位于像素中心;正值将滤镜向左移动,负值向右移动。通过传入一个长度等于输入数组维度数的原点序列,可以沿着每个轴指定不同的偏移量。

axes整数元组,可选

如果为 None,则input将沿着所有轴进行过滤。否则,input将沿着指定轴进行过滤。当指定axes时,footprint的维度以及用于sizeorigin的任何元组的长度必须与axes的长度匹配。footprint的第 i 个轴和这些元组中的第 i 个元素对应于axes的第 i 个元素。

batch_memory整数,默认值: 2**30

传递给 functionwindow 数组中数据所占用的最大字节数。

返回:
outputndarray

过滤后的数组。其 dtype 是function的输出 dtype。如果function应用于单个窗口时为标量值,则输出形状与input相同(除非 mode=='valid';请参阅mode文档)。如果function应用于单个窗口时为多值,则对应维度在输出形状中的位置完全取决于function的行为;请参阅示例。

说明

此函数通过根据modeinput进行填充,然后在填充数组上的滑动窗口视图块上调用提供的function。这种方法非常简单灵活,因此该函数具有一些其他过滤函数不具备的许多功能(例如,内存控制、float16 和复杂 dtype 支持,以及由function参数提供的任何 NaN 处理功能)。

然而,这种暴力方法可能会执行大量的冗余工作。如果可能,请使用专门的滤镜(例如,使用minimum_filter 而不是此函数与numpy.min 作为可调用对象;使用uniform_filter 而不是此函数与numpy.mean 作为可调用对象),因为它可能使用更高效的算法。

当没有专门的滤镜可用时,如果function是一个向量化的纯 Python 可调用对象,此函数是理想选择。通过将scipy.LowLevelCallable传递给generic_filter,可能会获得更好的性能。generic_filter也可能更适合那些具有大滤镜足迹的昂贵可调用对象,以及非向量化的可调用对象(即,那些不支持axis的)。

此函数不提供某些ndimage函数所提供的 extra_argumentsextra_keywords 参数。原因有二:

  • 用户可以实现直通(passthrough)功能:只需将原始可调用对象包装在另一个提供所需参数的函数中即可;例如,function=lambda input, axis: function(input, *extra_arguments, axis=axis, **extra_keywords)

  • 在某些用例中,function需要除了input之外,还传入额外的滑动窗口数据function。这尚未实现,但我们保留这些参数名称以备将来实现此功能,这将是增加能力,而不是为现有能力提供重复的接口。

示例

假设我们希望对一个 float16 图像执行偶数窗口大小的中值滤波。此外,图像中包含 NaN 值,我们希望将其忽略(并被滤镜有效移除)。median_filter 不支持 float16 数据,当存在 NaN 时其行为未定义,并且对于偶数窗口大小,它不返回通常的样本中值——即两个中间元素的平均值。这将是 vectorized_filterfunction=np.nanmedian 的一个极佳用例,它支持所需的接口:接受任何形状的数据数组作为第一个位置参数,以及轴元组作为关键字参数 axis

>>> import numpy as np
>>> from scipy import datasets, ndimage
>>> from scipy.ndimage import vectorized_filter
>>> import matplotlib.pyplot as plt
>>> ascent = ndimage.zoom(datasets.ascent(), 0.5).astype(np.float16)
>>> ascent[::16, ::16] = np.nan
>>> result = vectorized_filter(ascent, function=np.nanmedian, size=4)

绘制原始图像和过滤后的图像。

>>> fig = plt.figure()
>>> plt.gray()  # show the filtered result in grayscale
>>> ax1 = fig.add_subplot(121)  # left side
>>> ax2 = fig.add_subplot(122)  # right side
>>> ax1.imshow(ascent)
>>> ax2.imshow(result)
>>> fig.tight_layout()
>>> plt.show()
../../_images/scipy-ndimage-vectorized_filter-1_00_00.png

vectorized_filter 满足的另一个需求是执行多输出滤镜。例如,假设我们除了中值之外,还希望根据第 25 和 75 百分位数来过滤图像。我们可以单独执行这三个滤镜。

>>> ascent = ndimage.zoom(datasets.ascent(), 0.5)
>>> def get_quantile_fun(p):
...     return lambda x, axis: np.quantile(x, p, axis=axis)
>>> ref1 = vectorized_filter(ascent, get_quantile_fun(0.25), size=4)
>>> ref2 = vectorized_filter(ascent, get_quantile_fun(0.50), size=4)
>>> ref3 = vectorized_filter(ascent, get_quantile_fun(0.75), size=4)
>>> ref = np.stack([ref1, ref2, ref3])

然而,vectorized_filter 也支持返回多个输出的滤镜,只要output未指定且batch_memory足够高以在一个块中执行计算。

>>> def quartiles(x, axis):
...     return np.quantile(x, [0.25, 0.50, 0.75], axis=axis)
>>> res = vectorized_filter(ascent, quartiles, size=4, batch_memory=np.inf)
>>> np.all(np.isclose(res, ref))
np.True_

与多个输出对应的附加维度放置由function自行决定。quartiles恰好预置一个与三个输出对应的维度,仅仅因为这是np.quantile的行为。

>>> res.shape == (3,) + ascent.shape
True

如果我们希望将此维度附加到后面

>>> def quartiles(x, axis):
...     return np.moveaxis(np.quantile(x, [0.25, 0.50, 0.75], axis=axis), 0, -1)
>>> res = vectorized_filter(ascent, quartiles, size=4, batch_memory=np.inf)
>>> res.shape == ascent.shape + (3,)
True

假设我们希望实现一个“众数”滤镜——一个选择窗口中最常出现的值的滤镜。一个简单(但相当慢)的方法是使用 generic_filterscipy.stats.mode

>>> from scipy import stats
>>> rng = np.random.default_rng()
>>> input = rng.integers(255, size=(50, 50)).astype(np.uint8)
>>> def simple_mode(input):
...     return stats.mode(input, axis=None).mode
>>> ref = ndimage.generic_filter(input, simple_mode, size=5)

如果速度很重要,vectorized_filter 可以利用向量化可调用对象的性能优势。

>>> def vectorized_mode(x, axis=(-1,)):
...     n_axes = 1 if np.isscalar(axis) else len(axis)
...     x = np.moveaxis(x, axis, tuple(range(-n_axes, 0)))
...     x = np.reshape(x, x.shape[:-n_axes] + (-1,))
...     y = np.sort(x, axis=-1)
...     i = np.concatenate([np.ones(y.shape[:-1] + (1,), dtype=bool),
...                         y[..., :-1] != y[..., 1:]], axis=-1)
...     indices = np.arange(y.size)[i.ravel()]
...     counts = np.diff(indices, append=y.size)
...     counts = np.reshape(np.repeat(counts, counts), y.shape)
...     k = np.argmax(counts, axis=-1, keepdims=True)
...     return np.take_along_axis(y, k, axis=-1)[..., 0]
>>> res = vectorized_filter(input, vectorized_mode, size=5)
>>> np.all(res == ref)
np.True_

根据机器的不同,vectorized_filter 版本的速度可能快达 100 倍。