Merge pull request #632 from ahojnnes/local-blocks

Refactor N-dimensial array resampling and add additional functionality
This commit is contained in:
Josh Warner
2013-07-31 14:10:18 -07:00
10 changed files with 222 additions and 175 deletions
+2 -2
View File
@@ -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']
-38
View File
@@ -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)
+77
View File
@@ -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
+81
View File
@@ -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()
-16
View File
@@ -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)
+2 -2
View File
@@ -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
View File
@@ -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)
+7 -7
View File
@@ -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)
-12
View File
@@ -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)