From 5733a02853f2a5380f82e3853bdb6bde3e5e6b4a Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Mon, 7 Apr 2014 14:41:56 +0200 Subject: [PATCH 01/10] Do not allow view_as_* to return copies (closes #866) --- doc/source/api_changes.txt | 3 +++ skimage/util/shape.py | 18 +++++++++++------- skimage/util/tests/test_shape.py | 10 +++++++++- 3 files changed, 23 insertions(+), 8 deletions(-) diff --git a/doc/source/api_changes.txt b/doc/source/api_changes.txt index 527c9fb0..14a66975 100644 --- a/doc/source/api_changes.txt +++ b/doc/source/api_changes.txt @@ -1,6 +1,9 @@ Version 0.10 ------------ - Removed ``skimage.io.video`` functionality due to broken gstreamer bindings +- The functions `skimage.util.view_as_blocks` and + `skimage.util.view_as_windows` now raise exceptions if the input data is not + contiguous. Version 0.9 ----------- diff --git a/skimage/util/shape.py b/skimage/util/shape.py index 95e0a42e..d61a647e 100644 --- a/skimage/util/shape.py +++ b/skimage/util/shape.py @@ -11,16 +11,16 @@ def view_as_blocks(arr_in, block_shape): Parameters ---------- - arr_in: ndarray - The n-dimensional input array. + arr_in : ndarray + Contiguous N-d input array. - block_shape: tuple + block_shape : tuple The shape of the block. Each dimension must divide evenly into the corresponding dimensions of `arr_in`. Returns ------- - arr_out: ndarray + arr_out : ndarray Block view of the input array. Examples @@ -70,8 +70,6 @@ def view_as_blocks(arr_in, block_shape): [[[76, 77], [82, 83]]]]) """ - - # -- basic checks on arguments if not isinstance(block_shape, tuple): raise TypeError('block needs to be a tuple') @@ -88,6 +86,9 @@ def view_as_blocks(arr_in, block_shape): raise ValueError("'block_shape' is not compatible with 'arr_in'") # -- restride the array to build the block view + if not arr_in.flags.contiguous: + raise ValueError("Cannot provide views on a non-contiguous input array") + arr_in = np.ascontiguousarray(arr_in) new_shape = tuple(arr_shape / block_shape) + tuple(block_shape) @@ -107,7 +108,7 @@ def view_as_windows(arr_in, window_shape, step=1): Parameters ---------- arr_in: ndarray - The n-dimensional input array. + Contiguous N-d input array. window_shape: tuple Defines the shape of the elementary n-dimensional orthotope (better know as hyperrectangle [1]_) of the rolling window view. @@ -228,6 +229,9 @@ def view_as_windows(arr_in, window_shape, step=1): raise ValueError("`window_shape` is too small") # -- build rolling window view + if not arr_in.flags.contiguous: + raise ValueError("Cannot provide views on a non-contiguous input array") + arr_in = np.ascontiguousarray(arr_in) new_shape = tuple((arr_shape - window_shape) // step + 1) + \ diff --git a/skimage/util/tests/test_shape.py b/skimage/util/tests/test_shape.py index b66ac2e2..79fb844b 100644 --- a/skimage/util/tests/test_shape.py +++ b/skimage/util/tests/test_shape.py @@ -1,6 +1,6 @@ import numpy as np from nose.tools import raises -from numpy.testing import assert_equal +from numpy.testing import assert_equal, assert_raises from skimage.util.shape import view_as_blocks, view_as_windows @@ -147,5 +147,13 @@ def test_view_as_windows_With_skip(): assert_equal(C.shape, (1, 1, 2, 2)) +def test_views_non_contiguous(): + A = np.arange(16).reshape((4, 4)) + A = A[::2, :] + + assert_raises(ValueError, view_as_blocks, A, (2, 2)) + assert_raises(ValueError, view_as_windows, A, (2, 2)) + + if __name__ == '__main__': np.testing.run_module_suite() From 12af2ef7ed0a9b17ed5200353667fe624ae81bf3 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Mon, 7 Apr 2014 18:15:49 +0200 Subject: [PATCH 02/10] Take a softer approach and raise a warning when a non-contiguous input is provided --- doc/source/api_changes.txt | 3 --- skimage/util/shape.py | 20 ++++++++++++-------- skimage/util/tests/test_shape.py | 7 ++++--- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/doc/source/api_changes.txt b/doc/source/api_changes.txt index 14a66975..527c9fb0 100644 --- a/doc/source/api_changes.txt +++ b/doc/source/api_changes.txt @@ -1,9 +1,6 @@ Version 0.10 ------------ - Removed ``skimage.io.video`` functionality due to broken gstreamer bindings -- The functions `skimage.util.view_as_blocks` and - `skimage.util.view_as_windows` now raise exceptions if the input data is not - contiguous. Version 0.9 ----------- diff --git a/skimage/util/shape.py b/skimage/util/shape.py index d61a647e..d47150af 100644 --- a/skimage/util/shape.py +++ b/skimage/util/shape.py @@ -2,6 +2,7 @@ __all__ = ['view_as_blocks', 'view_as_windows'] import numpy as np from numpy.lib.stride_tricks import as_strided +from warnings import warn def view_as_blocks(arr_in, block_shape): @@ -12,8 +13,7 @@ def view_as_blocks(arr_in, block_shape): Parameters ---------- arr_in : ndarray - Contiguous N-d input array. - + N-d input array. block_shape : tuple The shape of the block. Each dimension must divide evenly into the corresponding dimensions of `arr_in`. @@ -21,7 +21,8 @@ def view_as_blocks(arr_in, block_shape): Returns ------- arr_out : ndarray - Block view of the input array. + Block view of the input array. If `arr_in` is non-contiguous, a copy + is made. Examples -------- @@ -86,8 +87,10 @@ def view_as_blocks(arr_in, block_shape): raise ValueError("'block_shape' is not compatible with 'arr_in'") # -- restride the array to build the block view + if not arr_in.flags.contiguous: - raise ValueError("Cannot provide views on a non-contiguous input array") + warn(RuntimeWarning("Cannot provide views on a non-contiguous input " + "array without copying.")) arr_in = np.ascontiguousarray(arr_in) @@ -107,9 +110,9 @@ def view_as_windows(arr_in, window_shape, step=1): Parameters ---------- - arr_in: ndarray + arr_in : ndarray Contiguous N-d input array. - window_shape: tuple + window_shape : tuple Defines the shape of the elementary n-dimensional orthotope (better know as hyperrectangle [1]_) of the rolling window view. step : int, optional @@ -119,7 +122,7 @@ def view_as_windows(arr_in, window_shape, step=1): Returns ------- - arr_out: ndarray + arr_out : ndarray (rolling) window view of the input array. Notes @@ -230,7 +233,8 @@ def view_as_windows(arr_in, window_shape, step=1): # -- build rolling window view if not arr_in.flags.contiguous: - raise ValueError("Cannot provide views on a non-contiguous input array") + warn(RuntimeWarning("Cannot provide views on a non-contiguous input " + "array without copying.")) arr_in = np.ascontiguousarray(arr_in) diff --git a/skimage/util/tests/test_shape.py b/skimage/util/tests/test_shape.py index 79fb844b..7a5640a3 100644 --- a/skimage/util/tests/test_shape.py +++ b/skimage/util/tests/test_shape.py @@ -1,6 +1,7 @@ import numpy as np from nose.tools import raises -from numpy.testing import assert_equal, assert_raises +from numpy.testing import assert_equal, assert_warns + from skimage.util.shape import view_as_blocks, view_as_windows @@ -151,8 +152,8 @@ def test_views_non_contiguous(): A = np.arange(16).reshape((4, 4)) A = A[::2, :] - assert_raises(ValueError, view_as_blocks, A, (2, 2)) - assert_raises(ValueError, view_as_windows, A, (2, 2)) + assert_warns(RuntimeWarning, view_as_blocks, A, (2, 2)) + assert_warns(RuntimeWarning, view_as_windows, A, (2, 2)) if __name__ == '__main__': From c18d4bbe591340ff721a1835d1f6c48e71356350 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Mon, 7 Apr 2014 18:18:09 +0200 Subject: [PATCH 03/10] Update view_as_windows docstring --- skimage/util/shape.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/skimage/util/shape.py b/skimage/util/shape.py index d47150af..910b5b97 100644 --- a/skimage/util/shape.py +++ b/skimage/util/shape.py @@ -123,7 +123,8 @@ def view_as_windows(arr_in, window_shape, step=1): Returns ------- arr_out : ndarray - (rolling) window view of the input array. + (rolling) window view of the input array. If `arr_in` is + non-contiguous, a copy is made. Notes ----- From 717f5feb51f831779a091c3e08af7c6698916767 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Tue, 8 Apr 2014 11:15:24 +0200 Subject: [PATCH 04/10] Fix typo in docstring --- skimage/util/shape.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/util/shape.py b/skimage/util/shape.py index 910b5b97..eed6b952 100644 --- a/skimage/util/shape.py +++ b/skimage/util/shape.py @@ -111,7 +111,7 @@ def view_as_windows(arr_in, window_shape, step=1): Parameters ---------- arr_in : ndarray - Contiguous N-d input array. + N-d input array. window_shape : tuple Defines the shape of the elementary n-dimensional orthotope (better know as hyperrectangle [1]_) of the rolling window view. From 132aa8bbd3baf56e4455e75cf2766ed59346d67f Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Sun, 27 Apr 2014 02:08:33 +0200 Subject: [PATCH 05/10] Add all_warnings context manager The context manager searches for __warningregistry__ entries that Python leaves all over the place, clearing them, thereby ensuring that warnings will always be raised. This is necessary for the test suite to detect warnings even if they were raised before under the "once" filter. --- skimage/util/__init__.py | 2 ++ skimage/util/_warnings.py | 60 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+) create mode 100644 skimage/util/_warnings.py diff --git a/skimage/util/__init__.py b/skimage/util/__init__.py index 5577e46b..07712230 100644 --- a/skimage/util/__init__.py +++ b/skimage/util/__init__.py @@ -7,6 +7,8 @@ from .arraypad import pad from ._regular_grid import regular_grid from .unique import unique_rows +from ._warnings import all_warnings + __all__ = ['img_as_float', 'img_as_int', diff --git a/skimage/util/_warnings.py b/skimage/util/_warnings.py new file mode 100644 index 00000000..f4bbf5c2 --- /dev/null +++ b/skimage/util/_warnings.py @@ -0,0 +1,60 @@ +__all__ = ['all_warnings'] + +from contextlib import contextmanager +import sys +import warnings +import inspect + +@contextmanager +def all_warnings(): + """ + Context for use in testing to ensure that all warnings are raised. + + Examples + -------- + >>> import warnings + >>> def foo(): + ... warnings.warn(RuntimeWarning("bar")) + + We raise the warning once, while the warning filter is set to "once". + Hereafter, the warning is invisible, even with custom filters: + + >>> with warnings.catch_warnings(): + ... warnings.simplefilter('once') + ... foo() + + We can now run ``foo()`` without a warning being raised: + + >>> from numpy.testing import assert_warns + >>> foo() + + To catch the warning, we call in the help of ``all_warnings``: + + >>> with all_warnings(): + ... assert_warns(RuntimeWarning, foo) + """ + + # Whenever a warning is triggered, Python adds a __warningregistry__ + # member to the *calling* module. The exercize here is to find + # and eradicate all those breadcrumbs that were left lying around. + # + # We proceed by first searching all parent calling frames and explicitly + # clearing their warning registries (necessary for the doctests above to + # pass). Then, we search for all submodules of skimage and clear theirs + # as well (necessary for the skimage test suite to pass). + + frame = inspect.currentframe() + if frame: + for f in inspect.getouterframes(frame): + f[0].f_locals['__warningregistry__'] = {} + del frame + + for mod in sys.modules.values(): + try: + mod.__warningregistry__.clear() + except AttributeError: + pass + + with warnings.catch_warnings(): + warnings.simplefilter("always") + yield From 337f3d175f42b2951d1c3d47ee9f7df7b8c86db1 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Sun, 27 Apr 2014 02:10:29 +0200 Subject: [PATCH 06/10] Update shape tests to use all_warnings --- skimage/util/tests/test_shape.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/skimage/util/tests/test_shape.py b/skimage/util/tests/test_shape.py index 7a5640a3..8f4c4291 100644 --- a/skimage/util/tests/test_shape.py +++ b/skimage/util/tests/test_shape.py @@ -3,6 +3,7 @@ from nose.tools import raises from numpy.testing import assert_equal, assert_warns from skimage.util.shape import view_as_blocks, view_as_windows +from skimage.util import all_warnings @raises(TypeError) @@ -132,7 +133,7 @@ def test_view_as_windows_2D(): [17, 18, 19]]]])) -def test_view_as_windows_With_skip(): +def test_view_as_windows_with_skip(): A = np.arange(20).reshape((5, 4)) B = view_as_windows(A, (2, 2), step=2) assert_equal(B, [[[[0, 1], @@ -152,8 +153,9 @@ def test_views_non_contiguous(): A = np.arange(16).reshape((4, 4)) A = A[::2, :] - assert_warns(RuntimeWarning, view_as_blocks, A, (2, 2)) - assert_warns(RuntimeWarning, view_as_windows, A, (2, 2)) + with all_warnings(): + assert_warns(RuntimeWarning, view_as_blocks, A, (2, 2)) + assert_warns(RuntimeWarning, view_as_windows, A, (2, 2)) if __name__ == '__main__': From 8fdd5a270d50a4f21d7db9a3edf8401820cfe189 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Sun, 27 Apr 2014 02:16:38 +0200 Subject: [PATCH 07/10] In all_warnings context manager, return warnings for examination --- skimage/util/_warnings.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/util/_warnings.py b/skimage/util/_warnings.py index f4bbf5c2..15680c76 100644 --- a/skimage/util/_warnings.py +++ b/skimage/util/_warnings.py @@ -55,6 +55,6 @@ def all_warnings(): except AttributeError: pass - with warnings.catch_warnings(): + with warnings.catch_warnings(record=True) as w: warnings.simplefilter("always") - yield + yield w From 86beb21e5a4092afdb2d5c8a2745a74a3979c1b4 Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Sun, 27 Apr 2014 02:20:33 +0200 Subject: [PATCH 08/10] Move all_warnings to skimage._shared.utils --- skimage/{util => _shared}/_warnings.py | 0 skimage/_shared/utils.py | 3 ++- skimage/util/__init__.py | 2 -- skimage/util/tests/test_shape.py | 2 +- 4 files changed, 3 insertions(+), 4 deletions(-) rename skimage/{util => _shared}/_warnings.py (100%) diff --git a/skimage/util/_warnings.py b/skimage/_shared/_warnings.py similarity index 100% rename from skimage/util/_warnings.py rename to skimage/_shared/_warnings.py diff --git a/skimage/_shared/utils.py b/skimage/_shared/utils.py index 980ff639..9148e7ff 100644 --- a/skimage/_shared/utils.py +++ b/skimage/_shared/utils.py @@ -4,8 +4,9 @@ import sys import six +from ._warnings import all_warnings -__all__ = ['deprecated', 'get_bound_method_class'] +__all__ = ['deprecated', 'get_bound_method_class', 'all_warnings'] class skimage_deprecation(Warning): diff --git a/skimage/util/__init__.py b/skimage/util/__init__.py index 07712230..5577e46b 100644 --- a/skimage/util/__init__.py +++ b/skimage/util/__init__.py @@ -7,8 +7,6 @@ from .arraypad import pad from ._regular_grid import regular_grid from .unique import unique_rows -from ._warnings import all_warnings - __all__ = ['img_as_float', 'img_as_int', diff --git a/skimage/util/tests/test_shape.py b/skimage/util/tests/test_shape.py index 8f4c4291..ee897e7c 100644 --- a/skimage/util/tests/test_shape.py +++ b/skimage/util/tests/test_shape.py @@ -3,7 +3,7 @@ from nose.tools import raises from numpy.testing import assert_equal, assert_warns from skimage.util.shape import view_as_blocks, view_as_windows -from skimage.util import all_warnings +from skimage._shared.utils import all_warnings @raises(TypeError) From 58ef146834496b2e4d2e52ea8d093ef47a205f2c Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Sun, 27 Apr 2014 04:05:52 +0200 Subject: [PATCH 09/10] Copy list of modules since it may change during iteration --- skimage/_shared/_warnings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/_shared/_warnings.py b/skimage/_shared/_warnings.py index 15680c76..99f2de0f 100644 --- a/skimage/_shared/_warnings.py +++ b/skimage/_shared/_warnings.py @@ -49,7 +49,7 @@ def all_warnings(): f[0].f_locals['__warningregistry__'] = {} del frame - for mod in sys.modules.values(): + for mod in list(sys.modules.values()): try: mod.__warningregistry__.clear() except AttributeError: From 321d9f372c5ab1a0b79e752bbe7cfafcc758fe8f Mon Sep 17 00:00:00 2001 From: Stefan van der Walt Date: Mon, 28 Apr 2014 01:10:15 +0200 Subject: [PATCH 10/10] Add some PEP8 --- skimage/_shared/_warnings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/_shared/_warnings.py b/skimage/_shared/_warnings.py index 99f2de0f..72de07b7 100644 --- a/skimage/_shared/_warnings.py +++ b/skimage/_shared/_warnings.py @@ -5,6 +5,7 @@ import sys import warnings import inspect + @contextmanager def all_warnings(): """