Repository: scikit-image/scikit-image

Issue:
ND multichannel arrays in color conversion
https://github.com/scikit-image/scikit-image/blob/ac4a8f1f31a28d34b81871ad3a6864b917474263/skimage/color/colorconv.py#L152

The line above only allows you to have 4D multichannel arrays.

Would you accept patches that ensures that we can work with ND multichannel arrays?

This is useful for example when you are trying to stitch tiles of acquired images with a microscope.

You might have large arrays, each RGB (3 dimensions) tiled in the x, y, and time dimension. 
6 dimensions! I'm sure somebody could think of more.

I know there is talk about using an other backend for color conversion, but the skimage color backend seems to do the trick for now.
Fix rgb2gray docstring to show that 2-D arrays are allowed as inputs
## Description

This is a docstring fix, refers to discussion in #3200, specifically [this reply](https://github.com/scikit-image/scikit-image/issues/3200#issuecomment-578013692).

`rgb2gray()` allows 2-D arrays as inputs, but the docstring stated it would raise a `ValueError`. This PR does the following:
- Changes the docstring to show that 2-D inputs are allowed.
- Explains what happens to 2-D inputs (essentially, nothing changes except converting it to row-major order, if needed).

## For reviewers

<!-- Don't remove the checklist below. -->
- [ ] Check that the PR title is short, concise, and will make sense 1 year
  later.
- [ ] Check that new functions are imported in corresponding `__init__.py`.
- [ ] Check that new features, API changes, and deprecations are mentioned in
      `doc/release/release_dev.rst`.
- [ ] Consider backporting the PR with `@meeseeksdev backport to v0.14.x`



Discussion:
Yes that line is super hacky! I was actually just thinking of this very line of code!!! LOL

If you can find a way to fix it without breaking backwards compatibility, we would welcome that very much. Otherwise we might have to go through a deprecation path.

The main question is whether we want to keep the behaviour of `gray2rgb(rgbimage)` being a no-op. It's kind of nice and I've used it a few times, but it's hard to implement if we want to support arbitrarily-shaped nd arrays.

But, either way, that line has to go. It's *very* magical behaviour, and, what's worse, there's no way to turn the magic off.
it seems that the parameter `multichannel` has been used to simplify the guessing logic.
Maybe we can issue warnings to deprecate the old behaviour
```
Warning: your array has much more than 3 dimensions in the `-1` index. Are you sure the array is RGB?
```

For example: I'm thinking of the following docstring for `rgb2gray`
```python
def rgb2gray(rgb):
    """Compute luminance of an RGB image.

    Parameters
    ----------
    rgb : array_like
        The image in RGB format, in a N-D array that can be sliced as
        ``[..., :3]``. For RGBA images, this the alpha channel is
        dropped.

    Returns
    -------
    out : ndarray
        The luminance image - an array which is the same size as the input
        array, but with the channel dimension removed.

    Raises
    ------
    ValueError
        If `rgb2gray` cannot be sliced as:
        ``[..., :3]``.

    References
    ----------
    .. [1] http://www.poynton.com/PDFs/ColorFAQ.pdf

    Notes
    -----
    The weights used in this conversion are calibrated for contemporary
    CRT phosphors::

        Y = 0.2125 R + 0.7154 G + 0.0721 B

    If there is an alpha channel present, it is ignored.

    Examples
    --------
    >>> from skimage.color import rgb2gray
    >>> from skimage import data
    >>> img = data.astronaut()
    >>> img_gray = rgb2gray(img)
    """
    pass
```
Why is this logic the way it is in the function `guess_spatial_dimensions`? It seems in both cases it should be `None` maybe?
```python
    if image.ndim == 3 and image.shape[-1] == 3:
        return None
    if image.ndim == 4 and image.shape[-1] == 3:
        return 3
```
https://github.com/scikit-image/scikit-image/blob/master/skimage/color/colorconv.py#L87
I tend to agree.
Seems like it is only used in `filters.gaussian`

```python
def gaussian(image, sigma=1, output=None, mode='nearest', cval=0,
             multichannel=None, preserve_range=False, truncate=4.0):
    spatial_dims = guess_spatial_dimensions(image)
    if spatial_dims is None and multichannel is None:
        msg = ("Images with dimensions (M, N, 3) are interpreted as 2D+RGB "
               "by default. Use `multichannel=False` to interpret as "
               "3D image with last dimension of length 3.")
        warn(RuntimeWarning(msg))
        multichannel = True
```


