Repository: napari/napari

Issue:
division by zero error while opening xarray data array
## 🐛 Bug

When I load a 110 Mb geotiff as an xarray data array and then try to open this with napari I get the following errors below. See screenshot for what the screen looks like during this error, doesn't seem like the image appears but a thumbnail does.

## To Reproduce

Steps to reproduce the behavior:

1. load landsat geotiff image as an xarray data array
2. view with napari
3. the error happens on startup

<!-- If you have a code sample, error messages, stack traces, please provide it here as well -->
```
In [7]: with napari.gui_qt(): 
   ...:     viewer = napari.view(band3) 
   ...:      
   ...:                                                                                              
INFO: Program has unset variables: {'texture2D_LUT'}
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
~/miniconda3/lib/python3.7/site-packages/napari/_qt/qt_range_slider.py in resizeEvent(self, event)
    230 
    231     def resizeEvent(self, event):
--> 232         self.updateDisplayValues()
    233 
    234     def setRange(self, slider_range):

~/miniconda3/lib/python3.7/site-packages/napari/_qt/qt_range_slider.py in updateDisplayValues(self)
    244     def updateDisplayValues(self):
    245         size = self.rangeSliderSize()
--> 246         range = int(size * (self.scale_min - self.start) / self.scale)
    247         self.display_min = range + self.bar_width / 2
    248         range = int(size * (self.scale_max - self.start) / self.scale)

ZeroDivisionError: float division by zero
---------------------------------------------------------------------------
ZeroDivisionError                         Traceback (most recent call last)
~/miniconda3/lib/python3.7/site-packages/napari/_qt/qt_range_slider.py in resizeEvent(self, event)
    230 
    231     def resizeEvent(self, event):
--> 232         self.updateDisplayValues()
    233 
    234     def setRange(self, slider_range):

~/miniconda3/lib/python3.7/site-packages/napari/_qt/qt_range_slider.py in updateDisplayValues(self)
    244     def updateDisplayValues(self):
    245         size = self.rangeSliderSize()
--> 246         range = int(size * (self.scale_min - self.start) / self.scale)
    247         self.display_min = range + self.bar_width / 2
    248         range = int(size * (self.scale_max - self.start) / self.scale)

ZeroDivisionError: float division by zero

```

and when I move the slider around a different error occurs
```
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~/miniconda3/lib/python3.7/site-packages/napari/_qt/qt_range_slider.py in mouseMoveEvent(self, event)
    133                     self.display_max = pos
    134             elif self.moving == "bar":
--> 135                 width = self.start_display_max - self.start_display_min
    136                 lower_part = self.start_pos - self.start_display_min
    137                 upper_part = self.start_display_max - self.start_pos

TypeError: unsupported operand type(s) for -: 'NoneType' and 'NoneType'
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
~/miniconda3/lib/python3.7/site-packages/napari/_qt/qt_range_slider.py in mouseMoveEvent(self, event)
    133                     self.display_max = pos
    134             elif self.moving == "bar":
--> 135                 width = self.start_display_max - self.start_display_min
    136                 lower_part = self.start_pos - self.start_display_min
    137                 upper_part = self.start_display_max - self.start_pos

TypeError: unsupported operand type(s) for -: 'NoneType' and 'NoneType'
---------------------------------------------------------------------------

```


## Expected behavior
no error on open
<!-- A clear and concise description of what you expected to happen. -->

## Environment

 - napari Version (e.g., 1.0):
 - OS (e.g., Linux):
 - Python version:
 - Any other relevant information:

## Additional context
the data array metadata
```
In [8]: band3                                                                                        
Out[8]: 
<xarray.DataArray (band: 1, y: 7041, x: 7821)>
array([[[-9999, -9999, ..., -9999, -9999],
        [-9999, -9999, ..., -9999, -9999],
        ...,
        [-9999, -9999, ..., -9999, -9999],
        [-9999, -9999, ..., -9999, -9999]]], dtype=int16)
Coordinates:
  * band     (band) int64 1
  * y        (y) float64 4.732e+06 4.732e+06 4.732e+06 ... 4.521e+06 4.521e+06
  * x        (x) float64 6.129e+05 6.129e+05 6.13e+05 ... 8.475e+05 8.475e+05
Attributes:
    transform:     (30.0, 0.0, 612885.0, 0.0, -30.0, 4732515.0)
    crs:           +init=epsg:32613
    res:           (30.0, 30.0)
    is_tiled:      0
    nodatavals:    (-9999.0,)
    scales:        (1.0,)
    offsets:       (0.0,)
    descriptions:  ('band 3 surface reflectance',)

```

