Coverage for models/rgb/cylindrical.py: 84%
120 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-16 22:49 +1300
1"""
2Cylindrical & Spherical Colour Models
3=====================================
5Define various cylindrical and spherical colour models:
7- :func:`colour.RGB_to_HSV`
8- :func:`colour.HSV_to_RGB`
9- :func:`colour.RGB_to_HSL`
10- :func:`colour.HSL_to_RGB`
11- :func:`colour.RGB_to_HCL`
12- :func:`colour.HCL_to_RGB`
14These colour models prioritise computational efficiency over perceptual
15uniformity. While unsuitable for rigorous colour science applications, they
16serve effectively in image analysis workflows and provide intuitive colour
17selection interfaces for end-user applications.
19These transformations are included for practical utility and comprehensive
20coverage of colour space conversions.
22References
23----------
24- :cite:`EasyRGBj` : EasyRGB. (n.d.). RGB --> HSV. Retrieved May 18, 2014,
25 from http://www.easyrgb.com/index.php?X=MATH&H=20#text20
26- :cite:`EasyRGBk` : EasyRGB. (n.d.). HSL --> RGB. Retrieved May 18, 2014,
27 from http://www.easyrgb.com/index.php?X=MATH&H=19#text19
28- :cite:`EasyRGBl` : EasyRGB. (n.d.). RGB --> HSL. Retrieved May 18, 2014,
29 from http://www.easyrgb.com/index.php?X=MATH&H=18#text18
30- :cite:`EasyRGBn` : EasyRGB. (n.d.). HSV --> RGB. Retrieved May 18, 2014,
31 from http://www.easyrgb.com/index.php?X=MATH&H=21#text21
32- :cite:`Smith1978b` : Smith, A. R. (1978). Color gamut transform pairs.
33 Proceedings of the 5th Annual Conference on Computer Graphics and
34 Interactive Techniques - SIGGRAPH "78, 12-19. doi:10.1145/800248.807361
35- :cite:`Wikipedia2003` : Wikipedia. (2003). HSL and HSV. Retrieved
36 September 10, 2014, from http://en.wikipedia.org/wiki/HSL_and_HSV
37- :cite:`Sarifuddin2005` : Sarifuddin, M., & Missaoui, R. (2005). A New
38 Perceptually Uniform Color Space with Associated Color Similarity Measure
39 for ContentBased Image and Video Retrieval.
40- :cite:`Sarifuddin2005a` : Sarifuddin, M., & Missaoui, R. (2005). HCL: a new
41 Color Space for a more Effective Content-based Image Retrieval.
42 http://w3.uqo.ca/missaoui/Publications/TRColorSpace.zip
43- :cite:`Sarifuddin2021` : Sarifuddin, M. (2021). RGB to HCL and HCL to RGB
44 color conversion (1.0.0). https://www.mathworks.com/matlabcentral/\
45fileexchange/100878-rgb-to-hcl-and-hcl-to-rgb-color-conversion
46- :cite:`Wikipedia2015` : Wikipedia. (2015). HCL color space. Retrieved
47 April 4, 2021, from https://en.wikipedia.org/wiki/HCL_color_space
48"""
50from __future__ import annotations
52import typing
54import numpy as np
56from colour.algebra import sdiv, sdiv_mode
58if typing.TYPE_CHECKING:
59 from colour.hints import Domain1, NDArrayFloat, Range1
61from colour.hints import ArrayLike, cast
62from colour.utilities import as_float_array, from_range_1, to_domain_1, tsplit, tstack
64__author__ = "Colour Developers"
65__copyright__ = "Copyright 2013 Colour Developers"
66__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
67__maintainer__ = "Colour Developers"
68__email__ = "colour-developers@colour-science.org"
69__status__ = "Production"
71__all__ = [
72 "RGB_to_HSV",
73 "HSV_to_RGB",
74 "RGB_to_HSL",
75 "HSL_to_RGB",
76 "RGB_to_HCL",
77 "HCL_to_RGB",
78]
81def RGB_to_HSV(RGB: Domain1) -> Range1:
82 """
83 Convert from *RGB* colourspace to *HSV* colourspace.
85 Parameters
86 ----------
87 RGB
88 *RGB* colourspace array.
90 Returns
91 -------
92 :class:`numpy.ndarray`
93 *HSV* colourspace array.
95 Notes
96 -----
97 +------------+-----------------------+---------------+
98 | **Domain** | **Scale - Reference** | **Scale - 1** |
99 +============+=======================+===============+
100 | ``RGB`` | 1 | 1 |
101 +------------+-----------------------+---------------+
103 +------------+-----------------------+---------------+
104 | **Range** | **Scale - Reference** | **Scale - 1** |
105 +============+=======================+===============+
106 | ``HSV`` | 1 | 1 |
107 +------------+-----------------------+---------------+
109 References
110 ----------
111 :cite:`EasyRGBj`, :cite:`Smith1978b`, :cite:`Wikipedia2003`
113 Examples
114 --------
115 >>> RGB = np.array([0.45620519, 0.03081071, 0.04091952])
116 >>> RGB_to_HSV(RGB) # doctest: +ELLIPSIS
117 array([ 0.9960394..., 0.9324630..., 0.4562051...])
118 """
120 RGB = to_domain_1(RGB)
122 maximum = np.amax(RGB, -1)
123 delta = np.ptp(RGB, -1)
125 V = maximum
127 R, G, B = tsplit(RGB)
129 with sdiv_mode():
130 S = sdiv(delta, maximum)
132 delta_R = sdiv(((maximum - R) / 6) + (delta / 2), delta)
133 delta_G = sdiv(((maximum - G) / 6) + (delta / 2), delta)
134 delta_B = sdiv(((maximum - B) / 6) + (delta / 2), delta)
136 H = delta_B - delta_G
137 H = np.where(maximum == G, (1 / 3) + delta_R - delta_B, H)
138 H = np.where(maximum == B, (2 / 3) + delta_G - delta_R, H)
139 H[np.asarray(H < 0)] += 1
140 H[np.asarray(H > 1)] -= 1
141 H[np.asarray(delta == 0)] = 0
143 HSV = tstack([H, S, V])
145 return from_range_1(HSV)
148def HSV_to_RGB(HSV: Domain1) -> Range1:
149 """
150 Convert from *HSV* colourspace to *RGB* colourspace.
152 Parameters
153 ----------
154 HSV
155 *HSV* colourspace array.
157 Returns
158 -------
159 :class:`numpy.ndarray`
160 *RGB* colourspace array.
162 Notes
163 -----
164 +------------+-----------------------+---------------+
165 | **Domain** | **Scale - Reference** | **Scale - 1** |
166 +============+=======================+===============+
167 | ``HSV`` | 1 | 1 |
168 +------------+-----------------------+---------------+
170 +------------+-----------------------+---------------+
171 | **Range** | **Scale - Reference** | **Scale - 1** |
172 +============+=======================+===============+
173 | ``RGB`` | 1 | 1 |
174 +------------+-----------------------+---------------+
176 References
177 ----------
178 :cite:`EasyRGBn`, :cite:`Smith1978b`, :cite:`Wikipedia2003`
180 Examples
181 --------
182 >>> HSV = np.array([0.99603944, 0.93246304, 0.45620519])
183 >>> HSV_to_RGB(HSV) # doctest: +ELLIPSIS
184 array([ 0.4562051..., 0.0308107..., 0.0409195...])
185 """
187 H, S, V = tsplit(to_domain_1(HSV))
189 h = as_float_array(H * 6)
190 h[np.asarray(h == 6)] = 0
192 i = np.floor(h)
193 j = V * (1 - S)
194 k = V * (1 - S * (h - i))
195 l = V * (1 - S * (1 - (h - i))) # noqa: E741
197 i = tstack([i, i, i]).astype(np.uint8)
199 RGB = np.choose(
200 i,
201 [
202 tstack([V, l, j]),
203 tstack([k, V, j]),
204 tstack([j, V, l]),
205 tstack([j, k, V]),
206 tstack([l, j, V]),
207 tstack([V, j, k]),
208 ],
209 mode="clip",
210 )
212 return from_range_1(RGB)
215def RGB_to_HSL(RGB: Domain1) -> Range1:
216 """
217 Convert from *RGB* colourspace to *HSL* colourspace.
219 Parameters
220 ----------
221 RGB
222 *RGB* colourspace array.
224 Returns
225 -------
226 :class:`numpy.ndarray`
227 *HSL* array.
229 Notes
230 -----
231 +------------+-----------------------+---------------+
232 | **Domain** | **Scale - Reference** | **Scale - 1** |
233 +============+=======================+===============+
234 | ``RGB`` | 1 | 1 |
235 +------------+-----------------------+---------------+
237 +------------+-----------------------+---------------+
238 | **Range** | **Scale - Reference** | **Scale - 1** |
239 +============+=======================+===============+
240 | ``HSL`` | 1 | 1 |
241 +------------+-----------------------+---------------+
243 References
244 ----------
245 :cite:`EasyRGBl`, :cite:`Smith1978b`, :cite:`Wikipedia2003`
247 Examples
248 --------
249 >>> RGB = np.array([0.45620519, 0.03081071, 0.04091952])
250 >>> RGB_to_HSL(RGB) # doctest: +ELLIPSIS
251 array([ 0.9960394..., 0.8734714..., 0.2435079...])
252 """
254 RGB = to_domain_1(RGB)
256 minimum = np.amin(RGB, -1)
257 maximum = np.amax(RGB, -1)
258 delta = np.ptp(RGB, -1)
260 R, G, B = tsplit(RGB)
262 L = (maximum + minimum) / 2
264 with sdiv_mode():
265 S = np.where(
266 L < 0.5,
267 sdiv(delta, maximum + minimum),
268 sdiv(delta, 2 - maximum - minimum),
269 )
271 delta_R = sdiv(((maximum - R) / 6) + (delta / 2), delta)
272 delta_G = sdiv(((maximum - G) / 6) + (delta / 2), delta)
273 delta_B = sdiv(((maximum - B) / 6) + (delta / 2), delta)
275 H = delta_B - delta_G
276 H = np.where(maximum == G, (1 / 3) + delta_R - delta_B, H)
277 H = np.where(maximum == B, (2 / 3) + delta_G - delta_R, H)
278 H[np.asarray(H < 0)] += 1
279 H[np.asarray(H > 1)] -= 1
280 H[np.asarray(delta == 0)] = 0
282 HSL = tstack([H, S, L])
284 return from_range_1(HSL)
287def HSL_to_RGB(HSL: Domain1) -> Range1:
288 """
289 Convert from *HSL* colourspace to *RGB* colourspace.
291 Parameters
292 ----------
293 HSL
294 *HSL* colourspace array.
296 Returns
297 -------
298 :class:`numpy.ndarray`
299 *RGB* colourspace array.
301 Notes
302 -----
303 +------------+-----------------------+---------------+
304 | **Domain** | **Scale - Reference** | **Scale - 1** |
305 +============+=======================+===============+
306 | ``HSL`` | 1 | 1 |
307 +------------+-----------------------+---------------+
309 +------------+-----------------------+---------------+
310 | **Range** | **Scale - Reference** | **Scale - 1** |
311 +============+=======================+===============+
312 | ``RGB`` | 1 | 1 |
313 +------------+-----------------------+---------------+
315 References
316 ----------
317 :cite:`EasyRGBk`, :cite:`Smith1978b`, :cite:`Wikipedia2003`
319 Examples
320 --------
321 >>> HSL = np.array([0.99603944, 0.87347144, 0.24350795])
322 >>> HSL_to_RGB(HSL) # doctest: +ELLIPSIS
323 array([ 0.4562051..., 0.0308107..., 0.0409195...])
324 """
326 H, S, L = tsplit(to_domain_1(HSL))
328 def H_to_RGB(vi: NDArrayFloat, vj: NDArrayFloat, vH: NDArrayFloat) -> NDArrayFloat:
329 """Convert *hue* value to *RGB* colourspace."""
331 vH = as_float_array(vH)
333 vH[np.asarray(vH < 0)] += 1
334 vH[np.asarray(vH > 1)] -= 1
336 v = np.where(
337 6 * vH < 1,
338 vi + (vj - vi) * 6 * vH,
339 np.nan,
340 )
341 v = np.where(np.logical_and(2 * vH < 1, np.isnan(v)), vj, v)
342 v = np.where(
343 np.logical_and(3 * vH < 2, np.isnan(v)),
344 vi + (vj - vi) * ((2 / 3) - vH) * 6,
345 v,
346 )
347 return np.where(np.isnan(v), vi, v)
349 j = np.where(L < 0.5, L * (1 + S), (L + S) - (S * L))
350 i = 2 * L - j
352 R = H_to_RGB(i, j, H + (1 / 3))
353 G = H_to_RGB(i, j, H)
354 B = H_to_RGB(i, j, H - (1 / 3))
356 R = np.where(S == 0, L, R)
357 G = np.where(S == 0, L, G)
358 B = np.where(S == 0, L, B)
360 RGB = tstack([R, G, B])
362 return from_range_1(RGB)
365def RGB_to_HCL(RGB: Domain1, gamma: float = 3, Y_0: float = 100) -> Range1:
366 """
367 Convert from *RGB* colourspace to *HCL* colourspace according to
368 *Sarifuddin and Missaoui (2005)* method.
370 Parameters
371 ----------
372 RGB
373 *RGB* colourspace array.
374 gamma
375 Non-linear lightness exponent matching *Lightness* :math:`L^*`.
376 Y_0
377 White reference luminance :math:`Y_0`.
379 Returns
380 -------
381 :class:`numpy.ndarray`
382 *HCL* array.
384 Notes
385 -----
386 +------------+-----------------------+---------------+
387 | **Domain** | **Scale - Reference** | **Scale - 1** |
388 +============+=======================+===============+
389 | ``RGB`` | 1 | 1 |
390 +------------+-----------------------+---------------+
392 +------------+-----------------------+---------------+
393 | **Range** | **Scale - Reference** | **Scale - 1** |
394 +============+=======================+===============+
395 | ``HCL`` | 1 | 1 |
396 +------------+-----------------------+---------------+
398 - This implementation uses the equations specified in
399 :cite:`Sarifuddin2005a` with the corrections from
400 :cite:`Sarifuddin2021`.
402 References
403 ----------
404 :cite:`Sarifuddin2005`, :cite:`Sarifuddin2005a`, :cite:`Wikipedia2015`
406 Examples
407 --------
408 >>> RGB = np.array([0.45620519, 0.03081071, 0.04091952])
409 >>> RGB_to_HCL(RGB) # doctest: +ELLIPSIS
410 array([-0.0316785..., 0.2841715..., 0.2285964...])
411 """
413 R, G, B = tsplit(to_domain_1(RGB))
415 Min = np.minimum(np.minimum(R, G), B)
416 Max = np.maximum(np.maximum(R, G), B)
418 with sdiv_mode():
419 Q = np.exp(sdiv(Min * gamma, Max * Y_0))
421 L = (Q * Max + (Q - 1) * Min) / 2
423 R_G = R - G
424 G_B = G - B
425 B_R = B - R
427 C = Q * (np.abs(R_G) + np.abs(G_B) + np.abs(B_R)) / 3
429 with sdiv_mode("Ignore"):
430 H = np.arctan(sdiv(G_B, R_G))
432 _2_H_3 = 2 * H / 3
433 _4_H_3 = 4 * H / 3
435 H = np.select(
436 [
437 C == 0,
438 np.logical_and(R_G >= 0, G_B >= 0),
439 np.logical_and(R_G >= 0, G_B < 0),
440 np.logical_and(R_G < 0, G_B >= 0),
441 np.logical_and(R_G < 0, G_B < 0),
442 ],
443 [
444 0,
445 _2_H_3,
446 _4_H_3,
447 np.pi + _4_H_3,
448 _2_H_3 - np.pi,
449 ],
450 )
452 HCL = tstack([H, C, L])
454 return from_range_1(HCL)
457def HCL_to_RGB(HCL: Domain1, gamma: float = 3, Y_0: float = 100) -> Range1:
458 """
459 Convert from *HCL* colourspace to *RGB* colourspace according to
460 *Sarifuddin and Missaoui (2005)* method.
462 Parameters
463 ----------
464 HCL
465 *HCL* colourspace array.
466 gamma
467 Non-linear lightness exponent matching *Lightness* :math:`L^*`.
468 Y_0
469 White reference luminance :math:`Y_0`.
471 Returns
472 -------
473 :class:`numpy.ndarray`
474 *RGB* colourspace array.
476 Notes
477 -----
478 +------------+-----------------------+---------------+
479 | **Domain** | **Scale - Reference** | **Scale - 1** |
480 +============+=======================+===============+
481 | ``HCL`` | 1 | 1 |
482 +------------+-----------------------+---------------+
484 +------------+-----------------------+---------------+
485 | **Range** | **Scale - Reference** | **Scale - 1** |
486 +============+=======================+===============+
487 | ``RGB`` | 1 | 1 |
488 +------------+-----------------------+---------------+
490 - This implementation uses the equations specified in
491 :cite:`Sarifuddin2005a` with the corrections from
492 :cite:`Sarifuddin2021`.
494 References
495 ----------
496 :cite:`Sarifuddin2005`, :cite:`Sarifuddin2005a`, :cite:`Wikipedia2015`
498 Examples
499 --------
500 >>> HCL = np.array([-0.03167854, 0.28417150, 0.22859647])
501 >>> HCL_to_RGB(HCL) # doctest: +ELLIPSIS
502 array([ 0.4562033..., 0.0308104..., 0.0409192...])
503 """
505 H, C, L = tsplit(to_domain_1(HCL))
507 with sdiv_mode():
508 Q = np.exp((1 - sdiv(3 * C, 4 * L)) * gamma / Y_0)
510 Min = sdiv(4 * L - 3 * C, 4 * Q - 2)
511 Max = Min + sdiv(3 * C, 2 * Q)
513 tan_3_2_H = np.tan(3 / 2 * H)
514 tan_3_4_H_MP = np.tan(3 / 4 * (H - np.pi))
515 tan_3_4_H = np.tan(3 / 4 * H)
516 tan_3_2_H_PP = np.tan(3 / 2 * (H + np.pi))
518 r_p60 = np.radians(60)
519 r_p120 = np.radians(120)
520 r_n60 = np.radians(-60)
521 r_n120 = np.radians(-120)
523 def _1_2_3(a: ArrayLike) -> NDArrayFloat:
524 """Tail-stack specified :math:`a` array as a *bool* dtype."""
526 return tstack(cast("ArrayLike", [a, a, a]), dtype=np.bool_)
528 with sdiv_mode():
529 RGB = np.select(
530 [
531 _1_2_3(np.logical_and(H >= 0, r_p60 >= H)),
532 _1_2_3(np.logical_and(r_p60 < H, r_p120 >= H)),
533 _1_2_3(np.logical_and(r_p120 < H, np.pi >= H)),
534 _1_2_3(np.logical_and(r_n60 <= H, H < 0)),
535 _1_2_3(np.logical_and(r_n120 <= H, r_n60 > H)),
536 _1_2_3(np.logical_and(-np.pi < H, r_n120 > H)),
537 ],
538 [
539 tstack(
540 [
541 Max,
542 (Max * tan_3_2_H + Min) / (1 + tan_3_2_H),
543 Min,
544 ]
545 ),
546 tstack(
547 [
548 sdiv(Max * (1 + tan_3_4_H_MP) - Min, tan_3_4_H_MP),
549 Max,
550 Min,
551 ]
552 ),
553 tstack(
554 [
555 Min,
556 Max,
557 Max * (1 + tan_3_4_H_MP) - Min * tan_3_4_H_MP,
558 ]
559 ),
560 tstack(
561 [
562 Max,
563 Min,
564 Min * (1 + tan_3_4_H) - Max * tan_3_4_H,
565 ]
566 ),
567 tstack(
568 [
569 sdiv(Min * (1 + tan_3_4_H) - Max, tan_3_4_H),
570 Min,
571 Max,
572 ]
573 ),
574 tstack(
575 [
576 Min,
577 (Min * tan_3_2_H_PP + Max) / (1 + tan_3_2_H_PP),
578 Max,
579 ]
580 ),
581 ],
582 )
584 return from_range_1(RGB)