Repository: scikit-image/scikit-image

Issue:
.util.shape.view_as_blocks doesn't return a view if the array is not c-contiguous
``` python
import numpy as np
from skimage.util.shape import view_as_blocks

arr = np.arange(0,144).reshape(12,12)
view = view_as_blocks(arr, (6,6))
view[0,1,1,2] = -1
assert arr[1,8] == -1

arr = np.arange(0,144).reshape(12,12)
arr = np.asfortranarray(arr)
view = view_as_blocks(arr, (6,6))
view[0,1,1,2] = -1
assert arr[1,8] == -1 # fails, as view operates on a copy
```

In my opinion you cannot call this a view anymore. I don't think it is easily possible to work-around this. From an end-user perspective I would rather want an exception in the case that a view cannot be created:

``` python
    if not arr_in.flags['C_CONTIGUOUS']:
        raise ValueError("'arr_in' must be c-contiguous")
```



Discussion:
I think a workaround is possible by simply changing the strides, no?  Would you be interested in working on that?

Well, I guess it may be possible for some cases but not for all, as there are many different stride possibilities. I'm not experienced enough on that though and don't need it myself (as my arrays are already c-contiguous), I just noticed it while looking through the code.

The only requirement is that the array is either C- or Fortran-contiguous, which is fairly easy to check.  Thanks for the heads up, I'll take a look into fixing it.

Ah, there is a misunderstanding here, caused by my initial example. I wasn't specifically talking about Fortran order (that was just an example), but any layout, e.g.:

``` python
>>> x = np.arange(100).reshape(10,10)[::2,::2]
>>> x
array([[ 0,  2,  4,  6,  8],
       [20, 22, 24, 26, 28],
       [40, 42, 44, 46, 48],
       [60, 62, 64, 66, 68],
       [80, 82, 84, 86, 88]])
>>> x.flags
  C_CONTIGUOUS : False
  F_CONTIGUOUS : False
  ...
```


Resolving patch:
diff --git a/skimage/_shared/_warnings.py b/skimage/_shared/_warnings.py
new file mode 100644
--- /dev/null
+++ b/skimage/_shared/_warnings.py
@@ -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
diff --git a/skimage/_shared/utils.py b/skimage/_shared/utils.py
--- a/skimage/_shared/utils.py
+++ b/skimage/_shared/utils.py
@@ -4,8 +4,9 @@
 
 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/shape.py b/skimage/util/shape.py
--- a/skimage/util/shape.py
+++ b/skimage/util/shape.py
@@ -2,6 +2,7 @@
 
 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) + \


Test patch:
diff --git a/skimage/util/tests/test_shape.py b/skimage/util/tests/test_shape.py
--- a/skimage/util/tests/test_shape.py
+++ b/skimage/util/tests/test_shape.py
@@ -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()