Resolving patch:
diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py
--- a/skimage/color/colorconv.py
+++ b/skimage/color/colorconv.py
@@ -77,7 +77,8 @@ def guess_spatial_dimensions(image):
         If the image array has less than two or more than four dimensions.
     """
     from ..filters import _guess_spatial_dimensions
-    warn('This function is deprecated and will be removed in 0.18', stacklevel=2)
+    warn('This function is deprecated and will be removed in 0.18',
+         stacklevel=2)
     return _guess_spatial_dimensions(image)
 
 
@@ -89,17 +90,24 @@ def convert_colorspace(arr, fromspace, tospace):
 
     Parameters
     ----------
-    arr : array_like
-        The image to convert.
-    fromspace : valid color space
+    arr : (..., 3) array_like
+        The image to convert. Final dimension denotes channels.
+    fromspace : str
         The color space to convert from. Can be specified in lower case.
-    tospace : valid color space
+    tospace : str
         The color space to convert to. Can be specified in lower case.
 
     Returns
     -------
-    out : ndarray
-        The converted image.
+    out : (..., 3) ndarray
+        The converted image. Same dimensions as input.
+
+    Raises
+    ------
+    ValueError
+        If fromspace is not a valid color space
+    ValueError
+        If tospace is not a valid color space
 
     Notes
     -----
@@ -132,54 +140,39 @@ def convert_colorspace(arr, fromspace, tospace):
     return todict[tospace](fromdict[fromspace](arr))
 
 
-def _prepare_colorarray(arr):
+def _prepare_colorarray(arr, force_copy=False):
     """Check the shape of the array and convert it to
     floating point representation.
     """
     arr = np.asanyarray(arr)
 
-    if arr.ndim not in [3, 4] or arr.shape[-1] != 3:
-        msg = ("the input array must be have a shape == (.., ..,[ ..,] 3)), " +
-               "got (" + (", ".join(map(str, arr.shape))) + ")")
-        raise ValueError(msg)
-
-    return dtype.img_as_float(arr)
-
-
-def _prepare_rgba_array(arr):
-    """Check the shape of the array to be RGBA and convert it to
-    floating point representation.
-    """
-    arr = np.asanyarray(arr)
-
-    if arr.ndim not in [3, 4] or arr.shape[-1] != 4:
-        msg = ("the input array must have a shape == (.., ..,[ ..,] 4)), "
-               "got {0}".format(arr.shape))
-        raise ValueError(msg)
+    if arr.shape[-1] != 3:
+        raise ValueError("Input array must have a shape == (..., 3)), "
+                         f"got {arr.shape}")
 
-    return dtype.img_as_float(arr)
+    return dtype.img_as_float(arr, force_copy=force_copy)
 
 
 def rgba2rgb(rgba, background=(1, 1, 1)):
-    """RGBA to RGB conversion.
+    """RGBA to RGB conversion using alpha blending [1]_.
 
     Parameters
     ----------
-    rgba : array_like
-        The image in RGBA format, in a 3-D array of shape ``(.., .., 4)``.
+    rgba : (..., 4) array_like
+        The image in RGBA format. Final dimension denotes channels.
     background : array_like
-        The color of the background to blend the image with. A tuple
-        containing 3 floats between 0 to 1 - the RGB value of the background.
+        The color of the background to blend the image with (3 floats
+        between 0 to 1 - the RGB value of the background).
 
     Returns
     -------
-    out : ndarray
-        The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
+    out : (..., 3) ndarray
+        The image in RGB format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `rgba` is not a 3-D array of shape ``(.., .., 4)``.
+        If `rgba` is not at least 2-D with shape (..., 4).
 
     References
     ----------
@@ -192,20 +185,30 @@ def rgba2rgb(rgba, background=(1, 1, 1)):
     >>> img_rgba = data.logo()
     >>> img_rgb = color.rgba2rgb(img_rgba)
     """
-    arr = _prepare_rgba_array(rgba)
-    if isinstance(background, tuple) and len(background) != 3:
-        raise ValueError('the background must be a tuple with 3 items - the '
-                         'RGB color of the background. Got {0} items.'
-                         .format(len(background)))
-
-    alpha = arr[..., -1]
-    channels = arr[..., :-1]
-    out = np.empty_like(channels)
-
-    for ichan in range(channels.shape[-1]):
-        out[..., ichan] = np.clip(
-            (1 - alpha) * background[ichan] + alpha * channels[..., ichan],
-            a_min=0, a_max=1)
+    arr = np.asanyarray(rgba)
+
+    if arr.shape[-1] != 4:
+        msg = ("the input array must have shape == (..., 4)), "
+               f"got {arr.shape}")
+        raise ValueError(msg)
+
+    arr = dtype.img_as_float(arr)
+
+    background = np.ravel(background).astype(arr.dtype)
+    if len(background) != 3:
+        raise ValueError('background must be an array-like containing 3 RGB '
+                         f'values. Got {len(background)} items')
+    if np.any(background < 0) or np.any(background > 1):
+        raise ValueError('background RGB values must be floats between '
+                         '0 and 1.')
+
+    background = background[np.newaxis, ...]
+    alpha = arr[..., -1, np.newaxis]
+    channels = arr[np.newaxis, ..., :-1]
+
+    out = np.squeeze(np.clip((1 - alpha) * background + alpha * channels,
+                             a_min=0, a_max=1),
+                     axis=0)
     return out
 
 
@@ -214,18 +217,18 @@ def rgb2hsv(rgb):
 
     Parameters
     ----------
-    rgb : array_like
-        The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
+    rgb : (..., 3) array_like
+        The image in RGB format. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in HSV format, in a 3-D array of shape ``(.., .., 3)``.
+    out : (..., 3) ndarray
+        The image in HSV format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `rgb` is not a 3-D array of shape ``(.., .., 3)``.
+        If `rgb` is not at least 2-D with shape (..., 3).
 
     Notes
     -----
@@ -243,6 +246,10 @@ def rgb2hsv(rgb):
     >>> img = data.astronaut()
     >>> img_hsv = color.rgb2hsv(img)
     """
+    input_is_one_pixel = rgb.ndim == 1
+    if input_is_one_pixel:
+        rgb = rgb[np.newaxis, ...]
+
     arr = _prepare_colorarray(rgb)
     out = np.empty_like(arr)
 
@@ -258,29 +265,32 @@ def rgb2hsv(rgb):
 
     # -- H channel
     # red is max
-    idx = (arr[:, :, 0] == out_v)
+    idx = (arr[..., 0] == out_v)
     out[idx, 0] = (arr[idx, 1] - arr[idx, 2]) / delta[idx]
 
     # green is max
-    idx = (arr[:, :, 1] == out_v)
+    idx = (arr[..., 1] == out_v)
     out[idx, 0] = 2. + (arr[idx, 2] - arr[idx, 0]) / delta[idx]
 
     # blue is max
-    idx = (arr[:, :, 2] == out_v)
+    idx = (arr[..., 2] == out_v)
     out[idx, 0] = 4. + (arr[idx, 0] - arr[idx, 1]) / delta[idx]
-    out_h = (out[:, :, 0] / 6.) % 1.
+    out_h = (out[..., 0] / 6.) % 1.
     out_h[delta == 0.] = 0.
 
     np.seterr(**old_settings)
 
     # -- output
-    out[:, :, 0] = out_h
-    out[:, :, 1] = out_s
-    out[:, :, 2] = out_v
+    out[..., 0] = out_h
+    out[..., 1] = out_s
+    out[..., 2] = out_v
 
-    # remove NaN
+    # # remove NaN
     out[np.isnan(out)] = 0
 
+    if input_is_one_pixel:
+        out = np.squeeze(out, axis=0)
+
     return out
 
 
@@ -289,18 +299,18 @@ def hsv2rgb(hsv):
 
     Parameters
     ----------
-    hsv : array_like
-        The image in HSV format, in a 3-D array of shape ``(.., .., 3)``.
+    hsv : (..., 3) array_like
+        The image in HSV format. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
+    out : (..., 3) ndarray
+        The image in RGB format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `hsv` is not a 3-D array of shape ``(.., .., 3)``.
+        If `hsv` is not at least 2-D with shape (..., 3).
 
     Notes
     -----
@@ -320,20 +330,20 @@ def hsv2rgb(hsv):
     """
     arr = _prepare_colorarray(hsv)
 
