Repository: napari/napari

Issue:
Bug with non-orthogonal slicing
## 🐛 Bug

As was maybe feared, we've got some problems with our non-orthogonal slicing during real world usage. I'm looking at some light sheet data - script is a follows

```python
import numpy as np
import napari
from napari.utils.transforms import shear_matrix_from_angle
from glob import glob
from dask_image.imread import imread

image_path = 'data/LLSM/AOLLSM_m4_raw/'
ch0_images = imread(image_path + 'ex6-2_CamB_ch0_*.tif')
ch1_images = imread(image_path + 'ex6-2_CamA_ch1_*.tif')
ch2_images = imread(image_path + 'ex6-2_CamA_ch2_*.tif')


with napari.gui_qt():
    viewer = napari.Viewer()
    viewer.add_image(ch0_images, name='488nm', colormap='cyan',
                     shear=shear_matrix_from_angle(31.5), scale=[2, 1, 1], blending='additive')
    viewer.add_image(ch1_images, name='560nm', colormap='yellow',
                     shear=shear_matrix_from_angle(31.5), scale=[2, 1, 1], blending='additive')
    viewer.add_image(ch2_images, name='642nm', colormap='magenta',
                     shear=shear_matrix_from_angle(31.5), scale=[2, 1, 1], blending='additive')
```

where each channel is `(5, 201, 1024, 768)` and I imagine this problem could be reproduced with a single numpy array of that shape and the same `shear` and `scale` values.

On loading in 2D mode you can see the sliders are way off

<img width="1200" alt="Screen Shot 2020-10-22 at 8 50 36 AM" src="https://user-images.githubusercontent.com/6531703/96898102-31c11080-1444-11eb-8c7d-5f9e8d541715.png">

Both the z and the time (which I find a bit surprising), and this could actually relate to problems with broadcasting as I have specified 3D transforms for a 4D dataset ... (see #1204 and #1754 for more on broadcasting of scale/ translate). I will investigate more!


Discussion:

Resolving patch:
diff --git a/napari/components/layerlist.py b/napari/components/layerlist.py
--- a/napari/components/layerlist.py
+++ b/napari/components/layerlist.py
@@ -248,7 +248,7 @@ def _step_size(self) -> np.ndarray:
         if len(self) == 0:
             return np.ones(self.ndim)
         else:
-            scales = [layer.scale[::-1] for layer in self]
+            scales = [abs(layer.scale[::-1]) for layer in self]
             full_scales = list(
                 np.array(
                     list(itertools.zip_longest(*scales, fillvalue=np.nan))


Test patch:
diff --git a/napari/components/_tests/test_layers_list.py b/napari/components/_tests/test_layers_list.py
--- a/napari/components/_tests/test_layers_list.py
+++ b/napari/components/_tests/test_layers_list.py
@@ -578,6 +578,22 @@ def test_world_extent_mixed_ndim():
     np.testing.assert_allclose(layers._step_size, (4, 6, 2))
 
 
+def test_world_extent_mixed_flipped():
+    """Test world extent after adding data with a flip."""
+    # Flipped data results in a negative scale value which should be
+    # made positive when taking into consideration for the step size
+    # calculation
+    np.random.seed(0)
+    layers = LayerList()
+
+    layer = Image(
+        np.random.random((15, 15)), affine=[[0, 1, 0], [1, 0, 0], [0, 0, 1]]
+    )
+    layers.append(layer)
+    np.testing.assert_allclose(layer.scale, (1, -1))
+    np.testing.assert_allclose(layers._step_size, (1, 1))
+
+
 def test_ndim():
     """Test world extent after adding layers."""
     np.random.seed(0)