Merge pull request #1302 from ahojnnes/warp

Clip to min and max range of input image, add missing clip parameter to ...
This commit is contained in:
Juan Nunez-Iglesias
2014-12-24 09:38:15 +11:00
8 changed files with 182 additions and 78 deletions
View File
+2 -2
View File
@@ -32,7 +32,7 @@ def test_binary_descriptors_lena_rotation_crosscheck_false():
img = data.lena()
img = rgb2gray(img)
tform = tf.SimilarityTransform(scale=1, rotation=0.15, translation=(0, 0))
rotated_img = tf.warp(img, tform)
rotated_img = tf.warp(img, tform, clip=False)
extractor = BRIEF(descriptor_size=512)
@@ -65,7 +65,7 @@ def test_binary_descriptors_lena_rotation_crosscheck_true():
img = data.lena()
img = rgb2gray(img)
tform = tf.SimilarityTransform(scale=1, rotation=0.15, translation=(0, 0))
rotated_img = tf.warp(img, tform)
rotated_img = tf.warp(img, tform, clip=False)
extractor = BRIEF(descriptor_size=512)
+8 -10
View File
@@ -1,11 +1,11 @@
import numpy as np
from numpy.testing import assert_array_equal, assert_almost_equal
from numpy.testing import assert_equal, assert_almost_equal, run_module_suite
from skimage.feature import ORB
from skimage.data import lena
from skimage import data
from skimage.color import rgb2gray
img = rgb2gray(lena())
img = rgb2gray(data.lena())
def test_keypoints_orb_desired_no_of_keypoints():
@@ -42,7 +42,6 @@ def test_keypoints_orb_desired_no_of_keypoints():
def test_keypoints_orb_less_than_desired_no_of_keypoints():
img = rgb2gray(lena())
detector_extractor = ORB(n_keypoints=15, fast_n=12,
fast_threshold=0.33, downscale=2, n_scales=2)
detector_extractor.detect(img)
@@ -102,14 +101,13 @@ def test_descriptor_orb():
detector_extractor.extract(img, detector_extractor.keypoints,
detector_extractor.scales,
detector_extractor.orientations)
assert_array_equal(exp_descriptors,
detector_extractor.descriptors[100:120, 10:20])
assert_equal(exp_descriptors,
detector_extractor.descriptors[100:120, 10:20])
detector_extractor.detect_and_extract(img)
assert_array_equal(exp_descriptors,
detector_extractor.descriptors[100:120, 10:20])
assert_equal(exp_descriptors,
detector_extractor.descriptors[100:120, 10:20])
if __name__ == '__main__':
from numpy import testing
testing.run_module_suite()
run_module_suite()
+3 -2
View File
@@ -353,8 +353,9 @@ class Picture(object):
if (value[0] != self.width) or (value[1] != self.height):
# skimage dimensions are flipped: y, x
new_size = (int(value[1]), int(value[0]))
new_array = resize(self.array, new_size, order=0)
self.array = img_as_ubyte(new_array)
new_array = resize(self.array, new_size, order=0,
preserve_range=True)
self.array = new_array.astype(np.uint8)
self._array_modified()
+85 -33
View File
@@ -4,8 +4,9 @@ import warnings
import numpy as np
from scipy import ndimage, spatial
from skimage._shared.utils import get_bound_method_class, safe_as_int
from skimage.util import img_as_float
from .._shared.utils import get_bound_method_class, safe_as_int
from ..util import img_as_float
from ..exposure import rescale_intensity
from ._warps_cy import _warp_fast
@@ -994,8 +995,64 @@ def warp_coords(coord_map, shape, dtype=np.float64):
return coords
def _convert_warp_input(image, preserve_range):
"""Convert input image to double image with the appropriate range."""
if preserve_range:
image = image.astype(np.double)
else:
image = img_as_float(image)
return image
def _clip_warp_output(input_image, output_image, order, mode, cval, clip):
"""Clip output image to range of values of input image.
Note that this function modifies the values of `output_image` in-place
and it is only modified if ``clip=True``.
Parameters
----------
input_image : ndarray
Input image.
output_image : ndarray
Output image, which is modified in-place.
Other parameters
----------------
order : int, optional
The order of the spline interpolation, default is 1. The order has to
be in the range 0-5. See `skimage.transform.warp` for detail.
mode : string, optional
Points outside the boundaries of the input are filled according
to the given mode ('constant', 'nearest', 'reflect' or 'wrap').
cval : float, optional
Used in conjunction with mode 'constant', the value outside
the image boundaries.
clip : bool, optional
Whether to clip the output to the range of values of the input image.
This is enabled by default, since higher order interpolation may
produce values outside the given input range.
"""
if clip and order != 0:
min_val = input_image.min()
max_val = input_image.max()
preserve_cval = mode == 'constant' and not \
(min_val <= cval <= max_val)
if preserve_cval:
cval_mask = output_image == cval
np.clip(output_image, min_val, max_val, out=output_image)
if preserve_cval:
output_image[cval_mask] = cval
def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1,
mode='constant', cval=0., clip=True):
mode='constant', cval=0., clip=True, preserve_range=False):
"""Warp an image according to a given coordinate transformation.
Parameters
@@ -1055,17 +1112,25 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1,
Used in conjunction with mode 'constant', the value outside
the image boundaries.
clip : bool, optional
Whether to clip the output to the float range of ``[0, 1]``, or
``[-1, 1]`` for input images with negative values. This is enabled by
default, since higher order interpolation may produce values outside
the given input range.
Whether to clip the output to the range of values of the input image.
This is enabled by default, since higher order interpolation may
produce values outside the given input range.
preserve_range : bool, optional
Whether to keep the original range of values. Otherwise, the input
image is converted according to the conventions of `img_as_float`.
Returns
-------
warped : double ndarray
The warped input image.
Notes
-----
In case of a `SimilarityTransform`, `AffineTransform` and
`ProjectiveTransform` and `order` in [0, 3] this function uses the
underlying transformation matrix to warp the image with a much faster
routine.
- The input image is converted to a `double` image.
- In case of a `SimilarityTransform`, `AffineTransform` and
`ProjectiveTransform` and `order` in [0, 3] this function uses the
underlying transformation matrix to warp the image with a much faster
routine.
Examples
--------
@@ -1124,7 +1189,8 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1,
"""
image = img_as_float(image)
image = _convert_warp_input(image, preserve_range)
input_shape = np.array(image.shape)
if output_shape is None:
@@ -1132,7 +1198,7 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1,
else:
output_shape = safe_as_int(output_shape)
out = None
warped = None
if order == 2:
# When fixing this issue, make sure to fix the branches further
@@ -1168,7 +1234,7 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1,
if matrix is not None:
matrix = matrix.astype(np.double)
if image.ndim == 2:
out = _warp_fast(image, matrix,
warped = _warp_fast(image, matrix,
output_shape=output_shape,
order=order, mode=mode, cval=cval)
elif image.ndim == 3:
@@ -1177,9 +1243,9 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1,
dims.append(_warp_fast(image[..., dim], matrix,
output_shape=output_shape,
order=order, mode=mode, cval=cval))
out = np.dstack(dims)
warped = np.dstack(dims)
if out is None:
if warped is None:
# use ndimage.map_coordinates
if (isinstance(inverse_map, np.ndarray)
@@ -1216,24 +1282,10 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1,
# Pre-filtering not necessary for order 0, 1 interpolation
prefilter = order > 1
out = ndimage.map_coordinates(image, coords, prefilter=prefilter,
warped = ndimage.map_coordinates(image, coords, prefilter=prefilter,
mode=mode, order=order, cval=cval)
if clip:
# The spline filters sometimes return results outside [0, 1],
# so clip to ensure valid data
if np.min(image) < 0:
min_val = -1
else:
min_val = 0
max_val = 1
_clip_warp_output(image, warped, order, mode, cval, clip)
clipped = np.clip(out, min_val, max_val)
if mode == 'constant' and not (0 <= cval <= 1):
clipped[out == cval] = cval
out = clipped
return out
return warped
+55 -19
View File
@@ -1,12 +1,13 @@
import numpy as np
from scipy import ndimage
from skimage.transform._geometric import (warp, SimilarityTransform,
AffineTransform)
from skimage.measure import block_reduce
from ..measure import block_reduce
from ._geometric import (warp, SimilarityTransform, AffineTransform,
_convert_warp_input, _clip_warp_output)
def resize(image, output_shape, order=1, mode='constant', cval=0.):
def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True,
preserve_range=False):
"""Resize image to match a certain size.
Performs interpolation to up-size or down-size images. For down-sampling
@@ -40,6 +41,13 @@ def resize(image, output_shape, order=1, mode='constant', cval=0.):
cval : float, optional
Used in conjunction with mode 'constant', the value outside
the image boundaries.
clip : bool, optional
Whether to clip the output to the range of values of the input image.
This is enabled by default, since higher order interpolation may
produce values outside the given input range.
preserve_range : bool, optional
Whether to keep the original range of values. Otherwise, the input
image is converted according to the conventions of `img_as_float`.
Examples
--------
@@ -71,8 +79,12 @@ def resize(image, output_shape, order=1, mode='constant', cval=0.):
coord_map = np.array([map_rows, map_cols, map_dims])
out = ndimage.map_coordinates(image, coord_map, order=order, mode=mode,
cval=cval)
image = _convert_warp_input(image, preserve_range)
out = ndimage.map_coordinates(image, coord_map, order=order,
mode=mode, cval=cval)
_clip_warp_output(image, out, order, mode, cval, clip)
else: # 2-dimensional interpolation
@@ -87,12 +99,13 @@ def resize(image, output_shape, order=1, mode='constant', cval=0.):
tform.estimate(src_corners, dst_corners)
out = warp(image, tform, output_shape=output_shape, order=order,
mode=mode, cval=cval)
mode=mode, cval=cval, clip=clip, preserve_range=preserve_range)
return out
def rescale(image, scale, order=1, mode='constant', cval=0.):
def rescale(image, scale, order=1, mode='constant', cval=0, clip=True,
preserve_range=False):
"""Scale image by a certain factor.
Performs interpolation to upscale or down-scale images. For down-sampling
@@ -124,6 +137,13 @@ def rescale(image, scale, order=1, mode='constant', cval=0.):
cval : float, optional
Used in conjunction with mode 'constant', the value outside
the image boundaries.
clip : bool, optional
Whether to clip the output to the range of values of the input image.
This is enabled by default, since higher order interpolation may
produce values outside the given input range.
preserve_range : bool, optional
Whether to keep the original range of values. Otherwise, the input
image is converted according to the conventions of `img_as_float`.
Examples
--------
@@ -147,11 +167,12 @@ def rescale(image, scale, order=1, mode='constant', cval=0.):
cols = np.round(col_scale * orig_cols)
output_shape = (rows, cols)
return resize(image, output_shape, order=order, mode=mode, cval=cval)
return resize(image, output_shape, order=order, mode=mode, cval=cval,
clip=clip, preserve_range=preserve_range)
def rotate(image, angle, resize=False, order=1, mode='constant', cval=0.,
center=None):
def rotate(image, angle, resize=False, center=None, order=1, mode='constant',
cval=0, clip=True, preserve_range=False):
"""Rotate image by a certain angle around its center.
Parameters
@@ -164,6 +185,9 @@ def rotate(image, angle, resize=False, order=1, mode='constant', cval=0.,
Determine whether the shape of the output image will be automatically
calculated, so the complete rotated image exactly fits. Default is
False.
center : iterable of length 2
The rotation center. If ``center=None``, the image is rotated around
its center, i.e. ``center=(rows / 2 - 0.5, cols / 2 - 0.5)``.
Returns
-------
@@ -181,9 +205,13 @@ def rotate(image, angle, resize=False, order=1, mode='constant', cval=0.,
cval : float, optional
Used in conjunction with mode 'constant', the value outside
the image boundaries.
center : iterable of length 2
The rotation center. If ``center=None``, the image is rotated around
its center, i.e. ``center=(rows / 2 - 0.5, cols / 2 - 0.5)``.
clip : bool, optional
Whether to clip the output to the range of values of the input image.
This is enabled by default, since higher order interpolation may
produce values outside the given input range.
preserve_range : bool, optional
Whether to keep the original range of values. Otherwise, the input
image is converted according to the conventions of `img_as_float`.
Examples
--------
@@ -230,10 +258,10 @@ def rotate(image, angle, resize=False, order=1, mode='constant', cval=0.,
tform = tform4 + tform
return warp(image, tform, output_shape=output_shape, order=order,
mode=mode, cval=cval)
mode=mode, cval=cval, clip=clip, preserve_range=preserve_range)
def downscale_local_mean(image, factors, cval=0):
def downscale_local_mean(image, factors, cval=0, clip=True):
"""Down-sample N-dimensional image by local averaging.
The image is padded with `cval` if it is not perfectly divisible by the
@@ -294,7 +322,8 @@ def _swirl_mapping(xy, center, rotation, strength, radius):
def swirl(image, center=None, strength=1, radius=100, rotation=0,
output_shape=None, order=1, mode='constant', cval=0):
output_shape=None, order=1, mode='constant', cval=0, clip=True,
preserve_range=False):
"""Perform a swirl transformation.
Parameters
@@ -330,6 +359,13 @@ def swirl(image, center=None, strength=1, radius=100, rotation=0,
cval : float, optional
Used in conjunction with mode 'constant', the value outside
the image boundaries.
clip : bool, optional
Whether to clip the output to the range of values of the input image.
This is enabled by default, since higher order interpolation may
produce values outside the given input range.
preserve_range : bool, optional
Whether to keep the original range of values. Otherwise, the input
image is converted according to the conventions of `img_as_float`.
"""
@@ -342,5 +378,5 @@ def swirl(image, center=None, strength=1, radius=100, rotation=0,
'radius': radius}
return warp(image, _swirl_mapping, map_args=warp_args,
output_shape=output_shape,
order=order, mode=mode, cval=cval)
output_shape=output_shape, order=order, mode=mode, cval=cval,
clip=clip, preserve_range=preserve_range)
-1
View File
@@ -3,7 +3,6 @@
#cython: nonecheck=False
#cython: wraparound=False
import numpy as np
cimport numpy as cnp
from skimage._shared.interpolation cimport (nearest_neighbour_interpolation,
bilinear_interpolation,
+29 -11
View File
@@ -1,5 +1,5 @@
from numpy.testing import (assert_almost_equal, run_module_suite,
assert_array_equal, assert_raises)
assert_equal, assert_raises)
import numpy as np
from scipy.ndimage import map_coordinates
@@ -75,14 +75,15 @@ def test_warp_nd():
def test_warp_clip():
x = 2 * np.ones((5, 5), dtype=np.double)
matrix = np.eye(3)
x = np.zeros((5, 5), dtype=np.double)
x[2, 2] = 1
outx = warp(x, matrix, order=0, clip=False)
assert_almost_equal(x, outx)
outx = rescale(x, 3, order=3, clip=False)
assert outx.min() < 0
outx = warp(x, matrix, order=0, clip=True)
assert_almost_equal(x / 2, outx)
outx = rescale(x, 3, order=3, clip=True)
assert_almost_equal(outx.min(), 0)
assert_almost_equal(outx.max(), 1)
def test_homography():
@@ -235,17 +236,16 @@ def test_downscale_local_mean():
out1 = downscale_local_mean(image1, (2, 3))
expected1 = np.array([[ 4., 7.],
[ 16., 19.]])
assert_array_equal(expected1, out1)
assert_equal(expected1, out1)
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)
assert_equal(expected2, out2)
def test_invalid():
assert_raises(ValueError, warp, np.ones((4, )), SimilarityTransform())
assert_raises(ValueError, warp, np.ones((4, 3, 3, 3)),
SimilarityTransform())
@@ -254,7 +254,7 @@ def test_inverse():
tform = SimilarityTransform(scale=0.5, rotation=0.1)
inverse_tform = SimilarityTransform(matrix=np.linalg.inv(tform.params))
image = np.arange(10 * 10).reshape(10, 10).astype(np.double)
assert_array_equal(warp(image, inverse_tform), warp(image, tform.inverse))
assert_equal(warp(image, inverse_tform), warp(image, tform.inverse))
def test_slow_warp_nonint_oshape():
@@ -266,5 +266,23 @@ def test_slow_warp_nonint_oshape():
warp(image, lambda xy: xy, output_shape=(13.0001, 19.9999))
def test_keep_range():
image = np.linspace(0, 2, 25).reshape(5, 5)
out = rescale(image, 2, preserve_range=False, clip=True, order=0)
assert out.min() == 0
assert out.max() == 2
out = rescale(image, 2, preserve_range=True, clip=True, order=0)
assert out.min() == 0
assert out.max() == 2
out = rescale(image.astype(np.uint8), 2, preserve_range=False,
clip=True, order=0)
assert out.min() == 0
assert out.max() == 2 / 255.0
if __name__ == "__main__":
run_module_suite()