Repository: napari/napari

Issue:
Refactor add_* methods in ViewerModel into mixin class
## 🚀 Refactor

I'd like us to consider refactoring the `add_*` methods currently inside the [ViewerModel file](https://github.com/napari/napari/blob/master/napari/components/viewer_model.py#L393-L1001) into a mixin class `AddLayersMixin` in it's own file `add_layers.py`

The reason for this is I currently find the `ViewerModel` file very large and hard to find things in, and often we just have to update either the `add_*` methods or the actual `ViewerModel` stuff and they are a little different in nature. This organization would then mirror the existence of the [`view_layers.py` file](https://github.com/napari/napari/blob/master/napari/view_layers.py) and make things much easier to parse.

Note, that I'd leave the [`add_layer` method](https://github.com/napari/napari/blob/a10a88b2115160211863b47ee7d3012fc8c7beff/napari/components/viewer_model.py#L368) layer inside the main `ViewerModel` file, though I might make it private `_add_layer` as I don't imagine users explicitly making their own layers and adding them in this way.

Aside from that minor change the rest of this would be a completely backwards compatible refactor and only aid our internal organization of the codebase.

What do you think @jni @kne42?  


Discussion:
I defer to @kne42 here, but on first glance it seems like a good idea to me! =)
SGTM! One thing you should check out first is building the docs since I'm not sure if Sphinx will document inherited methods (e.g. they may not show up under the `Viewer` class).

WRT renaming `add_layer`, I'm in favor of it, although it may make it more difficult for someone who wants to transfer a layer between viewers, but I think that use case is very uncommon.

