From 0ae8c4a74bc99cb9de7229e961eacee1c7da0840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Mon, 15 Dec 2014 23:27:50 +0100 Subject: [PATCH 01/13] Clip to min and max range of input image, add missing clip parameter to other functions --- skimage/transform/_geometric.py | 52 ++++++++++++++------------- skimage/transform/_warps.py | 51 +++++++++++++++++--------- skimage/transform/_warps_cy.pyx | 1 - skimage/transform/tests/test_warps.py | 13 +++---- 4 files changed, 70 insertions(+), 47 deletions(-) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 456570f2..4d932371 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -994,6 +994,24 @@ def warp_coords(coord_map, shape, dtype=np.float64): return coords +def _clip_warp_output(input_image, output_image, clip, mode, order, cval): + """Clip output image to range of values of input image, considering the + parameters of a call to warp. + """ + if clip and order != 0: + min_val = input_image.min() + max_val = input_image.max() + + clipped = np.clip(output_image, min_val, max_val) + + if mode == 'constant' and not (min_val <= cval <= max_val): + clipped[output_image == cval] = cval + + return clipped + + return output_image + + def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, mode='constant', cval=0., clip=True): """Warp an image according to a given coordinate transformation. @@ -1055,17 +1073,17 @@ 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. 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 +1142,7 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, """ - image = img_as_float(image) + image = image.astype(np.double) input_shape = np.array(image.shape) if output_shape is None: @@ -1219,21 +1237,7 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, out = 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 - - clipped = np.clip(out, min_val, max_val) - - if mode == 'constant' and not (0 <= cval <= 1): - clipped[out == cval] = cval - - out = clipped + out = _clip_warp_output(image, out, clip, mode, order, cval) return out diff --git a/skimage/transform/_warps.py b/skimage/transform/_warps.py index 4edefe73..3b9e7072 100644 --- a/skimage/transform/_warps.py +++ b/skimage/transform/_warps.py @@ -2,11 +2,11 @@ import numpy as np from scipy import ndimage from skimage.transform._geometric import (warp, SimilarityTransform, - AffineTransform) + AffineTransform, _clip_warp_output) from skimage.measure import block_reduce -def resize(image, output_shape, order=1, mode='constant', cval=0.): +def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True): """Resize image to match a certain size. Performs interpolation to up-size or down-size images. For down-sampling @@ -40,6 +40,10 @@ 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. Examples -------- @@ -71,8 +75,10 @@ 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) + out = ndimage.map_coordinates(image, coord_map, order=order, + mode=mode, cval=cval) + + out = _clip_warp_output(image, out, clip, mode, order, cval) else: # 2-dimensional interpolation @@ -87,12 +93,12 @@ 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) return out -def rescale(image, scale, order=1, mode='constant', cval=0.): +def rescale(image, scale, order=1, mode='constant', cval=0, clip=True): """Scale image by a certain factor. Performs interpolation to upscale or down-scale images. For down-sampling @@ -124,6 +130,10 @@ 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. Examples -------- @@ -147,11 +157,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) -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): """Rotate image by a certain angle around its center. Parameters @@ -164,6 +175,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 +195,10 @@ 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. Examples -------- @@ -230,10 +245,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) -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 +309,7 @@ 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): """Perform a swirl transformation. Parameters @@ -330,6 +345,10 @@ 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. """ @@ -343,4 +362,4 @@ 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) + order=order, mode=mode, cval=cval, clip=clip) diff --git a/skimage/transform/_warps_cy.pyx b/skimage/transform/_warps_cy.pyx index 433c586d..47a28e35 100644 --- a/skimage/transform/_warps_cy.pyx +++ b/skimage/transform/_warps_cy.pyx @@ -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, diff --git a/skimage/transform/tests/test_warps.py b/skimage/transform/tests/test_warps.py index a2d6265f..240ab7d5 100644 --- a/skimage/transform/tests/test_warps.py +++ b/skimage/transform/tests/test_warps.py @@ -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(): From 07791674774a0105b92f268429b5a3deda4953ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 16 Dec 2014 01:59:21 +0100 Subject: [PATCH 02/13] Introduce parameter to keep range of values --- skimage/transform/_geometric.py | 11 ++++++++-- skimage/transform/_warps.py | 38 +++++++++++++++++++++++++-------- 2 files changed, 38 insertions(+), 11 deletions(-) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 4d932371..516ffd82 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -1013,7 +1013,7 @@ def _clip_warp_output(input_image, output_image, clip, mode, order, 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, keep_range=False): """Warp an image according to a given coordinate transformation. Parameters @@ -1076,6 +1076,9 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, 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. + keep_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`. Notes ----- @@ -1142,7 +1145,11 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, """ - image = image.astype(np.double) + if keep_range: + image = image.astype(np.double) + else: + image = img_as_float(image) + input_shape = np.array(image.shape) if output_shape is None: diff --git a/skimage/transform/_warps.py b/skimage/transform/_warps.py index 3b9e7072..53f07faa 100644 --- a/skimage/transform/_warps.py +++ b/skimage/transform/_warps.py @@ -6,7 +6,8 @@ from skimage.transform._geometric import (warp, SimilarityTransform, from skimage.measure import block_reduce -def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True): +def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True, + keep_range=False): """Resize image to match a certain size. Performs interpolation to up-size or down-size images. For down-sampling @@ -44,6 +45,9 @@ def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True): 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. + keep_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 -------- @@ -75,6 +79,11 @@ def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True): coord_map = np.array([map_rows, map_cols, map_dims]) + if keep_range: + image = image.astype(np.double) + else: + image = img_as_float(image) + out = ndimage.map_coordinates(image, coord_map, order=order, mode=mode, cval=cval) @@ -93,12 +102,13 @@ def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True): tform.estimate(src_corners, dst_corners) out = warp(image, tform, output_shape=output_shape, order=order, - mode=mode, cval=cval, clip=clip) + mode=mode, cval=cval, clip=clip, keep_range=keep_range) return out -def rescale(image, scale, order=1, mode='constant', cval=0, clip=True): +def rescale(image, scale, order=1, mode='constant', cval=0, clip=True, + keep_range=False): """Scale image by a certain factor. Performs interpolation to upscale or down-scale images. For down-sampling @@ -134,6 +144,9 @@ def rescale(image, scale, order=1, mode='constant', cval=0, clip=True): 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. + keep_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 -------- @@ -158,11 +171,11 @@ def rescale(image, scale, order=1, mode='constant', cval=0, clip=True): output_shape = (rows, cols) return resize(image, output_shape, order=order, mode=mode, cval=cval, - clip=clip) + clip=clip, keep_range=keep_range) def rotate(image, angle, resize=False, center=None, order=1, mode='constant', - cval=0, clip=True): + cval=0, clip=True, keep_range=False): """Rotate image by a certain angle around its center. Parameters @@ -199,6 +212,9 @@ def rotate(image, angle, resize=False, center=None, order=1, mode='constant', 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. + keep_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 -------- @@ -245,7 +261,7 @@ def rotate(image, angle, resize=False, center=None, order=1, mode='constant', tform = tform4 + tform return warp(image, tform, output_shape=output_shape, order=order, - mode=mode, cval=cval, clip=clip) + mode=mode, cval=cval, clip=clip, keep_range=keep_range) def downscale_local_mean(image, factors, cval=0, clip=True): @@ -309,7 +325,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, clip=True): + output_shape=None, order=1, mode='constant', cval=0, clip=True, + keep_range=False): """Perform a swirl transformation. Parameters @@ -349,6 +366,9 @@ def swirl(image, center=None, strength=1, radius=100, rotation=0, 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. + keep_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`. """ @@ -361,5 +381,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, clip=clip) + output_shape=output_shape, order=order, mode=mode, cval=cval, + clip=clip, keep_range=keep_range) From dedcee6543590b60309fdb846cc9104a1f938490 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 16 Dec 2014 02:16:40 +0100 Subject: [PATCH 03/13] Fix keep_range and add test cases --- skimage/transform/_geometric.py | 10 +++++++--- skimage/transform/_warps.py | 13 +++++++++---- skimage/transform/tests/test_warps.py | 21 +++++++++++++++++---- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 516ffd82..0b74fe48 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -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 @@ -1148,7 +1149,10 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, if keep_range: image = image.astype(np.double) else: - image = img_as_float(image) + if image.dtype == np.double: + image = rescale_intensity(image) + else: + image = img_as_float(image) input_shape = np.array(image.shape) diff --git a/skimage/transform/_warps.py b/skimage/transform/_warps.py index 53f07faa..7ebcad6f 100644 --- a/skimage/transform/_warps.py +++ b/skimage/transform/_warps.py @@ -1,9 +1,11 @@ import numpy as np from scipy import ndimage -from skimage.transform._geometric import (warp, SimilarityTransform, - AffineTransform, _clip_warp_output) -from skimage.measure import block_reduce +from ..util import img_as_float +from ..exposure import rescale_intensity +from ..measure import block_reduce +from ._geometric import (warp, SimilarityTransform, AffineTransform, + _clip_warp_output) def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True, @@ -82,7 +84,10 @@ def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True, if keep_range: image = image.astype(np.double) else: - image = img_as_float(image) + if image.dtype == np.double: + image = rescale_intensity(image) + else: + image = img_as_float(image) out = ndimage.map_coordinates(image, coord_map, order=order, mode=mode, cval=cval) diff --git a/skimage/transform/tests/test_warps.py b/skimage/transform/tests/test_warps.py index 240ab7d5..a18ad492 100644 --- a/skimage/transform/tests/test_warps.py +++ b/skimage/transform/tests/test_warps.py @@ -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 @@ -236,13 +236,13 @@ 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(): @@ -255,7 +255,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(): @@ -267,5 +267,18 @@ 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, keep_range=False, clip=True, order=0) + assert out.min() == 0 + assert out.max() == 1 + + out = rescale(image, 2, keep_range=True, clip=True, order=0) + assert out.min() == 0 + assert out.max() == 2 + + + if __name__ == "__main__": run_module_suite() From 3e881fe35c373802fd83d0bef44704076a9482c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 16 Dec 2014 02:22:12 +0100 Subject: [PATCH 04/13] Create helper function for image conversion --- skimage/transform/_geometric.py | 20 +++++++++++++------- skimage/transform/_warps.py | 12 ++---------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 0b74fe48..a44a5eb5 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -995,6 +995,18 @@ def warp_coords(coord_map, shape, dtype=np.float64): return coords +def _convert_warp_input(image, keep_range): + """Convert input image to double image with the appropriate range.""" + if keep_range: + image = image.astype(np.double) + else: + if image.dtype == np.double: + image = rescale_intensity(image) + else: + image = img_as_float(image) + return image + + def _clip_warp_output(input_image, output_image, clip, mode, order, cval): """Clip output image to range of values of input image, considering the parameters of a call to warp. @@ -1146,13 +1158,7 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, """ - if keep_range: - image = image.astype(np.double) - else: - if image.dtype == np.double: - image = rescale_intensity(image) - else: - image = img_as_float(image) + image = _convert_warp_input(image, keep_range) input_shape = np.array(image.shape) diff --git a/skimage/transform/_warps.py b/skimage/transform/_warps.py index 7ebcad6f..6dad0865 100644 --- a/skimage/transform/_warps.py +++ b/skimage/transform/_warps.py @@ -1,11 +1,9 @@ import numpy as np from scipy import ndimage -from ..util import img_as_float -from ..exposure import rescale_intensity from ..measure import block_reduce from ._geometric import (warp, SimilarityTransform, AffineTransform, - _clip_warp_output) + _convert_warp_input, _clip_warp_output) def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True, @@ -81,13 +79,7 @@ def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True, coord_map = np.array([map_rows, map_cols, map_dims]) - if keep_range: - image = image.astype(np.double) - else: - if image.dtype == np.double: - image = rescale_intensity(image) - else: - image = img_as_float(image) + image = _convert_warp_input(image, keep_range) out = ndimage.map_coordinates(image, coord_map, order=order, mode=mode, cval=cval) From 313032d487c8c45d211e664ccb18851c8a2a986f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 16 Dec 2014 04:28:06 +0100 Subject: [PATCH 05/13] Make sure 1x1 images are 2-D --- skimage/transform/_geometric.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index a44a5eb5..2e5d5e2b 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -1002,6 +1002,7 @@ def _convert_warp_input(image, keep_range): else: if image.dtype == np.double: image = rescale_intensity(image) + image = np.atleast_2d(image) else: image = img_as_float(image) return image From e35f9c303e57df00109db737a46549493278a5b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 16 Dec 2014 11:49:38 +0100 Subject: [PATCH 06/13] Remove test for invalid shape --- skimage/transform/tests/test_warps.py | 1 - 1 file changed, 1 deletion(-) diff --git a/skimage/transform/tests/test_warps.py b/skimage/transform/tests/test_warps.py index a18ad492..c84d57fe 100644 --- a/skimage/transform/tests/test_warps.py +++ b/skimage/transform/tests/test_warps.py @@ -246,7 +246,6 @@ def test_downscale_local_mean(): def test_invalid(): - assert_raises(ValueError, warp, np.ones((4, )), SimilarityTransform()) assert_raises(ValueError, warp, np.ones((4, 3, 3, 3)), SimilarityTransform()) From 515ec9fe980fc62b77e2704429020c42e836807f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Wed, 17 Dec 2014 12:59:16 +0100 Subject: [PATCH 07/13] Rename keep_range to preserve_range, fix test cases --- skimage/transform/_geometric.py | 16 ++++++---------- skimage/transform/_warps.py | 26 +++++++++++++------------- skimage/transform/tests/test_warps.py | 15 ++++++++++----- 3 files changed, 29 insertions(+), 28 deletions(-) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 2e5d5e2b..334b2164 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -995,16 +995,12 @@ def warp_coords(coord_map, shape, dtype=np.float64): return coords -def _convert_warp_input(image, keep_range): +def _convert_warp_input(image, preserve_range): """Convert input image to double image with the appropriate range.""" - if keep_range: + if preserve_range: image = image.astype(np.double) else: - if image.dtype == np.double: - image = rescale_intensity(image) - image = np.atleast_2d(image) - else: - image = img_as_float(image) + image = img_as_float(image) return image @@ -1027,7 +1023,7 @@ def _clip_warp_output(input_image, output_image, clip, mode, order, cval): def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, - mode='constant', cval=0., clip=True, keep_range=False): + mode='constant', cval=0., clip=True, preserve_range=False): """Warp an image according to a given coordinate transformation. Parameters @@ -1090,7 +1086,7 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, 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. - keep_range : bool, optional + 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`. @@ -1159,7 +1155,7 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, """ - image = _convert_warp_input(image, keep_range) + image = _convert_warp_input(image, preserve_range) input_shape = np.array(image.shape) diff --git a/skimage/transform/_warps.py b/skimage/transform/_warps.py index 6dad0865..2153dd58 100644 --- a/skimage/transform/_warps.py +++ b/skimage/transform/_warps.py @@ -7,7 +7,7 @@ from ._geometric import (warp, SimilarityTransform, AffineTransform, def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True, - keep_range=False): + preserve_range=False): """Resize image to match a certain size. Performs interpolation to up-size or down-size images. For down-sampling @@ -45,7 +45,7 @@ def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True, 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. - keep_range : bool, optional + 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`. @@ -79,7 +79,7 @@ def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True, coord_map = np.array([map_rows, map_cols, map_dims]) - image = _convert_warp_input(image, keep_range) + image = _convert_warp_input(image, preserve_range) out = ndimage.map_coordinates(image, coord_map, order=order, mode=mode, cval=cval) @@ -99,13 +99,13 @@ def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True, tform.estimate(src_corners, dst_corners) out = warp(image, tform, output_shape=output_shape, order=order, - mode=mode, cval=cval, clip=clip, keep_range=keep_range) + mode=mode, cval=cval, clip=clip, preserve_range=preserve_range) return out def rescale(image, scale, order=1, mode='constant', cval=0, clip=True, - keep_range=False): + preserve_range=False): """Scale image by a certain factor. Performs interpolation to upscale or down-scale images. For down-sampling @@ -141,7 +141,7 @@ def rescale(image, scale, order=1, mode='constant', cval=0, clip=True, 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. - keep_range : bool, optional + 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`. @@ -168,11 +168,11 @@ def rescale(image, scale, order=1, mode='constant', cval=0, clip=True, output_shape = (rows, cols) return resize(image, output_shape, order=order, mode=mode, cval=cval, - clip=clip, keep_range=keep_range) + clip=clip, preserve_range=preserve_range) def rotate(image, angle, resize=False, center=None, order=1, mode='constant', - cval=0, clip=True, keep_range=False): + cval=0, clip=True, preserve_range=False): """Rotate image by a certain angle around its center. Parameters @@ -209,7 +209,7 @@ def rotate(image, angle, resize=False, center=None, order=1, mode='constant', 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. - keep_range : bool, optional + 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`. @@ -258,7 +258,7 @@ def rotate(image, angle, resize=False, center=None, order=1, mode='constant', tform = tform4 + tform return warp(image, tform, output_shape=output_shape, order=order, - mode=mode, cval=cval, clip=clip, keep_range=keep_range) + mode=mode, cval=cval, clip=clip, preserve_range=preserve_range) def downscale_local_mean(image, factors, cval=0, clip=True): @@ -323,7 +323,7 @@ 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, clip=True, - keep_range=False): + preserve_range=False): """Perform a swirl transformation. Parameters @@ -363,7 +363,7 @@ def swirl(image, center=None, strength=1, radius=100, rotation=0, 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. - keep_range : bool, optional + 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`. @@ -379,4 +379,4 @@ 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, - clip=clip, keep_range=keep_range) + clip=clip, preserve_range=preserve_range) diff --git a/skimage/transform/tests/test_warps.py b/skimage/transform/tests/test_warps.py index c84d57fe..09a93d69 100644 --- a/skimage/transform/tests/test_warps.py +++ b/skimage/transform/tests/test_warps.py @@ -269,14 +269,19 @@ def test_slow_warp_nonint_oshape(): def test_keep_range(): image = np.linspace(0, 2, 25).reshape(5, 5) - out = rescale(image, 2, keep_range=False, clip=True, order=0) - assert out.min() == 0 - assert out.max() == 1 - - out = rescale(image, 2, keep_range=True, clip=True, order=0) + 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__": From a1a2f9c988b381e602a504779272cb5e534c92ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Mon, 22 Dec 2014 20:39:18 +0100 Subject: [PATCH 08/13] Remove __init__.py from tests folder, remove unnecessary load of test image --- skimage/feature/tests/__init__.py | 0 skimage/feature/tests/test_orb.py | 18 ++++++++---------- 2 files changed, 8 insertions(+), 10 deletions(-) delete mode 100644 skimage/feature/tests/__init__.py diff --git a/skimage/feature/tests/__init__.py b/skimage/feature/tests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/skimage/feature/tests/test_orb.py b/skimage/feature/tests/test_orb.py index 30394d07..5bed2bf3 100644 --- a/skimage/feature/tests/test_orb.py +++ b/skimage/feature/tests/test_orb.py @@ -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() From 20d81529f4a874ab18fca4fb18848609cad7d5ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 23 Dec 2014 12:40:15 +0100 Subject: [PATCH 09/13] Fix matching test cases --- skimage/feature/tests/test_match.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/feature/tests/test_match.py b/skimage/feature/tests/test_match.py index 1b0a622f..695116ed 100644 --- a/skimage/feature/tests/test_match.py +++ b/skimage/feature/tests/test_match.py @@ -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) From 451d8aa697f3670ce1a5f855007201d1f9828def Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 23 Dec 2014 12:44:16 +0100 Subject: [PATCH 10/13] Fix novice module with new dtype behavior --- skimage/novice/_novice.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/skimage/novice/_novice.py b/skimage/novice/_novice.py index c8fd9284..03422b2e 100644 --- a/skimage/novice/_novice.py +++ b/skimage/novice/_novice.py @@ -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() From 0d2c5def06d432084320c7ba14c2bfd1f35204bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 23 Dec 2014 13:51:25 +0100 Subject: [PATCH 11/13] Use in-place operation for clipping --- skimage/transform/_geometric.py | 14 ++++++++------ skimage/transform/_warps.py | 2 +- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 334b2164..2133c735 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -1012,14 +1012,16 @@ def _clip_warp_output(input_image, output_image, clip, mode, order, cval): min_val = input_image.min() max_val = input_image.max() - clipped = np.clip(output_image, min_val, max_val) + preserve_cval = mode == 'constant' and not \ + (min_val <= cval <= max_val) - if mode == 'constant' and not (min_val <= cval <= max_val): - clipped[output_image == cval] = cval + if preserve_cval: + cval_mask = output_image == cval - return clipped + np.clip(output_image, min_val, max_val, out=output_image) - return output_image + if preserve_cval: + output_image[cval_mask] = cval def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, @@ -1252,6 +1254,6 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, mode=mode, order=order, cval=cval) - out = _clip_warp_output(image, out, clip, mode, order, cval) + _clip_warp_output(image, out, clip, mode, order, cval) return out diff --git a/skimage/transform/_warps.py b/skimage/transform/_warps.py index 2153dd58..402f73ef 100644 --- a/skimage/transform/_warps.py +++ b/skimage/transform/_warps.py @@ -84,7 +84,7 @@ def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True, out = ndimage.map_coordinates(image, coord_map, order=order, mode=mode, cval=cval) - out = _clip_warp_output(image, out, clip, mode, order, cval) + _clip_warp_output(image, out, clip, mode, order, cval) else: # 2-dimensional interpolation From e070274355a096003bedb8c5370b9d227f7a9316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 23 Dec 2014 15:20:46 +0100 Subject: [PATCH 12/13] Add more description to doc string of helper function --- skimage/transform/_geometric.py | 35 +++++++++++++++++++++++++++++---- skimage/transform/_warps.py | 2 +- 2 files changed, 32 insertions(+), 5 deletions(-) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 2133c735..7bec5ba0 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -1004,10 +1004,37 @@ def _convert_warp_input(image, preserve_range): return image -def _clip_warp_output(input_image, output_image, clip, mode, order, cval): - """Clip output image to range of values of input image, considering the - parameters of a call to warp. +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() @@ -1254,6 +1281,6 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, mode=mode, order=order, cval=cval) - _clip_warp_output(image, out, clip, mode, order, cval) + _clip_warp_output(image, out, order, mode, cval, clip) return out diff --git a/skimage/transform/_warps.py b/skimage/transform/_warps.py index 402f73ef..a27c1d42 100644 --- a/skimage/transform/_warps.py +++ b/skimage/transform/_warps.py @@ -84,7 +84,7 @@ def resize(image, output_shape, order=1, mode='constant', cval=0, clip=True, out = ndimage.map_coordinates(image, coord_map, order=order, mode=mode, cval=cval) - _clip_warp_output(image, out, clip, mode, order, cval) + _clip_warp_output(image, out, order, mode, cval, clip) else: # 2-dimensional interpolation From e38ed91c248b9b84474234d2a8231ab36122bbc5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Johannes=20Sch=C3=B6nberger?= Date: Tue, 23 Dec 2014 15:22:22 +0100 Subject: [PATCH 13/13] Add missing return value description --- skimage/transform/_geometric.py | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 7bec5ba0..e658d23e 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -1119,6 +1119,11 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, 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 ----- - The input image is converted to a `double` image. @@ -1193,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 @@ -1229,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: @@ -1238,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) @@ -1277,10 +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) - _clip_warp_output(image, out, order, mode, cval, clip) + _clip_warp_output(image, warped, order, mode, cval, clip) - return out + return warped