-    hi = np.floor(arr[:, :, 0] * 6)
-    f = arr[:, :, 0] * 6 - hi
-    p = arr[:, :, 2] * (1 - arr[:, :, 1])
-    q = arr[:, :, 2] * (1 - f * arr[:, :, 1])
-    t = arr[:, :, 2] * (1 - (1 - f) * arr[:, :, 1])
-    v = arr[:, :, 2]
-
-    hi = np.dstack([hi, hi, hi]).astype(np.uint8) % 6
-    out = np.choose(hi, [np.dstack((v, t, p)),
-                         np.dstack((q, v, p)),
-                         np.dstack((p, v, t)),
-                         np.dstack((p, q, v)),
-                         np.dstack((t, p, v)),
-                         np.dstack((v, p, q))])
+    hi = np.floor(arr[..., 0] * 6)
+    f = arr[..., 0] * 6 - hi
+    p = arr[..., 2] * (1 - arr[..., 1])
+    q = arr[..., 2] * (1 - f * arr[..., 1])
+    t = arr[..., 2] * (1 - (1 - f) * arr[..., 1])
+    v = arr[..., 2]
+
+    hi = np.stack([hi, hi, hi], axis=-1).astype(np.uint8) % 6
+    out = np.choose(hi, [np.stack((v, t, p), axis=-1),
+                         np.stack((q, v, p), axis=-1),
+                         np.stack((p, v, t), axis=-1),
+                         np.stack((p, q, v), axis=-1),
+                         np.stack((t, p, v), axis=-1),
+                         np.stack((v, p, q), axis=-1)])
 
     return out
 
@@ -575,13 +585,13 @@ def _convert(matrix, arr):
     ----------
     matrix : array_like
         The 3x3 matrix to use.
