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数组,可选
必须定义size或footprint之一。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的维度以及用于size或origin的任何元组的长度必须与axes的长度匹配。footprint的第 i 个轴和这些元组中的第 i 个元素对应于axes的第 i 个元素。
- batch_memory整数,默认值: 2**30
传递给
function
的window
数组中数据所占用的最大字节数。
- 返回:
- outputndarray
过滤后的数组。其 dtype 是function的输出 dtype。如果function应用于单个窗口时为标量值,则输出形状与input相同(除非
mode=='valid'
;请参阅mode文档)。如果function应用于单个窗口时为多值,则对应维度在输出形状中的位置完全取决于function的行为;请参阅示例。
说明
此函数通过根据mode对input进行填充,然后在填充数组上的滑动窗口视图块上调用提供的function。这种方法非常简单灵活,因此该函数具有一些其他过滤函数不具备的许多功能(例如,内存控制、
float16
和复杂 dtype 支持,以及由function参数提供的任何 NaN 处理功能)。然而,这种暴力方法可能会执行大量的冗余工作。如果可能,请使用专门的滤镜(例如,使用
minimum_filter
而不是此函数与numpy.min
作为可调用对象;使用uniform_filter
而不是此函数与numpy.mean
作为可调用对象),因为它可能使用更高效的算法。当没有专门的滤镜可用时,如果function是一个向量化的纯 Python 可调用对象,此函数是理想选择。通过将
scipy.LowLevelCallable
传递给generic_filter
,可能会获得更好的性能。generic_filter
也可能更适合那些具有大滤镜足迹的昂贵可调用对象,以及非向量化的可调用对象(即,那些不支持axis
的)。此函数不提供某些ndimage函数所提供的
extra_arguments
或extra_keywords
参数。原因有二:用户可以实现直通(passthrough)功能:只需将原始可调用对象包装在另一个提供所需参数的函数中即可;例如,
function=lambda input, axis: function(input, *extra_arguments, axis=axis, **extra_keywords)
。在某些用例中,function需要除了input之外,还传入额外的滑动窗口数据给function。这尚未实现,但我们保留这些参数名称以备将来实现此功能,这将是增加能力,而不是为现有能力提供重复的接口。
示例
假设我们希望对一个
float16
图像执行偶数窗口大小的中值滤波。此外,图像中包含 NaN 值,我们希望将其忽略(并被滤镜有效移除)。median_filter
不支持float16
数据,当存在 NaN 时其行为未定义,并且对于偶数窗口大小,它不返回通常的样本中值——即两个中间元素的平均值。这将是vectorized_filter
与function=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()
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_filter
和scipy.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 倍。