Merge pull request #971 from stefanv/bug/views_are_copies

Do not allow `view_as_*` to return copies (closes #866)
This commit is contained in:
Johannes L. Schönberger
2014-04-27 20:16:41 -04:00
4 changed files with 98 additions and 16 deletions
+61
View File
@@ -0,0 +1,61 @@
__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 list(sys.modules.values()):
try:
mod.__warningregistry__.clear()
except AttributeError:
pass
with warnings.catch_warnings(record=True) as w:
warnings.simplefilter("always")
yield w
+2 -1
View File
@@ -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):
+22 -13
View File
@@ -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):
@@ -11,17 +12,17 @@ def view_as_blocks(arr_in, block_shape):
Parameters
----------
arr_in: ndarray
The n-dimensional input array.
block_shape: tuple
arr_in : ndarray
N-d input array.
block_shape : tuple
The shape of the block. Each dimension must divide evenly into the
corresponding dimensions of `arr_in`.
Returns
-------
arr_out: ndarray
Block view of the input array.
arr_out : ndarray
Block view of the input array. If `arr_in` is non-contiguous, a copy
is made.
Examples
--------
@@ -70,8 +71,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 +87,11 @@ 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:
warn(RuntimeWarning("Cannot provide views on a non-contiguous input "
"array without copying."))
arr_in = np.ascontiguousarray(arr_in)
new_shape = tuple(arr_shape / block_shape) + tuple(block_shape)
@@ -106,9 +110,9 @@ def view_as_windows(arr_in, window_shape, step=1):
Parameters
----------
arr_in: ndarray
The n-dimensional input array.
window_shape: tuple
arr_in : ndarray
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.
step : int, optional
@@ -118,8 +122,9 @@ def view_as_windows(arr_in, window_shape, step=1):
Returns
-------
arr_out: ndarray
(rolling) window view of the input array.
arr_out : ndarray
(rolling) window view of the input array. If `arr_in` is
non-contiguous, a copy is made.
Notes
-----
@@ -228,6 +233,10 @@ 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:
warn(RuntimeWarning("Cannot provide views on a non-contiguous input "
"array without copying."))
arr_in = np.ascontiguousarray(arr_in)
new_shape = tuple((arr_shape - window_shape) // step + 1) + \
+13 -2
View File
@@ -1,7 +1,9 @@
import numpy as np
from nose.tools import raises
from numpy.testing import assert_equal
from numpy.testing import assert_equal, assert_warns
from skimage.util.shape import view_as_blocks, view_as_windows
from skimage._shared.utils import all_warnings
@raises(TypeError)
@@ -131,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],
@@ -147,5 +149,14 @@ 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, :]
with all_warnings():
assert_warns(RuntimeWarning, view_as_blocks, A, (2, 2))
assert_warns(RuntimeWarning, view_as_windows, A, (2, 2))
if __name__ == '__main__':
np.testing.run_module_suite()