mirror of
https://github.com/wassname/scikit-image.git
synced 2026-07-07 18:37:41 +08:00
Merge pull request #632 from ahojnnes/local-blocks
Refactor N-dimensial array resampling and add additional functionality
This commit is contained in:
@@ -3,7 +3,7 @@ from ._regionprops import regionprops, perimeter
|
||||
from ._structural_similarity import structural_similarity
|
||||
from ._polygon import approximate_polygon, subdivide_polygon
|
||||
from .fit import LineModel, CircleModel, EllipseModel, ransac
|
||||
from ._sum_blocks import sum_blocks
|
||||
from .block import block_reduce
|
||||
|
||||
|
||||
__all__ = ['find_contours',
|
||||
@@ -16,4 +16,4 @@ __all__ = ['find_contours',
|
||||
'CircleModel',
|
||||
'EllipseModel',
|
||||
'ransac',
|
||||
'sum_blocks']
|
||||
'block_reduce']
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
def sum_blocks(array, factors):
|
||||
"""Sums the elements in blocks of integer factors and pads the original
|
||||
array with zeroes if the dimensions are not perfectly divisible by factors.
|
||||
|
||||
This function is different from resize and rescale in transform._warps in
|
||||
the sense that they use interpolation to upsample or downsample on a 2D
|
||||
array, while this function performs only dawnsampling but on any
|
||||
n-dimensional array and returns the sum of elements in a block of size
|
||||
factors in the original array.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
array : ndarray
|
||||
Input n-dimensional array.
|
||||
factors: tuple
|
||||
Tuple containing integer values representing block length along each
|
||||
axis.
|
||||
|
||||
Returns
|
||||
-------
|
||||
array : ndarray
|
||||
Downsampled array with same number of dimensions as that of input
|
||||
array.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> a = np.arange(15).reshape(3, 5)
|
||||
>>> a
|
||||
array([[ 0, 1, 2, 3, 4],
|
||||
[ 5, 6, 7, 8, 9],
|
||||
[10, 11, 12, 13, 14]])
|
||||
>>> sum_blocks(a, (2,3))
|
||||
array([[21, 24],
|
||||
[33, 27]])
|
||||
|
||||
"""
|
||||
from ..transform._warps import _downsample
|
||||
return _downsample(array, factors)
|
||||
@@ -0,0 +1,77 @@
|
||||
import numpy as np
|
||||
from skimage.util import view_as_blocks, pad
|
||||
|
||||
|
||||
def block_reduce(image, block_size, func=np.sum, cval=0):
|
||||
"""Down-sample image by applying function to local blocks.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
N-dimensional input image.
|
||||
block_size : array_like
|
||||
Array containing down-sampling integer factor along each axis.
|
||||
func : callable
|
||||
Function object which is used to calculate the return value for each
|
||||
local block. This function must implement an ``axis`` parameter such as
|
||||
``numpy.sum`` or ``numpy.min``.
|
||||
cval : float
|
||||
Constant padding value if image is not perfectly divisible by the
|
||||
block size.
|
||||
|
||||
Returns
|
||||
-------
|
||||
image : ndarray
|
||||
Down-sampled image with same number of dimensions as input image.
|
||||
|
||||
Examples
|
||||
--------
|
||||
>>> from skimage.measure import block_reduce
|
||||
>>> image = np.arange(3*3*4).reshape(3, 3, 4)
|
||||
>>> image
|
||||
array([[[ 0, 1, 2, 3],
|
||||
[ 4, 5, 6, 7],
|
||||
[ 8, 9, 10, 11]],
|
||||
|
||||
[[12, 13, 14, 15],
|
||||
[16, 17, 18, 19],
|
||||
[20, 21, 22, 23]],
|
||||
|
||||
[[24, 25, 26, 27],
|
||||
[28, 29, 30, 31],
|
||||
[32, 33, 34, 35]]])
|
||||
>>> block_reduce(image, block_size=(3, 3, 1), func=np.mean)
|
||||
array([[[ 16., 17., 18., 19.]]])
|
||||
>>> block_reduce(image, block_size=(1, 3, 4), func=np.max)
|
||||
array([[[11]],
|
||||
|
||||
[[23]],
|
||||
|
||||
[[35]]])
|
||||
>>> block_reduce(image, block_size=(3, 1, 4), func=np.max)
|
||||
array([[[27],
|
||||
[31],
|
||||
[35]]])
|
||||
"""
|
||||
|
||||
if len(block_size) != image.ndim:
|
||||
raise ValueError("`block_size` must have the same length "
|
||||
"as `image.shape`.")
|
||||
|
||||
pad_width = []
|
||||
for i in range(len(block_size)):
|
||||
if image.shape[i] % block_size[i] != 0:
|
||||
after_width = block_size[i] - (image.shape[i] % block_size[i])
|
||||
else:
|
||||
after_width = 0
|
||||
pad_width.append((0, after_width))
|
||||
|
||||
image = pad(image, pad_width=pad_width, mode='constant',
|
||||
constant_values=cval)
|
||||
|
||||
out = view_as_blocks(image, block_size)
|
||||
|
||||
for i in range(len(out.shape) // 2):
|
||||
out = func(out, axis=-1)
|
||||
|
||||
return out
|
||||
@@ -0,0 +1,81 @@
|
||||
import numpy as np
|
||||
from numpy.testing import assert_array_equal
|
||||
from skimage.measure import block_reduce
|
||||
|
||||
|
||||
def test_block_reduce_sum():
|
||||
image1 = np.arange(4 * 6).reshape(4, 6)
|
||||
out1 = block_reduce(image1, (2, 3))
|
||||
expected1 = np.array([[ 24, 42],
|
||||
[ 96, 114]])
|
||||
assert_array_equal(expected1, out1)
|
||||
|
||||
image2 = np.arange(5 * 8).reshape(5, 8)
|
||||
out2 = block_reduce(image2, (3, 3))
|
||||
expected2 = np.array([[ 81, 108, 87],
|
||||
[174, 192, 138]])
|
||||
assert_array_equal(expected2, out2)
|
||||
|
||||
|
||||
def test_block_reduce_mean():
|
||||
image1 = np.arange(4 * 6).reshape(4, 6)
|
||||
out1 = block_reduce(image1, (2, 3), func=np.mean)
|
||||
expected1 = np.array([[ 4., 7.],
|
||||
[ 16., 19.]])
|
||||
assert_array_equal(expected1, out1)
|
||||
|
||||
image2 = np.arange(5 * 8).reshape(5, 8)
|
||||
out2 = block_reduce(image2, (4, 5), func=np.mean)
|
||||
expected2 = np.array([[14. , 10.8],
|
||||
[ 8.5, 5.7]])
|
||||
assert_array_equal(expected2, out2)
|
||||
|
||||
|
||||
def test_block_reduce_median():
|
||||
image1 = np.arange(4 * 6).reshape(4, 6)
|
||||
out1 = block_reduce(image1, (2, 3), func=np.median)
|
||||
expected1 = np.array([[ 4., 7.],
|
||||
[ 16., 19.]])
|
||||
assert_array_equal(expected1, out1)
|
||||
|
||||
image2 = np.arange(5 * 8).reshape(5, 8)
|
||||
out2 = block_reduce(image2, (4, 5), func=np.median)
|
||||
expected2 = np.array([[ 14., 17.],
|
||||
[ 0., 0.]])
|
||||
assert_array_equal(expected2, out2)
|
||||
|
||||
image3 = np.array([[1, 5, 5, 5], [5, 5, 5, 1000]])
|
||||
out3 = block_reduce(image3, (2, 4), func=np.median)
|
||||
assert_array_equal(5, out3)
|
||||
|
||||
|
||||
def test_block_reduce_min():
|
||||
image1 = np.arange(4 * 6).reshape(4, 6)
|
||||
out1 = block_reduce(image1, (2, 3), func=np.min)
|
||||
expected1 = np.array([[ 0, 3],
|
||||
[12, 15]])
|
||||
assert_array_equal(expected1, out1)
|
||||
|
||||
image2 = np.arange(5 * 8).reshape(5, 8)
|
||||
out2 = block_reduce(image2, (4, 5), func=np.min)
|
||||
expected2 = np.array([[0, 0],
|
||||
[0, 0]])
|
||||
assert_array_equal(expected2, out2)
|
||||
|
||||
|
||||
def test_block_reduce_max():
|
||||
image1 = np.arange(4 * 6).reshape(4, 6)
|
||||
out1 = block_reduce(image1, (2, 3), func=np.max)
|
||||
expected1 = np.array([[ 8, 11],
|
||||
[20, 23]])
|
||||
assert_array_equal(expected1, out1)
|
||||
|
||||
image2 = np.arange(5 * 8).reshape(5, 8)
|
||||
out2 = block_reduce(image2, (4, 5), func=np.max)
|
||||
expected2 = np.array([[28, 31],
|
||||
[36, 39]])
|
||||
assert_array_equal(expected2, out2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
np.testing.run_module_suite()
|
||||
@@ -68,5 +68,6 @@ def test_invalid_input():
|
||||
|
||||
assert_raises(ValueError, ssim, X, X, win_size=8)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
np.testing.run_module_suite()
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
import numpy as np
|
||||
from numpy.testing import assert_array_equal
|
||||
from skimage.measure._sum_blocks import sum_blocks
|
||||
|
||||
def test_downsample_sum_blocks():
|
||||
"""Verifying downsampling of an array with expected result in sum mode"""
|
||||
image1 = np.arange(4*6).reshape(4, 6)
|
||||
out1 = sum_blocks(image1, (2, 3))
|
||||
expected1 = np.array([[ 24, 42],
|
||||
[ 96, 114]])
|
||||
assert_array_equal(expected1, out1)
|
||||
image2 = np.arange(5*8).reshape(5, 8)
|
||||
out2 = sum_blocks(image2, (3, 3))
|
||||
expected2 = np.array([[ 81, 108, 87],
|
||||
[174, 192, 138]])
|
||||
assert_array_equal(expected2, out2)
|
||||
@@ -9,7 +9,7 @@ from ._geometric import (warp, warp_coords, estimate_transform,
|
||||
SimilarityTransform, AffineTransform,
|
||||
ProjectiveTransform, PolynomialTransform,
|
||||
PiecewiseAffineTransform)
|
||||
from ._warps import swirl, resize, rotate, rescale, downscale_local_means
|
||||
from ._warps import swirl, resize, rotate, rescale, downscale_local_mean
|
||||
from .pyramids import (pyramid_reduce, pyramid_expand,
|
||||
pyramid_gaussian, pyramid_laplacian)
|
||||
|
||||
@@ -41,7 +41,7 @@ __all__ = ['hough_circle',
|
||||
'resize',
|
||||
'rotate',
|
||||
'rescale',
|
||||
'downscale_local_means',
|
||||
'downscale_local_mean',
|
||||
'pyramid_reduce',
|
||||
'pyramid_expand',
|
||||
'pyramid_gaussian',
|
||||
|
||||
+52
-98
@@ -1,18 +1,18 @@
|
||||
import numpy as np
|
||||
from scipy import ndimage
|
||||
|
||||
from ._geometric import warp, SimilarityTransform, AffineTransform
|
||||
from skimage.util.shape import view_as_blocks, _pad_asymmetric_zeros
|
||||
from skimage.transform._geometric import (warp, SimilarityTransform,
|
||||
AffineTransform)
|
||||
from skimage.measure import block_reduce
|
||||
|
||||
|
||||
def resize(image, output_shape, order=1, mode='constant', cval=0.):
|
||||
"""Resize image to match a certain size.
|
||||
|
||||
Resize performs interpolation to upsample or downsample 2D arrays. For
|
||||
downsampling any n-dimensional array by performing arithmetic sum or
|
||||
arithmetic mean, see measure._sum_blocks.sum_blocks and
|
||||
transform._warps.downscale_local_means respectively.
|
||||
|
||||
Performs interpolation to up-size or down-size images. For down-sampling
|
||||
N-dimensional images by applying the arithmetic sum or mean, see
|
||||
`skimage.measure.local_sum` and `skimage.transform.downscale_local_mean`,
|
||||
respectively.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -95,10 +95,10 @@ def resize(image, output_shape, order=1, mode='constant', cval=0.):
|
||||
def rescale(image, scale, order=1, mode='constant', cval=0.):
|
||||
"""Scale image by a certain factor.
|
||||
|
||||
Rescale performs interpolation to upsample or downsample 2D arrays. For
|
||||
downsampling any n-dimensional array by performing arithmetic sum or
|
||||
arithmetic mean, see measure._sum_blocks.sum_blocks and
|
||||
transform._warps.downscale_local_means respectively.
|
||||
Performs interpolation to upscale or down-scale images. For down-sampling
|
||||
N-dimensional images with integer factors by applying the arithmetic sum or
|
||||
mean, see `skimage.measure.local_sum` and
|
||||
`skimage.transform.downscale_local_mean`, respectively.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
@@ -226,6 +226,47 @@ def rotate(image, angle, resize=False, order=1, mode='constant', cval=0.):
|
||||
mode=mode, cval=cval)
|
||||
|
||||
|
||||
def downscale_local_mean(image, factors, cval=0):
|
||||
"""Down-sample N-dimensional image by local averaging.
|
||||
|
||||
The image is padded with `cval` if it is not perfectly divisible by the
|
||||
integer factors.
|
||||
|
||||
In contrast to the 2-D interpolation in `skimage.transform.resize` and
|
||||
`skimage.transform.rescale` this function may be applied to N-dimensional
|
||||
images and calculates the local mean of elements in each block of size
|
||||
`factors` in the input image.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
image : ndarray
|
||||
N-dimensional input image.
|
||||
factors : array_like
|
||||
Array containing down-sampling integer factor along each axis.
|
||||
cval : float, optional
|
||||
Constant padding value if image is not perfectly divisible by the
|
||||
integer factors.
|
||||
|
||||
Returns
|
||||
-------
|
||||
image : ndarray
|
||||
Down-sampled image with same number of dimensions as input image.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> a = np.arange(15).reshape(3, 5)
|
||||
>>> a
|
||||
array([[ 0, 1, 2, 3, 4],
|
||||
[ 5, 6, 7, 8, 9],
|
||||
[10, 11, 12, 13, 14]])
|
||||
>>> downscale_local_mean(a, (2, 3))
|
||||
array([[3.5, 4.],
|
||||
[5.5, 4.5]])
|
||||
|
||||
"""
|
||||
return block_reduce(image, factors, np.mean, cval)
|
||||
|
||||
|
||||
def _swirl_mapping(xy, center, rotation, strength, radius):
|
||||
x, y = xy.T
|
||||
x0, y0 = center
|
||||
@@ -296,90 +337,3 @@ def swirl(image, center=None, strength=1, radius=100, rotation=0,
|
||||
return warp(image, _swirl_mapping, map_args=warp_args,
|
||||
output_shape=output_shape,
|
||||
order=order, mode=mode, cval=cval)
|
||||
|
||||
|
||||
def _downsample(array, factors, sum=True):
|
||||
"""Performs downsampling with integer factors.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
array : ndarray
|
||||
Input n-dimensional array.
|
||||
factors: tuple
|
||||
Tuple containing downsampling factor along each axis.
|
||||
sum : bool
|
||||
If True, downsampled element is the sum of its corresponding
|
||||
constituent elements in the input array. Default is True.
|
||||
|
||||
Returns
|
||||
-------
|
||||
array : ndarray
|
||||
Downsampled array with same number of dimensions as that of input
|
||||
array.
|
||||
|
||||
"""
|
||||
|
||||
pad_size = []
|
||||
if len(factors) != array.ndim:
|
||||
raise ValueError("'factors' must have the same length "
|
||||
"as 'array.shape'")
|
||||
else:
|
||||
for i in range(len(factors)):
|
||||
if array.shape[i] % factors[i] != 0:
|
||||
pad_size.append(factors[i] - (array.shape[i] % factors[i]))
|
||||
else:
|
||||
pad_size.append(0)
|
||||
|
||||
for i in range(len(pad_size)):
|
||||
array = _pad_asymmetric_zeros(array, pad_size[i], i)
|
||||
|
||||
out = view_as_blocks(array, factors)
|
||||
block_shape = out.shape
|
||||
|
||||
if sum:
|
||||
for i in range(len(block_shape) // 2):
|
||||
out = out.sum(-1)
|
||||
else:
|
||||
for i in range(len(block_shape) // 2):
|
||||
out = out.mean(-1)
|
||||
return out
|
||||
|
||||
|
||||
def downscale_local_means(array, factors):
|
||||
"""Downsamples the array in blocks of input integer factors after padding
|
||||
the original array with zeroes if the dimensions are not perfectly
|
||||
divisible by factors and replaces it with mean i.e. average value.
|
||||
|
||||
This function is different from resize and rescale in the sense that they
|
||||
use interpolation to upsample or downsample on a 2D array, while this
|
||||
function performs only dawnsampling but on any n-dimensional array and
|
||||
returns the arithmetic mean of elements in a block of size factors in the
|
||||
original array.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
array : ndarray
|
||||
Input n-dimensional array.
|
||||
factors: tuple
|
||||
Tuple containing integer values representing block length along each
|
||||
axis.
|
||||
|
||||
Returns
|
||||
-------
|
||||
array : ndarray
|
||||
Downsampled array with same number of dimensions as that of input
|
||||
array.
|
||||
|
||||
Example
|
||||
-------
|
||||
>>> a = np.arange(15).reshape(3, 5)
|
||||
>>> a
|
||||
array([[ 0, 1, 2, 3, 4],
|
||||
[ 5, 6, 7, 8, 9],
|
||||
[10, 11, 12, 13, 14]])
|
||||
>>> downscale_local_means(a, (2,3))
|
||||
array([[3.5, 4.],
|
||||
[5.5, 4.5]])
|
||||
|
||||
"""
|
||||
return _downsample(array, factors, False)
|
||||
|
||||
@@ -6,7 +6,7 @@ from skimage.transform import (warp, warp_coords, rotate, resize, rescale,
|
||||
AffineTransform,
|
||||
ProjectiveTransform,
|
||||
SimilarityTransform,
|
||||
downscale_local_means)
|
||||
downscale_local_mean)
|
||||
from skimage import transform as tf, data, img_as_float
|
||||
from skimage.color import rgb2gray
|
||||
|
||||
@@ -195,15 +195,15 @@ def test_warp_coords_example():
|
||||
map_coordinates(image[:, :, 0], coords[:2])
|
||||
|
||||
|
||||
def test_downscale_local_means():
|
||||
"""Verifying downsampling of an array with expected result in mean mode"""
|
||||
image1 = np.arange(4*6).reshape(4, 6)
|
||||
out1 = downscale_local_means(image1, (2, 3))
|
||||
def test_downscale_local_mean():
|
||||
image1 = np.arange(4 * 6).reshape(4, 6)
|
||||
out1 = downscale_local_mean(image1, (2, 3))
|
||||
expected1 = np.array([[ 4., 7.],
|
||||
[ 16., 19.]])
|
||||
assert_array_equal(expected1, out1)
|
||||
image2 = np.arange(5*8).reshape(5, 8)
|
||||
out2 = downscale_local_means(image2, (4, 5))
|
||||
|
||||
image2 = np.arange(5 * 8).reshape(5, 8)
|
||||
out2 = downscale_local_mean(image2, (4, 5))
|
||||
expected2 = np.array([[ 14. , 10.8],
|
||||
[ 8.5, 5.7]])
|
||||
assert_array_equal(expected2, out2)
|
||||
|
||||
@@ -230,15 +230,3 @@ def view_as_windows(arr_in, window_shape):
|
||||
arr_out = as_strided(arr_in, shape=new_shape, strides=new_strides)
|
||||
|
||||
return arr_out
|
||||
|
||||
|
||||
def _pad_asymmetric_zeros(arr, pad_amt, axis=-1):
|
||||
"""Pads `arr` with zeros by `pad_amt` along specified `axis`"""
|
||||
if axis == -1:
|
||||
axis = arr.ndim - 1
|
||||
|
||||
zeroshape = tuple([x if i != axis else pad_amt
|
||||
for (i, x) in enumerate(arr.shape)])
|
||||
|
||||
return np.concatenate((arr, np.zeros(zeroshape, dtype=arr.dtype)),
|
||||
axis=axis)
|
||||
|
||||
Reference in New Issue
Block a user