From 69f5e9d5fe9d6f07d44f7d1e63c5bac7e7f2939b Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Thu, 10 Apr 2014 12:11:45 +1000 Subject: [PATCH 01/16] Update viewer example to use plugin outputs --- doc/source/user_guide/viewer.txt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/doc/source/user_guide/viewer.txt b/doc/source/user_guide/viewer.txt index da7f0c50..4cdde7db 100644 --- a/doc/source/user_guide/viewer.txt +++ b/doc/source/user_guide/viewer.txt @@ -32,7 +32,14 @@ let's see an example of how a pre-defined plugin is added to the viewer: viewer = ImageViewer(image) viewer += LineProfile(viewer) - viewer.show() + overlay, data = viewer.show()[0] + +The viewer's ``show`` method will return an overlay (of the same shape as the +input image) and data (possibly ``None``) for each attached plugin. In this +case, there is only one plugin, so we can directly name both. You can see the +plugin class's ``output`` method to see what will be returned. Here, +``overlay`` contains a drawing of the line (including its width), and ``data`` +contains the measured line profile. At the moment, there are not many plugins pre-defined, but there is a really simple interface for creating your own plugin. First, let us create a plugin to @@ -74,8 +81,10 @@ All that's left is to create an image viewer and add the plugin to that viewer. viewer = ImageViewer(image) viewer += denoise_plugin - viewer.show() + denoised = viewer.show()[0][0] +When we close the viewer, ``denoised`` will contain the filtered image for the +last used setting of ``weight``. .. image:: data/denoise_viewer_window.png .. image:: data/denoise_plugin_window.png From 9ef9045875d09d1ed324ceb398f9133ea0ddb2aa Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Thu, 10 Apr 2014 12:16:14 +1000 Subject: [PATCH 02/16] Update lineprofile examples to use returned output --- viewer_examples/plugins/lineprofile.py | 2 +- viewer_examples/plugins/lineprofile_rgb.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/viewer_examples/plugins/lineprofile.py b/viewer_examples/plugins/lineprofile.py index 2f1b2cdc..dd80e277 100644 --- a/viewer_examples/plugins/lineprofile.py +++ b/viewer_examples/plugins/lineprofile.py @@ -6,4 +6,4 @@ from skimage.viewer.plugins.lineprofile import LineProfile image = data.camera() viewer = ImageViewer(image) viewer += LineProfile() -viewer.show() +line, profile = viewer.show()[0] diff --git a/viewer_examples/plugins/lineprofile_rgb.py b/viewer_examples/plugins/lineprofile_rgb.py index 86b71d5d..b08f5a00 100644 --- a/viewer_examples/plugins/lineprofile_rgb.py +++ b/viewer_examples/plugins/lineprofile_rgb.py @@ -6,4 +6,4 @@ from skimage.viewer.plugins.lineprofile import LineProfile image = data.chelsea() viewer = ImageViewer(image) viewer += LineProfile() -viewer.show() +line, profiles = viewer.show()[0] From 17541e2cdc43d8ea26e870b72281c8ef3719423c Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Thu, 10 Apr 2014 12:19:23 +1000 Subject: [PATCH 03/16] Update plugin class doc example to use output --- skimage/viewer/plugins/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skimage/viewer/plugins/base.py b/skimage/viewer/plugins/base.py index dd3aebb0..baded1cb 100644 --- a/skimage/viewer/plugins/base.py +++ b/skimage/viewer/plugins/base.py @@ -63,9 +63,9 @@ class Plugin(QtGui.QDialog): >>> plugin += Slider('threshold', 0, 255) # doctest: +SKIP >>> >>> image = data.coins() - >>> viewer = ImageViewer(image) # doctest: +SKIP - >>> viewer += plugin # doctest: +SKIP - >>> viewer.show() # doctest: +SKIP + >>> viewer = ImageViewer(image) # doctest: +SKIP + >>> viewer += plugin # doctest: +SKIP + >>> thresholded = viewer.show()[0][0] # doctest: +SKIP The plugin will automatically delegate parameters to `image_filter` based on its parameter type, i.e., `ptype` (widgets for required arguments must From f2699bd731760b9d938680a7c4ed5fc5874ce228 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Mon, 21 Apr 2014 00:11:55 +0200 Subject: [PATCH 04/16] Update Canny examples --- viewer_examples/plugins/canny.py | 2 +- viewer_examples/plugins/canny_simple.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/viewer_examples/plugins/canny.py b/viewer_examples/plugins/canny.py index eaa33330..686034b2 100644 --- a/viewer_examples/plugins/canny.py +++ b/viewer_examples/plugins/canny.py @@ -6,4 +6,4 @@ from skimage.viewer.plugins.canny import CannyPlugin image = data.camera() viewer = ImageViewer(image) viewer += CannyPlugin() -viewer.show() +canny_edges = viewer.show()[0][0] diff --git a/viewer_examples/plugins/canny_simple.py b/viewer_examples/plugins/canny_simple.py index c26ca08d..641ca138 100644 --- a/viewer_examples/plugins/canny_simple.py +++ b/viewer_examples/plugins/canny_simple.py @@ -21,4 +21,4 @@ plugin += SaveButtons(name='Save overlay to:') # Finally, attach the plugin to an image viewer. viewer = ImageViewer(image) viewer += plugin -viewer.show() +canny_edges = viewer.show()[0][0] From 00b4fa45e70b16454864c57f5b3b5b32dab3fa91 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Fri, 2 May 2014 16:01:35 +0200 Subject: [PATCH 05/16] Always interpret provided shapes as int. Add a note on using output_shape for color images. --- skimage/transform/_geometric.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 883aa88d..3c14f709 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -1003,7 +1003,8 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, Keyword arguments passed to `inverse_map`. output_shape : tuple (rows, cols), optional Shape of the output image generated. By default the shape of the input - image is preserved. + image is preserved. Note that, even for multi-band images, only rows + and columns need to be specified. order : int, optional The order of interpolation. The order has to be in the range 0-5: * 0: Nearest-neighbor @@ -1073,6 +1074,7 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, image = np.atleast_3d(img_as_float(image)) ishape = np.array(image.shape) bands = ishape[2] + output_shape = np.array(output_shape, dtype=int) out = None @@ -1102,8 +1104,8 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, dims = [] for dim in range(image.shape[2]): dims.append(_warp_fast(image[..., dim], matrix, - output_shape=output_shape, - order=order, mode=mode, cval=cval)) + output_shape=output_shape, + order=order, mode=mode, cval=cval)) out = np.dstack(dims) if orig_ndim == 2: out = out[..., 0] From 4c7cd9bd654c1a088c72391048ced98f5c97bdc7 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Fri, 2 May 2014 16:23:41 +0200 Subject: [PATCH 06/16] Add unit test to catch non-integer output shape bug --- skimage/transform/_geometric.py | 11 ++++++----- skimage/transform/tests/test_warps.py | 5 +++++ 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 3c14f709..53074c00 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -1074,7 +1074,12 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, image = np.atleast_3d(img_as_float(image)) ishape = np.array(image.shape) bands = ishape[2] - output_shape = np.array(output_shape, dtype=int) + + if output_shape is None: + output_shape = ishape + else: + output_shape = np.array(output_shape, dtype=int) + out = None @@ -1111,10 +1116,6 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, out = out[..., 0] if out is None: # use ndimage.map_coordinates - - if output_shape is None: - output_shape = ishape - rows, cols = output_shape[:2] # inverse_map is a transformation matrix as numpy array diff --git a/skimage/transform/tests/test_warps.py b/skimage/transform/tests/test_warps.py index e49ab098..f85125d0 100644 --- a/skimage/transform/tests/test_warps.py +++ b/skimage/transform/tests/test_warps.py @@ -248,5 +248,10 @@ def test_inverse(): assert_array_equal(warp(image, inverse_tform), warp(image, tform.inverse)) +def test_slow_warp_nonint_oshape(): + image = np.random.random((5, 5)) + warp(image, lambda xy: xy, output_shape=(13.1, 19.5)) + + if __name__ == "__main__": run_module_suite() From e9c793eb177661b34d39f0acaa834d7349b3d88c Mon Sep 17 00:00:00 2001 From: "Josh Warner (Mac)" Date: Tue, 6 May 2014 13:09:03 -0500 Subject: [PATCH 07/16] FEAT: Shared function for safe int casting --- skimage/_shared/safe_int_cast.py | 61 +++++++++++++++++++++ skimage/_shared/tests/test_safe_int_cast.py | 36 ++++++++++++ 2 files changed, 97 insertions(+) create mode 100644 skimage/_shared/safe_int_cast.py create mode 100644 skimage/_shared/tests/test_safe_int_cast.py diff --git a/skimage/_shared/safe_int_cast.py b/skimage/_shared/safe_int_cast.py new file mode 100644 index 00000000..0733cf61 --- /dev/null +++ b/skimage/_shared/safe_int_cast.py @@ -0,0 +1,61 @@ +import numpy as np + +__all__ == ['_safe_int'] + + +def _safe_int_cast(val, atol=1e-7): + """ + Attempt to safely cast values to integer format. + + Parameters + ---------- + val : scalar or iterable of scalars + Number or container of numbers which are intended to be interpreted as + integers, e.g., for indexing purposes, but which may not carry integer + type. + atol : float + Absolute tolerance away from nearest integer to consider values in + ``val`` functionally integers. + + Returns + ------- + val_int : NumPy scalar or ndarray of dtype `np.int32` + Returns the input value(s) coerced to dtype `np.int32` assuming all + were within ``atol`` of the nearest integer. + + Notes + ----- + This operation calculates ``val`` modulo 1, which returns the mantissa of + all values. Then all mantissas greater than 0.5 are subtracted from one. + Finally, the absolute tolerance from zero is calculated. If it is less + than ``atol`` for all value(s) in ``val``, they are rounded and returned + in an integer array. Or, if ``val`` was a scalar, a NumPy scalar type is + returned. + + If any value(s) are outside the specified tolerance, an informative error + is raised. + + Examples + -------- + >>> _safe_int_cast(7.0) + 7 + >>> _safe_int_cast([9, 4, 2.9999999999]) + array([9, 4, 3], dtype=int32) + + """ + mod = np.asarray(val) % 1 # Modulo 1 + + # Check for and subtract any mod values > 0.5 from 1 + if mod.ndim == 0: # Scalar input, cannot be indexed + if mod > 0.5: + mod = 1 - mod + else: # Iterable input, now ndarray + mod[mod > 0.5] = 1 - mod[mod > 0.5] # Test on each side of nearest int + + try: + np.testing.assert_allclose(mod, 0, atol=atol) + except AssertionError: + raise ValueError("Integer argument required but received " + "{0}, check inputs.".format(val)) + + return np.round(val).astype(np.int32) diff --git a/skimage/_shared/tests/test_safe_int_cast.py b/skimage/_shared/tests/test_safe_int_cast.py new file mode 100644 index 00000000..27f88323 --- /dev/null +++ b/skimage/_shared/tests/test_safe_int_cast.py @@ -0,0 +1,36 @@ +import numpy as np +from skimage._shared.safe_int_cast import _safe_int_cast + + +def test_int_cast_not_possible(): + np.testing.assert_raises(ValueError, _safe_int_cast, 7.1) + np.testing.assert_raises(ValueError, _safe_int_cast, [7.1, 0.9]) + np.testing.assert_raises(ValueError, _safe_int_cast, np.r_[7.1, 0.9]) + np.testing.assert_raises(ValueError, _safe_int_cast, (7.1, 0.9)) + np.testing.assert_raises(ValueError, _safe_int_cast, ((3, 4, 1), + (2, 7.6, 289))) + + np.testing.assert_raises(ValueError, _safe_int_cast, 7.1, 0.09) + np.testing.assert_raises(ValueError, _safe_int_cast, [7.1, 0.9], 0.09) + np.testing.assert_raises(ValueError, _safe_int_cast, np.r_[7.1, 0.9], 0.09) + np.testing.assert_raises(ValueError, _safe_int_cast, (7.1, 0.9), 0.09) + np.testing.assert_raises(ValueError, _safe_int_cast, ((3, 4, 1), + (2, 7.6, 289)), 0.25) + + +def test_int_cast_possible(): + np.testing.assert_equal(_safe_int_cast(7.1, atol=0.11), 7) + np.testing.assert_equal(_safe_int_cast(-7.1, atol=0.11), -7) + np.testing.assert_equal(_safe_int_cast(41.9, atol=0.11), 42) + np.testing.assert_array_equal(_safe_int_cast([2, 42, 5789234.0, 87, 4]), + np.r_[2, 42, 5789234, 87, 4]) + np.testing.assert_array_equal(_safe_int_cast(np.r_[[[3, 4, 1.000000001], + [7, 2, -8.999999999], + [6, 9, -4234918347.]]]), + np.r_[[[3, 4, 1], + [7, 2, -9], + [6, 9, -4234918347]]]) + + +if __name__ == '__main__': + np.testing.run_module_suite() From 07d3e6ede458093efa9b738c544f5e6e534718dc Mon Sep 17 00:00:00 2001 From: "Josh Warner (Mac)" Date: Wed, 7 May 2014 19:09:18 -0500 Subject: [PATCH 08/16] Convert name to _safe_as_int, output np.int64, expand Examples --- .../{safe_int_cast.py => safe_as_int.py} | 25 ++++++++----- skimage/_shared/tests/test_safe_as_int.py | 36 +++++++++++++++++++ skimage/_shared/tests/test_safe_int_cast.py | 36 ------------------- 3 files changed, 53 insertions(+), 44 deletions(-) rename skimage/_shared/{safe_int_cast.py => safe_as_int.py} (75%) create mode 100644 skimage/_shared/tests/test_safe_as_int.py delete mode 100644 skimage/_shared/tests/test_safe_int_cast.py diff --git a/skimage/_shared/safe_int_cast.py b/skimage/_shared/safe_as_int.py similarity index 75% rename from skimage/_shared/safe_int_cast.py rename to skimage/_shared/safe_as_int.py index 0733cf61..0d665f9f 100644 --- a/skimage/_shared/safe_int_cast.py +++ b/skimage/_shared/safe_as_int.py @@ -1,9 +1,9 @@ import numpy as np -__all__ == ['_safe_int'] +__all__ = ['_safe_as_int'] -def _safe_int_cast(val, atol=1e-7): +def _safe_as_int(val, atol=1e-7): """ Attempt to safely cast values to integer format. @@ -19,8 +19,8 @@ def _safe_int_cast(val, atol=1e-7): Returns ------- - val_int : NumPy scalar or ndarray of dtype `np.int32` - Returns the input value(s) coerced to dtype `np.int32` assuming all + val_int : NumPy scalar or ndarray of dtype `np.int64` + Returns the input value(s) coerced to dtype `np.int64` assuming all were within ``atol`` of the nearest integer. Notes @@ -37,13 +37,22 @@ def _safe_int_cast(val, atol=1e-7): Examples -------- - >>> _safe_int_cast(7.0) + >>> _safe_as_int(7.0) 7 - >>> _safe_int_cast([9, 4, 2.9999999999]) + + >>> _safe_as_int([9, 4, 2.9999999999]) array([9, 4, 3], dtype=int32) + >>> _safe_as_int(53.01) + Traceback (most recent call last): + ... + ValueError: Integer argument required but received 53.1, check inputs. + + >>> _safe_as_int(53.01, atol=0.01) + 53 + """ - mod = np.asarray(val) % 1 # Modulo 1 + mod = np.asarray(val) % 1 # Extract mantissa # Check for and subtract any mod values > 0.5 from 1 if mod.ndim == 0: # Scalar input, cannot be indexed @@ -58,4 +67,4 @@ def _safe_int_cast(val, atol=1e-7): raise ValueError("Integer argument required but received " "{0}, check inputs.".format(val)) - return np.round(val).astype(np.int32) + return np.round(val).astype(np.int64) diff --git a/skimage/_shared/tests/test_safe_as_int.py b/skimage/_shared/tests/test_safe_as_int.py new file mode 100644 index 00000000..1a3674e0 --- /dev/null +++ b/skimage/_shared/tests/test_safe_as_int.py @@ -0,0 +1,36 @@ +import numpy as np +from skimage._shared.safe_int_cast import _safe_as_int + + +def test_int_cast_not_possible(): + np.testing.assert_raises(ValueError, _safe_as_int, 7.1) + np.testing.assert_raises(ValueError, _safe_as_int, [7.1, 0.9]) + np.testing.assert_raises(ValueError, _safe_as_int, np.r_[7.1, 0.9]) + np.testing.assert_raises(ValueError, _safe_as_int, (7.1, 0.9)) + np.testing.assert_raises(ValueError, _safe_as_int, ((3, 4, 1), + (2, 7.6, 289))) + + np.testing.assert_raises(ValueError, _safe_as_int, 7.1, 0.09) + np.testing.assert_raises(ValueError, _safe_as_int, [7.1, 0.9], 0.09) + np.testing.assert_raises(ValueError, _safe_as_int, np.r_[7.1, 0.9], 0.09) + np.testing.assert_raises(ValueError, _safe_as_int, (7.1, 0.9), 0.09) + np.testing.assert_raises(ValueError, _safe_as_int, ((3, 4, 1), + (2, 7.6, 289)), 0.25) + + +def test_int_cast_possible(): + np.testing.assert_equal(_safe_as_int(7.1, atol=0.11), 7) + np.testing.assert_equal(_safe_as_int(-7.1, atol=0.11), -7) + np.testing.assert_equal(_safe_as_int(41.9, atol=0.11), 42) + np.testing.assert_array_equal(_safe_as_int([2, 42, 5789234.0, 87, 4]), + np.r_[2, 42, 5789234, 87, 4]) + np.testing.assert_array_equal(_safe_as_int(np.r_[[[3, 4, 1.000000001], + [7, 2, -8.999999999], + [6, 9, -4234918347.]]]), + np.r_[[[3, 4, 1], + [7, 2, -9], + [6, 9, -4234918347]]]) + + +if __name__ == '__main__': + np.testing.run_module_suite() diff --git a/skimage/_shared/tests/test_safe_int_cast.py b/skimage/_shared/tests/test_safe_int_cast.py deleted file mode 100644 index 27f88323..00000000 --- a/skimage/_shared/tests/test_safe_int_cast.py +++ /dev/null @@ -1,36 +0,0 @@ -import numpy as np -from skimage._shared.safe_int_cast import _safe_int_cast - - -def test_int_cast_not_possible(): - np.testing.assert_raises(ValueError, _safe_int_cast, 7.1) - np.testing.assert_raises(ValueError, _safe_int_cast, [7.1, 0.9]) - np.testing.assert_raises(ValueError, _safe_int_cast, np.r_[7.1, 0.9]) - np.testing.assert_raises(ValueError, _safe_int_cast, (7.1, 0.9)) - np.testing.assert_raises(ValueError, _safe_int_cast, ((3, 4, 1), - (2, 7.6, 289))) - - np.testing.assert_raises(ValueError, _safe_int_cast, 7.1, 0.09) - np.testing.assert_raises(ValueError, _safe_int_cast, [7.1, 0.9], 0.09) - np.testing.assert_raises(ValueError, _safe_int_cast, np.r_[7.1, 0.9], 0.09) - np.testing.assert_raises(ValueError, _safe_int_cast, (7.1, 0.9), 0.09) - np.testing.assert_raises(ValueError, _safe_int_cast, ((3, 4, 1), - (2, 7.6, 289)), 0.25) - - -def test_int_cast_possible(): - np.testing.assert_equal(_safe_int_cast(7.1, atol=0.11), 7) - np.testing.assert_equal(_safe_int_cast(-7.1, atol=0.11), -7) - np.testing.assert_equal(_safe_int_cast(41.9, atol=0.11), 42) - np.testing.assert_array_equal(_safe_int_cast([2, 42, 5789234.0, 87, 4]), - np.r_[2, 42, 5789234, 87, 4]) - np.testing.assert_array_equal(_safe_int_cast(np.r_[[[3, 4, 1.000000001], - [7, 2, -8.999999999], - [6, 9, -4234918347.]]]), - np.r_[[[3, 4, 1], - [7, 2, -9], - [6, 9, -4234918347]]]) - - -if __name__ == '__main__': - np.testing.run_module_suite() From 4aa162fc783629075baaf1a4bc615c95551ee3fa Mon Sep 17 00:00:00 2001 From: "Josh Warner (Mac)" Date: Wed, 7 May 2014 22:10:27 -0500 Subject: [PATCH 09/16] FIX: Remove unnecessary .transpose introduced in PR #984 --- skimage/segmentation/random_walker_segmentation.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/segmentation/random_walker_segmentation.py b/skimage/segmentation/random_walker_segmentation.py index d722569b..731518e3 100644 --- a/skimage/segmentation/random_walker_segmentation.py +++ b/skimage/segmentation/random_walker_segmentation.py @@ -389,7 +389,7 @@ def random_walker(data, labels, beta=130, mode='bf', tol=1.e-3, copy=True, dims = data[..., 0].shape # To reshape final labeled result data = img_as_float(data) if data.ndim == 3: # 2D multispectral, needs singleton in 3rd axis - data = data[:, :, np.newaxis, :].transpose((0, 1, 3, 2)) + data = data[:, :, np.newaxis, :] # Spacing kwarg checks if spacing is None: From b8334e85c800c7bdf3068c96ffd932c6ac2a2819 Mon Sep 17 00:00:00 2001 From: "Josh Warner (Mac)" Date: Wed, 7 May 2014 23:16:42 -0500 Subject: [PATCH 10/16] failUnless -> assertTrue --- skimage/morphology/tests/test_watershed.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/skimage/morphology/tests/test_watershed.py b/skimage/morphology/tests/test_watershed.py index 5dc0f07c..788d999e 100644 --- a/skimage/morphology/tests/test_watershed.py +++ b/skimage/morphology/tests/test_watershed.py @@ -154,7 +154,7 @@ class TestWatershed(unittest.TestCase): [-1, -1, 1, 1, 1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]], out) - self.failUnless(error < eps) + self.assertTrue(error < eps) def test_watershed03(self): "watershed 3" @@ -189,7 +189,7 @@ class TestWatershed(unittest.TestCase): [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]], out) - self.failUnless(error < eps) + self.assertTrue(error < eps) def test_watershed04(self): "watershed 4" @@ -224,7 +224,7 @@ class TestWatershed(unittest.TestCase): [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]], out) - self.failUnless(error < eps) + self.assertTrue(error < eps) def test_watershed05(self): "watershed 5" @@ -259,7 +259,7 @@ class TestWatershed(unittest.TestCase): [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]], out) - self.failUnless(error < eps) + self.assertTrue(error < eps) def test_watershed06(self): "watershed 6" @@ -291,7 +291,7 @@ class TestWatershed(unittest.TestCase): [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1], [-1, -1, -1, -1, -1, -1, -1]], out) - self.failUnless(error < eps) + self.assertTrue(error < eps) def test_watershed07(self): "A regression test of a competitive case that failed" From 548423bb940a38c599ac3b82bf8a1207b5112646 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Thu, 8 May 2014 16:24:50 +1000 Subject: [PATCH 11/16] Update variable name for RGB line profile --- viewer_examples/plugins/lineprofile_rgb.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/viewer_examples/plugins/lineprofile_rgb.py b/viewer_examples/plugins/lineprofile_rgb.py index b08f5a00..577933a1 100644 --- a/viewer_examples/plugins/lineprofile_rgb.py +++ b/viewer_examples/plugins/lineprofile_rgb.py @@ -6,4 +6,4 @@ from skimage.viewer.plugins.lineprofile import LineProfile image = data.chelsea() viewer = ImageViewer(image) viewer += LineProfile() -line, profiles = viewer.show()[0] +line, rgb_profiles = viewer.show()[0] From 3d2af717444ee80563f7eb00b4b957a6b43aee74 Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Thu, 8 May 2014 17:21:35 +1000 Subject: [PATCH 12/16] Update description of viewer output in docs --- doc/source/user_guide/viewer.txt | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/doc/source/user_guide/viewer.txt b/doc/source/user_guide/viewer.txt index 4cdde7db..73c4dc00 100644 --- a/doc/source/user_guide/viewer.txt +++ b/doc/source/user_guide/viewer.txt @@ -34,12 +34,16 @@ let's see an example of how a pre-defined plugin is added to the viewer: viewer += LineProfile(viewer) overlay, data = viewer.show()[0] -The viewer's ``show`` method will return an overlay (of the same shape as the -input image) and data (possibly ``None``) for each attached plugin. In this -case, there is only one plugin, so we can directly name both. You can see the -plugin class's ``output`` method to see what will be returned. Here, -``overlay`` contains a drawing of the line (including its width), and ``data`` -contains the measured line profile. +The viewer's ``show()`` method returns a list of tuples, one for each attached +plugin. Each tuple contains two elements: an overlay of the same shape as the +input image, and a data field (which may be ``None``). A plugin class documents +its return value in its ``output`` method. + +In this example, only one plugin is attached, so the list returned by ``show`` +will have length 1. We extract that single tuple and bind its ``overlay`` and +``data`` elements to individual variables. Here, ``overlay`` contains an image +of the line drawn on the viewer, and ``data`` contains the 1-dimensional +intensity profile along that line. At the moment, there are not many plugins pre-defined, but there is a really simple interface for creating your own plugin. First, let us create a plugin to @@ -83,8 +87,8 @@ All that's left is to create an image viewer and add the plugin to that viewer. viewer += denoise_plugin denoised = viewer.show()[0][0] -When we close the viewer, ``denoised`` will contain the filtered image for the -last used setting of ``weight``. +Here, we access only the overlay returned by the plugin, which contains the +filtered image for the last used setting of ``weight``. .. image:: data/denoise_viewer_window.png .. image:: data/denoise_plugin_window.png From 53de0db1649fda3b219bc03302a43d2b5eeda35f Mon Sep 17 00:00:00 2001 From: Juan Nunez-Iglesias Date: Thu, 8 May 2014 18:19:57 +1000 Subject: [PATCH 13/16] Very minor text change --- doc/source/user_guide/viewer.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/user_guide/viewer.txt b/doc/source/user_guide/viewer.txt index 73c4dc00..3145effd 100644 --- a/doc/source/user_guide/viewer.txt +++ b/doc/source/user_guide/viewer.txt @@ -40,7 +40,7 @@ input image, and a data field (which may be ``None``). A plugin class documents its return value in its ``output`` method. In this example, only one plugin is attached, so the list returned by ``show`` -will have length 1. We extract that single tuple and bind its ``overlay`` and +will have length 1. We extract the single tuple and bind its ``overlay`` and ``data`` elements to individual variables. Here, ``overlay`` contains an image of the line drawn on the viewer, and ``data`` contains the 1-dimensional intensity profile along that line. From 0b6e48fe2a2d13d82d10e0e12bba89ccd279e428 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Thu, 8 May 2014 10:45:04 +0200 Subject: [PATCH 14/16] Clarify regionprops output --- skimage/measure/_regionprops.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skimage/measure/_regionprops.py b/skimage/measure/_regionprops.py index 88f7f843..7bcfcf81 100644 --- a/skimage/measure/_regionprops.py +++ b/skimage/measure/_regionprops.py @@ -344,9 +344,9 @@ def regionprops(label_image, intensity_image=None, cache=True): Returns ------- - properties : list - List containing a properties for each region. The properties of each - region can be accessed as attributes and keys. + properties : list of RegionProperties + Each item describes one labeled region, and can be accessed using the + attributes listed below. Notes ----- From a9dcdc37131ee8e1a3b71e3fd77af3e3b5458e43 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Thu, 8 May 2014 19:28:29 +0200 Subject: [PATCH 15/16] Use safe_as_int to check provided output shape --- skimage/_shared/{safe_as_int.py => _safe_as_int.py} | 2 +- skimage/_shared/utils.py | 4 +++- skimage/transform/_geometric.py | 4 ++-- skimage/transform/tests/test_warps.py | 6 +++++- 4 files changed, 11 insertions(+), 5 deletions(-) rename skimage/_shared/{safe_as_int.py => _safe_as_int.py} (98%) diff --git a/skimage/_shared/safe_as_int.py b/skimage/_shared/_safe_as_int.py similarity index 98% rename from skimage/_shared/safe_as_int.py rename to skimage/_shared/_safe_as_int.py index 0d665f9f..200d6efb 100644 --- a/skimage/_shared/safe_as_int.py +++ b/skimage/_shared/_safe_as_int.py @@ -3,7 +3,7 @@ import numpy as np __all__ = ['_safe_as_int'] -def _safe_as_int(val, atol=1e-7): +def safe_as_int(val, atol=1e-3): """ Attempt to safely cast values to integer format. diff --git a/skimage/_shared/utils.py b/skimage/_shared/utils.py index 9148e7ff..f94edfc5 100644 --- a/skimage/_shared/utils.py +++ b/skimage/_shared/utils.py @@ -5,8 +5,10 @@ import sys import six from ._warnings import all_warnings +from ._safe_as_int import safe_as_int -__all__ = ['deprecated', 'get_bound_method_class', 'all_warnings'] +__all__ = ['deprecated', 'get_bound_method_class', 'all_warnings', + 'safe_as_int'] class skimage_deprecation(Warning): diff --git a/skimage/transform/_geometric.py b/skimage/transform/_geometric.py index 53074c00..6eb0f70a 100644 --- a/skimage/transform/_geometric.py +++ b/skimage/transform/_geometric.py @@ -4,7 +4,7 @@ import warnings import numpy as np from scipy import ndimage, spatial -from skimage._shared.utils import get_bound_method_class +from skimage._shared.utils import get_bound_method_class, safe_as_int from skimage.util import img_as_float from ._warps_cy import _warp_fast @@ -1078,7 +1078,7 @@ def warp(image, inverse_map=None, map_args={}, output_shape=None, order=1, if output_shape is None: output_shape = ishape else: - output_shape = np.array(output_shape, dtype=int) + output_shape = safe_as_int(output_shape) out = None diff --git a/skimage/transform/tests/test_warps.py b/skimage/transform/tests/test_warps.py index f85125d0..07054ab7 100644 --- a/skimage/transform/tests/test_warps.py +++ b/skimage/transform/tests/test_warps.py @@ -250,7 +250,11 @@ def test_inverse(): def test_slow_warp_nonint_oshape(): image = np.random.random((5, 5)) - warp(image, lambda xy: xy, output_shape=(13.1, 19.5)) + + assert_raises(ValueError, warp, image, lambda xy: xy, + output_shape=(13.1, 19.5)) + + warp(image, lambda xy: xy, output_shape=(13.0001, 19.9999)) if __name__ == "__main__": From e9c0cde8d46b0b0ff30aea175af70ea1e4cd1caf Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Thu, 8 May 2014 20:11:17 +0200 Subject: [PATCH 16/16] Move safe_as_int into utils --- skimage/_shared/_safe_as_int.py | 70 ----------------------- skimage/_shared/tests/test_safe_as_int.py | 32 +++++------ skimage/_shared/utils.py | 69 +++++++++++++++++++++- 3 files changed, 84 insertions(+), 87 deletions(-) delete mode 100644 skimage/_shared/_safe_as_int.py diff --git a/skimage/_shared/_safe_as_int.py b/skimage/_shared/_safe_as_int.py deleted file mode 100644 index 200d6efb..00000000 --- a/skimage/_shared/_safe_as_int.py +++ /dev/null @@ -1,70 +0,0 @@ -import numpy as np - -__all__ = ['_safe_as_int'] - - -def safe_as_int(val, atol=1e-3): - """ - Attempt to safely cast values to integer format. - - Parameters - ---------- - val : scalar or iterable of scalars - Number or container of numbers which are intended to be interpreted as - integers, e.g., for indexing purposes, but which may not carry integer - type. - atol : float - Absolute tolerance away from nearest integer to consider values in - ``val`` functionally integers. - - Returns - ------- - val_int : NumPy scalar or ndarray of dtype `np.int64` - Returns the input value(s) coerced to dtype `np.int64` assuming all - were within ``atol`` of the nearest integer. - - Notes - ----- - This operation calculates ``val`` modulo 1, which returns the mantissa of - all values. Then all mantissas greater than 0.5 are subtracted from one. - Finally, the absolute tolerance from zero is calculated. If it is less - than ``atol`` for all value(s) in ``val``, they are rounded and returned - in an integer array. Or, if ``val`` was a scalar, a NumPy scalar type is - returned. - - If any value(s) are outside the specified tolerance, an informative error - is raised. - - Examples - -------- - >>> _safe_as_int(7.0) - 7 - - >>> _safe_as_int([9, 4, 2.9999999999]) - array([9, 4, 3], dtype=int32) - - >>> _safe_as_int(53.01) - Traceback (most recent call last): - ... - ValueError: Integer argument required but received 53.1, check inputs. - - >>> _safe_as_int(53.01, atol=0.01) - 53 - - """ - mod = np.asarray(val) % 1 # Extract mantissa - - # Check for and subtract any mod values > 0.5 from 1 - if mod.ndim == 0: # Scalar input, cannot be indexed - if mod > 0.5: - mod = 1 - mod - else: # Iterable input, now ndarray - mod[mod > 0.5] = 1 - mod[mod > 0.5] # Test on each side of nearest int - - try: - np.testing.assert_allclose(mod, 0, atol=atol) - except AssertionError: - raise ValueError("Integer argument required but received " - "{0}, check inputs.".format(val)) - - return np.round(val).astype(np.int64) diff --git a/skimage/_shared/tests/test_safe_as_int.py b/skimage/_shared/tests/test_safe_as_int.py index 1a3674e0..009fa9a4 100644 --- a/skimage/_shared/tests/test_safe_as_int.py +++ b/skimage/_shared/tests/test_safe_as_int.py @@ -1,30 +1,30 @@ import numpy as np -from skimage._shared.safe_int_cast import _safe_as_int +from skimage._shared.utils import safe_as_int def test_int_cast_not_possible(): - np.testing.assert_raises(ValueError, _safe_as_int, 7.1) - np.testing.assert_raises(ValueError, _safe_as_int, [7.1, 0.9]) - np.testing.assert_raises(ValueError, _safe_as_int, np.r_[7.1, 0.9]) - np.testing.assert_raises(ValueError, _safe_as_int, (7.1, 0.9)) - np.testing.assert_raises(ValueError, _safe_as_int, ((3, 4, 1), + np.testing.assert_raises(ValueError, safe_as_int, 7.1) + np.testing.assert_raises(ValueError, safe_as_int, [7.1, 0.9]) + np.testing.assert_raises(ValueError, safe_as_int, np.r_[7.1, 0.9]) + np.testing.assert_raises(ValueError, safe_as_int, (7.1, 0.9)) + np.testing.assert_raises(ValueError, safe_as_int, ((3, 4, 1), (2, 7.6, 289))) - np.testing.assert_raises(ValueError, _safe_as_int, 7.1, 0.09) - np.testing.assert_raises(ValueError, _safe_as_int, [7.1, 0.9], 0.09) - np.testing.assert_raises(ValueError, _safe_as_int, np.r_[7.1, 0.9], 0.09) - np.testing.assert_raises(ValueError, _safe_as_int, (7.1, 0.9), 0.09) - np.testing.assert_raises(ValueError, _safe_as_int, ((3, 4, 1), + np.testing.assert_raises(ValueError, safe_as_int, 7.1, 0.09) + np.testing.assert_raises(ValueError, safe_as_int, [7.1, 0.9], 0.09) + np.testing.assert_raises(ValueError, safe_as_int, np.r_[7.1, 0.9], 0.09) + np.testing.assert_raises(ValueError, safe_as_int, (7.1, 0.9), 0.09) + np.testing.assert_raises(ValueError, safe_as_int, ((3, 4, 1), (2, 7.6, 289)), 0.25) def test_int_cast_possible(): - np.testing.assert_equal(_safe_as_int(7.1, atol=0.11), 7) - np.testing.assert_equal(_safe_as_int(-7.1, atol=0.11), -7) - np.testing.assert_equal(_safe_as_int(41.9, atol=0.11), 42) - np.testing.assert_array_equal(_safe_as_int([2, 42, 5789234.0, 87, 4]), + np.testing.assert_equal(safe_as_int(7.1, atol=0.11), 7) + np.testing.assert_equal(safe_as_int(-7.1, atol=0.11), -7) + np.testing.assert_equal(safe_as_int(41.9, atol=0.11), 42) + np.testing.assert_array_equal(safe_as_int([2, 42, 5789234.0, 87, 4]), np.r_[2, 42, 5789234, 87, 4]) - np.testing.assert_array_equal(_safe_as_int(np.r_[[[3, 4, 1.000000001], + np.testing.assert_array_equal(safe_as_int(np.r_[[[3, 4, 1.000000001], [7, 2, -8.999999999], [6, 9, -4234918347.]]]), np.r_[[[3, 4, 1], diff --git a/skimage/_shared/utils.py b/skimage/_shared/utils.py index f94edfc5..612a2b63 100644 --- a/skimage/_shared/utils.py +++ b/skimage/_shared/utils.py @@ -1,11 +1,11 @@ import warnings import functools import sys +import numpy as np import six from ._warnings import all_warnings -from ._safe_as_int import safe_as_int __all__ = ['deprecated', 'get_bound_method_class', 'all_warnings', 'safe_as_int'] @@ -74,3 +74,70 @@ def get_bound_method_class(m): """ return m.im_class if sys.version < '3' else m.__self__.__class__ + + +def safe_as_int(val, atol=1e-3): + """ + Attempt to safely cast values to integer format. + + Parameters + ---------- + val : scalar or iterable of scalars + Number or container of numbers which are intended to be interpreted as + integers, e.g., for indexing purposes, but which may not carry integer + type. + atol : float + Absolute tolerance away from nearest integer to consider values in + ``val`` functionally integers. + + Returns + ------- + val_int : NumPy scalar or ndarray of dtype `np.int64` + Returns the input value(s) coerced to dtype `np.int64` assuming all + were within ``atol`` of the nearest integer. + + Notes + ----- + This operation calculates ``val`` modulo 1, which returns the mantissa of + all values. Then all mantissas greater than 0.5 are subtracted from one. + Finally, the absolute tolerance from zero is calculated. If it is less + than ``atol`` for all value(s) in ``val``, they are rounded and returned + in an integer array. Or, if ``val`` was a scalar, a NumPy scalar type is + returned. + + If any value(s) are outside the specified tolerance, an informative error + is raised. + + Examples + -------- + >>> _safe_as_int(7.0) + 7 + + >>> _safe_as_int([9, 4, 2.9999999999]) + array([9, 4, 3], dtype=int32) + + >>> _safe_as_int(53.01) + Traceback (most recent call last): + ... + ValueError: Integer argument required but received 53.1, check inputs. + + >>> _safe_as_int(53.01, atol=0.01) + 53 + + """ + mod = np.asarray(val) % 1 # Extract mantissa + + # Check for and subtract any mod values > 0.5 from 1 + if mod.ndim == 0: # Scalar input, cannot be indexed + if mod > 0.5: + mod = 1 - mod + else: # Iterable input, now ndarray + mod[mod > 0.5] = 1 - mod[mod > 0.5] # Test on each side of nearest int + + try: + np.testing.assert_allclose(mod, 0, atol=atol) + except AssertionError: + raise ValueError("Integer argument required but received " + "{0}, check inputs.".format(val)) + + return np.round(val).astype(np.int64)