Repository: napari/napari

Issue:
affine non-orthogonal slicing warning is too sensitive
## 🐛 Bug

Even passing only scale and translate to a layer results in the warning being raised:

```
import numpy as np
import napari

image = np.random.random((33, 512, 512))
with napari.gui_qt():
    napari.view_image(
        image,
        scale=[2, 0.504, 0.504],
        translate=[-2000, -2000, 3500],
    )
```

raises:

```
/Users/jni/projects/napari/napari/layers/base/base.py:590: UserWarning: Non-orthogonal slicing is being requested, but is not fully supported. Data is displayed without applying an out-of-slice rotation or shear component.
  warnings.warn(
/Users/jni/projects/napari/napari/layers/base/base.py:590: UserWarning: Non-orthogonal slicing is being requested, but is not fully supported. Data is displayed without applying an out-of-slice rotation or shear component.
```


Discussion:
This should not be the case, thanks for flagging. I'll look into a fix

Resolving patch:
diff --git a/napari/layers/base/base.py b/napari/layers/base/base.py
--- a/napari/layers/base/base.py
+++ b/napari/layers/base/base.py
@@ -489,7 +489,7 @@ def position(self, position):
     def dims(self):
         warnings.warn(
             (
-                "The layer.dims parameter is deprecated and will be removed in version 0.4.1."
+                "The layer.dims parameter is deprecated and will be removed in version 0.4.2."
                 " Instead you should use the viewer.dims parameter on the main viewer object."
             ),
             category=DeprecationWarning,
@@ -575,22 +575,28 @@ def extent(self) -> Extent:
     @property
     def _slice_indices(self):
         """(D, ) array: Slice indices in data coordinates."""
-        # clipping plane in world coordinates
-        # clipping_plane = [1, 0, 0]
         inv_transform = self._transforms['data2world'].inverse
-        # data_clipping_plane = inv_transform(clipping_plane)
 
         if self.ndim > self._dims.ndisplay:
-            clipping_plane = np.ones(self.ndim)
-            clipping_plane[-self._dims.ndisplay :] = 0
-            mapped_clipping_plane = inv_transform(clipping_plane)
-            if not np.allclose(
-                mapped_clipping_plane[-self._dims.ndisplay :], 0
-            ):
+            # Subspace spanned by non displayed dimensions
+            non_displayed_subspace = np.zeros(self.ndim)
+            for d in self._dims.not_displayed:
+                non_displayed_subspace[d] = 1
+            # Map subspace through inverse transform, ignoring translation
+            mapped_nd_subspace = inv_transform(
+                non_displayed_subspace
+            ) - inv_transform(np.zeros(self.ndim))
+            # Look at displayed subspace
+            displayed_mapped_subspace = [
+                mapped_nd_subspace[d] for d in self._dims.displayed
+            ]
+            # Check that displayed subspace is null
+            if not np.allclose(displayed_mapped_subspace, 0):
                 warnings.warn(
                     'Non-orthogonal slicing is being requested, but'
                     ' is not fully supported. Data is displayed without'
-                    ' applying an out-of-slice rotation or shear component.'
+                    ' applying an out-of-slice rotation or shear component.',
+                    category=UserWarning,
                 )
 
         slice_inv_transform = inv_transform.set_slice(self._dims.not_displayed)
@@ -616,7 +622,7 @@ def shape(self):
         """
         warnings.warn(
             (
-                "The shape attribute is deprecated and will be removed in version 0.4.1."
+                "The shape attribute is deprecated and will be removed in version 0.4.2."
                 " Instead you should use the extent.data and extent.world attributes"
                 " to get the extent of the data in data or world coordinates."
             ),
diff --git a/napari/layers/image/image.py b/napari/layers/image/image.py
--- a/napari/layers/image/image.py
+++ b/napari/layers/image/image.py
@@ -495,7 +495,7 @@ def shape(self):
         """
         warnings.warn(
             (
-                "The shape attribute is deprecated and will be removed in version 0.4.1."
+                "The shape attribute is deprecated and will be removed in version 0.4.2."
                 " Instead you should use the extent.data and extent.world attributes"
                 " to get the extent of the data in data or world coordinates."
             ),


Test patch:
diff --git a/napari/components/_tests/test_world_coordinates.py b/napari/components/_tests/test_world_coordinates.py
--- a/napari/components/_tests/test_world_coordinates.py
+++ b/napari/components/_tests/test_world_coordinates.py
@@ -1,4 +1,5 @@
 import numpy as np
+import pytest
 
 from napari.components import ViewerModel
 
@@ -65,3 +66,31 @@ def test_both_scaled_and_translated_images():
     for i in range(viewer.dims.nsteps[0]):
         viewer.dims.set_current_step(0, i)
         assert viewer.dims.current_step[0] == i
+
+
+def test_no_warning_non_affine_slicing():
+    """Test no warning if not slicing into an affine."""
+    viewer = ViewerModel()
+    np.random.seed(0)
+    data = np.random.random((10, 10, 10))
+    viewer.add_image(data, scale=[2, 1, 1], translate=[10, 15, 20])
+    with pytest.warns(None) as recorded_warnings:
+        viewer.layers[0].refresh()
+    assert len(recorded_warnings) == 0
+
+
+def test_warning_affine_slicing():
+    """Test warning if slicing into an affine."""
+    viewer = ViewerModel()
+    np.random.seed(0)
+    data = np.random.random((10, 10, 10))
+    viewer.add_image(
+        data,
+        scale=[2, 1, 1],
+        translate=[10, 15, 20],
+        shear=[[1, 0, 0], [0, 1, 0], [10, 0, 1]],
+    )
+    with pytest.warns(None) as recorded_warnings:
+        viewer.layers[0].refresh()
+    # note right now refresh tiggers two `_slice_indices` calls
+    assert len(recorded_warnings) == 2