-    arr : array_like
-        The input array.
+    arr : (..., 3) array_like
+        The input array. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The converted array.
+    out : (..., 3) ndarray
+        The converted array. Same dimensions as input.
     """
     arr = _prepare_colorarray(arr)
 
@@ -593,18 +603,18 @@ def xyz2rgb(xyz):
 
     Parameters
     ----------
-    xyz : array_like
-        The image in XYZ format, in a 3-D array of shape ``(.., .., 3)``.
+    xyz : (..., 3) array_like
+        The image in XYZ format. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
+    out : (..., 3) ndarray
+        The image in RGB format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `xyz` is not a 3-D array of shape ``(.., .., 3)``.
+        If `xyz` is not at least 2-D with shape (..., 3).
 
     Notes
     -----
@@ -638,20 +648,18 @@ def rgb2xyz(rgb):
 
     Parameters
     ----------
-    rgb : array_like
-        The image in RGB format, in a 3- or 4-D array of shape
-        ``(.., ..,[ ..,] 3)``.
+    rgb : (..., 3) array_like
+        The image in RGB format. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in XYZ format, in a 3- or 4-D array of shape
-        ``(.., ..,[ ..,] 3)``.
+    out : (..., 3) ndarray
+        The image in XYZ format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `rgb` is not a 3- or 4-D array of shape ``(.., ..,[ ..,] 3)``.
+        If `rgb` is not at least 2-D with shape (..., 3).
 
     Notes
     -----
@@ -674,7 +682,7 @@ def rgb2xyz(rgb):
     mask = arr > 0.04045
     arr[mask] = np.power((arr[mask] + 0.055) / 1.055, 2.4)
     arr[~mask] /= 12.92
-    return _convert(xyz_from_rgb, arr)
+    return arr @ xyz_from_rgb.T.astype(arr.dtype)
 
 
 def rgb2rgbcie(rgb):
@@ -682,18 +690,18 @@ def rgb2rgbcie(rgb):
 
     Parameters
     ----------
-    rgb : array_like
-        The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
+    rgb : (..., 3) array_like
+        The image in RGB format. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in RGB CIE format, in a 3-D array of shape ``(.., .., 3)``.
+    out : (..., 3) ndarray
+        The image in RGB CIE format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `rgb` is not a 3-D array of shape ``(.., .., 3)``.
+        If `rgb` is not at least 2-D with shape (..., 3).
 
     References
     ----------
@@ -714,18 +722,18 @@ def rgbcie2rgb(rgbcie):
 
     Parameters
     ----------
-    rgbcie : array_like
-        The image in RGB CIE format, in a 3-D array of shape ``(.., .., 3)``.
+    rgbcie : (..., 3) array_like
+        The image in RGB CIE format. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
+    out : (..., 3) ndarray
+        The image in RGB format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `rgbcie` is not a 3-D array of shape ``(.., .., 3)``.
+        If `rgbcie` is not at least 2-D with shape (..., 3).
 
     References
     ----------
@@ -747,10 +755,8 @@ def rgb2gray(rgb):
 
     Parameters
     ----------
-    rgb : array_like
-        The image in RGB format, in a 3-D or 4-D array of shape
-        ``(.., ..,[ ..,] 3)``, or in RGBA format with shape
-        ``(.., ..,[ ..,] 4)``.
+    rgb : (..., 3) array_like
+        The image in RGB format. Final dimension denotes channels.
 
     Returns
     -------
@@ -761,8 +767,7 @@ def rgb2gray(rgb):
     Raises
     ------
     ValueError
-        If `rgb2gray` is not a 3-D or 4-D arrays of shape
-        ``(.., ..,[ ..,] 3)`` or ``(.., ..,[ ..,] 4)``.
+        If `rgb` is not at least 2-D with shape (..., 3).
 
     Notes
     -----
@@ -856,20 +861,15 @@ def gray2rgb(image, alpha=None):
     Parameters
     ----------
     image : array_like
-        Input image of shape ``(M[, N][, P])``.
+        Input image.
     alpha : bool, optional
-        Ensure that the output image has an alpha layer.  If None,
+        Ensure that the output image has an alpha layer. If None,
         alpha layers are passed through but not created.
 
     Returns
     -------
-    rgb : ndarray
-        RGB image of shape ``(M[, N][, P], 3)``.
-
-    Raises
-    ------
-    ValueError
-        If the input is not a 1-, 2- or 3-dimensional image.
+    rgb : (..., 3) ndarray
+        RGB image. A new dimension of length 3 is added to input image.
 
     Notes
     -----
@@ -909,18 +909,16 @@ def gray2rgb(image, alpha=None):
 
         return image
 
-    elif dims in (1, 2, 3):
+    else:
         image = image[..., np.newaxis]
 
         if alpha:
-            alpha_layer = (np.ones_like(image) * dtype_limits(image, clip_negative=False)[1])
+            alpha_layer = (np.ones_like(image)
+                           * dtype_limits(image, clip_negative=False)[1])
             return np.concatenate(3 * (image,) + (alpha_layer,), axis=-1)
         else:
             return np.concatenate(3 * (image,), axis=-1)
 
-    else:
-        raise ValueError("Input image expected to be RGB, RGBA or gray.")
-
 
 @functools.wraps(gray2rgb)
 def grey2rgb(image):
@@ -934,9 +932,8 @@ def xyz2lab(xyz, illuminant="D65", observer="2"):
 
     Parameters
     ----------
-    xyz : array_like
-        The image in XYZ format, in a 3- or 4-D array of shape
-        ``(.., ..,[ ..,] 3)``.
+    xyz : (..., 3) array_like
+        The image in XYZ format. Final dimension denotes channels.
     illuminant : {"A", "D50", "D55", "D65", "D75", "E"}, optional
         The name of the illuminant (the function is NOT case sensitive).
     observer : {"2", "10"}, optional
@@ -944,14 +941,13 @@ def xyz2lab(xyz, illuminant="D65", observer="2"):
 
     Returns
     -------
-    out : ndarray
-        The image in CIE-LAB format, in a 3- or 4-D array of shape
-        ``(.., ..,[ ..,] 3)``.
+    out : (..., 3) ndarray
+        The image in CIE-LAB format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `xyz` is not a 3-D array of shape ``(.., ..,[ ..,] 3)``.
+        If `xyz` is not at least 2-D with shape (..., 3).
     ValueError
         If either the illuminant or the observer angle is unsupported or
         unknown.
@@ -1002,8 +998,8 @@ def lab2xyz(lab, illuminant="D65", observer="2"):
 
     Parameters
     ----------
-    lab : array_like
-        The image in lab format, in a 3-D array of shape ``(.., .., 3)``.
+    lab : (..., 3) array_like
+        The image in Lab format. Final dimension denotes channels.
     illuminant : {"A", "D50", "D55", "D65", "D75", "E"}, optional
         The name of the illuminant (the function is NOT case sensitive).
     observer : {"2", "10"}, optional
@@ -1011,13 +1007,13 @@ def lab2xyz(lab, illuminant="D65", observer="2"):
 
     Returns
     -------
-    out : ndarray
-        The image in XYZ format, in a 3-D array of shape ``(.., .., 3)``.
+    out : (..., 3) ndarray
+        The image in XYZ format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `lab` is not a 3-D array of shape ``(.., .., 3)``.
+        If `lab` is not at least 2-D with shape (..., 3).
     ValueError
         If either the illuminant or the observer angle are not supported or
         unknown.
@@ -1037,7 +1033,7 @@ def lab2xyz(lab, illuminant="D65", observer="2"):
     """
     arr = _prepare_colorarray(lab).copy()
 
-    L, a, b = arr[:, :, 0], arr[:, :, 1], arr[:, :, 2]
+    L, a, b = arr[..., 0], arr[..., 1], arr[..., 2]
     y = (L + 16.) / 116.
     x = (a / 500.) + y
     z = y - (b / 200.)
@@ -1048,7 +1044,7 @@ def lab2xyz(lab, illuminant="D65", observer="2"):
              stacklevel=2)
         z[invalid] = 0
 
-    out = np.dstack([x, y, z])
+    out = np.stack([x, y, z], axis=-1)
 
     mask = out > 0.2068966
     out[mask] = np.power(out[mask], 3.)
@@ -1065,9 +1061,8 @@ def rgb2lab(rgb, illuminant="D65", observer="2"):
 
     Parameters
     ----------
-    rgb : array_like
-        The image in RGB format, in a 3- or 4-D array of shape
-        ``(.., ..,[ ..,] 3)``.
+    rgb : (..., 3) array_like
+        The image in RGB format. Final dimension denotes channels.
     illuminant : {"A", "D50", "D55", "D65", "D75", "E"}, optional
         The name of the illuminant (the function is NOT case sensitive).
     observer : {"2", "10"}, optional