Resolving patch:
diff --git a/napari/components/add_layers_mixin.py b/napari/components/add_layers_mixin.py
new file mode 100644
--- /dev/null
+++ b/napari/components/add_layers_mixin.py
@@ -0,0 +1,631 @@
+import itertools
+import numpy as np
+
+from .. import layers
+from ..utils import colormaps
+from ..utils.misc import ensure_iterable, is_iterable
+from ..utils import io
+
+
+class AddLayersMixin:
+    """A mixin that adds add_* methods for adding layers to the ViewerModel.
+
+    Each method corresponds to adding one or more layers to the viewer.
+    Methods that just add a single layer contain the keyword arguments and
+    copies of the documentation from that the layer. These are copied and
+    pasted instead of being autogenerated because IDEs like PyCharm parse the
+    source code for docs instead of pulling it up dynamically.
+
+    These methods are separated into a mixin to keep the ViewerModel class
+    easier to read and make these methods easier to maintain.
+    """
+
+    def add_image(
+        self,
+        data=None,
+        *,
+        channel_axis=None,
+        rgb=None,
+        is_pyramid=None,
+        colormap=None,
+        contrast_limits=None,
+        gamma=1,
+        interpolation='nearest',
+        rendering='mip',
+        iso_threshold=0.5,
+        attenuation=0.5,
+        name=None,
+        metadata=None,
+        scale=None,
+        translate=None,
+        opacity=1,
+        blending=None,
+        visible=True,
+        path=None,
+    ):
+        """Add an image layer to the layers list.
+
+        Parameters
+        ----------
+        data : array or list of array
+            Image data. Can be N dimensional. If the last dimension has length
+            3 or 4 can be interpreted as RGB or RGBA if rgb is `True`. If a
+            list and arrays are decreasing in shape then the data is treated as
+            an image pyramid.
+        channel_axis : int, optional
+            Axis to expand image along.
+        rgb : bool
+            Whether the image is rgb RGB or RGBA. If not specified by user and
+            the last dimension of the data has length 3 or 4 it will be set as
+            `True`. If `False` the image is interpreted as a luminance image.
+        is_pyramid : bool
+            Whether the data is an image pyramid or not. Pyramid data is
+            represented by a list of array like image data. If not specified by
+            the user and if the data is a list of arrays that decrease in shape
+            then it will be taken to be a pyramid. The first image in the list
+            should be the largest.
+        colormap : str, vispy.Color.Colormap, tuple, dict, list
+            Colormaps to use for luminance images. If a string must be the name
+            of a supported colormap from vispy or matplotlib. If a tuple the
+            first value must be a string to assign as a name to a colormap and
+            the second item must be a Colormap. If a dict the key must be a
+            string to assign as a name to a colormap and the value must be a
+            Colormap. If a list then must be same length as the axis that is
+            being expanded as channels, and each colormap is applied to each
+            new image layer.
+        contrast_limits : list (2,)
+            Color limits to be used for determining the colormap bounds for
+            luminance images. If not passed is calculated as the min and max of
+            the image. If list of lists then must be same length as the axis
+            that is being expanded and then each colormap is applied to each
+            image.
+        gamma : list, float
+            Gamma correction for determining colormap linearity. Defaults to 1.
+            If a list then must be same length as the axis that is being
+            expanded and then each entry in the list is applied to each image.
+        interpolation : str
+            Interpolation mode used by vispy. Must be one of our supported
+            modes.
+        rendering : str
+            Rendering mode used by vispy. Must be one of our supported
+            modes.
+        iso_threshold : float
+            Threshold for isosurface.
+        attenuation : float
+            Attenuation rate for attenuated maximum intensity projection.
+        name : str
+            Name of the layer.
+        metadata : dict
+            Layer metadata.
+        scale : tuple of float
+            Scale factors for the layer.
+        translate : tuple of float
+            Translation values for the layer.
+        opacity : float
+            Opacity of the layer visual, between 0.0 and 1.0.
+        blending : str
+            One of a list of preset blending modes that determines how RGB and
+            alpha values of the layer visual get mixed. Allowed values are
+            {'opaque', 'translucent', and 'additive'}.
+        visible : bool
+            Whether the layer visual is currently being displayed.
+        path : str or list of str
+            Path or list of paths to image data. Paths can be passed as strings
+            or `pathlib.Path` instances.
+
+        Returns
+        -------
+        layer : :class:`napari.layers.Image` or list
+            The newly-created image layer or list of image layers.
+        """
+        if data is None and path is None:
+            raise ValueError("One of either data or path must be provided")
+        elif data is not None and path is not None:
+            raise ValueError("Only one of data or path can be provided")
+        elif data is None:
+            data = io.magic_imread(path)
+
+        if channel_axis is None:
+            if colormap is None:
+                colormap = 'gray'
+            if blending is None:
+                blending = 'translucent'
+            layer = layers.Image(
+                data,
+                rgb=rgb,
+                is_pyramid=is_pyramid,
+                colormap=colormap,
+                contrast_limits=contrast_limits,
+                gamma=gamma,
+                interpolation=interpolation,
+                rendering=rendering,
+                iso_threshold=iso_threshold,
+                attenuation=attenuation,
+                name=name,
+                metadata=metadata,
+                scale=scale,
+                translate=translate,
+                opacity=opacity,
+                blending=blending,
+                visible=visible,
+            )
+            self.add_layer(layer)
+            return layer
+        else:
+            if is_pyramid:
+                n_channels = data[0].shape[channel_axis]
+            else:
+                n_channels = data.shape[channel_axis]
+
+            name = ensure_iterable(name)
+
+            if blending is None:
+                blending = 'additive'
+
+            if colormap is None:
+                if n_channels < 3:
+                    colormap = colormaps.MAGENTA_GREEN
+                else:
+                    colormap = itertools.cycle(colormaps.CYMRGB)
+            else:
+                colormap = ensure_iterable(colormap)
+
+            # If one pair of clim values is passed then need to iterate them to
+            # all layers.
+            if contrast_limits is not None and not is_iterable(
+                contrast_limits[0]
+            ):
+                contrast_limits = itertools.repeat(contrast_limits)
+            else:
+                contrast_limits = ensure_iterable(contrast_limits)
+
+            gamma = ensure_iterable(gamma)
+
+            layer_list = []
+            zipped_args = zip(
+                range(n_channels), colormap, contrast_limits, gamma, name
+            )
+            for i, cmap, clims, _gamma, name in zipped_args:
+                if is_pyramid:
+                    image = [
+                        np.take(data[j], i, axis=channel_axis)
+                        for j in range(len(data))
+                    ]
+                else:
+                    image = np.take(data, i, axis=channel_axis)
+                layer = layers.Image(
+                    image,
+                    rgb=rgb,
+                    colormap=cmap,
+                    contrast_limits=clims,
+                    gamma=_gamma,
+                    interpolation=interpolation,
+                    rendering=rendering,
+                    name=name,
+                    metadata=metadata,
+                    scale=scale,
+                    translate=translate,
+                    opacity=opacity,
+                    blending=blending,
+                    visible=visible,
+                )
+                self.add_layer(layer)
+                layer_list.append(layer)
+            return layer_list
+
+    def add_points(
+        self,
+        data=None,
+        *,
+        symbol='o',
+        size=10,
+        edge_width=1,
+        edge_color='black',
+        face_color='white',
+        n_dimensional=False,
+        name=None,
+        metadata=None,
+        scale=None,
+        translate=None,
+        opacity=1,
+        blending='translucent',
+        visible=True,
+    ):
+        """Add a points layer to the layers list.
+
+        Parameters
+        ----------
+        data : array (N, D)
+            Coordinates for N points in D dimensions.
+        symbol : str
+            Symbol to be used for the point markers. Must be one of the
+            following: arrow, clobber, cross, diamond, disc, hbar, ring,
+            square, star, tailed_arrow, triangle_down, triangle_up, vbar, x.
+        size : float, array
+            Size of the point marker. If given as a scalar, all points are made
+            the same size. If given as an array, size must be the same
+            broadcastable to the same shape as the data.
+        edge_width : float
+            Width of the symbol edge in pixels.
+        edge_color : str, array-like
+            Color of the point marker border. Numeric color values should be RGB(A).
+        face_color : str, array-like
+            Color of the point marker body. Numeric color values should be RGB(A).
+        n_dimensional : bool
+            If True, renders points not just in central plane but also in all
+            n-dimensions according to specified point marker size.
+        name : str
+            Name of the layer.
+        metadata : dict
+            Layer metadata.
+        scale : tuple of float
+            Scale factors for the layer.
+        translate : tuple of float
+            Translation values for the layer.
+        opacity : float
+            Opacity of the layer visual, between 0.0 and 1.0.
+        blending : str
+            One of a list of preset blending modes that determines how RGB and
+            alpha values of the layer visual get mixed. Allowed values are
+            {'opaque', 'translucent', and 'additive'}.
+        visible : bool
+            Whether the layer visual is currently being displayed.
+
+        Returns
+        -------
+        layer : :class:`napari.layers.Points`
+            The newly-created points layer.
+
+        Notes
+        -----
+        See vispy's marker visual docs for more details:
+        http://api.vispy.org/en/latest/visuals.html#vispy.visuals.MarkersVisual
+        """
+        if data is None:
+            ndim = max(self.dims.ndim, 2)
+            data = np.empty([0, ndim])
+
+        layer = layers.Points(
+            data=data,
+            symbol=symbol,
+            size=size,
+            edge_width=edge_width,
+            edge_color=edge_color,
+            face_color=face_color,
+            n_dimensional=n_dimensional,
+            name=name,
+            metadata=metadata,
+            scale=scale,
+            translate=translate,
+            opacity=opacity,
+            blending=blending,
+            visible=visible,
+        )
+        self.add_layer(layer)
+        return layer
+
+    def add_labels(
+        self,
+        data=None,
+        *,
+        is_pyramid=None,
+        num_colors=50,
+        seed=0.5,
+        name=None,
+        metadata=None,
+        scale=None,
+        translate=None,
+        opacity=0.7,
+        blending='translucent',
+        visible=True,
+        path=None,
+    ):
+        """Add a labels (or segmentation) layer to the layers list.
+
+        An image-like layer where every pixel contains an integer ID
+        corresponding to the region it belongs to.
+
+        Parameters
+        ----------
+        data : array or list of array
+            Labels data as an array or pyramid.
+        is_pyramid : bool
+            Whether the data is an image pyramid or not. Pyramid data is
+            represented by a list of array like image data. If not specified by
+            the user and if the data is a list of arrays that decrease in shape
+            then it will be taken to be a pyramid. The first image in the list
+            should be the largest.
+        num_colors : int
+            Number of unique colors to use in colormap.
+        seed : float
+            Seed for colormap random generator.
+        name : str
+            Name of the layer.
+        metadata : dict
+            Layer metadata.
+        scale : tuple of float
+            Scale factors for the layer.
+        translate : tuple of float
+            Translation values for the layer.
+        opacity : float
+            Opacity of the layer visual, between 0.0 and 1.0.
+        blending : str
+            One of a list of preset blending modes that determines how RGB and
+            alpha values of the layer visual get mixed. Allowed values are
+            {'opaque', 'translucent', and 'additive'}.
+        visible : bool
+            Whether the layer visual is currently being displayed.
+        path : str or list of str
+            Path or list of paths to image data. Paths can be passed as strings
+            or `pathlib.Path` instances.
+
+        Returns
+        -------
+        layer : :class:`napari.layers.Labels`
+            The newly-created labels layer.
+        """
+        if data is None and path is None:
+            raise ValueError("One of either data or path must be provided")
+        elif data is not None and path is not None:
+            raise ValueError("Only one of data or path can be provided")
+        elif data is None:
+            data = io.magic_imread(path)
+
+        layer = layers.Labels(
+            data,
+            is_pyramid=is_pyramid,
+            num_colors=num_colors,
+            seed=seed,
+            name=name,
+            metadata=metadata,
+            scale=scale,
+            translate=translate,
+            opacity=opacity,
+            blending=blending,
+            visible=visible,
+        )
+        self.add_layer(layer)
+        return layer
+
+    def add_shapes(
+        self,
+        data=None,
+        *,
+        shape_type='rectangle',
+        edge_width=1,
+        edge_color='black',
+        face_color='white',
+        z_index=0,
+        name=None,
+        metadata=None,
+        scale=None,
+        translate=None,
+        opacity=0.7,
+        blending='translucent',
+        visible=True,
+    ):
+        """Add a shapes layer to the layers list.
+
+        Parameters
+        ----------
+        data : list or array
+            List of shape data, where each element is an (N, D) array of the
+            N vertices of a shape in D dimensions. Can be an 3-dimensional
+            array if each shape has the same number of vertices.
+        shape_type : string or list
+            String of shape shape_type, must be one of "{'line', 'rectangle',
+            'ellipse', 'path', 'polygon'}". If a list is supplied it must be
+            the same length as the length of `data` and each element will be
+            applied to each shape otherwise the same value will be used for all
+            shapes.
+        edge_width : float or list
+            Thickness of lines and edges. If a list is supplied it must be the
+            same length as the length of `data` and each element will be
+            applied to each shape otherwise the same value will be used for all
+            shapes.
+        edge_color : str or list
+            If string can be any color name recognized by vispy or hex value if
+            starting with `#`. If array-like must be 1-dimensional array with 3
+            or 4 elements. If a list is supplied it must be the same length as
+            the length of `data` and each element will be applied to each shape
+            otherwise the same value will be used for all shapes.
+        face_color : str or list
+            If string can be any color name recognized by vispy or hex value if
+            starting with `#`. If array-like must be 1-dimensional array with 3
+            or 4 elements. If a list is supplied it must be the same length as
+            the length of `data` and each element will be applied to each shape
+            otherwise the same value will be used for all shapes.
+        z_index : int or list
+            Specifier of z order priority. Shapes with higher z order are
+            displayed ontop of others. If a list is supplied it must be the
+            same length as the length of `data` and each element will be
+            applied to each shape otherwise the same value will be used for all
+            shapes.
+        name : str
+            Name of the layer.
+        metadata : dict
+            Layer metadata.
+        scale : tuple of float
+            Scale factors for the layer.
+        translate : tuple of float
+            Translation values for the layer.
+        opacity : float or list
+            Opacity of the layer visual, between 0.0 and 1.0.
+        blending : str
+            One of a list of preset blending modes that determines how RGB and
+            alpha values of the layer visual get mixed. Allowed values are
+            {'opaque', 'translucent', and 'additive'}.
+        visible : bool
+            Whether the layer visual is currently being displayed.
+
+        Returns
+        -------
+        layer : :class:`napari.layers.Shapes`
+            The newly-created shapes layer.
+        """
+        if data is None:
+            ndim = max(self.dims.ndim, 2)
+            data = np.empty((0, 0, ndim))
+
+        layer = layers.Shapes(
+            data=data,
+            shape_type=shape_type,
+            edge_width=edge_width,
+            edge_color=edge_color,
+            face_color=face_color,
+            z_index=z_index,
+            name=name,
+            metadata=metadata,
+            scale=scale,
+            translate=translate,
+            opacity=opacity,
+            blending=blending,
+            visible=visible,
+        )
+        self.add_layer(layer)
+        return layer
+
+    def add_surface(
+        self,
+        data,
+        *,
+        colormap='gray',
+        contrast_limits=None,
+        gamma=1,
+        name=None,
+        metadata=None,
+        scale=None,
+        translate=None,
+        opacity=1,
+        blending='translucent',
+        visible=True,
+    ):
+        """Add a surface layer to the layers list.
+
+        Parameters
+        ----------
+        data : 3-tuple of array
+            The first element of the tuple is an (N, D) array of vertices of
+            mesh triangles. The second is an (M, 3) array of int of indices
+            of the mesh triangles. The third element is the (K0, ..., KL, N)
+            array of values used to color vertices where the additional L
+            dimensions are used to color the same mesh with different values.
+        colormap : str, vispy.Color.Colormap, tuple, dict
+            Colormap to use for luminance images. If a string must be the name
+            of a supported colormap from vispy or matplotlib. If a tuple the
+            first value must be a string to assign as a name to a colormap and
+            the second item must be a Colormap. If a dict the key must be a
+            string to assign as a name to a colormap and the value must be a
+            Colormap.
+        contrast_limits : list (2,)
+            Color limits to be used for determining the colormap bounds for
+            luminance images. If not passed is calculated as the min and max of
+            the image.
+        gamma : float
+            Gamma correction for determining colormap linearity. Defaults to 1.
+        name : str
+            Name of the layer.
+        metadata : dict
+            Layer metadata.
+        scale : tuple of float
+            Scale factors for the layer.
+        translate : tuple of float
+            Translation values for the layer.
+        opacity : float
+            Opacity of the layer visual, between 0.0 and 1.0.
+        blending : str
+            One of a list of preset blending modes that determines how RGB and
+            alpha values of the layer visual get mixed. Allowed values are
+            {'opaque', 'translucent', and 'additive'}.
+        visible : bool
+            Whether the layer visual is currently being displayed.
+
+        Returns
+        -------
+        layer : :class:`napari.layers.Surface`
+            The newly-created surface layer.
+        """
+        layer = layers.Surface(
+            data,
+            colormap=colormap,
+            contrast_limits=contrast_limits,
+            gamma=gamma,
+            name=name,
+            metadata=metadata,
+            scale=scale,
+            translate=translate,
+            opacity=opacity,
+            blending=blending,
+            visible=visible,
+        )
+        self.add_layer(layer)
+        return layer
+
+    def add_vectors(
+        self,
+        data,
+        *,
+        edge_width=1,
+        edge_color='red',
+        length=1,
+        name=None,
+        metadata=None,
+        scale=None,
+        translate=None,
+        opacity=0.7,
+        blending='translucent',
+        visible=True,
+    ):
+        """Add a vectors layer to the layers list.
+
+        Parameters
+        ----------
+        data : (N, 2, D) or (N1, N2, ..., ND, D) array
+            An (N, 2, D) array is interpreted as "coordinate-like" data and a
+            list of N vectors with start point and projections of the vector in
+            D dimensions. An (N1, N2, ..., ND, D) array is interpreted as
+            "image-like" data where there is a length D vector of the
+            projections at each pixel.
+        edge_width : float
+            Width for all vectors in pixels.
+        length : float
+             Multiplicative factor on projections for length of all vectors.
+        edge_color : str
+            Edge color of all the vectors.
+        name : str
+            Name of the layer.
+        metadata : dict
+            Layer metadata.
+        scale : tuple of float
+            Scale factors for the layer.
+        translate : tuple of float
+            Translation values for the layer.
+        opacity : float
+            Opacity of the layer visual, between 0.0 and 1.0.
+        blending : str
+            One of a list of preset blending modes that determines how RGB and
+            alpha values of the layer visual get mixed. Allowed values are
+            {'opaque', 'translucent', and 'additive'}.
+        visible : bool
+            Whether the layer visual is currently being displayed.
+
+        Returns
+        -------
+        layer : :class:`napari.layers.Vectors`
+            The newly-created vectors layer.
+        """
+        layer = layers.Vectors(
+            data,
+            edge_width=edge_width,
+            edge_color=edge_color,
+            length=length,
+            name=name,
+            metadata=metadata,
+            scale=scale,
+            translate=translate,
+            opacity=opacity,
+            blending=blending,
+            visible=visible,
+        )
+        self.add_layer(layer)
+        return layer
diff --git a/napari/components/viewer_model.py b/napari/components/viewer_model.py
--- a/napari/components/viewer_model.py
+++ b/napari/components/viewer_model.py
@@ -4,18 +4,15 @@
 
 import numpy as np
 
