Source code for colour.models.rgb.cylindrical

"""
Cylindrical & Spherical Colour Models
=====================================

Define various cylindrical and spherical colour models:

-   :func:`colour.RGB_to_HSV`
-   :func:`colour.HSV_to_RGB`
-   :func:`colour.RGB_to_HSL`
-   :func:`colour.HSL_to_RGB`
-   :func:`colour.RGB_to_HCL`
-   :func:`colour.HCL_to_RGB`

These colour models prioritise computational efficiency over perceptual
uniformity. While unsuitable for rigorous colour science applications, they
serve effectively in image analysis workflows and provide intuitive colour
selection interfaces for end-user applications.

These transformations are included for practical utility and comprehensive
coverage of colour space conversions.

References
----------
-   :cite:`EasyRGBj` : EasyRGB. (n.d.). RGB --> HSV. Retrieved May 18, 2014,
    from http://www.easyrgb.com/index.php?X=MATH&H=20#text20
-   :cite:`EasyRGBk` : EasyRGB. (n.d.). HSL --> RGB. Retrieved May 18, 2014,
    from http://www.easyrgb.com/index.php?X=MATH&H=19#text19
-   :cite:`EasyRGBl` : EasyRGB. (n.d.). RGB --> HSL. Retrieved May 18, 2014,
    from http://www.easyrgb.com/index.php?X=MATH&H=18#text18
-   :cite:`EasyRGBn` : EasyRGB. (n.d.). HSV --> RGB. Retrieved May 18, 2014,
    from http://www.easyrgb.com/index.php?X=MATH&H=21#text21
-   :cite:`Smith1978b` : Smith, A. R. (1978). Color gamut transform pairs.
    Proceedings of the 5th Annual Conference on Computer Graphics and
    Interactive Techniques - SIGGRAPH "78, 12-19. doi:10.1145/800248.807361
-   :cite:`Wikipedia2003` : Wikipedia. (2003). HSL and HSV. Retrieved
    September 10, 2014, from http://en.wikipedia.org/wiki/HSL_and_HSV
-   :cite:`Sarifuddin2005` : Sarifuddin, M., & Missaoui, R. (2005). A New
    Perceptually Uniform Color Space with Associated Color Similarity Measure
    for ContentBased Image and Video Retrieval.
-   :cite:`Sarifuddin2005a` : Sarifuddin, M., & Missaoui, R. (2005). HCL: a new
    Color Space for a more Effective Content-based Image Retrieval.
    http://w3.uqo.ca/missaoui/Publications/TRColorSpace.zip
-   :cite:`Sarifuddin2021` : Sarifuddin, M. (2021). RGB to HCL and HCL to RGB
    color conversion (1.0.0). https://www.mathworks.com/matlabcentral/\
fileexchange/100878-rgb-to-hcl-and-hcl-to-rgb-color-conversion
-   :cite:`Wikipedia2015` : Wikipedia. (2015). HCL color space. Retrieved
    April 4, 2021, from https://en.wikipedia.org/wiki/HCL_color_space
"""

from __future__ import annotations

import typing

import numpy as np

from colour.algebra import sdiv, sdiv_mode

if typing.TYPE_CHECKING:
    from colour.hints import Domain1, NDArrayBoolean, NDArrayFloat, Range1

from colour.hints import ArrayLike, cast
from colour.utilities import as_float_array, from_range_1, to_domain_1, tsplit, tstack

__author__ = "Colour Developers"
__copyright__ = "Copyright 2013 Colour Developers"
__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
__maintainer__ = "Colour Developers"
__email__ = "colour-developers@colour-science.org"
__status__ = "Production"

__all__ = [
    "RGB_to_HSV",
    "HSV_to_RGB",
    "RGB_to_HSL",
    "HSL_to_RGB",
    "RGB_to_HCL",
    "HCL_to_RGB",
]