@@ -1075,14 +1070,13 @@ def rgb2lab(rgb, illuminant="D65", observer="2"):
 
     Returns
     -------
-    out : ndarray
-        The image in Lab format, in a 3- or 4-D array of shape
-        ``(.., ..,[ ..,] 3)``.
+    out : (..., 3) ndarray
+        The image in Lab format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `rgb` is not a 3- or 4-D array of shape ``(.., ..,[ ..,] 3)``.
+        If `rgb` is not at least 2-D with shape (..., 3).
 
     Notes
     -----
@@ -1103,8 +1097,8 @@ def lab2rgb(lab, illuminant="D65", observer="2"):
 
     Parameters
     ----------
-    lab : array_like
-        The image in Lab format, in a 3-D array of shape ``(.., .., 3)``.
+    lab : (..., 3) array_like
+        The image in Lab format. Final dimension denotes channels.
     illuminant : {"A", "D50", "D55", "D65", "D75", "E"}, optional
         The name of the illuminant (the function is NOT case sensitive).
     observer : {"2", "10"}, optional
@@ -1112,13 +1106,13 @@ def lab2rgb(lab, illuminant="D65", observer="2"):
 
     Returns
     -------
-    out : ndarray
-        The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
+    out : (..., 3) ndarray
+        The image in RGB format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `lab` is not a 3-D array of shape ``(.., .., 3)``.
+        If `lab` is not at least 2-D with shape (..., 3).
 
     Notes
     -----
@@ -1139,9 +1133,8 @@ def xyz2luv(xyz, illuminant="D65", observer="2"):
 
     Parameters
     ----------
-    xyz : (M, N, [P,] 3) array_like
-        The 3 or 4 dimensional image in XYZ format. Final dimension denotes
-        channels.
+    xyz : (..., 3) array_like
+        The image in XYZ format. Final dimension denotes channels.
     illuminant : {"A", "D50", "D55", "D65", "D75", "E"}, optional
         The name of the illuminant (the function is NOT case sensitive).
     observer : {"2", "10"}, optional
@@ -1149,13 +1142,13 @@ def xyz2luv(xyz, illuminant="D65", observer="2"):
 
     Returns
     -------
-    out : (M, N, [P,] 3) ndarray
+    out : (..., 3) ndarray
         The image in CIE-Luv format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `xyz` is not a 3-D or 4-D array of shape ``(M, N, [P,] 3)``.
+        If `xyz` is not at least 2-D with shape (..., 3).
     ValueError
         If either the illuminant or the observer angle are not supported or
         unknown.
@@ -1180,6 +1173,10 @@ def xyz2luv(xyz, illuminant="D65", observer="2"):
     >>> img_xyz = rgb2xyz(img)
     >>> img_luv = xyz2luv(img_xyz)
     """
+    input_is_one_pixel = xyz.ndim == 1
+    if input_is_one_pixel:
+        xyz = xyz[np.newaxis, ...]
+
     arr = _prepare_colorarray(xyz)
 
     # extract channels
@@ -1208,7 +1205,12 @@ def fv(X, Y, Z):
     u = 13. * L * (fu(x, y, z) - u0)
     v = 13. * L * (fv(x, y, z) - v0)
 
-    return np.concatenate([q[..., np.newaxis] for q in [L, u, v]], axis=-1)
+    out = np.stack([L, u, v], axis=-1)
+
+    if input_is_one_pixel:
+        out = np.squeeze(out, axis=0)
+
+    return out
 
 
 def luv2xyz(luv, illuminant="D65", observer="2"):
@@ -1216,9 +1218,8 @@ def luv2xyz(luv, illuminant="D65", observer="2"):
 
     Parameters
     ----------
-    luv : (M, N, [P,] 3) array_like
-        The 3 or 4 dimensional image in CIE-Luv format. Final dimension denotes
-        channels.
+    luv : (..., 3) array_like
+        The image in CIE-Luv format. Final dimension denotes channels.
     illuminant : {"A", "D50", "D55", "D65", "D75", "E"}, optional
         The name of the illuminant (the function is NOT case sensitive).
     observer : {"2", "10"}, optional
@@ -1226,13 +1227,13 @@ def luv2xyz(luv, illuminant="D65", observer="2"):
 
     Returns
     -------
-    out : (M, N, [P,] 3) ndarray
+    out : (..., 3) ndarray
         The image in XYZ format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `luv` is not a 3-D or 4-D array of shape ``(M, N, [P,] 3)``.
+        If `luv` is not at least 2-D with shape (..., 3).
     ValueError
         If either the illuminant or the observer angle are not supported or
         unknown.
@@ -1250,7 +1251,7 @@ def luv2xyz(luv, illuminant="D65", observer="2"):
     """
     arr = _prepare_colorarray(luv).copy()
 
-    L, u, v = arr[:, :, 0], arr[:, :, 1], arr[:, :, 2]
+    L, u, v = arr[..., 0], arr[..., 1], arr[..., 2]
 
     eps = np.finfo(np.float).eps
 
@@ -1284,19 +1285,18 @@ def rgb2luv(rgb):
 
     Parameters
     ----------
-    rgb : (M, N, [P,] 3) array_like
-        The 3 or 4 dimensional image in RGB format. Final dimension denotes
-        channels.
+    rgb : (..., 3) array_like
+        The image in RGB format. Final dimension denotes channels.
 
     Returns
     -------
-    out : (M, N, [P,] 3) ndarray
+    out : (..., 3) ndarray
         The image in CIE Luv format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `rgb` is not a 3-D or 4-D array of shape ``(M, N, [P,] 3)``.
+        If `rgb` is not at least 2-D with shape (..., 3).
 
     Notes
     -----
@@ -1316,19 +1316,18 @@ def luv2rgb(luv):
 
     Parameters
     ----------