+from .add_layers_mixin import AddLayersMixin
 from .dims import Dims
 from .layerlist import LayerList
-from .. import layers
-from ..utils import colormaps
 from ..utils.event import EmitterGroup, Event
 from ..utils.keybindings import KeymapMixin
 from ..utils.theme import palettes
-from ..utils.misc import ensure_iterable, is_iterable
-from ..utils import io
 
 
-class ViewerModel(KeymapMixin):
+class ViewerModel(AddLayersMixin, KeymapMixin):
     """Viewer containing the rendered scene, layers, and controlling elements
     including dimension sliders, and control bars for color limits.
 
@@ -390,616 +387,6 @@ def add_layer(self, layer):
         if len(self.layers) == 1:
             self.reset_view()
 
-    def add_image(
-        self,
-        data=None,
-        *,
-        channel_axis=None,
-        rgb=None,
-        is_pyramid=None,
-        colormap=None,
-        contrast_limits=None,
-        gamma=1,
-        interpolation='nearest',
-        rendering='mip',
-        iso_threshold=0.5,
-        attenuation=0.5,
-        name=None,
-        metadata=None,
-        scale=None,
-        translate=None,
-        opacity=1,
-        blending=None,
-        visible=True,
-        path=None,
-    ):
-        """Add an image layer to the layers list.
-
-        Parameters
-        ----------
-        data : array or list of array
-            Image data. Can be N dimensional. If the last dimension has length
-            3 or 4 can be interpreted as RGB or RGBA if rgb is `True`. If a
-            list and arrays are decreasing in shape then the data is treated as
-            an image pyramid.
-        channel_axis : int, optional
-            Axis to expand image along.
-        rgb : bool
-            Whether the image is rgb RGB or RGBA. If not specified by user and
-            the last dimension of the data has length 3 or 4 it will be set as
-            `True`. If `False` the image is interpreted as a luminance image.
-        is_pyramid : bool
-            Whether the data is an image pyramid or not. Pyramid data is
-            represented by a list of array like image data. If not specified by
-            the user and if the data is a list of arrays that decrease in shape
-            then it will be taken to be a pyramid. The first image in the list
-            should be the largest.
-        colormap : str, vispy.Color.Colormap, tuple, dict, list
-            Colormaps to use for luminance images. If a string must be the name
-            of a supported colormap from vispy or matplotlib. If a tuple the
-            first value must be a string to assign as a name to a colormap and
-            the second item must be a Colormap. If a dict the key must be a
-            string to assign as a name to a colormap and the value must be a
-            Colormap. If a list then must be same length as the axis that is
-            being expanded as channels, and each colormap is applied to each
-            new image layer.
-        contrast_limits : list (2,)
-            Color limits to be used for determining the colormap bounds for
-            luminance images. If not passed is calculated as the min and max of
-            the image. If list of lists then must be same length as the axis
-            that is being expanded and then each colormap is applied to each
-            image.
-        gamma : list, float
-            Gamma correction for determining colormap linearity. Defaults to 1.
-            If a list then must be same length as the axis that is being
-            expanded and then each entry in the list is applied to each image.
-        interpolation : str
-            Interpolation mode used by vispy. Must be one of our supported
-            modes.
-        rendering : str
-            Rendering mode used by vispy. Must be one of our supported
-            modes.
-        iso_threshold : float
-            Threshold for isosurface.
-        attenuation : float
-            Attenuation rate for attenuated maximum intensity projection.
-        name : str
-            Name of the layer.
-        metadata : dict
-            Layer metadata.
-        scale : tuple of float
-            Scale factors for the layer.
-        translate : tuple of float
-            Translation values for the layer.
-        opacity : float
-            Opacity of the layer visual, between 0.0 and 1.0.
-        blending : str
-            One of a list of preset blending modes that determines how RGB and
-            alpha values of the layer visual get mixed. Allowed values are
-            {'opaque', 'translucent', and 'additive'}.
-        visible : bool
-            Whether the layer visual is currently being displayed.
-        path : str or list of str
-            Path or list of paths to image data. Paths can be passed as strings
-            or `pathlib.Path` instances.
-
-        Returns
-        -------
-        layer : :class:`napari.layers.Image` or list
-            The newly-created image layer or list of image layers.
-        """
-        if data is None and path is None:
-            raise ValueError("One of either data or path must be provided")
-        elif data is not None and path is not None:
-            raise ValueError("Only one of data or path can be provided")
-        elif data is None:
-            data = io.magic_imread(path)
-
-        if channel_axis is None:
-            if colormap is None:
-                colormap = 'gray'
-            if blending is None:
-                blending = 'translucent'
-            layer = layers.Image(
-                data,
-                rgb=rgb,
-                is_pyramid=is_pyramid,
-                colormap=colormap,
-                contrast_limits=contrast_limits,
-                gamma=gamma,
-                interpolation=interpolation,
-                rendering=rendering,
-                iso_threshold=iso_threshold,
-                attenuation=attenuation,
-                name=name,
-                metadata=metadata,
-                scale=scale,
-                translate=translate,
-                opacity=opacity,
-                blending=blending,
-                visible=visible,
-            )
-            self.add_layer(layer)
-            return layer
-        else:
-            if is_pyramid:
-                n_channels = data[0].shape[channel_axis]
-            else:
-                n_channels = data.shape[channel_axis]
-
-            name = ensure_iterable(name)
-
-            if blending is None:
-                blending = 'additive'
-
-            if colormap is None:
-                if n_channels < 3:
-                    colormap = colormaps.MAGENTA_GREEN
-                else:
-                    colormap = itertools.cycle(colormaps.CYMRGB)
-            else:
-                colormap = ensure_iterable(colormap)
-
-            # If one pair of clim values is passed then need to iterate them to
-            # all layers.
-            if contrast_limits is not None and not is_iterable(
-                contrast_limits[0]
-            ):
-                contrast_limits = itertools.repeat(contrast_limits)
-            else:
-                contrast_limits = ensure_iterable(contrast_limits)
-
-            gamma = ensure_iterable(gamma)
-
-            layer_list = []
-            zipped_args = zip(
-                range(n_channels), colormap, contrast_limits, gamma, name
-            )
-            for i, cmap, clims, _gamma, name in zipped_args:
-                if is_pyramid:
-                    image = [
-                        np.take(data[j], i, axis=channel_axis)
-                        for j in range(len(data))
-                    ]
-                else:
-                    image = np.take(data, i, axis=channel_axis)
-                layer = layers.Image(
-                    image,
-                    rgb=rgb,
-                    colormap=cmap,
-                    contrast_limits=clims,
-                    gamma=_gamma,
-                    interpolation=interpolation,
-                    rendering=rendering,
-                    name=name,
-                    metadata=metadata,
-                    scale=scale,
-                    translate=translate,
-                    opacity=opacity,
-                    blending=blending,
-                    visible=visible,
-                )
-                self.add_layer(layer)
-                layer_list.append(layer)
-            return layer_list
-
-    def add_points(
-        self,
-        data=None,
-        *,
-        symbol='o',
-        size=10,
-        edge_width=1,
-        edge_color='black',
-        face_color='white',
-        n_dimensional=False,
-        name=None,
-        metadata=None,
-        scale=None,
-        translate=None,
-        opacity=1,
-        blending='translucent',
-        visible=True,
-    ):
-        """Add a points layer to the layers list.
-
-        Parameters
-        ----------
-        data : array (N, D)
-            Coordinates for N points in D dimensions.
-        symbol : str
-            Symbol to be used for the point markers. Must be one of the
-            following: arrow, clobber, cross, diamond, disc, hbar, ring,
-            square, star, tailed_arrow, triangle_down, triangle_up, vbar, x.
-        size : float, array
-            Size of the point marker. If given as a scalar, all points are made
-            the same size. If given as an array, size must be the same
-            broadcastable to the same shape as the data.
-        edge_width : float
-            Width of the symbol edge in pixels.
-        edge_color : str, array-like
-            Color of the point marker border. Numeric color values should be RGB(A).
-        face_color : str, array-like
-            Color of the point marker body. Numeric color values should be RGB(A).
-        n_dimensional : bool
-            If True, renders points not just in central plane but also in all
-            n-dimensions according to specified point marker size.
-        name : str
-            Name of the layer.
-        metadata : dict
-            Layer metadata.
-        scale : tuple of float
-            Scale factors for the layer.
-        translate : tuple of float
-            Translation values for the layer.
-        opacity : float
-            Opacity of the layer visual, between 0.0 and 1.0.
-        blending : str
-            One of a list of preset blending modes that determines how RGB and
-            alpha values of the layer visual get mixed. Allowed values are
-            {'opaque', 'translucent', and 'additive'}.
-        visible : bool
-            Whether the layer visual is currently being displayed.
-
-        Returns
-        -------
-        layer : :class:`napari.layers.Points`
-            The newly-created points layer.
-
-        Notes
-        -----
-        See vispy's marker visual docs for more details:
-        http://api.vispy.org/en/latest/visuals.html#vispy.visuals.MarkersVisual
-        """
-        if data is None:
-            ndim = max(self.dims.ndim, 2)
-            data = np.empty([0, ndim])
-
-        layer = layers.Points(
-            data=data,
-            symbol=symbol,
-            size=size,
-            edge_width=edge_width,
-            edge_color=edge_color,
-            face_color=face_color,
-            n_dimensional=n_dimensional,
-            name=name,
-            metadata=metadata,
-            scale=scale,
-            translate=translate,
-            opacity=opacity,
-            blending=blending,
-            visible=visible,
-        )
-        self.add_layer(layer)
-        return layer
-
-    def add_labels(
-        self,
-        data=None,
-        *,
-        is_pyramid=None,
-        num_colors=50,
-        seed=0.5,
-        name=None,
-        metadata=None,
-        scale=None,
-        translate=None,
-        opacity=0.7,
-        blending='translucent',
-        visible=True,
-        path=None,
-    ):
-        """Add a labels (or segmentation) layer to the layers list.
-
-        An image-like layer where every pixel contains an integer ID
-        corresponding to the region it belongs to.
-
-        Parameters
-        ----------
-        data : array or list of array
-            Labels data as an array or pyramid.
-        is_pyramid : bool
-            Whether the data is an image pyramid or not. Pyramid data is
-            represented by a list of array like image data. If not specified by
-            the user and if the data is a list of arrays that decrease in shape
-            then it will be taken to be a pyramid. The first image in the list
-            should be the largest.
-        num_colors : int
-            Number of unique colors to use in colormap.
-        seed : float
-            Seed for colormap random generator.
-        name : str
-            Name of the layer.
-        metadata : dict
-            Layer metadata.
-        scale : tuple of float
-            Scale factors for the layer.
-        translate : tuple of float
-            Translation values for the layer.
-        opacity : float
-            Opacity of the layer visual, between 0.0 and 1.0.
-        blending : str
-            One of a list of preset blending modes that determines how RGB and
-            alpha values of the layer visual get mixed. Allowed values are
-            {'opaque', 'translucent', and 'additive'}.
-        visible : bool
-            Whether the layer visual is currently being displayed.
-        path : str or list of str
-            Path or list of paths to image data. Paths can be passed as strings
-            or `pathlib.Path` instances.
-
-        Returns
-        -------
-        layer : :class:`napari.layers.Labels`
-            The newly-created labels layer.
-        """
-        if data is None and path is None:
-            raise ValueError("One of either data or path must be provided")
-        elif data is not None and path is not None:
-            raise ValueError("Only one of data or path can be provided")
-        elif data is None:
-            data = io.magic_imread(path)
-
-        layer = layers.Labels(
-            data,
-            is_pyramid=is_pyramid,
-            num_colors=num_colors,
-            seed=seed,
-            name=name,
-            metadata=metadata,
-            scale=scale,
-            translate=translate,
-            opacity=opacity,
-            blending=blending,
-            visible=visible,
-        )
-        self.add_layer(layer)
-        return layer
-
-    def add_shapes(
-        self,
-        data=None,
-        *,
-        shape_type='rectangle',
-        edge_width=1,
-        edge_color='black',
-        face_color='white',
-        z_index=0,
-        name=None,
-        metadata=None,
-        scale=None,
-        translate=None,
-        opacity=0.7,
-        blending='translucent',
-        visible=True,
-    ):
-        """Add a shapes layer to the layers list.
-
-        Parameters
-        ----------
-        data : list or array
-            List of shape data, where each element is an (N, D) array of the
-            N vertices of a shape in D dimensions. Can be an 3-dimensional
-            array if each shape has the same number of vertices.
-        shape_type : string or list
-            String of shape shape_type, must be one of "{'line', 'rectangle',
-            'ellipse', 'path', 'polygon'}". If a list is supplied it must be
-            the same length as the length of `data` and each element will be
-            applied to each shape otherwise the same value will be used for all
-            shapes.
-        edge_width : float or list
-            Thickness of lines and edges. If a list is supplied it must be the
-            same length as the length of `data` and each element will be
-            applied to each shape otherwise the same value will be used for all
-            shapes.
-        edge_color : str or list
-            If string can be any color name recognized by vispy or hex value if
-            starting with `#`. If array-like must be 1-dimensional array with 3
-            or 4 elements. If a list is supplied it must be the same length as
-            the length of `data` and each element will be applied to each shape
-            otherwise the same value will be used for all shapes.
-        face_color : str or list
-            If string can be any color name recognized by vispy or hex value if
-            starting with `#`. If array-like must be 1-dimensional array with 3
-            or 4 elements. If a list is supplied it must be the same length as
-            the length of `data` and each element will be applied to each shape
-            otherwise the same value will be used for all shapes.
-        z_index : int or list
-            Specifier of z order priority. Shapes with higher z order are
-            displayed ontop of others. If a list is supplied it must be the
-            same length as the length of `data` and each element will be
-            applied to each shape otherwise the same value will be used for all
-            shapes.
-        name : str
-            Name of the layer.
-        metadata : dict
-            Layer metadata.
-        scale : tuple of float
-            Scale factors for the layer.
-        translate : tuple of float
-            Translation values for the layer.
-        opacity : float or list
-            Opacity of the layer visual, between 0.0 and 1.0.
-        blending : str
-            One of a list of preset blending modes that determines how RGB and
-            alpha values of the layer visual get mixed. Allowed values are
-            {'opaque', 'translucent', and 'additive'}.
-        visible : bool
-            Whether the layer visual is currently being displayed.
-
-        Returns
-        -------
-        layer : :class:`napari.layers.Shapes`
-            The newly-created shapes layer.
-        """
-        if data is None:
-            ndim = max(self.dims.ndim, 2)
-            data = np.empty((0, 0, ndim))
-
-        layer = layers.Shapes(
-            data=data,
-            shape_type=shape_type,
-            edge_width=edge_width,
-            edge_color=edge_color,
-            face_color=face_color,
-            z_index=z_index,
-            name=name,
-            metadata=metadata,
-            scale=scale,
-            translate=translate,
-            opacity=opacity,
-            blending=blending,
-            visible=visible,
-        )
-        self.add_layer(layer)
-        return layer
-
-    def add_surface(
-        self,
-        data,
-        *,
-        colormap='gray',
-        contrast_limits=None,
-        gamma=1,
-        name=None,
-        metadata=None,
-        scale=None,
-        translate=None,
-        opacity=1,
-        blending='translucent',
-        visible=True,
-    ):
-        """Add a surface layer to the layers list.
-
-        Parameters
-        ----------
-        data : 3-tuple of array
-            The first element of the tuple is an (N, D) array of vertices of
-            mesh triangles. The second is an (M, 3) array of int of indices
-            of the mesh triangles. The third element is the (K0, ..., KL, N)
-            array of values used to color vertices where the additional L
-            dimensions are used to color the same mesh with different values.
-        colormap : str, vispy.Color.Colormap, tuple, dict
-            Colormap to use for luminance images. If a string must be the name
-            of a supported colormap from vispy or matplotlib. If a tuple the
-            first value must be a string to assign as a name to a colormap and
-            the second item must be a Colormap. If a dict the key must be a
-            string to assign as a name to a colormap and the value must be a
-            Colormap.
-        contrast_limits : list (2,)
-            Color limits to be used for determining the colormap bounds for
-            luminance images. If not passed is calculated as the min and max of
-            the image.
-        gamma : float
-            Gamma correction for determining colormap linearity. Defaults to 1.
-        name : str
-            Name of the layer.
-        metadata : dict
-            Layer metadata.
-        scale : tuple of float
-            Scale factors for the layer.
-        translate : tuple of float
-            Translation values for the layer.
-        opacity : float
-            Opacity of the layer visual, between 0.0 and 1.0.
-        blending : str
-            One of a list of preset blending modes that determines how RGB and
-            alpha values of the layer visual get mixed. Allowed values are
-            {'opaque', 'translucent', and 'additive'}.
-        visible : bool
-            Whether the layer visual is currently being displayed.
-
-        Returns
-        -------
-        layer : :class:`napari.layers.Surface`
-            The newly-created surface layer.
-        """
-        layer = layers.Surface(
-            data,
-            colormap=colormap,
-            contrast_limits=contrast_limits,
-            gamma=gamma,
-            name=name,
-            metadata=metadata,
-            scale=scale,
-            translate=translate,
-            opacity=opacity,
-            blending=blending,
-            visible=visible,
-        )
-        self.add_layer(layer)
-        return layer
-
-    def add_vectors(
-        self,
-        data,
-        *,
-        edge_width=1,
-        edge_color='red',
-        length=1,
-        name=None,
-        metadata=None,
-        scale=None,
-        translate=None,
-        opacity=0.7,
-        blending='translucent',
-        visible=True,
-    ):
-        """Add a vectors layer to the layers list.
-
-        Parameters
-        ----------
-        data : (N, 2, D) or (N1, N2, ..., ND, D) array
-            An (N, 2, D) array is interpreted as "coordinate-like" data and a
-            list of N vectors with start point and projections of the vector in
-            D dimensions. An (N1, N2, ..., ND, D) array is interpreted as
-            "image-like" data where there is a length D vector of the
-            projections at each pixel.
-        edge_width : float
-            Width for all vectors in pixels.
-        length : float
-             Multiplicative factor on projections for length of all vectors.
-        edge_color : str
-            Edge color of all the vectors.
-        name : str
-            Name of the layer.
-        metadata : dict
-            Layer metadata.
-        scale : tuple of float
-            Scale factors for the layer.
-        translate : tuple of float
-            Translation values for the layer.
-        opacity : float
-            Opacity of the layer visual, between 0.0 and 1.0.
-        blending : str
-            One of a list of preset blending modes that determines how RGB and
-            alpha values of the layer visual get mixed. Allowed values are
-            {'opaque', 'translucent', and 'additive'}.
-        visible : bool
-            Whether the layer visual is currently being displayed.
-
-        Returns
-        -------
-        layer : :class:`napari.layers.Vectors`
-            The newly-created vectors layer.
-        """
-        layer = layers.Vectors(
-            data,
-            edge_width=edge_width,
-            edge_color=edge_color,
-            length=length,
-            name=name,
-            metadata=metadata,
-            scale=scale,
-            translate=translate,
-            opacity=opacity,
-            blending=blending,
-            visible=visible,
-        )
-        self.add_layer(layer)
-        return layer
-
     def _new_labels(self):
         if self.dims.ndim == 0:
             dims = (512, 512)


Test patch: