Repository: napari/napari

Issue:
register `LayerData` or purpose-specific type instead of `napari.layers.Image` as return type with magicgui.
## 🐛 Bug

the trick of using `napari.layers.Image` as a return annotation to automatically add the result of a function to the current napari viewer "works" fine (and feels quite magical! :joy:) ... but it breaks linters, and is a technically incorrect type annotation, since the return type is _not_ a `napari.layers.Layer` ... but rather just a plain numpy array, or a layerdata tuple.

We should register some type or string specific for the task (like `napari.types.LayerData` or `napari.types.MagicLayer`)


Discussion:
Agreed, I remember being confused by that the first time too.

I kinda like `napari.types.LayerData` as a type which includes the `layerdata tuple` and where a plain numpy array is interpreted as a singleton of the layerdata tuple which defaults to image.

Would be open to other ideas, have to think about it more maybe
Was this done on the magicgui side? Is this something for `0.2.1`? Just wondering where this stands
I guess actually it has to be on the napari side? Maybe something we get in for #1856?
Yeah, needs to change in utils._magicgui. 
It's a breaking change so will require some extra deprecation code too
I'm trying on `0.2.1rc1` right now with #1856 and if I do this

```python
def image_arithmetic(layerA: 'napari.layers.Image', operation: Operation, layerB: 'napari.layers.Image') -> napari.layers.Image:
```

everything works fine, but if I do
```python
def image_arithmetic(layerA: 'napari.layers.Image', operation: Operation, layerB: 'napari.layers.Image') -> 'napari.layers.Image':
```

then when I click "execute" nothing appears. Is that expected, or do we need to fix this to enable that?
Similarly there is no output layer with

```python
def gaussian_blur(layer: 'napari.layers.Image', sigma: float = 1.0, mode="nearest") -> 'napari.layers.Image':
```
So the above issue with the forward references has now been fixed in magicgui but the original issue remains. From the sounds of discussion with @tlambert03 though this will require more discussion though and so is fine to come after the `0.4.3` release

Resolving patch:
diff --git a/napari/types.py b/napari/types.py
--- a/napari/types.py
+++ b/napari/types.py
@@ -1,23 +1,32 @@
 from functools import wraps
 from types import TracebackType
-from typing import Any, Callable, Dict, List, Tuple, Type, Union
+from typing import (
+    TYPE_CHECKING,
+    Any,
+    Callable,
+    Dict,
+    List,
+    NewType,
+    Tuple,
+    Type,
+    Union,
+)
 
-import dask.array as da
 import numpy as np
 
-try:
+if TYPE_CHECKING:
+    import dask.array
     import zarr
-except ImportError:
-    zarr = None
+
 
 # This is a WOEFULLY inadequate stub for a duck-array type.
 # Mostly, just a placeholder for the concept of needing an ArrayLike type.
 # Ultimately, this should come from https://github.com/napari/image-types
 # and should probably be replaced by a typing.Protocol
-if zarr:
-    ArrayLike = Union[np.ndarray, da.Array, zarr.Array]
-else:
-    ArrayLike = Union[np.ndarray, da.Array]
+# note, numpy.typing.ArrayLike (in v1.20) is not quite what we want either,
+# since it includes all valid arguments for np.array() ( int, float, str...)
+ArrayLike = Union[np.ndarray, 'dask.array.Array', 'zarr.Array']
+
 
 # layer data may be: (data,) (data, meta), or (data, meta, layer_type)
 # using "Any" for the data type until ArrayLike is more mature.
@@ -33,6 +42,36 @@
     Tuple[None, None, None],
 ]
 
