1"""Functions for converting between color spaces.
2
3The "central" color space in this module is RGB, more specifically the linear
4sRGB color space using D65 as a white-point [1]_.  This represents a
5standard monitor (w/o gamma correction). For a good FAQ on color spaces see
6[2]_.
7
8The API consists of functions to convert to and from RGB as defined above, as
9well as a generic function to convert to and from any supported color space
10(which is done through RGB in most cases).
11
12
13Supported color spaces
14----------------------
15* RGB : Red Green Blue.
16        Here the sRGB standard [1]_.
17* HSV : Hue, Saturation, Value.
18        Uniquely defined when related to sRGB [3]_.
19* RGB CIE : Red Green Blue.
20        The original RGB CIE standard from 1931 [4]_. Primary colors are 700 nm
21        (red), 546.1 nm (blue) and 435.8 nm (green).
22* XYZ CIE : XYZ
23        Derived from the RGB CIE color space. Chosen such that
24        ``x == y == z == 1/3`` at the whitepoint, and all color matching
25        functions are greater than zero everywhere.
26* LAB CIE : Lightness, a, b
27        Colorspace derived from XYZ CIE that is intended to be more
28        perceptually uniform
29* LUV CIE : Lightness, u, v
30        Colorspace derived from XYZ CIE that is intended to be more
31        perceptually uniform
32* LCH CIE : Lightness, Chroma, Hue
33        Defined in terms of LAB CIE.  C and H are the polar representation of
34        a and b.  The polar angle C is defined to be on ``(0, 2*pi)``
35
36:author: Nicolas Pinto (rgb2hsv)
37:author: Ralf Gommers (hsv2rgb)
38:author: Travis Oliphant (XYZ and RGB CIE functions)
39:author: Matt Terry (lab2lch)
40:author: Alex Izvorski (yuv2rgb, rgb2yuv and related)
41
42:license: modified BSD
43
44References
45----------
46.. [1] Official specification of sRGB, IEC 61966-2-1:1999.
47.. [2] http://www.poynton.com/ColorFAQ.html
48.. [3] https://en.wikipedia.org/wiki/HSL_and_HSV
49.. [4] https://en.wikipedia.org/wiki/CIE_1931_color_space
50"""
51
52from warnings import warn
53
54import numpy as np
55from scipy import linalg
56
57
58from .._shared.utils import (_supported_float_type, channel_as_last_axis,
59                             identity, reshape_nd, slice_at_axis)
60from ..util import dtype, dtype_limits
61
62
63def convert_colorspace(arr, fromspace, tospace, *, channel_axis=-1):
64    """Convert an image array to a new color space.
65
66    Valid color spaces are:
67        'RGB', 'HSV', 'RGB CIE', 'XYZ', 'YUV', 'YIQ', 'YPbPr', 'YCbCr', 'YDbDr'
68
69    Parameters
70    ----------
71    arr : (..., 3, ...) array_like
72        The image to convert. By default, the final dimension denotes
73        channels.
74    fromspace : str
75        The color space to convert from. Can be specified in lower case.
76    tospace : str
77        The color space to convert to. Can be specified in lower case.
78    channel_axis : int, optional
79        This parameter indicates which axis of the array corresponds to
80        channels.
81
82        .. versionadded:: 0.19
83           ``channel_axis`` was added in 0.19.
84
85    Returns
86    -------
87    out : (..., 3, ...) ndarray
88        The converted image. Same dimensions as input.
89
90    Raises
91    ------
92    ValueError
93        If fromspace is not a valid color space
94    ValueError
95        If tospace is not a valid color space
96
97    Notes
98    -----
99    Conversion is performed through the "central" RGB color space,
100    i.e. conversion from XYZ to HSV is implemented as ``XYZ -> RGB -> HSV``
101    instead of directly.
102
103    Examples
104    --------
105    >>> from skimage import data
106    >>> img = data.astronaut()
107    >>> img_hsv = convert_colorspace(img, 'RGB', 'HSV')
108    """
109    fromdict = {'rgb': identity, 'hsv': hsv2rgb, 'rgb cie': rgbcie2rgb,
110                'xyz': xyz2rgb, 'yuv': yuv2rgb, 'yiq': yiq2rgb,
111                'ypbpr': ypbpr2rgb, 'ycbcr': ycbcr2rgb, 'ydbdr': ydbdr2rgb}
112    todict = {'rgb': identity, 'hsv': rgb2hsv, 'rgb cie': rgb2rgbcie,
113              'xyz': rgb2xyz, 'yuv': rgb2yuv, 'yiq': rgb2yiq,
114              'ypbpr': rgb2ypbpr, 'ycbcr': rgb2ycbcr, 'ydbdr': rgb2ydbdr}
115
116    fromspace = fromspace.lower()
117    tospace = tospace.lower()
118    if fromspace not in fromdict:
119        msg = f'`fromspace` has to be one of {fromdict.keys()}'
120        raise ValueError(msg)
121    if tospace not in todict:
122        msg = f'`tospace` has to be one of {todict.keys()}'
123        raise ValueError(msg)
124
125    return todict[tospace](
126        fromdict[fromspace](arr, channel_axis=channel_axis),
127        channel_axis=channel_axis
128    )
129
130
131def _prepare_colorarray(arr, force_copy=False, *, channel_axis=-1):
132    """Check the shape of the array and convert it to
133    floating point representation.
134    """
135    arr = np.asanyarray(arr)
136
137    if arr.shape[channel_axis] != 3:
138        msg = (f'the input array must have size 3 along `channel_axis`, '
139               f'got {arr.shape}')
140        raise ValueError(msg)
141
142    float_dtype = _supported_float_type(arr.dtype)
143    if float_dtype == np.float32:
144        _func = dtype.img_as_float32
145    else:
146        _func = dtype.img_as_float64
147    return _func(arr, force_copy=force_copy)
148
149
150def _validate_channel_axis(channel_axis, ndim):
151    if not isinstance(channel_axis, int):
152        raise TypeError("channel_axis must be an integer")
153    if channel_axis < -ndim or channel_axis >= ndim:
154        raise np.AxisError("channel_axis exceeds array dimensions")
155
156
157def rgba2rgb(rgba, background=(1, 1, 1), *, channel_axis=-1):
158    """RGBA to RGB conversion using alpha blending [1]_.
159
160    Parameters
161    ----------
162    rgba : (..., 4, ...) array_like
163        The image in RGBA format. By default, the final dimension denotes
164        channels.
165    background : array_like
166        The color of the background to blend the image with (3 floats
167        between 0 to 1 - the RGB value of the background).
168    channel_axis : int, optional
169        This parameter indicates which axis of the array corresponds to
170        channels.
171
172        .. versionadded:: 0.19
173           ``channel_axis`` was added in 0.19.
174
175    Returns
176    -------
177    out : (..., 3, ...) ndarray
178        The image in RGB format. Same dimensions as input.
179
180    Raises
181    ------
182    ValueError
183        If `rgba` is not at least 2D with shape (..., 4, ...).
184
185    References
186    ----------
187    .. [1] https://en.wikipedia.org/wiki/Alpha_compositing#Alpha_blending
188
189    Examples
190    --------
191    >>> from skimage import color
192    >>> from skimage import data
193    >>> img_rgba = data.logo()
194    >>> img_rgb = color.rgba2rgb(img_rgba)
195    """
196    arr = np.asanyarray(rgba)
197    _validate_channel_axis(channel_axis, arr.ndim)
198    channel_axis = channel_axis % arr.ndim
199
200    if arr.shape[channel_axis] != 4:
201        msg = (f'the input array must have size 4 along `channel_axis`, '
202               f'got {arr.shape}')
203        raise ValueError(msg)
204
205    float_dtype = _supported_float_type(arr.dtype)
206    if float_dtype == np.float32:
207        arr = dtype.img_as_float32(arr)
208    else:
209        arr = dtype.img_as_float64(arr)
210
211    background = np.ravel(background).astype(arr.dtype)
212    if len(background) != 3:
213        raise ValueError('background must be an array-like containing 3 RGB '
214                         f'values. Got {len(background)} items')
215    if np.any(background < 0) or np.any(background > 1):
216        raise ValueError('background RGB values must be floats between '
217                         '0 and 1.')
218    # reshape background for broadcasting along non-channel axes
219    background = reshape_nd(background, arr.ndim, channel_axis)
220
221    alpha = arr[slice_at_axis(slice(3, 4), axis=channel_axis)]
222    channels = arr[slice_at_axis(slice(3), axis=channel_axis)]
223    out = np.clip((1 - alpha) * background + alpha * channels,
224                  a_min=0, a_max=1)
225    return out
226
227
228@channel_as_last_axis()
229def rgb2hsv(rgb, *, channel_axis=-1):
230    """RGB to HSV color space conversion.
231
232    Parameters
233    ----------
234    rgb : (..., 3, ...) array_like
235        The image in RGB format. By default, the final dimension denotes
236        channels.
237    channel_axis : int, optional
238        This parameter indicates which axis of the array corresponds to
239        channels.
240
241        .. versionadded:: 0.19
242           ``channel_axis`` was added in 0.19.
243
244    Returns
245    -------
246    out : (..., 3, ...) ndarray
247        The image in HSV format. Same dimensions as input.
248
249    Raises
250    ------
251    ValueError
252        If `rgb` is not at least 2-D with shape (..., 3, ...).
253
254    Notes
255    -----
256    Conversion between RGB and HSV color spaces results in some loss of
257    precision, due to integer arithmetic and rounding [1]_.
258
259    References
260    ----------
261    .. [1] https://en.wikipedia.org/wiki/HSL_and_HSV
262
263    Examples
264    --------
265    >>> from skimage import color
266    >>> from skimage import data
267    >>> img = data.astronaut()
268    >>> img_hsv = color.rgb2hsv(img)
269    """
270    input_is_one_pixel = rgb.ndim == 1
271    if input_is_one_pixel:
272        rgb = rgb[np.newaxis, ...]
273
274    arr = _prepare_colorarray(rgb, channel_axis=-1)
275    out = np.empty_like(arr)
276
277    # -- V channel
278    out_v = arr.max(-1)
279
280    # -- S channel
281    delta = arr.ptp(-1)
282    # Ignore warning for zero divided by zero
283    old_settings = np.seterr(invalid='ignore')
284    out_s = delta / out_v
285    out_s[delta == 0.] = 0.
286
287    # -- H channel
288    # red is max
289    idx = (arr[..., 0] == out_v)
290    out[idx, 0] = (arr[idx, 1] - arr[idx, 2]) / delta[idx]
291
292    # green is max
293    idx = (arr[..., 1] == out_v)
294    out[idx, 0] = 2. + (arr[idx, 2] - arr[idx, 0]) / delta[idx]
295
296    # blue is max
297    idx = (arr[..., 2] == out_v)
298    out[idx, 0] = 4. + (arr[idx, 0] - arr[idx, 1]) / delta[idx]
299    out_h = (out[..., 0] / 6.) % 1.
300    out_h[delta == 0.] = 0.
301
302    np.seterr(**old_settings)
303
304    # -- output
305    out[..., 0] = out_h
306    out[..., 1] = out_s
307    out[..., 2] = out_v
308
309    # # remove NaN
310    out[np.isnan(out)] = 0
311
312    if input_is_one_pixel:
313        out = np.squeeze(out, axis=0)
314
315    return out
316
317
318@channel_as_last_axis()
319def hsv2rgb(hsv, *, channel_axis=-1):
320    """HSV to RGB color space conversion.
321
322    Parameters
323    ----------
324    hsv : (..., 3, ...) array_like
325        The image in HSV format. By default, the final dimension denotes
326        channels.
327    channel_axis : int, optional
328        This parameter indicates which axis of the array corresponds to
329        channels.
330
331        .. versionadded:: 0.19
332           ``channel_axis`` was added in 0.19.
333
334    Returns
335    -------
336    out : (..., 3, ...) ndarray
337        The image in RGB format. Same dimensions as input.
338
339    Raises
340    ------
341    ValueError
342        If `hsv` is not at least 2-D with shape (..., 3, ...).
343
344    Notes
345    -----
346    Conversion between RGB and HSV color spaces results in some loss of
347    precision, due to integer arithmetic and rounding [1]_.
348
349    References
350    ----------
351    .. [1] https://en.wikipedia.org/wiki/HSL_and_HSV
352
353    Examples
354    --------
355    >>> from skimage import data
356    >>> img = data.astronaut()
357    >>> img_hsv = rgb2hsv(img)
358    >>> img_rgb = hsv2rgb(img_hsv)
359    """
360    arr = _prepare_colorarray(hsv, channel_axis=-1)
361
362    hi = np.floor(arr[..., 0] * 6)
363    f = arr[..., 0] * 6 - hi
364    p = arr[..., 2] * (1 - arr[..., 1])
365    q = arr[..., 2] * (1 - f * arr[..., 1])
366    t = arr[..., 2] * (1 - (1 - f) * arr[..., 1])
367    v = arr[..., 2]
368
369    hi = np.stack([hi, hi, hi], axis=-1).astype(np.uint8) % 6
370    out = np.choose(
371        hi, np.stack([np.stack((v, t, p), axis=-1),
372                      np.stack((q, v, p), axis=-1),
373                      np.stack((p, v, t), axis=-1),
374                      np.stack((p, q, v), axis=-1),
375                      np.stack((t, p, v), axis=-1),
376                      np.stack((v, p, q), axis=-1)]))
377
378    return out
379
380
381# ---------------------------------------------------------------
382# Primaries for the coordinate systems
383# ---------------------------------------------------------------
384cie_primaries = np.array([700, 546.1, 435.8])
385sb_primaries = np.array([1. / 155, 1. / 190, 1. / 225]) * 1e5
386
387# ---------------------------------------------------------------
388# Matrices that define conversion between different color spaces
389# ---------------------------------------------------------------
390
391# From sRGB specification
392xyz_from_rgb = np.array([[0.412453, 0.357580, 0.180423],
393                         [0.212671, 0.715160, 0.072169],
394                         [0.019334, 0.119193, 0.950227]])
395
396rgb_from_xyz = linalg.inv(xyz_from_rgb)
397
398# From https://en.wikipedia.org/wiki/CIE_1931_color_space
399# Note: Travis's code did not have the divide by 0.17697
400xyz_from_rgbcie = np.array([[0.49, 0.31, 0.20],
401                            [0.17697, 0.81240, 0.01063],
402                            [0.00, 0.01, 0.99]]) / 0.17697
403
404rgbcie_from_xyz = linalg.inv(xyz_from_rgbcie)
405
406# construct matrices to and from rgb:
407rgbcie_from_rgb = rgbcie_from_xyz @ xyz_from_rgb
408rgb_from_rgbcie = rgb_from_xyz @ xyz_from_rgbcie
409
410
411gray_from_rgb = np.array([[0.2125, 0.7154, 0.0721],
412                          [0, 0, 0],
413                          [0, 0, 0]])
414
415yuv_from_rgb = np.array([[ 0.299     ,  0.587     ,  0.114      ],
416                         [-0.14714119, -0.28886916,  0.43601035 ],
417                         [ 0.61497538, -0.51496512, -0.10001026 ]])
418
419rgb_from_yuv = linalg.inv(yuv_from_rgb)
420
421yiq_from_rgb = np.array([[0.299     ,  0.587     ,  0.114     ],
422                         [0.59590059, -0.27455667, -0.32134392],
423                         [0.21153661, -0.52273617,  0.31119955]])
424
425rgb_from_yiq = linalg.inv(yiq_from_rgb)
426
427ypbpr_from_rgb = np.array([[ 0.299   , 0.587   , 0.114   ],
428                           [-0.168736,-0.331264, 0.5     ],
429                           [ 0.5     ,-0.418688,-0.081312]])
430
431rgb_from_ypbpr = linalg.inv(ypbpr_from_rgb)
432
433ycbcr_from_rgb = np.array([[    65.481,   128.553,    24.966],
434                           [   -37.797,   -74.203,   112.0  ],
435                           [   112.0  ,   -93.786,   -18.214]])
436
437rgb_from_ycbcr = linalg.inv(ycbcr_from_rgb)
438
439ydbdr_from_rgb = np.array([[    0.299,   0.587,    0.114],
440                           [   -0.45 ,  -0.883,    1.333],
441                           [   -1.333,   1.116,    0.217]])
442
443rgb_from_ydbdr = linalg.inv(ydbdr_from_rgb)
444
445
446# CIE LAB constants for Observer=2A, Illuminant=D65
447# NOTE: this is actually the XYZ values for the illuminant above.
448lab_ref_white = np.array([0.95047, 1., 1.08883])
449
450# XYZ coordinates of the illuminants, scaled to [0, 1]. For each illuminant I
451# we have:
452#
453#   illuminant[I]['2'] corresponds to the XYZ coordinates for the 2 degree
454#   field of view.
455#
456#   illuminant[I]['10'] corresponds to the XYZ coordinates for the 10 degree
457#   field of view.
458#
459#   illuminant[I]['R'] corresponds to the XYZ coordinates for R illuminants
460#   in grDevices::convertColor
461#
462# The XYZ coordinates are calculated from [1], using the formula:
463#
464#   X = x * ( Y / y )
465#   Y = Y
466#   Z = ( 1 - x - y ) * ( Y / y )
467#
468# where Y = 1. The only exception is the illuminant "D65" with aperture angle
469# 2, whose coordinates are copied from 'lab_ref_white' for
470# backward-compatibility reasons.
471#
472#     References
473#    ----------
474#    .. [1] https://en.wikipedia.org/wiki/Standard_illuminant
475
476illuminants = \
477    {"A": {'2': (1.098466069456375, 1, 0.3558228003436005),
478           '10': (1.111420406956693, 1, 0.3519978321919493),
479           'R': (1.098466069456375, 1, 0.3558228003436005)},
480     "B": {'2': (0.9909274480248003, 1, 0.8531327322886154),
481           '10': (0.9917777147717607, 1, 0.8434930535866175),
482           'R': (0.9909274480248003, 1, 0.8531327322886154)},
483     "C": {'2': (0.980705971659919, 1, 1.1822494939271255),
484           '10': (0.9728569189782166, 1, 1.1614480488951577),
485           'R': (0.980705971659919, 1, 1.1822494939271255)},
486     "D50": {'2': (0.9642119944211994, 1, 0.8251882845188288),
487             '10': (0.9672062750333777, 1, 0.8142801513128616),
488             'R': (0.9639501491621826, 1, 0.8241280285499208)},
489     "D55": {'2': (0.956797052643698, 1, 0.9214805860173273),
490             '10': (0.9579665682254781, 1, 0.9092525159847462),
491             'R': (0.9565317453467969, 1, 0.9202554587037198)},
492     "D65": {'2': (0.95047, 1., 1.08883),   # This was: `lab_ref_white`
493             '10': (0.94809667673716, 1, 1.0730513595166162),
494             'R': (0.9532057125493769, 1, 1.0853843816469158)},
495     "D75": {'2': (0.9497220898840717, 1, 1.226393520724154),
496             '10': (0.9441713925645873, 1, 1.2064272211720228),
497             'R': (0.9497220898840717, 1, 1.226393520724154)},
498     "E": {'2': (1.0, 1.0, 1.0),
499           '10': (1.0, 1.0, 1.0),
500           'R': (1.0, 1.0, 1.0)}}
501
502
503def get_xyz_coords(illuminant, observer, dtype=float):
504    """Get the XYZ coordinates of the given illuminant and observer [1]_.
505
506    Parameters
507    ----------
508    illuminant : {"A", "B", "C", "D50", "D55", "D65", "D75", "E"}, optional
509        The name of the illuminant (the function is NOT case sensitive).
510    observer : {"2", "10", "R"}, optional
511        One of: 2-degree observer, 10-degree observer, or 'R' observer as in
512        R function grDevices::convertColor.
513    dtype: dtype, optional
514        Output data type.
515
516    Returns
517    -------
518    out : array
519        Array with 3 elements containing the XYZ coordinates of the given
520        illuminant.
521
522    Raises
523    ------
524    ValueError
525        If either the illuminant or the observer angle are not supported or
526        unknown.
527
528    References
529    ----------
530    .. [1] https://en.wikipedia.org/wiki/Standard_illuminant
531    """
532    illuminant = illuminant.upper()
533    observer = observer.upper()
534    try:
535        return np.asarray(illuminants[illuminant][observer], dtype=dtype)
536    except KeyError:
537        raise ValueError(f'Unknown illuminant/observer combination '
538                         f'(`{illuminant}`, `{observer}`)')
539
540
541# Haematoxylin-Eosin-DAB colorspace
542# From original Ruifrok's paper: A. C. Ruifrok and D. A. Johnston,
543# "Quantification of histochemical staining by color deconvolution,"
544# Analytical and quantitative cytology and histology / the International
545# Academy of Cytology [and] American Society of Cytology, vol. 23, no. 4,
546# pp. 291-9, Aug. 2001.
547rgb_from_hed = np.array([[0.65, 0.70, 0.29],
548                         [0.07, 0.99, 0.11],
549                         [0.27, 0.57, 0.78]])
550hed_from_rgb = linalg.inv(rgb_from_hed)
551
552# Following matrices are adapted form the Java code written by G.Landini.
553# The original code is available at:
554# https://web.archive.org/web/20160624145052/http://www.mecourse.com/landinig/software/cdeconv/cdeconv.html
555
556# Hematoxylin + DAB
557rgb_from_hdx = np.array([[0.650, 0.704, 0.286],
558                         [0.268, 0.570, 0.776],
559                         [0.0, 0.0, 0.0]])
560rgb_from_hdx[2, :] = np.cross(rgb_from_hdx[0, :], rgb_from_hdx[1, :])
561hdx_from_rgb = linalg.inv(rgb_from_hdx)
562
563# Feulgen + Light Green
564rgb_from_fgx = np.array([[0.46420921, 0.83008335, 0.30827187],
565                         [0.94705542, 0.25373821, 0.19650764],
566                         [0.0, 0.0, 0.0]])
567rgb_from_fgx[2, :] = np.cross(rgb_from_fgx[0, :], rgb_from_fgx[1, :])
568fgx_from_rgb = linalg.inv(rgb_from_fgx)
569
570# Giemsa: Methyl Blue + Eosin
571rgb_from_bex = np.array([[0.834750233, 0.513556283, 0.196330403],
572                         [0.092789, 0.954111, 0.283111],
573                         [0.0, 0.0, 0.0]])
574rgb_from_bex[2, :] = np.cross(rgb_from_bex[0, :], rgb_from_bex[1, :])
575bex_from_rgb = linalg.inv(rgb_from_bex)
576
577# FastRed + FastBlue +  DAB
578rgb_from_rbd = np.array([[0.21393921, 0.85112669, 0.47794022],
579                         [0.74890292, 0.60624161, 0.26731082],
580                         [0.268, 0.570, 0.776]])
581rbd_from_rgb = linalg.inv(rgb_from_rbd)
582
583# Methyl Green + DAB
584rgb_from_gdx = np.array([[0.98003, 0.144316, 0.133146],
585                         [0.268, 0.570, 0.776],
586                         [0.0, 0.0, 0.0]])
587rgb_from_gdx[2, :] = np.cross(rgb_from_gdx[0, :], rgb_from_gdx[1, :])
588gdx_from_rgb = linalg.inv(rgb_from_gdx)
589
590# Hematoxylin + AEC
591rgb_from_hax = np.array([[0.650, 0.704, 0.286],
592                         [0.2743, 0.6796, 0.6803],
593                         [0.0, 0.0, 0.0]])
594rgb_from_hax[2, :] = np.cross(rgb_from_hax[0, :], rgb_from_hax[1, :])
595hax_from_rgb = linalg.inv(rgb_from_hax)
596
597# Blue matrix Anilline Blue + Red matrix Azocarmine + Orange matrix Orange-G
598rgb_from_bro = np.array([[0.853033, 0.508733, 0.112656],
599                         [0.09289875, 0.8662008, 0.49098468],
600                         [0.10732849, 0.36765403, 0.9237484]])
601bro_from_rgb = linalg.inv(rgb_from_bro)
602
603# Methyl Blue + Ponceau Fuchsin
604rgb_from_bpx = np.array([[0.7995107, 0.5913521, 0.10528667],
605                         [0.09997159, 0.73738605, 0.6680326],
606                         [0.0, 0.0, 0.0]])
607rgb_from_bpx[2, :] = np.cross(rgb_from_bpx[0, :], rgb_from_bpx[1, :])
608bpx_from_rgb = linalg.inv(rgb_from_bpx)
609
610# Alcian Blue + Hematoxylin
611rgb_from_ahx = np.array([[0.874622, 0.457711, 0.158256],
612                         [0.552556, 0.7544, 0.353744],
613                         [0.0, 0.0, 0.0]])
614rgb_from_ahx[2, :] = np.cross(rgb_from_ahx[0, :], rgb_from_ahx[1, :])
615ahx_from_rgb = linalg.inv(rgb_from_ahx)
616
617# Hematoxylin + PAS
618rgb_from_hpx = np.array([[0.644211, 0.716556, 0.266844],
619                         [0.175411, 0.972178, 0.154589],
620                         [0.0, 0.0, 0.0]])
621rgb_from_hpx[2, :] = np.cross(rgb_from_hpx[0, :], rgb_from_hpx[1, :])
622hpx_from_rgb = linalg.inv(rgb_from_hpx)
623
624# -------------------------------------------------------------
625# The conversion functions that make use of the matrices above
626# -------------------------------------------------------------
627
628
629def _convert(matrix, arr):
630    """Do the color space conversion.
631
632    Parameters
633    ----------
634    matrix : array_like
635        The 3x3 matrix to use.
636    arr : (..., 3, ...) array_like
637        The input array. By default, the final dimension denotes
638        channels.
639
640    Returns
641    -------
642    out : (..., 3, ...) ndarray
643        The converted array. Same dimensions as input.
644    """
645    arr = _prepare_colorarray(arr)
646
647    return arr @ matrix.T.astype(arr.dtype)
648
649
650@channel_as_last_axis()
651def xyz2rgb(xyz, *, channel_axis=-1):
652    """XYZ to RGB color space conversion.
653
654    Parameters
655    ----------
656    xyz : (..., 3, ...) array_like
657        The image in XYZ format. By default, the final dimension denotes
658        channels.
659    channel_axis : int, optional
660        This parameter indicates which axis of the array corresponds to
661        channels.
662
663        .. versionadded:: 0.19
664           ``channel_axis`` was added in 0.19.
665
666    Returns
667    -------
668    out : (..., 3, ...) ndarray
669        The image in RGB format. Same dimensions as input.
670
671    Raises
672    ------
673    ValueError
674        If `xyz` is not at least 2-D with shape (..., 3, ...).
675
676    Notes
677    -----
678    The CIE XYZ color space is derived from the CIE RGB color space. Note
679    however that this function converts to sRGB.
680
681    References
682    ----------
683    .. [1] https://en.wikipedia.org/wiki/CIE_1931_color_space
684
685    Examples
686    --------
687    >>> from skimage import data
688    >>> from skimage.color import rgb2xyz, xyz2rgb
689    >>> img = data.astronaut()
690    >>> img_xyz = rgb2xyz(img)
691    >>> img_rgb = xyz2rgb(img_xyz)
692    """
693    # Follow the algorithm from http://www.easyrgb.com/index.php
694    # except we don't multiply/divide by 100 in the conversion
695    arr = _convert(rgb_from_xyz, xyz)
696    mask = arr > 0.0031308
697    arr[mask] = 1.055 * np.power(arr[mask], 1 / 2.4) - 0.055
698    arr[~mask] *= 12.92
699    np.clip(arr, 0, 1, out=arr)
700    return arr
701
702
703@channel_as_last_axis()
704def rgb2xyz(rgb, *, channel_axis=-1):
705    """RGB to XYZ color space conversion.
706
707    Parameters
708    ----------
709    rgb : (..., 3, ...) array_like
710        The image in RGB format. By default, the final dimension denotes
711        channels.
712    channel_axis : int, optional
713        This parameter indicates which axis of the array corresponds to
714        channels.
715
716        .. versionadded:: 0.19
717           ``channel_axis`` was added in 0.19.
718
719    Returns
720    -------
721    out : (..., 3, ...) ndarray
722        The image in XYZ format. Same dimensions as input.
723
724    Raises
725    ------
726    ValueError
727        If `rgb` is not at least 2-D with shape (..., 3, ...).
728
729    Notes
730    -----
731    The CIE XYZ color space is derived from the CIE RGB color space. Note
732    however that this function converts from sRGB.
733
734    References
735    ----------
736    .. [1] https://en.wikipedia.org/wiki/CIE_1931_color_space
737
738    Examples
739    --------
740    >>> from skimage import data
741    >>> img = data.astronaut()
742    >>> img_xyz = rgb2xyz(img)
743    """
744    # Follow the algorithm from http://www.easyrgb.com/index.php
745    # except we don't multiply/divide by 100 in the conversion
746    arr = _prepare_colorarray(rgb, channel_axis=-1).copy()
747    mask = arr > 0.04045
748    arr[mask] = np.power((arr[mask] + 0.055) / 1.055, 2.4)
749    arr[~mask] /= 12.92
750    return arr @ xyz_from_rgb.T.astype(arr.dtype)
751
752
753@channel_as_last_axis()
754def rgb2rgbcie(rgb, *, channel_axis=-1):
755    """RGB to RGB CIE color space conversion.
756
757    Parameters
758    ----------
759    rgb : (..., 3, ...) array_like
760        The image in RGB format. By default, the final dimension denotes
761        channels.
762    channel_axis : int, optional
763        This parameter indicates which axis of the array corresponds to
764        channels.
765
766        .. versionadded:: 0.19
767           ``channel_axis`` was added in 0.19.
768
769    Returns
770    -------
771    out : (..., 3, ...) ndarray
772        The image in RGB CIE format. Same dimensions as input.
773
774    Raises
775    ------
776    ValueError
777        If `rgb` is not at least 2-D with shape (..., 3, ...).
778
779    References
780    ----------
781    .. [1] https://en.wikipedia.org/wiki/CIE_1931_color_space
782
783    Examples
784    --------
785    >>> from skimage import data
786    >>> from skimage.color import rgb2rgbcie
787    >>> img = data.astronaut()
788    >>> img_rgbcie = rgb2rgbcie(img)
789    """
790    return _convert(rgbcie_from_rgb, rgb)
791
792
793@channel_as_last_axis()
794def rgbcie2rgb(rgbcie, *, channel_axis=-1):
795    """RGB CIE to RGB color space conversion.
796
797    Parameters
798    ----------
799    rgbcie : (..., 3, ...) array_like
800        The image in RGB CIE format. By default, the final dimension denotes
801        channels.
802    channel_axis : int, optional
803        This parameter indicates which axis of the array corresponds to
804        channels.
805
806        .. versionadded:: 0.19
807           ``channel_axis`` was added in 0.19.
808
809    Returns
810    -------
811    out : (..., 3, ...) ndarray
812        The image in RGB format. Same dimensions as input.
813
814    Raises
815    ------
816    ValueError
817        If `rgbcie` is not at least 2-D with shape (..., 3, ...).
818
819    References
820    ----------
821    .. [1] https://en.wikipedia.org/wiki/CIE_1931_color_space
822
823    Examples
824    --------
825    >>> from skimage import data
826    >>> from skimage.color import rgb2rgbcie, rgbcie2rgb
827    >>> img = data.astronaut()
828    >>> img_rgbcie = rgb2rgbcie(img)
829    >>> img_rgb = rgbcie2rgb(img_rgbcie)
830    """
831    return _convert(rgb_from_rgbcie, rgbcie)
832
833
834@channel_as_last_axis(multichannel_output=False)
835def rgb2gray(rgb, *, channel_axis=-1):
836    """Compute luminance of an RGB image.
837
838    Parameters
839    ----------
840    rgb : (..., 3, ...) array_like
841        The image in RGB format. By default, the final dimension denotes
842        channels.
843
844    Returns
845    -------
846    out : ndarray
847        The luminance image - an array which is the same size as the input
848        array, but with the channel dimension removed.
849
850    Raises
851    ------
852    ValueError
853        If `rgb` is not at least 2-D with shape (..., 3, ...).
854
855    Notes
856    -----
857    The weights used in this conversion are calibrated for contemporary
858    CRT phosphors::
859
860        Y = 0.2125 R + 0.7154 G + 0.0721 B
861
862    If there is an alpha channel present, it is ignored.
863
864    References
865    ----------
866    .. [1] http://poynton.ca/PDFs/ColorFAQ.pdf
867
868    Examples
869    --------
870    >>> from skimage.color import rgb2gray
871    >>> from skimage import data
872    >>> img = data.astronaut()
873    >>> img_gray = rgb2gray(img)
874    """
875    rgb = _prepare_colorarray(rgb)
876    coeffs = np.array([0.2125, 0.7154, 0.0721], dtype=rgb.dtype)
877    return rgb @ coeffs
878
879
880def gray2rgba(image, alpha=None, *, channel_axis=-1):
881    """Create a RGBA representation of a gray-level image.
882
883    Parameters
884    ----------
885    image : array_like
886        Input image.
887    alpha : array_like, optional
888        Alpha channel of the output image. It may be a scalar or an
889        array that can be broadcast to ``image``. If not specified it is
890        set to the maximum limit corresponding to the ``image`` dtype.
891    channel_axis : int, optional
892        This parameter indicates which axis of the output array will correspond
893        to channels.
894
895        .. versionadded:: 0.19
896           ``channel_axis`` was added in 0.19.
897
898    Returns
899    -------
900    rgba : ndarray
901        RGBA image. A new dimension of length 4 is added to input
902        image shape.
903    """
904
905    arr = np.asarray(image)
906
907    alpha_min, alpha_max = dtype_limits(arr, clip_negative=False)
908
909    if alpha is None:
910        alpha = alpha_max
911
912    if not np.can_cast(alpha, arr.dtype):
913        warn(f'alpha cannot be safely cast to image dtype {arr.dtype.name}',
914             stacklevel=2)
915    if np.isscalar(alpha):
916        alpha = np.full(arr.shape, alpha, dtype=arr.dtype)
917    elif alpha.shape != arr.shape:
918        raise ValueError("alpha.shape must match image.shape")
919    rgba = np.stack((arr,) * 3 + (alpha,), axis=channel_axis)
920    return rgba
921
922
923def gray2rgb(image, *, channel_axis=-1):
924    """Create an RGB representation of a gray-level image.
925
926    Parameters
927    ----------
928    image : array_like
929        Input image.
930    channel_axis : int, optional
931        This parameter indicates which axis of the output array will correspond
932        to channels.
933
934    Returns
935    -------
936    rgb : (..., 3, ...) ndarray
937        RGB image. A new dimension of length 3 is added to input image.
938
939    Notes
940    -----
941    If the input is a 1-dimensional image of shape ``(M, )``, the output
942    will be shape ``(M, 3)``.
943    """
944    return np.stack(3 * (image,), axis=channel_axis)
945
946
947@channel_as_last_axis()
948def xyz2lab(xyz, illuminant="D65", observer="2", *, channel_axis=-1):
949    """XYZ to CIE-LAB color space conversion.
950
951    Parameters
952    ----------
953    xyz : (..., 3, ...) array_like
954        The image in XYZ format. By default, the final dimension denotes
955        channels.
956    illuminant : {"A", "B", "C", "D50", "D55", "D65", "D75", "E"}, optional
957        The name of the illuminant (the function is NOT case sensitive).
958    observer : {"2", "10", "R"}, optional
959        One of: 2-degree observer, 10-degree observer, or 'R' observer as in
960        R function grDevices::convertColor.
961    channel_axis : int, optional
962        This parameter indicates which axis of the array corresponds to
963        channels.
964
965        .. versionadded:: 0.19
966           ``channel_axis`` was added in 0.19.
967
968    Returns
969    -------
970    out : (..., 3, ...) ndarray
971        The image in CIE-LAB format. Same dimensions as input.
972
973    Raises
974    ------
975    ValueError
976        If `xyz` is not at least 2-D with shape (..., 3, ...).
977    ValueError
978        If either the illuminant or the observer angle is unsupported or
979        unknown.
980
981    Notes
982    -----
983    By default Observer="2", Illuminant="D65". CIE XYZ tristimulus values
984    x_ref=95.047, y_ref=100., z_ref=108.883. See function `get_xyz_coords` for
985    a list of supported illuminants.
986
987    References
988    ----------
989    .. [1] http://www.easyrgb.com/index.php?X=MATH&H=07
990    .. [2] https://en.wikipedia.org/wiki/Lab_color_space
991
992    Examples
993    --------
994    >>> from skimage import data
995    >>> from skimage.color import rgb2xyz, xyz2lab
996    >>> img = data.astronaut()
997    >>> img_xyz = rgb2xyz(img)
998    >>> img_lab = xyz2lab(img_xyz)
999    """
1000    arr = _prepare_colorarray(xyz, channel_axis=-1)
1001
1002    xyz_ref_white = get_xyz_coords(illuminant, observer, arr.dtype)
1003
1004    # scale by CIE XYZ tristimulus values of the reference white point
1005    arr = arr / xyz_ref_white
1006
1007    # Nonlinear distortion and linear transformation
1008    mask = arr > 0.008856
1009    arr[mask] = np.cbrt(arr[mask])
1010    arr[~mask] = 7.787 * arr[~mask] + 16. / 116.
1011
1012    x, y, z = arr[..., 0], arr[..., 1], arr[..., 2]
1013
1014    # Vector scaling
1015    L = (116. * y) - 16.
1016    a = 500.0 * (x - y)
1017    b = 200.0 * (y - z)
1018
1019    return np.concatenate([x[..., np.newaxis] for x in [L, a, b]], axis=-1)
1020
1021
1022@channel_as_last_axis()
1023def lab2xyz(lab, illuminant="D65", observer="2", *, channel_axis=-1):
1024    """CIE-LAB to XYZcolor space conversion.
1025
1026    Parameters
1027    ----------
1028    lab : (..., 3, ...) array_like
1029        The image in Lab format. By default, the final dimension denotes
1030        channels.
1031    illuminant : {"A", "B", "C", "D50", "D55", "D65", "D75", "E"}, optional
1032        The name of the illuminant (the function is NOT case sensitive).
1033    observer : {"2", "10", "R"}, optional
1034        The aperture angle of the observer.
1035    channel_axis : int, optional
1036        This parameter indicates which axis of the array corresponds to
1037        channels.
1038
1039        .. versionadded:: 0.19
1040           ``channel_axis`` was added in 0.19.
1041
1042    Returns
1043    -------
1044    out : (..., 3, ...) ndarray
1045        The image in XYZ format. Same dimensions as input.
1046
1047    Raises
1048    ------
1049    ValueError
1050        If `lab` is not at least 2-D with shape (..., 3, ...).
1051    ValueError
1052        If either the illuminant or the observer angle are not supported or
1053        unknown.
1054    UserWarning
1055        If any of the pixels are invalid (Z < 0).
1056
1057    Notes
1058    -----
1059    By default Observer="2", Illuminant="D65". CIE XYZ tristimulus values x_ref
1060    = 95.047, y_ref = 100., z_ref = 108.883. See function 'get_xyz_coords' for
1061    a list of supported illuminants.
1062
1063    References
1064    ----------
1065    .. [1] http://www.easyrgb.com/index.php?X=MATH&H=07
1066    .. [2] https://en.wikipedia.org/wiki/Lab_color_space
1067    """
1068    arr = _prepare_colorarray(lab, channel_axis=-1).copy()
1069
1070    L, a, b = arr[..., 0], arr[..., 1], arr[..., 2]
1071    y = (L + 16.) / 116.
1072    x = (a / 500.) + y
1073    z = y - (b / 200.)
1074
1075    if np.any(z < 0):
1076        invalid = np.nonzero(z < 0)
1077        warn('Color data out of range: Z < 0 in %s pixels' % invalid[0].size,
1078             stacklevel=2)
1079        z[invalid] = 0
1080
1081    out = np.stack([x, y, z], axis=-1)
1082
1083    mask = out > 0.2068966
1084    out[mask] = np.power(out[mask], 3.)
1085    out[~mask] = (out[~mask] - 16.0 / 116.) / 7.787
1086
1087    # rescale to the reference white (illuminant)
1088    xyz_ref_white = get_xyz_coords(illuminant, observer)
1089    out *= xyz_ref_white
1090    return out
1091
1092
1093@channel_as_last_axis()
1094def rgb2lab(rgb, illuminant="D65", observer="2", *, channel_axis=-1):
1095    """Conversion from the sRGB color space (IEC 61966-2-1:1999)
1096    to the CIE Lab colorspace under the given illuminant and observer.
1097
1098    Parameters
1099    ----------
1100    rgb : (..., 3, ...) array_like
1101        The image in RGB format. By default, the final dimension denotes
1102        channels.
1103    illuminant : {"A", "B", "C", "D50", "D55", "D65", "D75", "E"}, optional
1104        The name of the illuminant (the function is NOT case sensitive).
1105    observer : {"2", "10", "R"}, optional
1106        The aperture angle of the observer.
1107    channel_axis : int, optional
1108        This parameter indicates which axis of the array corresponds to
1109        channels.
1110
1111        .. versionadded:: 0.19
1112           ``channel_axis`` was added in 0.19.
1113
1114    Returns
1115    -------
1116    out : (..., 3, ...) ndarray
1117        The image in Lab format. Same dimensions as input.
1118
1119    Raises
1120    ------
1121    ValueError
1122        If `rgb` is not at least 2-D with shape (..., 3, ...).
1123
1124    Notes
1125    -----
1126    RGB is a device-dependent color space so, if you use this function, be
1127    sure that the image you are analyzing has been mapped to the sRGB color
1128    space.
1129
1130    This function uses rgb2xyz and xyz2lab.
1131    By default Observer="2", Illuminant="D65". CIE XYZ tristimulus values
1132    x_ref=95.047, y_ref=100., z_ref=108.883. See function `get_xyz_coords` for
1133    a list of supported illuminants.
1134
1135    References
1136    ----------
1137    .. [1] https://en.wikipedia.org/wiki/Standard_illuminant
1138    """
1139    return xyz2lab(rgb2xyz(rgb), illuminant, observer)
1140
1141
1142@channel_as_last_axis()
1143def lab2rgb(lab, illuminant="D65", observer="2", *, channel_axis=-1):
1144    """Lab to RGB color space conversion.
1145
1146    Parameters
1147    ----------
1148    lab : (..., 3, ...) array_like
1149        The image in Lab format. By default, the final dimension denotes
1150        channels.
1151    illuminant : {"A", "B", "C", "D50", "D55", "D65", "D75", "E"}, optional
1152        The name of the illuminant (the function is NOT case sensitive).
1153    observer : {"2", "10", "R"}, optional
1154        The aperture angle of the observer.
1155    channel_axis : int, optional
1156        This parameter indicates which axis of the array corresponds to
1157        channels.
1158
1159        .. versionadded:: 0.19
1160           ``channel_axis`` was added in 0.19.
1161
1162    Returns
1163    -------
1164    out : (..., 3, ...) ndarray
1165        The image in RGB format. Same dimensions as input.
1166
1167    Raises
1168    ------
1169    ValueError
1170        If `lab` is not at least 2-D with shape (..., 3, ...).
1171
1172    Notes
1173    -----
1174    This function uses lab2xyz and xyz2rgb.
1175    By default Observer="2", Illuminant="D65". CIE XYZ tristimulus values
1176    x_ref=95.047, y_ref=100., z_ref=108.883. See function `get_xyz_coords` for
1177    a list of supported illuminants.
1178
1179    References
1180    ----------
1181    .. [1] https://en.wikipedia.org/wiki/Standard_illuminant
1182    """
1183    return xyz2rgb(lab2xyz(lab, illuminant, observer))
1184
1185
1186@channel_as_last_axis()
1187def xyz2luv(xyz, illuminant="D65", observer="2", *, channel_axis=-1):
1188    """XYZ to CIE-Luv color space conversion.
1189
1190    Parameters
1191    ----------
1192    xyz : (..., 3, ...) array_like
1193        The image in XYZ format. By default, the final dimension denotes
1194        channels.
1195    illuminant : {"A", "B", "C", "D50", "D55", "D65", "D75", "E"}, optional
1196        The name of the illuminant (the function is NOT case sensitive).
1197    observer : {"2", "10", "R"}, optional
1198        The aperture angle of the observer.
1199    channel_axis : int, optional
1200        This parameter indicates which axis of the array corresponds to
1201        channels.
1202
1203        .. versionadded:: 0.19
1204           ``channel_axis`` was added in 0.19.
1205
1206    Returns
1207    -------
1208    out : (..., 3, ...) ndarray
1209        The image in CIE-Luv format. Same dimensions as input.
1210
1211    Raises
1212    ------
1213    ValueError
1214        If `xyz` is not at least 2-D with shape (..., 3, ...).
1215    ValueError
1216        If either the illuminant or the observer angle are not supported or
1217        unknown.
1218
1219    Notes
1220    -----
1221    By default XYZ conversion weights use observer=2A. Reference whitepoint
1222    for D65 Illuminant, with XYZ tristimulus values of ``(95.047, 100.,
1223    108.883)``. See function 'get_xyz_coords' for a list of supported
1224    illuminants.
1225
1226    References
1227    ----------
1228    .. [1] http://www.easyrgb.com/index.php?X=MATH&H=16#text16
1229    .. [2] https://en.wikipedia.org/wiki/CIELUV
1230
1231    Examples
1232    --------
1233    >>> from skimage import data
1234    >>> from skimage.color import rgb2xyz, xyz2luv
1235    >>> img = data.astronaut()
1236    >>> img_xyz = rgb2xyz(img)
1237    >>> img_luv = xyz2luv(img_xyz)
1238    """
1239    input_is_one_pixel = xyz.ndim == 1
1240    if input_is_one_pixel:
1241        xyz = xyz[np.newaxis, ...]
1242
1243    arr = _prepare_colorarray(xyz, channel_axis=-1)
1244
1245    # extract channels
1246    x, y, z = arr[..., 0], arr[..., 1], arr[..., 2]
1247
1248    eps = np.finfo(float).eps
1249
1250    # compute y_r and L
1251    xyz_ref_white = np.array(get_xyz_coords(illuminant, observer))
1252    L = y / xyz_ref_white[1]
1253    mask = L > 0.008856
1254    L[mask] = 116. * np.cbrt(L[mask]) - 16.
1255    L[~mask] = 903.3 * L[~mask]
1256
1257    u0 = 4 * xyz_ref_white[0] / ([1, 15, 3] @ xyz_ref_white)
1258    v0 = 9 * xyz_ref_white[1] / ([1, 15, 3] @ xyz_ref_white)
1259
1260    # u' and v' helper functions
1261    def fu(X, Y, Z):
1262        return (4. * X) / (X + 15. * Y + 3. * Z + eps)
1263
1264    def fv(X, Y, Z):
1265        return (9. * Y) / (X + 15. * Y + 3. * Z + eps)
1266
1267    # compute u and v using helper functions
1268    u = 13. * L * (fu(x, y, z) - u0)
1269    v = 13. * L * (fv(x, y, z) - v0)
1270
1271    out = np.stack([L, u, v], axis=-1)
1272
1273    if input_is_one_pixel:
1274        out = np.squeeze(out, axis=0)
1275
1276    return out
1277
1278
1279@channel_as_last_axis()
1280def luv2xyz(luv, illuminant="D65", observer="2", *, channel_axis=-1):
1281    """CIE-Luv to XYZ color space conversion.
1282
1283    Parameters
1284    ----------
1285    luv : (..., 3, ...) array_like
1286        The image in CIE-Luv format. By default, the final dimension denotes
1287        channels.
1288    illuminant : {"A", "B", "C", "D50", "D55", "D65", "D75", "E"}, optional
1289        The name of the illuminant (the function is NOT case sensitive).
1290    observer : {"2", "10", "R"}, optional
1291        The aperture angle of the observer.
1292    channel_axis : int, optional
1293        This parameter indicates which axis of the array corresponds to
1294        channels.
1295
1296        .. versionadded:: 0.19
1297           ``channel_axis`` was added in 0.19.
1298
1299    Returns
1300    -------
1301    out : (..., 3, ...) ndarray
1302        The image in XYZ format. Same dimensions as input.
1303
1304    Raises
1305    ------
1306    ValueError
1307        If `luv` is not at least 2-D with shape (..., 3, ...).
1308    ValueError
1309        If either the illuminant or the observer angle are not supported or
1310        unknown.
1311
1312    Notes
1313    -----
1314    XYZ conversion weights use observer=2A. Reference whitepoint for D65
1315    Illuminant, with XYZ tristimulus values of ``(95.047, 100., 108.883)``. See
1316    function 'get_xyz_coords' for a list of supported illuminants.
1317
1318    References
1319    ----------
1320    .. [1] http://www.easyrgb.com/index.php?X=MATH&H=16#text16
1321    .. [2] https://en.wikipedia.org/wiki/CIELUV
1322    """
1323    arr = _prepare_colorarray(luv, channel_axis=-1).copy()
1324
1325    L, u, v = arr[..., 0], arr[..., 1], arr[..., 2]
1326
1327    eps = np.finfo(float).eps
1328
1329    # compute y
1330    y = L.copy()
1331    mask = y > 7.999625
1332    y[mask] = np.power((y[mask] + 16.) / 116., 3.)
1333    y[~mask] = y[~mask] / 903.3
1334    xyz_ref_white = get_xyz_coords(illuminant, observer)
1335    y *= xyz_ref_white[1]
1336
1337    # reference white x,z
1338    uv_weights = np.array([1, 15, 3])
1339    u0 = 4 * xyz_ref_white[0] / (uv_weights @ xyz_ref_white)
1340    v0 = 9 * xyz_ref_white[1] / (uv_weights @ xyz_ref_white)
1341
1342    # compute intermediate values
1343    a = u0 + u / (13. * L + eps)
1344    b = v0 + v / (13. * L + eps)
1345    c = 3 * y * (5 * b - 3)
1346
1347    # compute x and z
1348    z = ((a - 4) * c - 15 * a * b * y) / (12 * b)
1349    x = -(c / b + 3. * z)
1350
1351    return np.concatenate([q[..., np.newaxis] for q in [x, y, z]], axis=-1)
1352
1353
1354@channel_as_last_axis()
1355def rgb2luv(rgb, *, channel_axis=-1):
1356    """RGB to CIE-Luv color space conversion.
1357
1358    Parameters
1359    ----------
1360    rgb : (..., 3, ...) array_like
1361        The image in RGB format. By default, the final dimension denotes
1362        channels.
1363    channel_axis : int, optional
1364        This parameter indicates which axis of the array corresponds to
1365        channels.
1366
1367        .. versionadded:: 0.19
1368           ``channel_axis`` was added in 0.19.
1369
1370    Returns
1371    -------
1372    out : (..., 3, ...) ndarray
1373        The image in CIE Luv format. Same dimensions as input.
1374
1375    Raises
1376    ------
1377    ValueError
1378        If `rgb` is not at least 2-D with shape (..., 3, ...).
1379
1380    Notes
1381    -----
1382    This function uses rgb2xyz and xyz2luv.
1383
1384    References
1385    ----------
1386    .. [1] http://www.easyrgb.com/index.php?X=MATH&H=16#text16
1387    .. [2] http://www.easyrgb.com/index.php?X=MATH&H=02#text2
1388    .. [3] https://en.wikipedia.org/wiki/CIELUV
1389    """
1390    return xyz2luv(rgb2xyz(rgb))
1391
1392
1393@channel_as_last_axis()
1394def luv2rgb(luv, *, channel_axis=-1):
1395    """Luv to RGB color space conversion.
1396
1397    Parameters
1398    ----------
1399    luv : (..., 3, ...) array_like
1400        The image in CIE Luv format. By default, the final dimension denotes
1401        channels.
1402
1403    Returns
1404    -------
1405    out : (..., 3, ...) ndarray
1406        The image in RGB format. Same dimensions as input.
1407
1408    Raises
1409    ------
1410    ValueError
1411        If `luv` is not at least 2-D with shape (..., 3, ...).
1412
1413    Notes
1414    -----
1415    This function uses luv2xyz and xyz2rgb.
1416    """
1417    return xyz2rgb(luv2xyz(luv))
1418
1419
1420@channel_as_last_axis()
1421def rgb2hed(rgb, *, channel_axis=-1):
1422    """RGB to Haematoxylin-Eosin-DAB (HED) color space conversion.
1423
1424    Parameters
1425    ----------
1426    rgb : (..., 3, ...) array_like
1427        The image in RGB format. By default, the final dimension denotes
1428        channels.
1429    channel_axis : int, optional
1430        This parameter indicates which axis of the array corresponds to
1431        channels.
1432
1433        .. versionadded:: 0.19
1434           ``channel_axis`` was added in 0.19.
1435
1436    Returns
1437    -------
1438    out : (..., 3, ...) ndarray
1439        The image in HED format. Same dimensions as input.
1440
1441    Raises
1442    ------
1443    ValueError
1444        If `rgb` is not at least 2-D with shape (..., 3, ...).
1445
1446    References
1447    ----------
1448    .. [1] A. C. Ruifrok and D. A. Johnston, "Quantification of histochemical
1449           staining by color deconvolution.," Analytical and quantitative
1450           cytology and histology / the International Academy of Cytology [and]
1451           American Society of Cytology, vol. 23, no. 4, pp. 291-9, Aug. 2001.
1452
1453    Examples
1454    --------
1455    >>> from skimage import data
1456    >>> from skimage.color import rgb2hed
1457    >>> ihc = data.immunohistochemistry()
1458    >>> ihc_hed = rgb2hed(ihc)
1459    """
1460    return separate_stains(rgb, hed_from_rgb)
1461
1462
1463@channel_as_last_axis()
1464def hed2rgb(hed, *, channel_axis=-1):
1465    """Haematoxylin-Eosin-DAB (HED) to RGB color space conversion.
1466
1467    Parameters
1468    ----------
1469    hed : (..., 3, ...) array_like
1470        The image in the HED color space. By default, the final dimension
1471        denotes channels.
1472    channel_axis : int, optional
1473        This parameter indicates which axis of the array corresponds to
1474        channels.
1475
1476        .. versionadded:: 0.19
1477           ``channel_axis`` was added in 0.19.
1478
1479    Returns
1480    -------
1481    out : (..., 3, ...) ndarray
1482        The image in RGB. Same dimensions as input.
1483
1484    Raises
1485    ------
1486    ValueError
1487        If `hed` is not at least 2-D with shape (..., 3, ...).
1488
1489    References
1490    ----------
1491    .. [1] A. C. Ruifrok and D. A. Johnston, "Quantification of histochemical
1492           staining by color deconvolution.," Analytical and quantitative
1493           cytology and histology / the International Academy of Cytology [and]
1494           American Society of Cytology, vol. 23, no. 4, pp. 291-9, Aug. 2001.
1495
1496    Examples
1497    --------
1498    >>> from skimage import data
1499    >>> from skimage.color import rgb2hed, hed2rgb
1500    >>> ihc = data.immunohistochemistry()
1501    >>> ihc_hed = rgb2hed(ihc)
1502    >>> ihc_rgb = hed2rgb(ihc_hed)
1503    """
1504    return combine_stains(hed, rgb_from_hed)
1505
1506
1507@channel_as_last_axis()
1508def separate_stains(rgb, conv_matrix, *, channel_axis=-1):
1509    """RGB to stain color space conversion.
1510
1511    Parameters
1512    ----------
1513    rgb : (..., 3, ...) array_like
1514        The image in RGB format. By default, the final dimension denotes
1515        channels.
1516    conv_matrix: ndarray
1517        The stain separation matrix as described by G. Landini [1]_.
1518    channel_axis : int, optional
1519        This parameter indicates which axis of the array corresponds to
1520        channels.
1521
1522        .. versionadded:: 0.19
1523           ``channel_axis`` was added in 0.19.
1524
1525    Returns
1526    -------
1527    out : (..., 3, ...) ndarray
1528        The image in stain color space. Same dimensions as input.
1529
1530    Raises
1531    ------
1532    ValueError
1533        If `rgb` is not at least 2-D with shape (..., 3, ...).
1534
1535    Notes
1536    -----
1537    Stain separation matrices available in the ``color`` module and their
1538    respective colorspace:
1539
1540    * ``hed_from_rgb``: Hematoxylin + Eosin + DAB
1541    * ``hdx_from_rgb``: Hematoxylin + DAB
1542    * ``fgx_from_rgb``: Feulgen + Light Green
1543    * ``bex_from_rgb``: Giemsa stain : Methyl Blue + Eosin
1544    * ``rbd_from_rgb``: FastRed + FastBlue +  DAB
1545    * ``gdx_from_rgb``: Methyl Green + DAB
1546    * ``hax_from_rgb``: Hematoxylin + AEC
1547    * ``bro_from_rgb``: Blue matrix Anilline Blue + Red matrix Azocarmine\
1548                        + Orange matrix Orange-G
1549    * ``bpx_from_rgb``: Methyl Blue + Ponceau Fuchsin
1550    * ``ahx_from_rgb``: Alcian Blue + Hematoxylin
1551    * ``hpx_from_rgb``: Hematoxylin + PAS
1552
1553    This implementation borrows some ideas from DIPlib [2]_, e.g. the
1554    compensation using a small value to avoid log artifacts when
1555    calculating the Beer-Lambert law.
1556
1557    References
1558    ----------
1559    .. [1] https://web.archive.org/web/20160624145052/http://www.mecourse.com/landinig/software/cdeconv/cdeconv.html
1560    .. [2] https://github.com/DIPlib/diplib/
1561    .. [3] A. C. Ruifrok and D. A. Johnston, “Quantification of histochemical
1562           staining by color deconvolution,” Anal. Quant. Cytol. Histol., vol.
1563           23, no. 4, pp. 291–299, Aug. 2001.
1564
1565    Examples
1566    --------
1567    >>> from skimage import data
1568    >>> from skimage.color import separate_stains, hdx_from_rgb
1569    >>> ihc = data.immunohistochemistry()
1570    >>> ihc_hdx = separate_stains(ihc, hdx_from_rgb)
1571    """
1572    rgb = _prepare_colorarray(rgb, force_copy=True, channel_axis=-1)
1573    np.maximum(rgb, 1E-6, out=rgb)  # avoiding log artifacts
1574    log_adjust = np.log(1E-6)  # used to compensate the sum above
1575
1576    stains = (np.log(rgb) / log_adjust) @ conv_matrix
1577
1578    np.maximum(stains, 0, out=stains)
1579
1580    return stains
1581
1582
1583@channel_as_last_axis()
1584def combine_stains(stains, conv_matrix, *, channel_axis=-1):
1585    """Stain to RGB color space conversion.
1586
1587    Parameters
1588    ----------
1589    stains : (..., 3, ...) array_like
1590        The image in stain color space. By default, the final dimension denotes
1591        channels.
1592    conv_matrix: ndarray
1593        The stain separation matrix as described by G. Landini [1]_.
1594    channel_axis : int, optional
1595        This parameter indicates which axis of the array corresponds to
1596        channels.
1597
1598        .. versionadded:: 0.19
1599           ``channel_axis`` was added in 0.19.
1600
1601    Returns
1602    -------
1603    out : (..., 3, ...) ndarray
1604        The image in RGB format. Same dimensions as input.
1605
1606    Raises
1607    ------
1608    ValueError
1609        If `stains` is not at least 2-D with shape (..., 3, ...).
1610
1611    Notes
1612    -----
1613    Stain combination matrices available in the ``color`` module and their
1614    respective colorspace:
1615
1616    * ``rgb_from_hed``: Hematoxylin + Eosin + DAB
1617    * ``rgb_from_hdx``: Hematoxylin + DAB
1618    * ``rgb_from_fgx``: Feulgen + Light Green
1619    * ``rgb_from_bex``: Giemsa stain : Methyl Blue + Eosin
1620    * ``rgb_from_rbd``: FastRed + FastBlue +  DAB
1621    * ``rgb_from_gdx``: Methyl Green + DAB
1622    * ``rgb_from_hax``: Hematoxylin + AEC
1623    * ``rgb_from_bro``: Blue matrix Anilline Blue + Red matrix Azocarmine\
1624                        + Orange matrix Orange-G
1625    * ``rgb_from_bpx``: Methyl Blue + Ponceau Fuchsin
1626    * ``rgb_from_ahx``: Alcian Blue + Hematoxylin
1627    * ``rgb_from_hpx``: Hematoxylin + PAS
1628
1629    References
1630    ----------
1631    .. [1] https://web.archive.org/web/20160624145052/http://www.mecourse.com/landinig/software/cdeconv/cdeconv.html
1632    .. [2] A. C. Ruifrok and D. A. Johnston, “Quantification of histochemical
1633           staining by color deconvolution,” Anal. Quant. Cytol. Histol., vol.
1634           23, no. 4, pp. 291–299, Aug. 2001.
1635
1636    Examples
1637    --------
1638    >>> from skimage import data
1639    >>> from skimage.color import (separate_stains, combine_stains,
1640    ...                            hdx_from_rgb, rgb_from_hdx)
1641    >>> ihc = data.immunohistochemistry()
1642    >>> ihc_hdx = separate_stains(ihc, hdx_from_rgb)
1643    >>> ihc_rgb = combine_stains(ihc_hdx, rgb_from_hdx)
1644    """
1645    stains = _prepare_colorarray(stains, channel_axis=-1)
1646
1647    # log_adjust here is used to compensate the sum within separate_stains().
1648    log_adjust = -np.log(1E-6)
1649    log_rgb = -(stains * log_adjust) @ conv_matrix
1650    rgb = np.exp(log_rgb)
1651
1652    return np.clip(rgb, a_min=0, a_max=1)
1653
1654
1655@channel_as_last_axis()
1656def lab2lch(lab, *, channel_axis=-1):
1657    """CIE-LAB to CIE-LCH color space conversion.
1658
1659    LCH is the cylindrical representation of the LAB (Cartesian) colorspace
1660
1661    Parameters
1662    ----------
1663    lab : (..., 3, ...) array_like
1664        The N-D image in CIE-LAB format. The last (``N+1``-th) dimension must
1665        have at least 3 elements, corresponding to the ``L``, ``a``, and ``b``
1666        color channels. Subsequent elements are copied.
1667    channel_axis : int, optional
1668        This parameter indicates which axis of the array corresponds to
1669        channels.
1670
1671        .. versionadded:: 0.19
1672           ``channel_axis`` was added in 0.19.
1673
1674    Returns
1675    -------
1676    out : (..., 3, ...) ndarray
1677        The image in LCH format, in a N-D array with same shape as input `lab`.
1678
1679    Raises
1680    ------
1681    ValueError
1682        If `lch` does not have at least 3 color channels (i.e. l, a, b).
1683
1684    Notes
1685    -----
1686    The Hue is expressed as an angle between ``(0, 2*pi)``
1687
1688    Examples
1689    --------
1690    >>> from skimage import data
1691    >>> from skimage.color import rgb2lab, lab2lch
1692    >>> img = data.astronaut()
1693    >>> img_lab = rgb2lab(img)
1694    >>> img_lch = lab2lch(img_lab)
1695    """
1696    lch = _prepare_lab_array(lab)
1697
1698    a, b = lch[..., 1], lch[..., 2]
1699    lch[..., 1], lch[..., 2] = _cart2polar_2pi(a, b)
1700    return lch
1701
1702
1703def _cart2polar_2pi(x, y):
1704    """convert cartesian coordinates to polar (uses non-standard theta range!)
1705
1706    NON-STANDARD RANGE! Maps to ``(0, 2*pi)`` rather than usual ``(-pi, +pi)``
1707    """
1708    r, t = np.hypot(x, y), np.arctan2(y, x)
1709    t += np.where(t < 0., 2 * np.pi, 0)
1710    return r, t
1711
1712
1713@channel_as_last_axis()
1714def lch2lab(lch, *, channel_axis=-1):
1715    """CIE-LCH to CIE-LAB color space conversion.
1716
1717    LCH is the cylindrical representation of the LAB (Cartesian) colorspace
1718
1719    Parameters
1720    ----------
1721    lch : (..., 3, ...) array_like
1722        The N-D image in CIE-LCH format. The last (``N+1``-th) dimension must
1723        have at least 3 elements, corresponding to the ``L``, ``a``, and ``b``
1724        color channels.  Subsequent elements are copied.
1725    channel_axis : int, optional
1726        This parameter indicates which axis of the array corresponds to
1727        channels.
1728
1729        .. versionadded:: 0.19
1730           ``channel_axis`` was added in 0.19.
1731
1732    Returns
1733    -------
1734    out : (..., 3, ...) ndarray
1735        The image in LAB format, with same shape as input `lch`.
1736
1737    Raises
1738    ------
1739    ValueError
1740        If `lch` does not have at least 3 color channels (i.e. l, c, h).
1741
1742    Examples
1743    --------
1744    >>> from skimage import data
1745    >>> from skimage.color import rgb2lab, lch2lab, lab2lch
1746    >>> img = data.astronaut()
1747    >>> img_lab = rgb2lab(img)
1748    >>> img_lch = lab2lch(img_lab)
1749    >>> img_lab2 = lch2lab(img_lch)
1750    """
1751    lch = _prepare_lab_array(lch)
1752
1753    c, h = lch[..., 1], lch[..., 2]
1754    lch[..., 1], lch[..., 2] = c * np.cos(h), c * np.sin(h)
1755    return lch
1756
1757
1758def _prepare_lab_array(arr, force_copy=True):
1759    """Ensure input for lab2lch, lch2lab are well-posed.
1760
1761    Arrays must be in floating point and have at least 3 elements in
1762    last dimension.  Return a new array.
1763    """
1764    arr = np.asarray(arr)
1765    shape = arr.shape
1766    if shape[-1] < 3:
1767        raise ValueError('Input array has less than 3 color channels')
1768    float_dtype = _supported_float_type(arr.dtype)
1769    if float_dtype == np.float32:
1770        _func = dtype.img_as_float32
1771    else:
1772        _func = dtype.img_as_float64
1773    return _func(arr, force_copy=force_copy)
1774
1775
1776@channel_as_last_axis()
1777def rgb2yuv(rgb, *, channel_axis=-1):
1778    """RGB to YUV color space conversion.
1779
1780    Parameters
1781    ----------
1782    rgb : (..., 3, ...) array_like
1783        The image in RGB format. By default, the final dimension denotes
1784        channels.
1785    channel_axis : int, optional
1786        This parameter indicates which axis of the array corresponds to
1787        channels.
1788
1789        .. versionadded:: 0.19
1790           ``channel_axis`` was added in 0.19.
1791
1792    Returns
1793    -------
1794    out : (..., 3, ...) ndarray
1795        The image in YUV format. Same dimensions as input.
1796
1797    Raises
1798    ------
1799    ValueError
1800        If `rgb` is not at least 2-D with shape (..., 3, ...).
1801
1802    Notes
1803    -----
1804    Y is between 0 and 1.  Use YCbCr instead of YUV for the color space
1805    commonly used by video codecs, where Y ranges from 16 to 235.
1806
1807    References
1808    ----------
1809    .. [1] https://en.wikipedia.org/wiki/YUV
1810    """
1811    return _convert(yuv_from_rgb, rgb)
1812
1813
1814@channel_as_last_axis()
1815def rgb2yiq(rgb, *, channel_axis=-1):
1816    """RGB to YIQ color space conversion.
1817
1818    Parameters
1819    ----------
1820    rgb : (..., 3, ...) array_like
1821        The image in RGB format. By default, the final dimension denotes
1822        channels.
1823    channel_axis : int, optional
1824        This parameter indicates which axis of the array corresponds to
1825        channels.
1826
1827        .. versionadded:: 0.19
1828           ``channel_axis`` was added in 0.19.
1829
1830    Returns
1831    -------
1832    out : (..., 3, ...) ndarray
1833        The image in YIQ format. Same dimensions as input.
1834
1835    Raises
1836    ------
1837    ValueError
1838        If `rgb` is not at least 2-D with shape (..., 3, ...).
1839    """
1840    return _convert(yiq_from_rgb, rgb)
1841
1842
1843@channel_as_last_axis()
1844def rgb2ypbpr(rgb, *, channel_axis=-1):
1845    """RGB to YPbPr color space conversion.
1846
1847    Parameters
1848    ----------
1849    rgb : (..., 3, ...) array_like
1850        The image in RGB format. By default, the final dimension denotes
1851        channels.
1852    channel_axis : int, optional
1853        This parameter indicates which axis of the array corresponds to
1854        channels.
1855
1856        .. versionadded:: 0.19
1857           ``channel_axis`` was added in 0.19.
1858
1859    Returns
1860    -------
1861    out : (..., 3, ...) ndarray
1862        The image in YPbPr format. Same dimensions as input.
1863
1864    Raises
1865    ------
1866    ValueError
1867        If `rgb` is not at least 2-D with shape (..., 3, ...).
1868
1869    References
1870    ----------
1871    .. [1] https://en.wikipedia.org/wiki/YPbPr
1872    """
1873    return _convert(ypbpr_from_rgb, rgb)
1874
1875
1876@channel_as_last_axis()
1877def rgb2ycbcr(rgb, *, channel_axis=-1):
1878    """RGB to YCbCr color space conversion.
1879
1880    Parameters
1881    ----------
1882    rgb : (..., 3, ...) array_like
1883        The image in RGB format. By default, the final dimension denotes
1884        channels.
1885    channel_axis : int, optional
1886        This parameter indicates which axis of the array corresponds to
1887        channels.
1888
1889        .. versionadded:: 0.19
1890           ``channel_axis`` was added in 0.19.
1891
1892    Returns
1893    -------
1894    out : (..., 3, ...) ndarray
1895        The image in YCbCr format. Same dimensions as input.
1896
1897    Raises
1898    ------
1899    ValueError
1900        If `rgb` is not at least 2-D with shape (..., 3, ...).
1901
1902    Notes
1903    -----
1904    Y is between 16 and 235. This is the color space commonly used by video
1905    codecs; it is sometimes incorrectly called "YUV".
1906
1907    References
1908    ----------
1909    .. [1] https://en.wikipedia.org/wiki/YCbCr
1910    """
1911    arr = _convert(ycbcr_from_rgb, rgb)
1912    arr[..., 0] += 16
1913    arr[..., 1] += 128
1914    arr[..., 2] += 128
1915    return arr
1916
1917
1918@channel_as_last_axis()
1919def rgb2ydbdr(rgb, *, channel_axis=-1):
1920    """RGB to YDbDr color space conversion.
1921
1922    Parameters
1923    ----------
1924    rgb : (..., 3, ...) array_like
1925        The image in RGB format. By default, the final dimension denotes
1926        channels.
1927    channel_axis : int, optional
1928        This parameter indicates which axis of the array corresponds to
1929        channels.
1930
1931        .. versionadded:: 0.19
1932           ``channel_axis`` was added in 0.19.
1933
1934    Returns
1935    -------
1936    out : (..., 3, ...) ndarray
1937        The image in YDbDr format. Same dimensions as input.
1938
1939    Raises
1940    ------
1941    ValueError
1942        If `rgb` is not at least 2-D with shape (..., 3, ...).
1943
1944    Notes
1945    -----
1946    This is the color space commonly used by video codecs. It is also the
1947    reversible color transform in JPEG2000.
1948
1949    References
1950    ----------
1951    .. [1] https://en.wikipedia.org/wiki/YDbDr
1952    """
1953    arr = _convert(ydbdr_from_rgb, rgb)
1954    return arr
1955
1956
1957@channel_as_last_axis()
1958def yuv2rgb(yuv, *, channel_axis=-1):
1959    """YUV to RGB color space conversion.
1960
1961    Parameters
1962    ----------
1963    yuv : (..., 3, ...) array_like
1964        The image in YUV format. By default, the final dimension denotes
1965        channels.
1966
1967    Returns
1968    -------
1969    out : (..., 3, ...) ndarray
1970        The image in RGB format. Same dimensions as input.
1971
1972    Raises
1973    ------
1974    ValueError
1975        If `yuv` is not at least 2-D with shape (..., 3, ...).
1976
1977    References
1978    ----------
1979    .. [1] https://en.wikipedia.org/wiki/YUV
1980    """
1981    return _convert(rgb_from_yuv, yuv)
1982
1983
1984@channel_as_last_axis()
1985def yiq2rgb(yiq, *, channel_axis=-1):
1986    """YIQ to RGB color space conversion.
1987
1988    Parameters
1989    ----------
1990    yiq : (..., 3, ...) array_like
1991        The image in YIQ format. By default, the final dimension denotes
1992        channels.
1993    channel_axis : int, optional
1994        This parameter indicates which axis of the array corresponds to
1995        channels.
1996
1997        .. versionadded:: 0.19
1998           ``channel_axis`` was added in 0.19.
1999
2000    Returns
2001    -------
2002    out : (..., 3, ...) ndarray
2003        The image in RGB format. Same dimensions as input.
2004
2005    Raises
2006    ------
2007    ValueError
2008        If `yiq` is not at least 2-D with shape (..., 3, ...).
2009    """
2010    return _convert(rgb_from_yiq, yiq)
2011
2012
2013@channel_as_last_axis()
2014def ypbpr2rgb(ypbpr, *, channel_axis=-1):
2015    """YPbPr to RGB color space conversion.
2016
2017    Parameters
2018    ----------
2019    ypbpr : (..., 3, ...) array_like
2020        The image in YPbPr format. By default, the final dimension denotes
2021        channels.
2022    channel_axis : int, optional
2023        This parameter indicates which axis of the array corresponds to
2024        channels.
2025
2026        .. versionadded:: 0.19
2027           ``channel_axis`` was added in 0.19.
2028
2029    Returns
2030    -------
2031    out : (..., 3, ...) ndarray
2032        The image in RGB format. Same dimensions as input.
2033
2034    Raises
2035    ------
2036    ValueError
2037        If `ypbpr` is not at least 2-D with shape (..., 3, ...).
2038
2039    References
2040    ----------
2041    .. [1] https://en.wikipedia.org/wiki/YPbPr
2042    """
2043    return _convert(rgb_from_ypbpr, ypbpr)
2044
2045
2046@channel_as_last_axis()
2047def ycbcr2rgb(ycbcr, *, channel_axis=-1):
2048    """YCbCr to RGB color space conversion.
2049
2050    Parameters
2051    ----------
2052    ycbcr : (..., 3, ...) array_like
2053        The image in YCbCr format. By default, the final dimension denotes
2054        channels.
2055    channel_axis : int, optional
2056        This parameter indicates which axis of the array corresponds to
2057        channels.
2058
2059        .. versionadded:: 0.19
2060           ``channel_axis`` was added in 0.19.
2061
2062    Returns
2063    -------
2064    out : (..., 3, ...) ndarray
2065        The image in RGB format. Same dimensions as input.
2066
2067    Raises
2068    ------
2069    ValueError
2070        If `ycbcr` is not at least 2-D with shape (..., 3, ...).
2071
2072    Notes
2073    -----
2074    Y is between 16 and 235. This is the color space commonly used by video
2075    codecs; it is sometimes incorrectly called "YUV".
2076
2077    References
2078    ----------
2079    .. [1] https://en.wikipedia.org/wiki/YCbCr
2080    """
2081    arr = ycbcr.copy()
2082    arr[..., 0] -= 16
2083    arr[..., 1] -= 128
2084    arr[..., 2] -= 128
2085    return _convert(rgb_from_ycbcr, arr)
2086
2087
2088@channel_as_last_axis()
2089def ydbdr2rgb(ydbdr, *, channel_axis=-1):
2090    """YDbDr to RGB color space conversion.
2091
2092    Parameters
2093    ----------
2094    ydbdr : (..., 3, ...) array_like
2095        The image in YDbDr format. By default, the final dimension denotes
2096        channels.
2097    channel_axis : int, optional
2098        This parameter indicates which axis of the array corresponds to
2099        channels.
2100
2101        .. versionadded:: 0.19
2102           ``channel_axis`` was added in 0.19.
2103
2104    Returns
2105    -------
2106    out : (..., 3, ...) ndarray
2107        The image in RGB format. Same dimensions as input.
2108
2109    Raises
2110    ------
2111    ValueError
2112        If `ydbdr` is not at least 2-D with shape (..., 3, ...).
2113
2114    Notes
2115    -----
2116    This is the color space commonly used by video codecs, also called the
2117    reversible color transform in JPEG2000.
2118
2119    References
2120    ----------
2121    .. [1] https://en.wikipedia.org/wiki/YDbDr
2122    """
2123    return _convert(rgb_from_ydbdr, ydbdr)
2124