Repository: napari/napari

Issue:
Loading multiscale labels layer throws error
## 🐛 Bug

Currently if you try to load a multiscale labels layer you get an error

```python
WARNING: Traceback (most recent call last):
  File "/home/draga/CZI/napari/napari/_qt/qt_event_loop.py", line 261, in gui_qt
    yield app
  File "/home/draga/Honours/napari-sentinel-to-zarr/sentinel_to_zarr/scripts/load-images-with-intensities.py", line 79, in main
    viewer.add_labels(
  File "<string>", line 5, in add_labels
  File "/home/draga/anaconda3/envs/summit-talk/lib/python3.8/_collections_abc.py", line 962, in append
    self.insert(len(self), value)
  File "/home/draga/CZI/napari/napari/components/layerlist.py", line 85, in insert
    super().insert(index, new_layer)
  File "/home/draga/CZI/napari/napari/utils/events/containers/_selectable_list.py", line 63, in insert
    super().insert(index, value)
  File "/home/draga/CZI/napari/napari/utils/events/containers/_evented_list.py", line 179, in insert
    self.events.inserted(index=index, value=value)
  File "/home/draga/CZI/napari/napari/utils/events/event.py", line 573, in __call__
    self._invoke_callback(cb, event)
  File "/home/draga/CZI/napari/napari/utils/events/event.py", line 595, in _invoke_callback
    _handle_exception(
  File "/home/draga/CZI/napari/napari/utils/events/event.py", line 593, in _invoke_callback
    cb(event)
  File "/home/draga/CZI/napari/napari/_qt/layer_controls/qt_layer_controls_container.py", line 118, in _add
    controls = create_qt_layer_controls(layer)
  File "/home/draga/CZI/napari/napari/_qt/layer_controls/qt_layer_controls_container.py", line 49, in create_qt_layer_controls
    return controls(layer)
  File "/home/draga/CZI/napari/napari/_qt/layer_controls/qt_labels_controls.py", line 95, in __init__
    self.selectionSpinBox.set_dtype(self.layer.data.dtype)
AttributeError: 'list' object has no attribute 'dtype'
```

We introduced it as part of #2647 . I'll submit a PR shortly to fix.

## To Reproduce

```python
import napari
import numpy as np

v = napari.Viewer()
labels = np.random.randint(0, 10, size=(200, 200))
labels2 = labels[::2, ::2]

v.add_labels([labels, labels2])

napari.run()
```



Discussion:

Resolving patch:
diff --git a/napari/_qt/layer_controls/qt_labels_controls.py b/napari/_qt/layer_controls/qt_labels_controls.py
--- a/napari/_qt/layer_controls/qt_labels_controls.py
+++ b/napari/_qt/layer_controls/qt_labels_controls.py
@@ -16,6 +16,7 @@
     LABEL_COLOR_MODE_TRANSLATIONS,
     Mode,
 )
+from ...layers.labels._labels_utils import get_dtype
 from ...utils.events import disconnect_events
 from ...utils.interactions import Shortcut
 from ...utils.translations import trans
@@ -92,7 +93,8 @@ def __init__(self, layer):
 
         # selection spinbox
         self.selectionSpinBox = QtLargeIntSpinBox()
-        self.selectionSpinBox.set_dtype(self.layer.data.dtype)
+        layer_dtype = get_dtype(layer)
+        self.selectionSpinBox.set_dtype(layer_dtype)
         self.selectionSpinBox.setKeyboardTracking(False)
         self.selectionSpinBox.valueChanged.connect(self.changeSelection)
         self.selectionSpinBox.setAlignment(Qt.AlignCenter)
@@ -124,7 +126,7 @@ def __init__(self, layer):
         self._on_n_edit_dimensions_change()
 
         self.contourSpinBox = QtLargeIntSpinBox()
-        self.contourSpinBox.set_dtype(self.layer.data.dtype)
+        self.contourSpinBox.set_dtype(layer_dtype)
         self.contourSpinBox.setToolTip(trans._('display contours of labels'))
         self.contourSpinBox.valueChanged.connect(self.change_contour)
         self.contourSpinBox.setKeyboardTracking(False)
diff --git a/napari/layers/labels/_labels_utils.py b/napari/layers/labels/_labels_utils.py
--- a/napari/layers/labels/_labels_utils.py
+++ b/napari/layers/labels/_labels_utils.py
@@ -103,3 +103,28 @@ def indices_in_shape(idxs, shape):
     if np_index:  # convert back to original format
         filtered = tuple(filtered.T)
     return filtered
+
+
+def get_dtype(layer):
+    """Returns dtype of layer data
+
+    Parameters
+    ----------
+    layer : Labels
+        Labels layer (may be multiscale)
+
+    Returns
+    -------
+    dtype
+        dtype of Layer data
+    """
+    layer_data = layer.data
+    if not isinstance(layer_data, list):
+        layer_data = [layer_data]
+    layer_data_level = layer_data[0]
+    if hasattr(layer_data_level, 'dtype'):
+        layer_dtype = layer_data_level[0].dtype
+    else:
+        layer_dtype = type(layer_data_level)
+
+    return layer_dtype


Test patch:
diff --git a/napari/layers/labels/_tests/test_labels_utils.py b/napari/layers/labels/_tests/test_labels_utils.py
--- a/napari/layers/labels/_tests/test_labels_utils.py
+++ b/napari/layers/labels/_tests/test_labels_utils.py
@@ -1,6 +1,10 @@
 import numpy as np
 
-from napari.layers.labels._labels_utils import interpolate_coordinates
+from napari.layers.labels import Labels
+from napari.layers.labels._labels_utils import (
+    get_dtype,
+    interpolate_coordinates,
+)
 
 
 def test_interpolate_coordinates():
@@ -25,3 +29,20 @@ def test_interpolate_coordinates():
         ]
     )
     assert np.all(coords == expected_coords)
+
+
+def test_get_dtype():
+    np.random.seed(0)
+    data = np.random.randint(20, size=(50, 50))
+    layer = Labels(data)
+
+    assert get_dtype(layer) == data.dtype
+
+    data2 = data[::2, ::2]
+    layer_data = [data, data2]
+    multiscale_layer = Labels(layer_data)
+    assert get_dtype(multiscale_layer) == layer_data[0].dtype
+
+    data = data.astype(int)
+    int_layer = Labels(data)
+    assert get_dtype(int_layer) == int