[docs] def RGB_to_HSV(RGB: Domain1) -> Range1: """ Convert from *RGB* colourspace to *HSV* colourspace. Parameters ---------- RGB *RGB* colourspace array. Returns ------- :class:`numpy.ndarray` *HSV* colourspace array. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``RGB`` | 1 | 1 | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``HSV`` | 1 | 1 | +------------+-----------------------+---------------+ References ---------- :cite:`EasyRGBj`, :cite:`Smith1978b`, :cite:`Wikipedia2003` Examples -------- >>> RGB = np.array([0.45620519, 0.03081071, 0.04091952]) >>> RGB_to_HSV(RGB) # doctest: +ELLIPSIS array([ 0.9960394..., 0.9324630..., 0.4562051...]) """ RGB = to_domain_1(RGB) maximum = np.amax(RGB, -1) delta = np.ptp(RGB, -1) V = maximum R, G, B = tsplit(RGB) with sdiv_mode(): S = sdiv(delta, maximum) delta_R = sdiv(((maximum - R) / 6) + (delta / 2), delta) delta_G = sdiv(((maximum - G) / 6) + (delta / 2), delta) delta_B = sdiv(((maximum - B) / 6) + (delta / 2), delta) H = delta_B - delta_G H = np.where(maximum == G, (1 / 3) + delta_R - delta_B, H) H = np.where(maximum == B, (2 / 3) + delta_G - delta_R, H) H[np.asarray(H < 0)] += 1 H[np.asarray(H > 1)] -= 1 H[np.asarray(delta == 0)] = 0 HSV = tstack([H, S, V]) return from_range_1(HSV)
[docs] def HSV_to_RGB(HSV: Domain1) -> Range1: """ Convert from *HSV* colourspace to *RGB* colourspace. Parameters ---------- HSV *HSV* colourspace array. Returns ------- :class:`numpy.ndarray` *RGB* colourspace array. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``HSV`` | 1 | 1 | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``RGB`` | 1 | 1 | +------------+-----------------------+---------------+ References ---------- :cite:`EasyRGBn`, :cite:`Smith1978b`, :cite:`Wikipedia2003` Examples -------- >>> HSV = np.array([0.99603944, 0.93246304, 0.45620519]) >>> HSV_to_RGB(HSV) # doctest: +ELLIPSIS array([ 0.4562051..., 0.0308107..., 0.0409195...]) """ H, S, V = tsplit(to_domain_1(HSV)) h = as_float_array(H * 6) h[np.asarray(h == 6)] = 0 i = np.floor(h) j = V * (1 - S) k = V * (1 - S * (h - i)) l = V * (1 - S * (1 - (h - i))) # noqa: E741 i = tstack([i, i, i]).astype(np.uint8) RGB = np.choose( i, [ tstack([V, l, j]), tstack([k, V, j]), tstack([j, V, l]), tstack([j, k, V]), tstack([l, j, V]), tstack([V, j, k]), ], mode="clip", ) return from_range_1(RGB)
[docs] def RGB_to_HSL(RGB: Domain1) -> Range1: """ Convert from *RGB* colourspace to *HSL* colourspace. Parameters ---------- RGB *RGB* colourspace array. Returns ------- :class:`numpy.ndarray` *HSL* array. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``RGB`` | 1 | 1 | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``HSL`` | 1 | 1 | +------------+-----------------------+---------------+ References ---------- :cite:`EasyRGBl`, :cite:`Smith1978b`, :cite:`Wikipedia2003` Examples -------- >>> RGB = np.array([0.45620519, 0.03081071, 0.04091952]) >>> RGB_to_HSL(RGB) # doctest: +ELLIPSIS array([ 0.9960394..., 0.8734714..., 0.2435079...]) """ RGB = to_domain_1(RGB) minimum = np.amin(RGB, -1) maximum = np.amax(RGB, -1) delta = np.ptp(RGB, -1) R, G, B = tsplit(RGB) L = (maximum + minimum) / 2 with sdiv_mode(): S = np.where( L < 0.5, sdiv(delta, maximum + minimum), sdiv(delta, 2 - maximum - minimum), ) delta_R = sdiv(((maximum - R) / 6) + (delta / 2), delta) delta_G = sdiv(((maximum - G) / 6) + (delta / 2), delta) delta_B = sdiv(((maximum - B) / 6) + (delta / 2), delta) H = delta_B - delta_G H = np.where(maximum == G, (1 / 3) + delta_R - delta_B, H) H = np.where(maximum == B, (2 / 3) + delta_G - delta_R, H) H[np.asarray(H < 0)] += 1 H[np.asarray(H > 1)] -= 1 H[np.asarray(delta == 0)] = 0 HSL = tstack([H, S, L]) return from_range_1(HSL)
[docs] def HSL_to_RGB(HSL: Domain1) -> Range1: """ Convert from *HSL* colourspace to *RGB* colourspace. Parameters ---------- HSL *HSL* colourspace array. Returns ------- :class:`numpy.ndarray` *RGB* colourspace array. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``HSL`` | 1 | 1 | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``RGB`` | 1 | 1 | +------------+-----------------------+---------------+ References ---------- :cite:`EasyRGBk`, :cite:`Smith1978b`, :cite:`Wikipedia2003` Examples -------- >>> HSL = np.array([0.99603944, 0.87347144, 0.24350795]) >>> HSL_to_RGB(HSL) # doctest: +ELLIPSIS array([ 0.4562051..., 0.0308107..., 0.0409195...]) """ H, S, L = tsplit(to_domain_1(HSL)) def H_to_RGB(vi: NDArrayFloat, vj: NDArrayFloat, vH: NDArrayFloat) -> NDArrayFloat: """Convert *hue* value to *RGB* colourspace.""" vH = as_float_array(vH) vH[np.asarray(vH < 0)] += 1 vH[np.asarray(vH > 1)] -= 1 v = np.where( 6 * vH < 1, vi + (vj - vi) * 6 * vH, np.nan, ) v = np.where(np.logical_and(2 * vH < 1, np.isnan(v)), vj, v) v = np.where( np.logical_and(3 * vH < 2, np.isnan(v)), vi + (vj - vi) * ((2 / 3) - vH) * 6, v, ) return np.where(np.isnan(v), vi, v) j = np.where(L < 0.5, L * (1 + S), (L + S) - (S * L)) i = 2 * L - j R = H_to_RGB(i, j, H + (1 / 3)) G = H_to_RGB(i, j, H) B = H_to_RGB(i, j, H - (1 / 3)) R = np.where(S == 0, L, R) G = np.where(S == 0, L, G) B = np.where(S == 0, L, B) RGB = tstack([R, G, B]) return from_range_1(RGB)
[docs] def RGB_to_HCL(RGB: Domain1, gamma: float = 3, Y_0: float = 100) -> Range1: """ Convert from *RGB* colourspace to *HCL* colourspace according to *Sarifuddin and Missaoui (2005)* method. Parameters ---------- RGB *RGB* colourspace array. gamma Non-linear lightness exponent matching *Lightness* :math:`L^*`. Y_0 White reference luminance :math:`Y_0`. Returns ------- :class:`numpy.ndarray` *HCL* array. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``RGB`` | 1 | 1 | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``HCL`` | 1 | 1 | +------------+-----------------------+---------------+ - This implementation uses the equations specified in :cite:`Sarifuddin2005a` with the corrections from :cite:`Sarifuddin2021`. References ---------- :cite:`Sarifuddin2005`, :cite:`Sarifuddin2005a`, :cite:`Wikipedia2015` Examples -------- >>> RGB = np.array([0.45620519, 0.03081071, 0.04091952]) >>> RGB_to_HCL(RGB) # doctest: +ELLIPSIS array([-0.0316785..., 0.2841715..., 0.2285964...]) """ R, G, B = tsplit(to_domain_1(RGB)) Min = np.minimum(np.minimum(R, G), B) Max = np.maximum(np.maximum(R, G), B) with sdiv_mode(): Q = np.exp(sdiv(Min * gamma, Max * Y_0)) L = (Q * Max + (Q - 1) * Min) / 2 R_G = R - G G_B = G - B B_R = B - R C = Q * (np.abs(R_G) + np.abs(G_B) + np.abs(B_R)) / 3 with sdiv_mode("Ignore"): H = np.arctan(sdiv(G_B, R_G)) _2_H_3 = 2 * H / 3 _4_H_3 = 4 * H / 3 H = np.select( [ C == 0, np.logical_and(R_G >= 0, G_B >= 0), np.logical_and(R_G >= 0, G_B < 0), np.logical_and(R_G < 0, G_B >= 0), np.logical_and(R_G < 0, G_B < 0), ], [ 0, _2_H_3, _4_H_3, np.pi + _4_H_3, _2_H_3 - np.pi, ], ) HCL = tstack([H, C, L]) return from_range_1(HCL)
[docs] def HCL_to_RGB(HCL: Domain1, gamma: float = 3, Y_0: float = 100) -> Range1: """ Convert from *HCL* colourspace to *RGB* colourspace according to *Sarifuddin and Missaoui (2005)* method. Parameters ---------- HCL *HCL* colourspace array. gamma Non-linear lightness exponent matching *Lightness* :math:`L^*`. Y_0 White reference luminance :math:`Y_0`. Returns ------- :class:`numpy.ndarray` *RGB* colourspace array. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``HCL`` | 1 | 1 | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``RGB`` | 1 | 1 | +------------+-----------------------+---------------+ - This implementation uses the equations specified in :cite:`Sarifuddin2005a` with the corrections from :cite:`Sarifuddin2021`. References ---------- :cite:`Sarifuddin2005`, :cite:`Sarifuddin2005a`, :cite:`Wikipedia2015` Examples -------- >>> HCL = np.array([-0.03167854, 0.28417150, 0.22859647]) >>> HCL_to_RGB(HCL) # doctest: +ELLIPSIS array([ 0.4562033..., 0.0308104..., 0.0409192...]) """ H, C, L = tsplit(to_domain_1(HCL)) with sdiv_mode(): Q = np.exp((1 - sdiv(3 * C, 4 * L)) * gamma / Y_0) Min = sdiv(4 * L - 3 * C, 4 * Q - 2) Max = Min + sdiv(3 * C, 2 * Q) tan_3_2_H = np.tan(3 / 2 * H) tan_3_4_H_MP = np.tan(3 / 4 * (H - np.pi)) tan_3_4_H = np.tan(3 / 4 * H) tan_3_2_H_PP = np.tan(3 / 2 * (H + np.pi)) r_p60 = np.radians(60) r_p120 = np.radians(120) r_n60 = np.radians(-60) r_n120 = np.radians(-120) def _1_2_3(a: ArrayLike) -> NDArrayBoolean: """Tail-stack specified :math:`a` array as a *bool* dtype.""" return tstack(cast("ArrayLike", [a, a, a]), dtype=np.bool_) with sdiv_mode(): RGB = np.select( [ _1_2_3(np.logical_and(H >= 0, r_p60 >= H)), _1_2_3(np.logical_and(r_p60 < H, r_p120 >= H)), _1_2_3(np.logical_and(r_p120 < H, np.pi >= H)), _1_2_3(np.logical_and(r_n60 <= H, H < 0)), _1_2_3(np.logical_and(r_n120 <= H, r_n60 > H)), _1_2_3(np.logical_and(-np.pi < H, r_n120 > H)), ], [ tstack( [ Max, (Max * tan_3_2_H + Min) / (1 + tan_3_2_H), Min, ] ), tstack( [ sdiv(Max * (1 + tan_3_4_H_MP) - Min, tan_3_4_H_MP), Max, Min, ] ), tstack( [ Min, Max, Max * (1 + tan_3_4_H_MP) - Min * tan_3_4_H_MP, ] ), tstack( [ Max, Min, Min * (1 + tan_3_4_H) - Max * tan_3_4_H, ] ), tstack( [ sdiv(Min * (1 + tan_3_4_H) - Max, tan_3_4_H), Min, Max, ] ), tstack( [ Min, (Min * tan_3_2_H_PP + Max) / (1 + tan_3_2_H_PP), Max, ] ), ], ) return from_range_1(RGB)