-    luv : (M, N, [P,] 3) array_like
-        The 3 or 4 dimensional image in CIE Luv format. Final dimension denotes
-        channels.
+    luv : (..., 3) array_like
+        The image in CIE Luv format. Final dimension denotes channels.
 
     Returns
     -------
-    out : (M, N, [P,] 3) ndarray
+    out : (..., 3) ndarray
         The image in RGB format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `luv` is not a 3-D or 4-D array of shape ``(M, N, [P,] 3)``.
+        If `luv` is not at least 2-D with shape (..., 3).
 
     Notes
     -----
@@ -1342,18 +1341,18 @@ def rgb2hed(rgb):
 
     Parameters
     ----------
-    rgb : array_like
-        The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
+    rgb : (..., 3) array_like
+        The image in RGB format. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in HED format, in a 3-D array of shape ``(.., .., 3)``.
+    out : (..., 3) ndarray
+        The image in HED format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `rgb` is not a 3-D array of shape ``(.., .., 3)``.
+        If `rgb` is not at least 2-D with shape (..., 3).
 
     References
     ----------
@@ -1377,19 +1376,18 @@ def hed2rgb(hed):
 
     Parameters
     ----------
-    hed : array_like
-        The image in the HED color space, in a 3-D array of shape
-        ``(.., .., 3)``.
+    hed : (..., 3) array_like
+        The image in the HED color space. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in RGB, in a 3-D array of shape ``(.., .., 3)``.
+    out : (..., 3) ndarray
+        The image in RGB. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `hed` is not a 3-D array of shape ``(.., .., 3)``.
+        If `hed` is not at least 2-D with shape (..., 3).
 
     References
     ----------
@@ -1414,21 +1412,20 @@ def separate_stains(rgb, conv_matrix):
 
     Parameters
     ----------
-    rgb : array_like
-        The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
-    conv_matrix : ndarray
+    rgb : (..., 3) array_like
+        The image in RGB format. Final dimension denotes channels.
+    conv_matrix: ndarray
         The stain separation matrix as described by G. Landini [1]_.
 
     Returns
     -------
-    out : ndarray
-        The image in stain color space, in a 3-D array of shape
-        ``(.., .., 3)``.
+    out : (..., 3) ndarray
+        The image in stain color space. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `rgb` is not a 3-D array of shape ``(.., .., 3)``.
+        If `rgb` is not at least 2-D with shape (..., 3).
 
     Notes
     -----
@@ -1459,7 +1456,7 @@ def separate_stains(rgb, conv_matrix):
     >>> ihc = data.immunohistochemistry()
     >>> ihc_hdx = separate_stains(ihc, hdx_from_rgb)
     """
-    rgb = dtype.img_as_float(rgb, force_copy=True)
+    rgb = _prepare_colorarray(rgb, force_copy=True)
     rgb += 2
     stains = np.reshape(-np.log10(rgb), (-1, 3)) @ conv_matrix
     return np.reshape(stains, rgb.shape)
@@ -1470,21 +1467,20 @@ def combine_stains(stains, conv_matrix):
 
     Parameters
     ----------
-    stains : array_like
-        The image in stain color space, in a 3-D array of shape
-        ``(.., .., 3)``.
-    conv_matrix : ndarray
+    stains : (..., 3) array_like
+        The image in stain color space. Final dimension denotes channels.
+    conv_matrix: ndarray
         The stain separation matrix as described by G. Landini [1]_.
 
     Returns
     -------
-    out : ndarray
-        The image in RGB format, in a 3-D array of shape ``(.., .., 3)``.
+    out : (..., 3) ndarray
+        The image in RGB format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `stains` is not a 3-D array of shape ``(.., .., 3)``.
+        If `stains` is not at least 2-D with shape (..., 3).
 
     Notes
     -----
@@ -1519,7 +1515,7 @@ def combine_stains(stains, conv_matrix):
     """
     from ..exposure import rescale_intensity
 