+# these types are mostly "intentionality" placeholders.  While it's still hard
+# to use actual types to define what is acceptable data for a given layer,
+# these types let us point to a concrete namespace to indicate "this data is
+# intended to be (and is capable of) being turned into X layer type".
+# while their names should not change (without deprecation), their typing
+# implementations may... or may be rolled over to napari/image-types
+
+if tuple(np.__version__.split('.')) < ('1', '20'):
+    # this hack is because NewType doesn't allow `Any` as a base type
+    # and numpy <=1.20 didn't provide type stubs for np.ndarray
+    # https://github.com/python/mypy/issues/6701#issuecomment-609638202
+    class ArrayBase(np.ndarray):
+        def __getattr__(self, name: str) -> Any:
+            return super().__getattr__(name)
+
+
+else:
+    ArrayBase = np.ndarray  # type: ignore
+
+
+ImageData = NewType("ImageData", ArrayBase)
+LabelsData = NewType("LabelsData", ArrayBase)
+PointsData = NewType("PointsData", ArrayBase)
+ShapesData = NewType("ShapesData", List[ArrayBase])
+SurfaceData = NewType("SurfaceData", Tuple[ArrayBase, ArrayBase, ArrayBase])
+TracksData = NewType("TracksData", ArrayBase)
+VectorsData = NewType("VectorsData", ArrayBase)
+
+LayerDataTuple = NewType("LayerDataTuple", tuple)
+
 
 def image_reader_to_layerdata_reader(
     func: Callable[[PathLike], ArrayLike]
diff --git a/napari/utils/_magicgui.py b/napari/utils/_magicgui.py
--- a/napari/utils/_magicgui.py
+++ b/napari/utils/_magicgui.py
@@ -9,9 +9,11 @@
 of those custom classes, magicgui will know what to do with it.
 
 """
-from typing import Any, Optional, Tuple, Type
+import warnings
+from typing import Any, List, Optional, Tuple, Type
 
-from ..layers import Layer
+from .. import layers, types
+from ..utils.misc import ensure_list_of_layer_data_tuple
 from ..viewer import Viewer
 
 try:
@@ -29,15 +31,157 @@ def register_types_with_magicgui():
         napari.layers.Layer, will be rendered as a ComboBox.
             if a parameter is annotated as a subclass Layer type, then the
             combobox options will be limited to that layer type.
+        napari.Viewer, will be rendered as a ComboBox, with the current viewer
+            as the only choice.
 
     Return Annotations -> Widgets:
         napari.layers.Layer will add a new layer to the Viewer.
             if a return is annotated as a subclass of Layer, then the
-            corresponding layer type will be added.
-            see `show_layer_result` for detail
+            corresponding layer type will be added.  As of 0.4.3, the user
+            must return an actual layer instance
+            see `add_layer_to_viewer` for detail
+        napari.types.<layer_type>Data will add a new layer to the Viewer.
+            using a bare data array (e.g. numpy array) as a return value.
+        napari.types.LayerDataTuple will add a new layer to the Viewer.
+            and expects the user to return a single layer data tuple
+        List[napari.types.LayerDataTuple] will add multiple new layer to the
+            Viewer. And expects the user to return a list of layer data tuples.
+
     """
-    register_type(Layer, choices=get_layers, return_callback=show_layer_result)
+    register_type(
+        layers.Layer, choices=get_layers, return_callback=add_layer_to_viewer
+    )
     register_type(Viewer, choices=get_viewers)
+    register_type(
+        types.LayerDataTuple,
+        return_callback=add_layer_data_tuples_to_viewer,
+    )
+    register_type(
+        List[types.LayerDataTuple],
+        return_callback=add_layer_data_tuples_to_viewer,
+    )
+    for layer_name in layers.NAMES:
+        data_type = getattr(types, f'{layer_name.title()}Data')
+        register_type(
+            data_type,
+            choices=get_layers_data,
+            return_callback=add_layer_data_to_viewer,
+        )
+
+
+def add_layer_data_to_viewer(gui, result, return_type):
+    """Show a magicgui result in the viewer.
+
+    This function will be called when a magicgui-decorated function has a
+    return annotation of one of the `napari.types.<layer_name>Data` ... and
+    will add the data in ``result`` to the current viewer as the corresponding
+    layer type.
+
+    Parameters
+    ----------
+    gui : MagicGui or QWidget
+        The instantiated MagicGui widget.  May or may not be docked in a
+        dock widget.
+    result : Any
+        The result of the function call. For this function, this should be
+        *just* the data part of the corresponding layer type.
+    return_type : type
+        The return annotation that was used in the decorated function.
+
+    Examples
+    --------
+    This allows the user to do this, and add the result as a viewer Image.
+
+    >>> @magicgui
+    ... def make_layer() -> napari.types.ImageData:
+    ...     return np.random.rand(256, 256)
+
+
+    """
+
+    if result is None:
+        return
+
+    viewer = find_viewer_ancestor(gui)
+    if not viewer:
+        return
+
+    try:
+        viewer.layers[gui.result_name].data = result
+    except KeyError:
+        layer_type = return_type.__name__.replace("Data", "").lower()
+        adder = getattr(viewer, f'add_{layer_type}')
+        adder(data=result, name=gui.result_name)
+
+
+def add_layer_data_tuples_to_viewer(gui, result, return_type):
+    """Show a magicgui result in the viewer.
+
+    This function will be called when a magicgui-decorated function has a
+    return annotation of one of the `napari.types.<layer_name>Data` ... and
+    will add the data in ``result`` to the current viewer as the corresponding
+    layer type.
+
+    Parameters
+    ----------
+    gui : MagicGui or QWidget
+        The instantiated MagicGui widget.  May or may not be docked in a
+        dock widget.
+    result : Any
+        The result of the function call. For this function, this should be
+        *just* the data part of the corresponding layer type.
+    return_type : type
+        The return annotation that was used in the decorated function.
+
+    Examples
+    --------
+    This allows the user to do this, and add the result to the viewer
+
+    >>> @magicgui
+    ... def make_layer() -> napari.types.LayerDataTuple:
+    ...     return (np.ones((10,10)), {'name': 'hi'})
+
+    >>> @magicgui
+    ... def make_layer() -> List[napari.types.LayerDataTuple]:
+    ...     return [(np.ones((10,10)), {'name': 'hi'})]
+
+    """
+
+    if result is None:
+        return
+
+    viewer = find_viewer_ancestor(gui)
+    if not viewer:
+        return
+
+    result = result if isinstance(result, list) else [result]
+    try:
+        result = ensure_list_of_layer_data_tuple(result)
+    except TypeError:
+        raise TypeError(
+            f'magicgui function {gui} annotated with a return type of '
+            'napari.types.LayerDataTuple did not return LayerData tuple(s)'
+        )
+
+    for layer_datum in result:
+        # if the layer data has a meta dict with a 'name' key in it...
+        if (
+            len(layer_datum) > 1
+            and isinstance(layer_datum[1], dict)
+            and layer_datum[1].get("name")
+        ):
+            # then try to update the viewer layer with that name.
+            try:
+                layer = viewer.layers[layer_datum[1].get('name')]
+                layer.data = layer_datum[0]
+                for k, v in layer_datum[1].items():
+                    setattr(layer, k, v)
+                continue
+            except KeyError:  # layer not in the viewer
+                pass
+        # otherwise create a new layer from the layer data
+        layer = viewer._add_layer_from_data(*layer_datum)
+        layer._source = gui
 
 
 def find_viewer_ancestor(widget) -> Optional[Viewer]:
@@ -66,7 +210,7 @@ def find_viewer_ancestor(widget) -> Optional[Viewer]:
     return None
 
 
-def get_viewers(gui, *args) -> Tuple[Viewer, ...]:
+def get_viewers(gui) -> Tuple[Viewer, ...]:
     """Return the viewer that the magicwidget is in, or a list of all Viewers."""
     viewer = find_viewer_ancestor(gui)
     if viewer:
@@ -77,7 +221,7 @@ def get_viewers(gui, *args) -> Tuple[Viewer, ...]:
         return tuple(v for v in globals().values() if isinstance(v, Viewer))
 
 
-def get_layers(gui) -> Tuple[Layer, ...]:
+def get_layers(gui) -> Tuple[layers.Layer, ...]:
     """Retrieve layers matching gui.annotation, from the Viewer the gui is in.
 
     Parameters
@@ -107,7 +251,47 @@ def get_layers(gui) -> Tuple[Layer, ...]:
     return tuple(x for x in viewer.layers if isinstance(x, gui.annotation))
 
 
-def show_layer_result(gui, result: Any, return_type: Type[Layer]) -> None:
+def get_layers_data(gui) -> Tuple[Tuple[str, Any], ...]:
+    """Retrieve layers matching gui.annotation, from the Viewer the gui is in.
+
+    As opposed to `get_layers`, this function returns just `layer.data` rather
+    than the full layer object.
+
+    Parameters
+    ----------
+    gui : magicgui.widgets.Widget
+        The instantiated MagicGui widget.  May or may not be docked in a
+        dock widget.
+
+    Returns
+    -------
+    tuple
+        Tuple of layer.data from layers of type ``gui.annotation``
+
+    Examples
+    --------
+    This allows the user to do this, and get a dropdown box in their GUI
+    that shows the available image layers, but just get the data from the image
+    as function input
+
+    >>> @magicgui
+    ... def get_layer_mean(data: napari.types.ImageData) -> float:
+    ...     return data.mean()
+
+    """
+
+    lay_type = gui.annotation.__name__.replace("Data", "").title()
+    prior, gui.annotation = gui.annotation, getattr(layers, lay_type)
+    try:
+        layerlist = get_layers(gui)
+    finally:
+        gui.annotation = prior
+    return tuple((f'{x.name} (data)', x.data) for x in layerlist)
+
+
+def add_layer_to_viewer(
+    gui, result: Any, return_type: Type[layers.Layer]
+) -> None:
     """Show a magicgui result in the viewer.
 
     Parameters
@@ -119,53 +303,61 @@ def show_layer_result(gui, result: Any, return_type: Type[Layer]) -> None:
         The result of the function call.
     return_type : type
         The return annotation that was used in the decorated function.
+
+    Examples
+    --------
+    This allows the user to do this, and add the resulting layer to the viewer.
+
+    >>> @magicgui
+    ... def make_layer() -> napari.layers.Image:
+    ...     return napari.layers.Image(np.random.rand(64, 64))
     """
     if result is None:
         return
 
+    # This is the pre 0.4.3 API, warn user and pass to the correct function.
+    if not isinstance(result, layers.Layer):
+        import textwrap
+
+        if return_type == layers.Layer:
+            msg = (
+                'Annotating a magicgui function with a return type of '
+                '`napari.layers.Layer` is deprecated.  To indicate that your '
+                'function returns a layer data tuple, please use a return '
+                'annotation of `napari.types.LayerDataTuple` or '
+                '`List[napari.types.LayerDataTuple]`\n'
+                'This will raise an exception in napari v0.4.5'
+            )
+            msg = "\n" + "\n".join(textwrap.wrap(msg, width=70))
+            warnings.warn(msg)
+            return add_layer_data_tuples_to_viewer(
+                gui, result, types.LayerDataTuple
+            )
+
+        # it's a layer subclass
+        msg = (
+            'As of napari 0.4.3 magicgui functions with a return annotation of '
+            "a napari layer type (such as 'napari.layers.Image') must now "
+            f"return an actual layer instance, rather than {type(result)}. To "
+            "have a plain array object converted to a napari layer, please "
+            "use a return annotation of napari.types.<layer_name>Data, (with "
+            "the corresponding layer name.  For example, the following "
+            "would add an image layer to the viewer:"
+        )
+        msg = "\n" + "\n".join(textwrap.wrap(msg, width=70))
+        msg += (
+            "\n\n@magicgui\n"
+            "def func(nx: int, ny: int) -> napari.types.ImageData:\n"
+            "    return np.random.rand(ny, nx)\n\n"
+            "This will raise an exception in napari v0.4.5"
+        )
+        warnings.warn(msg)
+        data_type = getattr(types, f'{return_type.__name__.title()}Data')
+        return add_layer_data_to_viewer(gui, result, data_type)
+
     viewer = find_viewer_ancestor(gui)
     if not viewer:
         return
 
-    # if they have annotated the return type as a base layer (layers.Layer),
-    # NOT a subclass of it, then the function MUST return a list of
-    # LayerData tuples where:
-    # LayerData = Union[Tuple[data], Tuple[data, Dict], Tuple[data, Dict, str]]
-    # where `data` is the data for a given layer
-    # and `Dict` is a dict of keywords args that could be passed to that layer
-    # type's add_* method
-    if return_type == Layer:
-        if (
-            isinstance(result, list)
-            and (len(result))
-            and all(isinstance(i, tuple) and (0 < len(i) <= 3) for i in result)
-        ):
-            for layer_datum in result:
-                # if the layer data has a meta dict with a 'name' key in it...
-                if (
-                    len(layer_datum) > 1
-                    and isinstance(layer_datum[1], dict)
-                    and layer_datum[1].get('name')
-                ):
-                    # then try to update the viewer layer with that name.
-                    try:
-                        name = layer_datum[1].get('name')
-                        viewer.layers[name].data = layer_datum[0]
-                        continue
-                    except KeyError:
-                        pass
-                # otherwise create a new layer from the layer data
-                viewer._add_layer_from_data(*layer_datum)
-            return
-        raise TypeError(
-            'Functions annotated with a return type of '
-            'napari.layers.Layer MUST return a list of LayerData tuples'
-        )
-
-    # Otherwise they annotated it as a subclass of layers.Layer, and we allow
-    # the simpler behavior where they only return the layer data.
-    try:
-        viewer.layers[gui.result_name].data = result
-    except KeyError:
-        adder = getattr(viewer, f'add_{return_type.__name__.lower()}')
-        adder(data=result, name=gui.result_name)
+    # After 0.4.3 a return type of a Layer subclass should return a layer.
+    viewer.add_layer(result)
diff --git a/napari/utils/misc.py b/napari/utils/misc.py
--- a/napari/utils/misc.py
+++ b/napari/utils/misc.py
@@ -360,3 +360,18 @@ def ensure_n_tuple(val, n, fill=0):
     assert n > 0, 'n must be greater than 0'
     tuple_value = tuple(val)
     return (fill,) * (n - len(tuple_value)) + tuple_value[-n:]
+
+
+def ensure_layer_data_tuple(val):
+    if not (isinstance(val, tuple) and (0 < len(val) <= 3)):
+        raise TypeError(f'Not a valid layer data tuple: {val!r}')
+    return val
+
+
+def ensure_list_of_layer_data_tuple(val):
+    if isinstance(val, list) and len(val):
+        try:
+            return [ensure_layer_data_tuple(v) for v in val]
+        except TypeError:
+            pass
+    raise TypeError('Not a valid list of layer data tuples!')


Test patch:
diff --git a/napari/utils/_tests/test_magicgui.py b/napari/utils/_tests/test_magicgui.py
--- a/napari/utils/_tests/test_magicgui.py
+++ b/napari/utils/_tests/test_magicgui.py
@@ -1,11 +1,15 @@
 import os
 import sys
+from typing import List
 
 import numpy as np
 import pytest
 from magicgui import magicgui
 
+from napari import types
+from napari._tests.utils import layer_test_data
 from napari.layers import Image, Labels, Layer, Points
+from napari.utils.misc import all_subclasses
 
 try:
     import qtpy  # noqa
@@ -17,8 +21,21 @@
     )
 
 
+# only test the first of each layer type
+test_data = []
+for cls in all_subclasses(Layer):
+    try:
+        test_data.append(next(x for x in layer_test_data if x[0] is cls))
+    except StopIteration:
+        # OctTree Image doesn't have layer_test_data
+        pass
+
+
 def test_magicgui_returns_image(make_test_viewer):
-    """make sure a magicgui function returning Image adds an Image."""
+    """make sure a magicgui function returning Image adds an Image.
+
+    This is deprecated and now emits a warning
+    """
     viewer = make_test_viewer()
 
     @magicgui
@@ -27,18 +44,23 @@ def add_image() -> Image:
 
     viewer.window.add_dock_widget(add_image)
     assert len(viewer.layers) == 0
-    add_image()  # should add a new layer to the list
+    with pytest.warns(UserWarning):
+        add_image()  # should add a new layer to the list
     assert len(viewer.layers) == 1
     assert viewer.layers[0].name == 'add_image result'
 
-    add_image()  # should just update existing layer on subsequent calls
+    with pytest.warns(UserWarning):
+        add_image()  # should just update existing layer on subsequent calls
     assert len(viewer.layers) == 1
     assert viewer.layers[0].name == 'add_image result'
     assert isinstance(viewer.layers[0], Image)
 
 
 def test_magicgui_returns_label(make_test_viewer):
-    """make sure a magicgui function returning Labels adds a Labels."""
+    """make sure a magicgui function returning Labels adds a Labels.
+
+    This is deprecated and now emits a warning
+    """
     viewer = make_test_viewer()
 
     @magicgui
@@ -47,7 +69,8 @@ def add_labels() -> Labels:
 
     viewer.window.add_dock_widget(add_labels)
     assert len(viewer.layers) == 0
-    add_labels()  # should add a new layer to the list
+    with pytest.warns(UserWarning):
+        add_labels()  # should add a new layer to the list
     assert len(viewer.layers) == 1
     assert viewer.layers[0].name == 'add_labels result'
     assert isinstance(viewer.layers[0], Image)
@@ -58,7 +81,10 @@ def add_labels() -> Labels:
     reason="segfault on mac CI",
 )
 def test_magicgui_returns_layer_tuple(make_test_viewer):
-    """make sure a magicgui function returning Layer adds the right type."""
+    """make sure a magicgui function returning Layer adds the right type.
+
+    This is deprecated and now emits a warning
+    """
     viewer = make_test_viewer()
 
     @magicgui
@@ -68,9 +94,100 @@ def add_layer() -> Layer:
     viewer.window.add_dock_widget(add_layer)
     assert len(viewer.layers) == 0
 
-    add_layer()  # should add a new layer to the list
+    with pytest.warns(UserWarning):
+        add_layer()  # should add a new layer to the list
     assert len(viewer.layers) == 1
     layer = viewer.layers[0]
     assert layer.name == 'foo'
     assert isinstance(layer, Points)
     assert layer.data.shape == (10, 3)
+
+
+@pytest.mark.parametrize('LayerType, data, ndim', test_data)
+def test_magicgui_add_data(make_test_viewer, LayerType, data, ndim):
+    """Test that annotating with napari.types.<layer_type>Data works.
+
+    It expects a raw data format (like a numpy array) and will add a layer
+    of the corresponding type to the viewer.
+    """
+    viewer = make_test_viewer()
+    dtype = getattr(types, f'{LayerType.__name__}Data')
+
+    @magicgui
+    # where `dtype` is something like napari.types.ImageData
+    def add_data() -> dtype:
+        # and data is just the bare numpy-array or similar
+        return data
+
+    viewer.window.add_dock_widget(add_data)
+    add_data()
+    assert len(viewer.layers) == 1
+    assert isinstance(viewer.layers[0], LayerType)
+
+
+@pytest.mark.parametrize('LayerType, data, ndim', test_data)
+def test_magicgui_get_data(make_test_viewer, LayerType, data, ndim):
+    """Test that annotating parameters with napari.types.<layer_type>Data.
+
+    This will provide the same dropdown menu appearance as when annotating
+    a parameter with napari.layers.<layer_type>... but the function will
+    receive `layer.data` rather than `layer`
+    """
+    viewer = make_test_viewer()
+    dtype = getattr(types, f'{LayerType.__name__}Data')
+
+    @magicgui
+    # where `dtype` is something like napari.types.ImageData
+    def add_data(x: dtype):
+        # and data is just the bare numpy-array or similar
+        return data
+
+    viewer.window.add_dock_widget(add_data)
+    layer = LayerType(data)
+    viewer.add_layer(layer)
+
+
+@pytest.mark.parametrize('LayerType, data, ndim', test_data)
+def test_magicgui_add_layer(make_test_viewer, LayerType, data, ndim):
+    viewer = make_test_viewer()
+
+    @magicgui
+    def add_layer() -> LayerType:
+        return LayerType(data)
+
+    viewer.window.add_dock_widget(add_layer)
+    add_layer()
+    assert len(viewer.layers) == 1
+    assert isinstance(viewer.layers[0], LayerType)
+
+
+def test_magicgui_add_layer_data_tuple(make_test_viewer):
+    viewer = make_test_viewer()
+
+    @magicgui
+    def add_layer() -> types.LayerDataTuple:
+        data = (np.random.rand(10, 10), {'name': 'hi'}, 'labels')
+        # it works fine to just return `data`
+        # but this will avoid mypy/linter errors and has no runtime burden
+        return types.LayerDataTuple(data)
+
+    viewer.window.add_dock_widget(add_layer)
+    add_layer()
+    assert len(viewer.layers) == 1
+    assert isinstance(viewer.layers[0], Labels)
+
+
+def test_magicgui_add_layer_data_tuple_list(make_test_viewer):
+    viewer = make_test_viewer()
+
+    @magicgui
+    def add_layer() -> List[types.LayerDataTuple]:
+        data1 = (np.random.rand(10, 10), {'name': 'hi'})
+        data2 = (np.random.rand(10, 10), {'name': 'hi2'}, 'labels')
+        return [data1, data2]  # type: ignore
+
+    viewer.window.add_dock_widget(add_layer)
+    add_layer()
+    assert len(viewer.layers) == 2
+    assert isinstance(viewer.layers[0], Image)
+    assert isinstance(viewer.layers[1], Labels)