![Selection_532](https://user-images.githubusercontent.com/22258697/61174213-2ea55800-a552-11e9-8d23-ed28978b46ac.png)




Discussion:
Hi @rbavery I'm trying to reproduce, but my minimal example with xarray works fine - i'm new to xarray so might be missing something.

Here's what I ran
```python
import numpy as np
import xarray as xr
from napari import Viewer, gui_qt

data = np.random.random((100, 50))
xdata = xr.DataArray(data, dims=['x', 'y'])

with gui_qt():
    # create an empty viewer
    viewer = Viewer()

    # add the xarray
    layer = viewer.add_image(xdata, name='xarray')
```
and the result below

![image](https://user-images.githubusercontent.com/6531703/61176291-ea32b000-a583-11e9-8b75-f178ab57ecf8.png)

can you tell me more about what `band3` is / share with me a script / link to generate `band3` or try and reproduce with an xarray generated on fake data.

I'm at the back of room 106 in the SciPy sprints if you want to chat

Howdy! Here's my minimal example for a single band

```
import xarray as xr
import napari
band3_path = "./LT050320312005040601T1-SC20190418222326/LT05_L1TP_032031_20050406_20160912_01_T1_sr_band3.tif"
band3_xr = xr.open_rasterio(band3_path, chunks={'band': 1, 'x': 1024, 'y': 1024})
%gui qt
viewer = napari.view(band3)  
```

Resolving patch:
diff --git a/examples/5D_image.py b/examples/5D_image.py
--- a/examples/5D_image.py
+++ b/examples/5D_image.py
@@ -9,4 +9,4 @@
 
 with napari.gui_qt():
 
-    viewer = napari.view(np.random.random((10, 20, 15, 30, 40)))
+    viewer = napari.view(np.random.random((10, 1, 15, 30, 40)))
diff --git a/napari/_qt/qt_dims.py b/napari/_qt/qt_dims.py
--- a/napari/_qt/qt_dims.py
+++ b/napari/_qt/qt_dims.py
@@ -39,6 +39,7 @@ def __init__(self, dims: Dims, parent=None):
 
         # list of sliders
         self.sliders = []
+        self._slider_axis = []
 
         # Initialises the layout:
         layout = QGridLayout()
@@ -85,33 +86,41 @@ def nsliders(self):
         """
         return len(self.sliders)
 
-    def _update_slider(self, slider_index: int):
+    def _update_slider(self, axis: int):
         """
-        Updates everything for a given slider
+        Updates everything for a given slider.
 
         Parameters
         ----------
-        slider_index : slider index (corresponds to axis index)
+        axis : int
+            Axis index.
         """
-        if slider_index >= self.nsliders:
+
+        if axis not in self._slider_axis:
             return
+        slider_index = self._slider_axis.index(axis)
 
         slider = self.sliders[slider_index]
 
-        mode = self.dims.mode[slider_index]
+        mode = self.dims.mode[axis]
         if mode == DimsMode.POINT:
             slider.collapse()
-            slider.setValue(self.dims.point[slider_index])
+            slider.setValue(self.dims.point[axis])
         elif mode == DimsMode.INTERVAL:
             slider.expand()
-            slider.setValues(self.dims.interval[slider_index])
+            slider.setValues(self.dims.interval[axis])
         # Set the maximum values of the range slider to be one step less than
         # the range of the layer as otherwise the slider can move beyond the
         # shape of the layer as the endpoint is included
-        range = self.dims.range[slider_index]
+        range = self.dims.range[axis]
         range = (range[0], range[1] - range[2], range[2])
         if range not in (None, (None, None, None)):
-            slider.setRange(range)
+            if range[1] == 0:
+                self._remove_slider(slider_index)
+            else:
+                slider.setRange(range)
+        else:
+            self._remove_slider(slider_index)
 
     def _update_nsliders(self):
         """
@@ -147,6 +156,8 @@ def _create_sliders(self, number_of_sliders):
             slider = self._create_range_slider_widget(dim_axis)
             self.layout().addWidget(slider)
             self.sliders.insert(0, slider)
+            self._slider_axis = [a + 1 for a in self._slider_axis]
+            self._slider_axis.insert(0, 0)
             self.setMinimumHeight(self.nsliders * self.SLIDERHEIGHT)
 
     def _trim_sliders(self, number_of_sliders):
@@ -160,10 +171,23 @@ def _trim_sliders(self, number_of_sliders):
         # remove extra sliders so that only number_of_sliders are left
         # remove from the beginning of the list
         for slider_num in range(number_of_sliders, self.nsliders):
-            slider = self.sliders[0]
-            self.sliders = self.sliders[1:]
-            self.layout().removeWidget(slider)
-            slider.deleteLater()
+            self._remove_slider(0)
+
+    def _remove_slider(self, index):
+        """
+        Remove slider at index
+
+        Parameters
+        ----------
+        axis : int
+            Index of slider to remove
+        """
+        # remove particular slider
+        slider = self.sliders.pop(index)
+        self._slider_axis.pop(index)
+        # self._slider_axis.insert(0, 0)
+        self.layout().removeWidget(slider)
+        slider.deleteLater()
         self.setMinimumHeight(self.nsliders * self.SLIDERHEIGHT)
 
     def _create_range_slider_widget(self, axis):
diff --git a/napari/_qt/qt_range_slider.py b/napari/_qt/qt_range_slider.py
--- a/napari/_qt/qt_range_slider.py
+++ b/napari/_qt/qt_range_slider.py
@@ -243,10 +243,14 @@ def setEmitWhileMoving(self, flag):
 
     def updateDisplayValues(self):
         size = self.rangeSliderSize()
-        range = int(size * (self.scale_min - self.start) / self.scale)
-        self.display_min = range + self.bar_width / 2
-        range = int(size * (self.scale_max - self.start) / self.scale)
-        self.display_max = range + self.bar_width / 2
+        if self.scale == 0:
+            range_min = 0
+            range_max = 0
+        else:
+            range_min = int(size * (self.scale_min - self.start) / self.scale)
+            range_max = int(size * (self.scale_max - self.start) / self.scale)
+        self.display_min = range_min + self.bar_width / 2
+        self.display_max = range_max + self.bar_width / 2
 
     def updateScaleValues(self):
         size = self.rangeSliderSize()


Test patch:
diff --git a/napari/layers/image/tests/test_image.py b/napari/layers/image/tests/test_image.py
--- a/napari/layers/image/tests/test_image.py
+++ b/napari/layers/image/tests/test_image.py
@@ -56,6 +56,19 @@ def test_3D_image():
     assert layer._data_view.shape == shape[-2:]
 
 
+def test_3D_image_shape_1():
+    """Test instantiating Image layer with random 3D data with shape 1 axis."""
+    shape = (1, 10, 15)
+    np.random.seed(0)
+    data = np.random.random(shape)
+    layer = Image(data)
+    assert np.all(layer.data == data)
+    assert layer.ndim == len(shape)
+    assert layer.shape == shape
+    assert layer.multichannel == False
+    assert layer._data_view.shape == shape[-2:]
+
+
 def test_4D_image():
     """Test instantiating Image layer with random 4D data."""
     shape = (10, 15, 6, 8)
@@ -69,6 +82,19 @@ def test_4D_image():
     assert layer._data_view.shape == shape[-2:]
 
 
+def test_5D_image_shape_1():
+    """Test instantiating Image layer with random 5D data with shape 1 axis."""
+    shape = (4, 1, 2, 10, 15)
+    np.random.seed(0)
+    data = np.random.random(shape)
+    layer = Image(data)
+    assert np.all(layer.data == data)
+    assert layer.ndim == len(shape)
+    assert layer.shape == shape
+    assert layer.multichannel == False
+    assert layer._data_view.shape == shape[-2:]
+
+
 def test_rgb_image():
     """Test instantiating Image layer with RGB data."""
     shape = (10, 15, 3)