-    stains = dtype.img_as_float(stains)
+    stains = _prepare_colorarray(stains)
     logrgb2 = -np.reshape(stains, (-1, 3)) @ conv_matrix
     rgb2 = np.power(10, logrgb2)
     return rescale_intensity(np.reshape(rgb2 - 2, stains.shape),
@@ -1533,14 +1529,14 @@ def lab2lch(lab):
 
     Parameters
     ----------
-    lab : array_like
+    lab : (..., 3) array_like
         The N-D image in CIE-LAB format. The last (``N+1``-th) dimension must
         have at least 3 elements, corresponding to the ``L``, ``a``, and ``b``
-        color channels.  Subsequent elements are copied.
+        color channels. Subsequent elements are copied.
 
     Returns
     -------
-    out : ndarray
+    out : (..., 3) ndarray
         The image in LCH format, in a N-D array with same shape as input `lab`.
 
     Raises
@@ -1584,14 +1580,14 @@ def lch2lab(lch):
 
     Parameters
     ----------
-    lch : array_like
+    lch : (..., 3) array_like
         The N-D image in CIE-LCH format. The last (``N+1``-th) dimension must
         have at least 3 elements, corresponding to the ``L``, ``a``, and ``b``
         color channels.  Subsequent elements are copied.
 
     Returns
     -------
-    out : ndarray
+    out : (..., 3) ndarray
         The image in LAB format, with same shape as input `lch`.
 
     Raises
@@ -1615,7 +1611,7 @@ def lch2lab(lch):
     return lch
 
 
-def _prepare_lab_array(arr):
+def _prepare_lab_array(arr, force_copy=True):
     """Ensure input for lab2lch, lch2lab are well-posed.
 
     Arrays must be in floating point and have at least 3 elements in
@@ -1625,7 +1621,7 @@ def _prepare_lab_array(arr):
     shape = arr.shape
     if shape[-1] < 3:
         raise ValueError('Input array has less than 3 color channels')
-    return dtype.img_as_float(arr, force_copy=True)
+    return dtype.img_as_float(arr, force_copy=force_copy)
 
 
 def rgb2yuv(rgb):
@@ -1633,20 +1629,18 @@ def rgb2yuv(rgb):
 
     Parameters
     ----------
-    rgb : array_like
-        The image in RGB format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    rgb : (..., 3) array_like
+        The image in RGB format. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in YUV format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    out : (..., 3) ndarray
+        The image in YUV format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `rgb` is not a 3- or 4-D array of shape ``(M, N, [P,] 3)``.
+        If `rgb` is not at least 2-D with shape (..., 3).
 
     Notes
     -----
@@ -1665,20 +1659,18 @@ def rgb2yiq(rgb):
 
     Parameters
     ----------
-    rgb : array_like
-        The image in RGB format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    rgb : (..., 3) array_like
+        The image in RGB format. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in YIQ format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    out : (..., 3) ndarray
+        The image in YIQ format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `rgb` is not a 3- or 4-D array of shape ``(M, N, [P,] 3)``.
+        If `rgb` is not at least 2-D with shape (..., 3).
     """
     return _convert(yiq_from_rgb, rgb)
 
@@ -1688,20 +1680,18 @@ def rgb2ypbpr(rgb):
 
     Parameters
     ----------
-    rgb : array_like
-        The image in RGB format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    rgb : (..., 3) array_like
+        The image in RGB format. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in YPbPr format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    out : (..., 3) ndarray
+        The image in YPbPr format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `rgb` is not a 3- or 4-D array of shape ``(M, N, [P,] 3)``.
+        If `rgb` is not at least 2-D with shape (..., 3).
 
     References
     ----------
@@ -1715,20 +1705,18 @@ def rgb2ycbcr(rgb):
 
     Parameters
     ----------
-    rgb : array_like
-        The image in RGB format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    rgb : (..., 3) array_like
+        The image in RGB format. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in YCbCr format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    out : (..., 3) ndarray
+        The image in YCbCr format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `rgb` is not a 3- or 4-D array of shape ``(M, N, [P,] 3)``.
+        If `rgb` is not at least 2-D with shape (..., 3).
 
     Notes
     -----
@@ -1751,20 +1739,18 @@ def rgb2ydbdr(rgb):
 
     Parameters
     ----------
-    rgb : array_like
-        The image in RGB format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    rgb : (..., 3) array_like
+        The image in RGB format. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in YDbDr format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    out : (..., 3) ndarray
+        The image in YDbDr format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `rgb` is not a 3- or 4-D array of shape ``(M, N, [P,] 3)``.
+        If `rgb` is not at least 2-D with shape (..., 3).
 
     Notes
     -----
@@ -1784,20 +1770,18 @@ def yuv2rgb(yuv):
 
     Parameters
     ----------
-    yuv : array_like
-        The image in YUV format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    yuv : (..., 3) array_like
+        The image in YUV format. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in RGB format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    out : (..., 3) ndarray
+        The image in RGB format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `yuv` is not a 3- or 4-D array of shape ``(M, N, [P,] 3)``.
+        If `yuv` is not at least 2-D with shape (..., 3).
 
     References
     ----------
@@ -1811,20 +1795,18 @@ def yiq2rgb(yiq):
 
     Parameters
     ----------
-    yiq : array_like
-        The image in YIQ format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    yiq : (..., 3) array_like
+        The image in YIQ format. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in RGB format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    out : (..., 3) ndarray
+        The image in RGB format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `yiq` is not a 3- or 4-D array of shape ``(M, N, [P,] 3)``.
+        If `yiq` is not at least 2-D with shape (..., 3).
     """
     return _convert(rgb_from_yiq, yiq)
 
@@ -1834,20 +1816,18 @@ def ypbpr2rgb(ypbpr):
 
     Parameters
     ----------
-    ypbpr : array_like
-        The image in YPbPr format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    ypbpr : (..., 3) array_like
+        The image in YPbPr format. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in RGB format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    out : (..., 3) ndarray
+        The image in RGB format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `ypbpr` is not a 3- or 4-D array of shape ``(M, N, [P,] 3)``.
+        If `ypbpr` is not at least 2-D with shape (..., 3).
 
     References
     ----------
@@ -1861,20 +1841,18 @@ def ycbcr2rgb(ycbcr):
 
     Parameters
     ----------
-    ycbcr : array_like
-        The image in YCbCr format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    ycbcr : (..., 3) array_like
+        The image in YCbCr format. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in RGB format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    out : (..., 3) ndarray
+        The image in RGB format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `ycbcr` is not a 3- or 4-D array of shape ``(M, N, [P,] 3)``.
+        If `ycbcr` is not at least 2-D with shape (..., 3).
 
     Notes
     -----
@@ -1897,20 +1875,18 @@ def ydbdr2rgb(ydbdr):
 
     Parameters
     ----------
-    ydbdr : array_like
-        The image in YDbDr format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    ydbdr : (..., 3) array_like
+        The image in YDbDr format. Final dimension denotes channels.
 
     Returns
     -------
-    out : ndarray
-        The image in RGB format, in a 3- or 4-D array of shape
-        ``(M, N, [P,] 3)``.
+    out : (..., 3) ndarray
+        The image in RGB format. Same dimensions as input.
 
     Raises
     ------
     ValueError
-        If `ydbdr` is not a 3- or 4-D array of shape ``(M, N, [P,] 3)``.
+        If `ydbdr` is not at least 2-D with shape (..., 3).
 
     Notes
     -----
@@ -1921,5 +1897,4 @@ def ydbdr2rgb(ydbdr):
     ----------
     .. [1] https://en.wikipedia.org/wiki/YDbDr
     """
-    arr = ydbdr.copy()
-    return _convert(rgb_from_ydbdr, arr)
+    return _convert(rgb_from_ydbdr, ydbdr)
diff --git a/skimage/color/colorlabel.py b/skimage/color/colorlabel.py
--- a/skimage/color/colorlabel.py
+++ b/skimage/color/colorlabel.py
@@ -164,7 +164,8 @@ def _label2rgb_overlay(label, image=None, colors=None, alpha=0.3,
         if image.min() < 0:
             warn("Negative intensities in `image` are not supported")
 
-        image = img_as_float(rgb2gray(image))
+        if image.ndim > label.ndim:
+            image = img_as_float(rgb2gray(image))
         image = gray2rgb(image) * image_alpha + (1 - image_alpha)
 
     # Ensure that all labels are non-negative so we can index into


Test patch:
diff --git a/skimage/color/tests/test_colorconv.py b/skimage/color/tests/test_colorconv.py
--- a/skimage/color/tests/test_colorconv.py
+++ b/skimage/color/tests/test_colorconv.py
@@ -111,9 +111,6 @@ def test_rgb2hsv_conversion(self):
     def test_rgb2hsv_error_grayscale(self):
         self.assertRaises(ValueError, rgb2hsv, self.img_grayscale)
 
-    def test_rgb2hsv_error_one_element(self):
-        self.assertRaises(ValueError, rgb2hsv, self.img_rgb[0, 0])
-
     def test_rgb2hsv_dtype(self):
         rgb = img_as_float(self.img_rgb)
         rgb32 = img_as_float32(self.img_rgb)
@@ -134,9 +131,6 @@ def test_hsv2rgb_conversion(self):
     def test_hsv2rgb_error_grayscale(self):
         self.assertRaises(ValueError, hsv2rgb, self.img_grayscale)
 
-    def test_hsv2rgb_error_one_element(self):
-        self.assertRaises(ValueError, hsv2rgb, self.img_rgb[0, 0])
-
     def test_hsv2rgb_dtype(self):
         rgb = self.img_rgb.astype("float32")[::16, ::16]
         # create HSV image with colorsys
@@ -165,9 +159,6 @@ def test_rgb2xyz_conversion(self):
     def test_rgb2xyz_error_grayscale(self):
         self.assertRaises(ValueError, rgb2xyz, self.img_grayscale)
 
-    def test_rgb2xyz_error_one_element(self):
-        self.assertRaises(ValueError, rgb2xyz, self.img_rgb[0, 0])
-
     def test_rgb2xyz_dtype(self):
         img = self.colbars_array
         img32 = img.astype('float32')
@@ -730,3 +721,58 @@ def test_gray2rgba_alpha():
     with pytest.raises(ValueError) as err:
         rgba = gray2rgba(img, alpha)
     assert expected_err_msg == str(err.value)
+
+
+@pytest.mark.parametrize("func", [rgb2gray, gray2rgb, gray2rgba])
+@pytest.mark.parametrize("shape", ([(3, ), (2, 3), (4, 5, 3), (5, 4, 5, 3),
+                                    (4, 5, 4, 5, 3)]))
+def test_nD_gray_conversion(func, shape):
+    img = np.random.rand(*shape)
+    out = func(img)
+
+    msg_list = []
+    if img.ndim == 3 and func == gray2rgb:
+        msg_list.append('Pass-through of possibly RGB images in gray2rgb')
+    elif img.ndim == 2 and func == rgb2gray:
+        msg_list.append('The behavior of rgb2gray will change')
+
+    with expected_warnings(msg_list):
+        out = func(img)
+
+    common_ndim = min(out.ndim, len(shape))
+
+    assert out.shape[:common_ndim] == shape[:common_ndim]
+
+
+@pytest.mark.parametrize("func", [rgb2hsv, hsv2rgb,
+                                  rgb2xyz, xyz2rgb,
+                                  rgb2hed, hed2rgb,
+                                  rgb2rgbcie, rgbcie2rgb,
+                                  xyz2lab, lab2xyz,
+                                  lab2rgb, rgb2lab,
+                                  xyz2luv, luv2xyz,
+                                  luv2rgb, rgb2luv,
+                                  lab2lch, lch2lab,
+                                  rgb2yuv, yuv2rgb,
+                                  rgb2yiq, yiq2rgb,
+                                  rgb2ypbpr, ypbpr2rgb,
+                                  rgb2ycbcr, ycbcr2rgb,
+                                  rgb2ydbdr, ydbdr2rgb])
+@pytest.mark.parametrize("shape", ([(3, ), (2, 3), (4, 5, 3), (5, 4, 5, 3),
+                                    (4, 5, 4, 5, 3)]))
+def test_nD_color_conversion(func, shape):
+    img = np.random.rand(*shape)
+    out = func(img)
+
+    assert out.shape == img.shape
+
+
+@pytest.mark.parametrize("shape", ([(4, ), (2, 4), (4, 5, 4), (5, 4, 5, 4),
+                                    (4, 5, 4, 5, 4)]))
+def test_rgba2rgb_nD(shape):
+    img = np.random.rand(*shape)
+    out = rgba2rgb(img)
+
+    expected_shape = shape[:-1] + (3, )
+
+    assert out.shape == expected_shape
diff --git a/skimage/color/tests/test_colorlabel.py b/skimage/color/tests/test_colorlabel.py
--- a/skimage/color/tests/test_colorlabel.py
+++ b/skimage/color/tests/test_colorlabel.py
@@ -30,13 +30,12 @@ def test_shape_mismatch():
 
 
 def test_wrong_kind():
-    image = np.ones((3, 3))
-    label = image
+    label = np.ones((3, 3))
     # Must not raise an error.
-    label2rgb(image, label, bg_label=-1)
+    label2rgb(label, bg_label=-1)
     # kind='foo' is wrong.
     with testing.raises(ValueError):
-        label2rgb(image, label, kind='foo', bg_label=-1)
+        label2rgb(label, kind='foo', bg_label=-1)
 
 
 def test_rgb():