Coverage for colour/graph/conversion.py: 100%
181 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
1"""
2Automatic Colour Conversion Graph
3=================================
5Define the automatic colour conversion graph objects.
7- :func:`colour.conversion_path`
8- :func:`colour.describe_conversion_path`
9- :func:`colour.convert`
10"""
12from __future__ import annotations
14import inspect
15import itertools
16import re
17import sys
18import textwrap
19import typing
20from copy import copy
21from dataclasses import dataclass
22from functools import partial
23from pprint import pformat
25import numpy as np
27import colour
28import colour.models
29from colour.appearance import (
30 CAM16_to_XYZ,
31 CAM_Specification_CAM16,
32 CAM_Specification_CIECAM02,
33 CAM_Specification_CIECAM16,
34 CAM_Specification_Hellwig2022,
35 CAM_Specification_Kim2009,
36 CAM_Specification_sCAM,
37 CAM_Specification_ZCAM,
38 CIECAM02_to_XYZ,
39 CIECAM16_to_XYZ,
40 Hellwig2022_to_XYZ,
41 Kim2009_to_XYZ,
42 XYZ_to_ATD95,
43 XYZ_to_CAM16,
44 XYZ_to_CIECAM02,
45 XYZ_to_CIECAM16,
46 XYZ_to_Hellwig2022,
47 XYZ_to_Hunt,
48 XYZ_to_Kim2009,
49 XYZ_to_LLAB,
50 XYZ_to_Nayatani95,
51 XYZ_to_RLAB,
52 XYZ_to_sCAM,
53 XYZ_to_ZCAM,
54 ZCAM_to_XYZ,
55 sCAM_to_XYZ,
56)
57from colour.appearance.ciecam02 import CAM_KWARGS_CIECAM02_sRGB
58from colour.colorimetry import (
59 CCS_ILLUMINANTS,
60 TVS_ILLUMINANTS_HUNTERLAB,
61 colorimetric_purity,
62 complementary_wavelength,
63 dominant_wavelength,
64 excitation_purity,
65 lightness,
66 luminance,
67 luminous_efficacy,
68 luminous_efficiency,
69 luminous_flux,
70 sd_to_XYZ,
71 wavelength_to_XYZ,
72 whiteness,
73 yellowness,
74)
76if typing.TYPE_CHECKING:
77 from colour.hints import (
78 Any,
79 ArrayLike,
80 Callable,
81 Domain1,
82 Domain100_100_360,
83 List,
84 Literal,
85 Range1,
86 Range100_100_360,
87 )
89from colour.hints import Annotated, NDArrayFloat, cast
90from colour.models import (
91 COLOURSPACE_MODELS_POLAR_CONVERSIONS,
92 CAM02LCD_to_JMh_CIECAM02,
93 CAM02SCD_to_JMh_CIECAM02,
94 CAM02UCS_to_JMh_CIECAM02,
95 CAM16LCD_to_JMh_CAM16,
96 CAM16SCD_to_JMh_CAM16,
97 CAM16UCS_to_JMh_CAM16,
98 CIE1960UCS_to_XYZ,
99 CIE1976UCS_to_XYZ,
100 CMY_to_CMYK,
101 CMY_to_RGB,
102 CMYK_to_CMY,
103 DIN99_to_XYZ,
104 HCL_to_RGB,
105 HSL_to_RGB,
106 HSV_to_RGB,
107 Hunter_Lab_to_XYZ,
108 Hunter_Rdab_to_XYZ,
109 ICaCb_to_XYZ,
110 ICtCp_to_XYZ,
111 IgPgTg_to_XYZ,
112 IHLS_to_RGB,
113 IPT_Ragoo2021_to_XYZ,
114 IPT_to_XYZ,
115 JMh_CAM16_to_CAM16LCD,
116 JMh_CAM16_to_CAM16SCD,
117 JMh_CAM16_to_CAM16UCS,
118 JMh_CIECAM02_to_CAM02LCD,
119 JMh_CIECAM02_to_CAM02SCD,
120 JMh_CIECAM02_to_CAM02UCS,
121 Jzazbz_to_XYZ,
122 Lab_to_XYZ,
123 Luv_to_uv,
124 Luv_to_XYZ,
125 Luv_uv_to_xy,
126 Oklab_to_XYZ,
127 OSA_UCS_to_XYZ,
128 Prismatic_to_RGB,
129 ProLab_to_XYZ,
130 RGB_Colourspace,
131 RGB_COLOURSPACE_sRGB,
132 RGB_luminance,
133 RGB_to_CMY,
134 RGB_to_HCL,
135 RGB_to_HSL,
136 RGB_to_HSV,
137 RGB_to_IHLS,
138 RGB_to_Prismatic,
139 RGB_to_RGB,
140 RGB_to_XYZ,
141 RGB_to_YCbCr,
142 RGB_to_YcCbcCrc,
143 RGB_to_YCoCg,
144 UCS_to_uv,
145 UCS_to_XYZ,
146 UCS_uv_to_xy,
147 UVW_to_XYZ,
148 XYZ_to_CIE1960UCS,
149 XYZ_to_CIE1976UCS,
150 XYZ_to_DIN99,
151 XYZ_to_hdr_CIELab,
152 XYZ_to_hdr_IPT,
153 XYZ_to_Hunter_Lab,
154 XYZ_to_Hunter_Rdab,
155 XYZ_to_ICaCb,
156 XYZ_to_ICtCp,
157 XYZ_to_IgPgTg,
158 XYZ_to_IPT,
159 XYZ_to_IPT_Ragoo2021,
160 XYZ_to_Jzazbz,
161 XYZ_to_Lab,
162 XYZ_to_Luv,
163 XYZ_to_Oklab,
164 XYZ_to_OSA_UCS,
165 XYZ_to_ProLab,
166 XYZ_to_RGB,
167 XYZ_to_sRGB,
168 XYZ_to_sUCS,
169 XYZ_to_UCS,
170 XYZ_to_UVW,
171 XYZ_to_xy,
172 XYZ_to_xyY,
173 XYZ_to_Yrg,
174 YCbCr_to_RGB,
175 YcCbcCrc_to_RGB,
176 YCoCg_to_RGB,
177 Yrg_to_XYZ,
178 cctf_decoding,
179 cctf_encoding,
180 hdr_CIELab_to_XYZ,
181 hdr_IPT_to_XYZ,
182 sRGB_to_XYZ,
183 sUCS_to_XYZ,
184 uv_to_Luv,
185 uv_to_UCS,
186 xy_to_Luv_uv,
187 xy_to_UCS_uv,
188 xy_to_xyY,
189 xy_to_XYZ,
190 xyY_to_xy,
191 xyY_to_XYZ,
192)
193from colour.notation import (
194 HEX_to_RGB,
195 RGB_to_HEX,
196 keyword_to_RGB_CSSColor3,
197 munsell_colour_to_xyY,
198 munsell_value,
199 xyY_to_munsell_colour,
200)
201from colour.quality import colour_quality_scale, colour_rendering_index
202from colour.recovery import XYZ_to_sd
203from colour.temperature import CCT_to_mired, CCT_to_uv, mired_to_CCT, uv_to_CCT
204from colour.utilities import (
205 as_float_array,
206 domain_range_scale,
207 filter_kwargs,
208 get_domain_range_scale_metadata,
209 message_box,
210 required,
211 tsplit,
212 tstack,
213 validate_method,
214 zeros,
215)
217__author__ = "Colour Developers"
218__copyright__ = "Copyright 2013 Colour Developers"
219__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
220__maintainer__ = "Colour Developers"
221__email__ = "colour-developers@colour-science.org"
222__status__ = "Production"
224__all__ = [
225 "Conversion_Specification",
226 "CIECAM02_to_JMh_CIECAM02",
227 "JMh_CIECAM02_to_CIECAM02",
228 "CAM16_to_JMh_CAM16",
229 "JMh_CAM16_to_CAM16",
230 "CIECAM16_to_JMh_CIECAM16",
231 "JMh_CIECAM16_to_CIECAM16",
232 "Hellwig2022_to_JMh_Hellwig2022",
233 "JMh_Hellwig2022_to_Hellwig2022",
234 "sCAM_to_JMh_sCAM",
235 "JMh_sCAM_to_sCAM",
236 "ZCAM_to_JMh_ZCAM",
237 "JMh_ZCAM_to_ZCAM",
238 "Kim2009_to_JMh_Kim2009",
239 "JMh_Kim2009_to_Kim2009",
240 "XYZ_to_luminance",
241 "RGB_luminance_to_RGB",
242 "CCT_D_uv_to_mired",
243 "mired_to_CCT_D_uv",
244 "CONVERSION_SPECIFICATIONS_DATA",
245 "CONVERSION_GRAPH_NODE_LABELS",
246 "CONVERSION_SPECIFICATIONS",
247 "CONVERSION_GRAPH",
248 "conversion_path",
249 "describe_conversion_path",
250 "convert",
251]
254@dataclass(frozen=True)
255class Conversion_Specification:
256 """
257 Define a conversion specification for the *Colour* graph used in automatic
258 colour space conversions.
260 The specification describes the relationship between two nodes (colour
261 spaces or representations) and the transformation function that connects
262 them within the conversion graph.
264 Parameters
265 ----------
266 source
267 Source node in the graph.
268 target
269 Target node in the graph.
270 conversion_function
271 Callable converting from the ``source`` node to the ``target`` node.
272 """
274 source: str
275 target: str
276 conversion_function: Callable
278 def __post_init__(self) -> None:
279 """
280 Post-initialise the class.
282 Convert the source and target attribute values to lowercase for
283 consistent case-insensitive comparisons.
284 """
286 object.__setattr__(self, "source", self.source.lower())
287 object.__setattr__(self, "target", self.target.lower())
290def CIECAM02_to_JMh_CIECAM02(
291 specification: Annotated[
292 CAM_Specification_CIECAM02, (100, 100, 360, 100, 100, 100, 400)
293 ],
294) -> Range100_100_360:
295 """
296 Convert from *CIECAM02* specification to *CIECAM02* :math:`JMh`
297 correlates.
299 Parameters
300 ----------
301 specification
302 *CIECAM02* colour appearance model specification.
304 Returns
305 -------
306 :class:`numpy.ndarray`
307 *CIECAM02* :math:`JMh` correlates.
309 Notes
310 -----
311 +---------------------+-----------------------+-----------------+
312 | **Domain** | **Scale - Reference** | **Scale - 1** |
313 +=====================+=======================+=================+
314 | ``specification.J`` | 100 | 1 |
315 +---------------------+-----------------------+-----------------+
316 | ``specification.C`` | 100 | 1 |
317 +---------------------+-----------------------+-----------------+
318 | ``specification.h`` | 360 | 1 |
319 +---------------------+-----------------------+-----------------+
320 | ``specification.s`` | 100 | 1 |
321 +---------------------+-----------------------+-----------------+
322 | ``specification.Q`` | 100 | 1 |
323 +---------------------+-----------------------+-----------------+
324 | ``specification.M`` | 100 | 1 |
325 +---------------------+-----------------------+-----------------+
326 | ``specification.H`` | 400 | 1 |
327 +---------------------+-----------------------+-----------------+
329 +---------------------+-----------------------+-----------------+
330 | **Range** | **Scale - Reference** | **Scale - 1** |
331 +=====================+=======================+=================+
332 | ``JMh`` | ``J`` : 100 | ``J`` : 1 |
333 | | | |
334 | | ``M`` : 100 | ``M`` : 1 |
335 | | | |
336 | | ``h`` : 360 | ``h`` : 1 |
337 +---------------------+-----------------------+-----------------+
339 Examples
340 --------
341 >>> specification = CAM_Specification_CIECAM02(
342 ... J=34.434525727859, M=70.024939419291385, h=22.279164147957076
343 ... )
344 >>> CIECAM02_to_JMh_CIECAM02(specification) # doctest: +ELLIPSIS
345 array([ 34.4345257..., 70.0249394..., 22.2791641...])
346 """
348 return tstack(
349 [
350 cast("NDArrayFloat", specification.J),
351 cast("NDArrayFloat", specification.M),
352 cast("NDArrayFloat", specification.h),
353 ]
354 )
357def JMh_CIECAM02_to_CIECAM02(
358 JMh: Domain100_100_360,
359) -> Annotated[CAM_Specification_CIECAM02, (100, 100, 360, 100, 100, 100, 400)]:
360 """
361 Convert from *CIECAM02* :math:`JMh` correlates to *CIECAM02*
362 specification.
364 Parameters
365 ----------
366 JMh
367 *CIECAM02* :math:`JMh` correlates.
369 Returns
370 -------
371 :class:`colour.CAM_Specification_CIECAM02`
372 *CIECAM02* colour appearance model specification.
374 Notes
375 -----
376 +---------------------+-----------------------+-----------------+
377 | **Domain** | **Scale - Reference** | **Scale - 1** |
378 +=====================+=======================+=================+
379 | ``JMh`` | ``J`` : 100 | ``J`` : 1 |
380 | | | |
381 | | ``M`` : 100 | ``M`` : 1 |
382 | | | |
383 | | ``h`` : 360 | ``h`` : 1 |
384 +---------------------+-----------------------+-----------------+
386 +---------------------+-----------------------+-----------------+
387 | **Range** | **Scale - Reference** | **Scale - 1** |
388 +=====================+=======================+=================+
389 | ``specification.J`` | 100 | 1 |
390 +---------------------+-----------------------+-----------------+
391 | ``specification.C`` | 100 | 1 |
392 +---------------------+-----------------------+-----------------+
393 | ``specification.h`` | 360 | 1 |
394 +---------------------+-----------------------+-----------------+
395 | ``specification.s`` | 100 | 1 |
396 +---------------------+-----------------------+-----------------+
397 | ``specification.Q`` | 100 | 1 |
398 +---------------------+-----------------------+-----------------+
399 | ``specification.M`` | 100 | 1 |
400 +---------------------+-----------------------+-----------------+
401 | ``specification.H`` | 400 | 1 |
402 +---------------------+-----------------------+-----------------+
404 Examples
405 --------
406 >>> import numpy as np
407 >>> JMh = np.array([34.4345257, 70.0249394, 22.2791641])
408 >>> JMh_CIECAM02_to_CIECAM02(JMh) # doctest: +ELLIPSIS
409 CAM_Specification_CIECAM02(J=34.4345257..., C=None, h=22.2791640..., \
410s=None, Q=None, M=70.0249393..., H=None, HC=None)
411 """
413 J, M, h = tsplit(JMh)
415 return CAM_Specification_CIECAM02(J=J, M=M, h=h)
418def CAM16_to_JMh_CAM16(
419 specification: Annotated[
420 CAM_Specification_CAM16, (100, 100, 360, 100, 100, 100, 400)
421 ],
422) -> Range100_100_360:
423 """
424 Convert from *CAM16* specification to *CAM16* :math:`JMh` correlates.
426 Parameters
427 ----------
428 specification
429 *CAM16* colour appearance model specification.
431 Returns
432 -------
433 :class:`numpy.ndarray`
434 *CAM16* :math:`JMh` correlates.
436 Notes
437 -----
438 +---------------------+-----------------------+-----------------+
439 | **Domain** | **Scale - Reference** | **Scale - 1** |
440 +=====================+=======================+=================+
441 | ``specification.J`` | 100 | 1 |
442 +---------------------+-----------------------+-----------------+
443 | ``specification.C`` | 100 | 1 |
444 +---------------------+-----------------------+-----------------+
445 | ``specification.h`` | 360 | 1 |
446 +---------------------+-----------------------+-----------------+
447 | ``specification.s`` | 100 | 1 |
448 +---------------------+-----------------------+-----------------+
449 | ``specification.Q`` | 100 | 1 |
450 +---------------------+-----------------------+-----------------+
451 | ``specification.M`` | 100 | 1 |
452 +---------------------+-----------------------+-----------------+
453 | ``specification.H`` | 400 | 1 |
454 +---------------------+-----------------------+-----------------+
456 +---------------------+-----------------------+-----------------+
457 | **Range** | **Scale - Reference** | **Scale - 1** |
458 +=====================+=======================+=================+
459 | ``JMh`` | ``J`` : 100 | ``J`` : 1 |
460 | | | |
461 | | ``M`` : 100 | ``M`` : 1 |
462 | | | |
463 | | ``h`` : 360 | ``h`` : 1 |
464 +---------------------+-----------------------+-----------------+
466 Examples
467 --------
468 >>> specification = CAM_Specification_CAM16(
469 ... J=33.880368498111686, M=72.18638534116765, h=19.510887327451748
470 ... )
471 >>> CAM16_to_JMh_CAM16(specification) # doctest: +ELLIPSIS
472 array([ 33.8803685 , 72.18638534, 19.51088733])
473 """
475 return tstack([specification.J, specification.M, specification.h]) # pyright: ignore
478def JMh_CAM16_to_CAM16(
479 JMh: Domain100_100_360,
480) -> Annotated[CAM_Specification_CAM16, (100, 100, 360, 100, 100, 100, 400)]:
481 """
482 Convert from *CAM16* :math:`JMh` correlates to *CAM16* specification.
484 Parameters
485 ----------
486 JMh
487 *CAM16* :math:`JMh` correlates.
489 Returns
490 -------
491 :class:`colour.CAM_Specification_CAM16`
492 *CAM16* colour appearance model specification.
494 Notes
495 -----
496 +---------------------+-----------------------+-----------------+
497 | **Domain** | **Scale - Reference** | **Scale - 1** |
498 +=====================+=======================+=================+
499 | ``JMh`` | ``J`` : 100 | ``J`` : 1 |
500 | | | |
501 | | ``M`` : 100 | ``M`` : 1 |
502 | | | |
503 | | ``h`` : 360 | ``h`` : 1 |
504 +---------------------+-----------------------+-----------------+
506 +---------------------+-----------------------+-----------------+
507 | **Range** | **Scale - Reference** | **Scale - 1** |
508 +=====================+=======================+=================+
509 | ``specification.J`` | 100 | 1 |
510 +---------------------+-----------------------+-----------------+
511 | ``specification.C`` | 100 | 1 |
512 +---------------------+-----------------------+-----------------+
513 | ``specification.h`` | 360 | 1 |
514 +---------------------+-----------------------+-----------------+
515 | ``specification.s`` | 100 | 1 |
516 +---------------------+-----------------------+-----------------+
517 | ``specification.Q`` | 100 | 1 |
518 +---------------------+-----------------------+-----------------+
519 | ``specification.M`` | 100 | 1 |
520 +---------------------+-----------------------+-----------------+
521 | ``specification.H`` | 400 | 1 |
522 +---------------------+-----------------------+-----------------+
524 Examples
525 --------
526 >>> import numpy as np
527 >>> JMh = np.array([33.8803685, 72.1863853, 19.5108873])
528 >>> JMh_CAM16_to_CAM16(JMh) # doctest: +ELLIPSIS
529 CAM_Specification_CAM16(J=33.8803685..., C=None, h=19.5108873, s=None, \
530Q=None, M=72.1863852..., H=None, HC=None)
531 """
533 J, M, h = tsplit(JMh)
535 return CAM_Specification_CAM16(J=J, M=M, h=h)
538def CIECAM16_to_JMh_CIECAM16(
539 specification: Annotated[
540 CAM_Specification_CIECAM16, (100, 100, 360, 100, 100, 100, 400)
541 ],
542) -> Range100_100_360:
543 """
544 Convert from *CIECAM16* specification to *CIECAM16* :math:`JMh`
545 correlates.
547 Parameters
548 ----------
549 specification
550 *CIECAM16* colour appearance model specification.
552 Returns
553 -------
554 :class:`numpy.ndarray`
555 *CIECAM16* :math:`JMh` correlates.
557 Notes
558 -----
559 +---------------------+-----------------------+-----------------+
560 | **Domain** | **Scale - Reference** | **Scale - 1** |
561 +=====================+=======================+=================+
562 | ``specification.J`` | 100 | 1 |
563 +---------------------+-----------------------+-----------------+
564 | ``specification.C`` | 100 | 1 |
565 +---------------------+-----------------------+-----------------+
566 | ``specification.h`` | 360 | 1 |
567 +---------------------+-----------------------+-----------------+
568 | ``specification.s`` | 100 | 1 |
569 +---------------------+-----------------------+-----------------+
570 | ``specification.Q`` | 100 | 1 |
571 +---------------------+-----------------------+-----------------+
572 | ``specification.M`` | 100 | 1 |
573 +---------------------+-----------------------+-----------------+
574 | ``specification.H`` | 400 | 1 |
575 +---------------------+-----------------------+-----------------+
577 +---------------------+-----------------------+-----------------+
578 | **Range** | **Scale - Reference** | **Scale - 1** |
579 +=====================+=======================+=================+
580 | ``JMh`` | ``J`` : 100 | ``J`` : 1 |
581 | | | |
582 | | ``M`` : 100 | ``M`` : 1 |
583 | | | |
584 | | ``h`` : 360 | ``h`` : 1 |
585 +---------------------+-----------------------+-----------------+
587 Examples
588 --------
589 >>> specification = CAM_Specification_CIECAM16(
590 ... J=33.880368498111686, M=72.18638534116765, h=19.510887327451748
591 ... )
592 >>> CIECAM16_to_JMh_CIECAM16(specification) # doctest: +ELLIPSIS
593 array([ 33.8803685 , 72.18638534, 19.51088733])
594 """
596 return tstack([specification.J, specification.M, specification.h]) # pyright: ignore
599def JMh_CIECAM16_to_CIECAM16(
600 JMh: Domain100_100_360,
601) -> Annotated[CAM_Specification_CIECAM16, (100, 100, 360, 100, 100, 100, 400)]:
602 """
603 Convert from *CIECAM16* :math:`JMh` correlates to *CIECAM16*
604 specification.
606 Parameters
607 ----------
608 JMh
609 *CIECAM16* :math:`JMh` correlates.
611 Returns
612 -------
613 :class:`colour.CAM_Specification_CIECAM16`
614 *CIECAM16* colour appearance model specification.
616 Notes
617 -----
618 +---------------------+-----------------------+-----------------+
619 | **Domain** | **Scale - Reference** | **Scale - 1** |
620 +=====================+=======================+=================+
621 | ``JMh`` | ``J`` : 100 | ``J`` : 1 |
622 | | | |
623 | | ``M`` : 100 | ``M`` : 1 |
624 | | | |
625 | | ``h`` : 360 | ``h`` : 1 |
626 +---------------------+-----------------------+-----------------+
628 +---------------------+-----------------------+-----------------+
629 | **Range** | **Scale - Reference** | **Scale - 1** |
630 +=====================+=======================+=================+
631 | ``specification.J`` | 100 | 1 |
632 +---------------------+-----------------------+-----------------+
633 | ``specification.C`` | 100 | 1 |
634 +---------------------+-----------------------+-----------------+
635 | ``specification.h`` | 360 | 1 |
636 +---------------------+-----------------------+-----------------+
637 | ``specification.s`` | 100 | 1 |
638 +---------------------+-----------------------+-----------------+
639 | ``specification.Q`` | 100 | 1 |
640 +---------------------+-----------------------+-----------------+
641 | ``specification.M`` | 100 | 1 |
642 +---------------------+-----------------------+-----------------+
643 | ``specification.H`` | 400 | 1 |
644 +---------------------+-----------------------+-----------------+
646 Examples
647 --------
648 >>> import numpy as np
649 >>> JMh = np.array([33.8803685, 72.1863853, 19.5108873])
650 >>> JMh_CIECAM16_to_CIECAM16(JMh) # doctest: +ELLIPSIS
651 CAM_Specification_CIECAM16(J=33.8803685..., C=None, h=19.5108873, \
652s=None, Q=None, M=72.1863852..., H=None, HC=None)
653 """
655 J, M, h = tsplit(JMh)
657 return CAM_Specification_CIECAM16(J=J, M=M, h=h)
660def Hellwig2022_to_JMh_Hellwig2022(
661 specification: Annotated[
662 CAM_Specification_Hellwig2022, (100, 100, 360, 100, 100, 100, 400, 100, 100)
663 ],
664) -> Range100_100_360:
665 """
666 Convert from *Hellwig and Fairchild (2022)* specification to
667 *Hellwig and Fairchild (2022)* :math:`JMh` correlates.
669 Parameters
670 ----------
671 specification
672 *Hellwig and Fairchild (2022)* colour appearance model
673 specification.
675 Returns
676 -------
677 :class:`numpy.ndarray`
678 *Hellwig and Fairchild (2022)* :math:`JMh` correlates.
680 Notes
681 -----
682 +-------------------------+-----------------------+-----------------+
683 | **Domain** | **Scale - Reference** | **Scale - 1** |
684 +=========================+=======================+=================+
685 | ``specification.J`` | 100 | 1 |
686 +-------------------------+-----------------------+-----------------+
687 | ``specification.C`` | 100 | 1 |
688 +-------------------------+-----------------------+-----------------+
689 | ``specification.h`` | 360 | 1 |
690 +-------------------------+-----------------------+-----------------+
691 | ``specification.s`` | 100 | 1 |
692 +-------------------------+-----------------------+-----------------+
693 | ``specification.Q`` | 100 | 1 |
694 +-------------------------+-----------------------+-----------------+
695 | ``specification.M`` | 100 | 1 |
696 +-------------------------+-----------------------+-----------------+
697 | ``specification.H`` | 400 | 1 |
698 +-------------------------+-----------------------+-----------------+
699 | ``specification.HC`` | 100 | 1 |
700 +-------------------------+-----------------------+-----------------+
701 | ``specification.J_HK`` | 100 | 1 |
702 +-------------------------+-----------------------+-----------------+
703 | ``specification.Q_HK`` | 100 | 1 |
704 +-------------------------+-----------------------+-----------------+
706 +-------------------------+-----------------------+-----------------+
707 | **Range** | **Scale - Reference** | **Scale - 1** |
708 +=========================+=======================+=================+
709 | ``JMh`` | ``J`` : 100 | ``J`` : 1 |
710 | | | |
711 | | ``M`` : 100 | ``M`` : 1 |
712 | | | |
713 | | ``h`` : 360 | ``h`` : 1 |
714 +-------------------------+-----------------------+-----------------+
716 Examples
717 --------
718 >>> specification = CAM_Specification_Hellwig2022(
719 ... J=33.880368498111686, M=49.57713161802121, h=19.510887327451748
720 ... )
721 >>> Hellwig2022_to_JMh_Hellwig2022(specification) # doctest: +ELLIPSIS
722 array([ 33.8803685 , 49.57713162, 19.51088733])
723 """
725 return tstack([specification.J, specification.M, specification.h]) # pyright: ignore
728def JMh_Hellwig2022_to_Hellwig2022(
729 JMh: Domain100_100_360,
730) -> Annotated[
731 CAM_Specification_Hellwig2022, (100, 100, 360, 100, 100, 100, 400, 100, 100)
732]:
733 """
734 Convert from *Hellwig and Fairchild (2022)* :math:`JMh` correlates to
735 *Hellwig and Fairchild (2022)* specification.
737 Parameters
738 ----------
739 JMh
740 *Hellwig and Fairchild (2022)* :math:`JMh` correlates.
742 Returns
743 -------
744 :class:`colour.CAM_Specification_Hellwig2022`
745 *Hellwig and Fairchild (2022)* colour appearance model specification.
747 Notes
748 -----
749 +-------------------------+-----------------------+-----------------+
750 | **Domain** | **Scale - Reference** | **Scale - 1** |
751 +=========================+=======================+=================+
752 | ``JMh`` | ``J`` : 100 | ``J`` : 1 |
753 | | | |
754 | | ``M`` : 100 | ``M`` : 1 |
755 | | | |
756 | | ``h`` : 360 | ``h`` : 1 |
757 +-------------------------+-----------------------+-----------------+
759 +-------------------------+-----------------------+-----------------+
760 | **Range** | **Scale - Reference** | **Scale - 1** |
761 +=========================+=======================+=================+
762 | ``specification.J`` | 100 | 1 |
763 +-------------------------+-----------------------+-----------------+
764 | ``specification.C`` | 100 | 1 |
765 +-------------------------+-----------------------+-----------------+
766 | ``specification.h`` | 360 | 1 |
767 +-------------------------+-----------------------+-----------------+
768 | ``specification.s`` | 100 | 1 |
769 +-------------------------+-----------------------+-----------------+
770 | ``specification.Q`` | 100 | 1 |
771 +-------------------------+-----------------------+-----------------+
772 | ``specification.M`` | 100 | 1 |
773 +-------------------------+-----------------------+-----------------+
774 | ``specification.H`` | 400 | 1 |
775 +-------------------------+-----------------------+-----------------+
776 | ``specification.HC`` | 100 | 1 |
777 +-------------------------+-----------------------+-----------------+
778 | ``specification.J_HK`` | 100 | 1 |
779 +-------------------------+-----------------------+-----------------+
780 | ``specification.Q_HK`` | 100 | 1 |
781 +-------------------------+-----------------------+-----------------+
783 Examples
784 --------
785 >>> import numpy as np
786 >>> JMh = np.array([33.8803685, 49.5771316, 19.5108873])
787 >>> JMh_Hellwig2022_to_Hellwig2022(JMh) # doctest: +ELLIPSIS
788 CAM_Specification_Hellwig2022(J=33.8803685..., C=None, h=19.5108873..., \
789s=None, Q=None, M=49.5771316..., H=None, HC=None, J_HK=None, Q_HK=None)
790 """
792 J, M, h = tsplit(JMh)
794 return CAM_Specification_Hellwig2022(J=J, M=M, h=h)
797def sCAM_to_JMh_sCAM(
798 specification: Annotated[
799 CAM_Specification_sCAM, (100, 100, 360, 100, 100, 400, 100, 100, 100, 100)
800 ],
801) -> Range100_100_360:
802 """
803 Convert from *sCAM* specification to *sCAM* :math:`JMh` correlates.
805 Parameters
806 ----------
807 specification
808 *sCAM* colour appearance model specification.
810 Returns
811 -------
812 :class:`numpy.ndarray`
813 *sCAM* :math:`JMh` correlates.
815 Notes
816 -----
817 +---------------------+-----------------------+-----------------+
818 | **Domain** | **Scale - Reference** | **Scale - 1** |
819 +=====================+=======================+=================+
820 | ``specification.J`` | 100 | 1 |
821 +---------------------+-----------------------+-----------------+
822 | ``specification.C`` | 100 | 1 |
823 +---------------------+-----------------------+-----------------+
824 | ``specification.h`` | 360 | 1 |
825 +---------------------+-----------------------+-----------------+
826 | ``specification.Q`` | 100 | 1 |
827 +---------------------+-----------------------+-----------------+
828 | ``specification.M`` | 100 | 1 |
829 +---------------------+-----------------------+-----------------+
830 | ``specification.H`` | 400 | 1 |
831 +---------------------+-----------------------+-----------------+
832 | ``specification.HC`` | 100 | 1 |
833 +---------------------+-----------------------+-----------------+
834 | ``specification.V`` | 100 | 1 |
835 +---------------------+-----------------------+-----------------+
836 | ``specification.K`` | 100 | 1 |
837 +---------------------+-----------------------+-----------------+
838 | ``specification.W`` | 100 | 1 |
839 +---------------------+-----------------------+-----------------+
840 | ``specification.D`` | 100 | 1 |
841 +---------------------+-----------------------+-----------------+
843 +---------------------+-----------------------+-----------------+
844 | **Range** | **Scale - Reference** | **Scale - 1** |
845 +=====================+=======================+=================+
846 | ``JMh`` | ``J`` : 100 | ``J`` : 1 |
847 | | | |
848 | | ``M`` : 100 | ``M`` : 1 |
849 | | | |
850 | | ``h`` : 360 | ``h`` : 1 |
851 +---------------------+-----------------------+-----------------+
853 Examples
854 --------
855 >>> specification = CAM_Specification_sCAM(
856 ... J=42.55099214246278, M=14.325369984981474, h=20.90445543302642
857 ... )
858 >>> sCAM_to_JMh_sCAM(specification) # doctest: +ELLIPSIS
859 array([ 42.5509921..., 14.3253699..., 20.9044554...])
860 """
862 return tstack([specification.J, specification.M, specification.h]) # pyright: ignore
865def JMh_sCAM_to_sCAM(
866 JMh: Domain100_100_360,
867) -> Annotated[
868 CAM_Specification_sCAM, (100, 100, 360, 100, 100, 400, 100, 100, 100, 100)
869]:
870 """
871 Convert from *sCAM* :math:`JMh` correlates to *sCAM* specification.
873 Parameters
874 ----------
875 JMh
876 *sCAM* :math:`JMh` correlates.
878 Returns
879 -------
880 :class:`colour.CAM_Specification_sCAM`
881 *sCAM* colour appearance model specification.
883 Notes
884 -----
885 +---------------------+-----------------------+-----------------+
886 | **Domain** | **Scale - Reference** | **Scale - 1** |
887 +=====================+=======================+=================+
888 | ``JMh`` | ``J`` : 100 | ``J`` : 1 |
889 | | | |
890 | | ``M`` : 100 | ``M`` : 1 |
891 | | | |
892 | | ``h`` : 360 | ``h`` : 1 |
893 +---------------------+-----------------------+-----------------+
895 +---------------------+-----------------------+-----------------+
896 | **Range** | **Scale - Reference** | **Scale - 1** |
897 +=====================+=======================+=================+
898 | ``specification.J`` | 100 | 1 |
899 +---------------------+-----------------------+-----------------+
900 | ``specification.C`` | 100 | 1 |
901 +---------------------+-----------------------+-----------------+
902 | ``specification.h`` | 360 | 1 |
903 +---------------------+-----------------------+-----------------+
904 | ``specification.Q`` | 100 | 1 |
905 +---------------------+-----------------------+-----------------+
906 | ``specification.M`` | 100 | 1 |
907 +---------------------+-----------------------+-----------------+
908 | ``specification.H`` | 400 | 1 |
909 +---------------------+-----------------------+-----------------+
910 | ``specification.HC`` | 100 | 1 |
911 +---------------------+-----------------------+-----------------+
912 | ``specification.V`` | 100 | 1 |
913 +---------------------+-----------------------+-----------------+
914 | ``specification.K`` | 100 | 1 |
915 +---------------------+-----------------------+-----------------+
916 | ``specification.W`` | 100 | 1 |
917 +---------------------+-----------------------+-----------------+
918 | ``specification.D`` | 100 | 1 |
919 +---------------------+-----------------------+-----------------+
921 Examples
922 --------
923 >>> import numpy as np
924 >>> JMh = np.array([42.5509921, 14.3253700, 20.9044554])
925 >>> JMh_sCAM_to_sCAM(JMh) # doctest: +ELLIPSIS
926 CAM_Specification_sCAM(J=42.5509921..., C=None, h=20.9044554, Q=None, \
927M=14.3253699..., H=None, HC=None, V=None, K=None, W=None, D=None)
928 """
930 J, M, h = tsplit(JMh)
932 return CAM_Specification_sCAM(J=J, M=M, h=h)
935def ZCAM_to_JMh_ZCAM(
936 specification: Annotated[
937 CAM_Specification_ZCAM, (1, 1, 360, 1, 1, 1, 400, 1, 1, 1)
938 ],
939) -> Annotated[NDArrayFloat, (1, 1, 360)]:
940 """
941 Convert from *ZCAM* specification to *ZCAM* :math:`JMh` correlates.
943 Parameters
944 ----------
945 specification
946 *ZCAM* colour appearance model specification.
948 Returns
949 -------
950 :class:`numpy.ndarray`
951 *ZCAM* :math:`JMh` correlates.
953 Notes
954 -----
955 +---------------------+-----------------------+-----------------+
956 | **Domain** | **Scale - Reference** | **Scale - 1** |
957 +=====================+=======================+=================+
958 | ``specification.J`` | 1 | 1 |
959 +---------------------+-----------------------+-----------------+
960 | ``specification.C`` | 1 | 1 |
961 +---------------------+-----------------------+-----------------+
962 | ``specification.h`` | 360 | 1 |
963 +---------------------+-----------------------+-----------------+
964 | ``specification.s`` | 1 | 1 |
965 +---------------------+-----------------------+-----------------+
966 | ``specification.Q`` | 1 | 1 |
967 +---------------------+-----------------------+-----------------+
968 | ``specification.M`` | 1 | 1 |
969 +---------------------+-----------------------+-----------------+
970 | ``specification.H`` | 400 | 1 |
971 +---------------------+-----------------------+-----------------+
972 | ``specification.HC`` | 1 | 1 |
973 +---------------------+-----------------------+-----------------+
974 | ``specification.V`` | 1 | 1 |
975 +---------------------+-----------------------+-----------------+
976 | ``specification.K`` | 1 | 1 |
977 +---------------------+-----------------------+-----------------+
978 | ``specification.W`` | 1 | 1 |
979 +---------------------+-----------------------+-----------------+
981 +---------------------+-----------------------+-----------------+
982 | **Range** | **Scale - Reference** | **Scale - 1** |
983 +=====================+=======================+=================+
984 | ``JMh`` | ``J`` : 100 | ``J`` : 1 |
985 | | | |
986 | | ``M`` : 100 | ``M`` : 1 |
987 | | | |
988 | | ``h`` : 360 | ``h`` : 1 |
989 +---------------------+-----------------------+-----------------+
991 Examples
992 --------
993 >>> specification = CAM_Specification_ZCAM(
994 ... J=38.34718627895636, M=42.40380583390051, h=33.71157893109518
995 ... )
996 >>> ZCAM_to_JMh_ZCAM(specification) # doctest: +ELLIPSIS
997 array([ 38.3471862..., 42.4038058..., 33.7115789...])
998 """
1000 return tstack(
1001 [
1002 cast("NDArrayFloat", specification.J),
1003 cast("NDArrayFloat", specification.M),
1004 cast("NDArrayFloat", specification.h),
1005 ]
1006 )
1009def JMh_ZCAM_to_ZCAM(
1010 JMh: Annotated[ArrayLike, (1, 1, 360)],
1011) -> Annotated[CAM_Specification_ZCAM, (1, 1, 360, 1, 1, 1, 400, 1, 1, 1)]:
1012 """
1013 Convert from *ZCAM* :math:`JMh` correlates to *ZCAM* specification.
1015 Parameters
1016 ----------
1017 JMh
1018 *ZCAM* :math:`JMh` correlates.
1020 Returns
1021 -------
1022 :class:`colour.CAM_Specification_ZCAM`
1023 *ZCAM* colour appearance model specification.
1025 Notes
1026 -----
1027 +---------------------+-----------------------+-----------------+
1028 | **Domain** | **Scale - Reference** | **Scale - 1** |
1029 +=====================+=======================+=================+
1030 | ``JMh`` | ``J`` : 100 | ``J`` : 1 |
1031 | | | |
1032 | | ``M`` : 100 | ``M`` : 1 |
1033 | | | |
1034 | | ``h`` : 360 | ``h`` : 1 |
1035 +---------------------+-----------------------+-----------------+
1037 +---------------------+-----------------------+-----------------+
1038 | **Range** | **Scale - Reference** | **Scale - 1** |
1039 +=====================+=======================+=================+
1040 | ``specification.J`` | 1 | 1 |
1041 +---------------------+-----------------------+-----------------+
1042 | ``specification.C`` | 1 | 1 |
1043 +---------------------+-----------------------+-----------------+
1044 | ``specification.h`` | 360 | 1 |
1045 +---------------------+-----------------------+-----------------+
1046 | ``specification.s`` | 1 | 1 |
1047 +---------------------+-----------------------+-----------------+
1048 | ``specification.Q`` | 1 | 1 |
1049 +---------------------+-----------------------+-----------------+
1050 | ``specification.M`` | 1 | 1 |
1051 +---------------------+-----------------------+-----------------+
1052 | ``specification.H`` | 400 | 1 |
1053 +---------------------+-----------------------+-----------------+
1054 | ``specification.HC`` | 1 | 1 |
1055 +---------------------+-----------------------+-----------------+
1056 | ``specification.V`` | 1 | 1 |
1057 +---------------------+-----------------------+-----------------+
1058 | ``specification.K`` | 1 | 1 |
1059 +---------------------+-----------------------+-----------------+
1060 | ``specification.W`` | 1 | 1 |
1061 +---------------------+-----------------------+-----------------+
1063 Examples
1064 --------
1065 >>> import numpy as np
1066 >>> JMh = np.array([38.3471863, 42.4038058, 33.7115789])
1067 >>> JMh_ZCAM_to_ZCAM(JMh) # doctest: +ELLIPSIS
1068 CAM_Specification_ZCAM(J=38.3471862..., C=None, h=33.7115788..., s=None, \
1069Q=None, M=42.4038058..., H=None, HC=None, V=None, K=None, W=None)
1070 """
1072 J, M, h = tsplit(JMh)
1074 return CAM_Specification_ZCAM(J=J, M=M, h=h)
1077def Kim2009_to_JMh_Kim2009(
1078 specification: Annotated[
1079 CAM_Specification_Kim2009, (100, 100, 360, 100, 100, 100, 400)
1080 ],
1081) -> Range100_100_360:
1082 """
1083 Convert from *Kim, Weyrich and Kautz (2009)* specification to
1084 *Kim, Weyrich and Kautz (2009)* :math:`JMh` correlates.
1086 Parameters
1087 ----------
1088 specification
1089 *Kim, Weyrich and Kautz (2009)* colour appearance model specification.
1091 Returns
1092 -------
1093 :class:`numpy.ndarray`
1094 *Kim, Weyrich and Kautz (2009)* :math:`JMh` correlates.
1096 Notes
1097 -----
1098 +---------------------+-----------------------+-----------------+
1099 | **Domain** | **Scale - Reference** | **Scale - 1** |
1100 +=====================+=======================+=================+
1101 | ``specification.J`` | 100 | 1 |
1102 +---------------------+-----------------------+-----------------+
1103 | ``specification.C`` | 100 | 1 |
1104 +---------------------+-----------------------+-----------------+
1105 | ``specification.h`` | 360 | 1 |
1106 +---------------------+-----------------------+-----------------+
1107 | ``specification.s`` | 100 | 1 |
1108 +---------------------+-----------------------+-----------------+
1109 | ``specification.Q`` | 100 | 1 |
1110 +---------------------+-----------------------+-----------------+
1111 | ``specification.M`` | 100 | 1 |
1112 +---------------------+-----------------------+-----------------+
1113 | ``specification.H`` | 400 | 1 |
1114 +---------------------+-----------------------+-----------------+
1116 +---------------------+-----------------------+-----------------+
1117 | **Range** | **Scale - Reference** | **Scale - 1** |
1118 +=====================+=======================+=================+
1119 | ``JMh`` | ``J`` : 100 | ``J`` : 1 |
1120 | | | |
1121 | | ``M`` : 100 | ``M`` : 1 |
1122 | | | |
1123 | | ``h`` : 360 | ``h`` : 1 |
1124 +---------------------+-----------------------+-----------------+
1126 Examples
1127 --------
1128 >>> specification = CAM_Specification_Kim2009(
1129 ... J=19.879918542450937, M=46.34641585822787, h=22.01338816509003
1130 ... )
1131 >>> Kim2009_to_JMh_Kim2009(specification) # doctest: +ELLIPSIS
1132 array([ 19.8799185..., 46.3464158..., 22.0133881...])
1133 """
1135 return tstack(
1136 [
1137 cast("NDArrayFloat", specification.J),
1138 cast("NDArrayFloat", specification.M),
1139 cast("NDArrayFloat", specification.h),
1140 ]
1141 )
1144def JMh_Kim2009_to_Kim2009(
1145 JMh: Domain100_100_360,
1146) -> Annotated[CAM_Specification_Kim2009, (100, 100, 360, 100, 100, 100, 400)]:
1147 """
1148 Convert from *Kim, Weyrich and Kautz (2009)* :math:`JMh` correlates to
1149 *Kim, Weyrich and Kautz (2009)* specification.
1151 Parameters
1152 ----------
1153 JMh
1154 *Kim, Weyrich and Kautz (2009)* :math:`JMh` correlates.
1156 Returns
1157 -------
1158 :class:`colour.CAM_Specification_Kim2009`
1159 *Kim, Weyrich and Kautz (2009)* colour appearance model specification.
1161 Notes
1162 -----
1163 +---------------------+-----------------------+-----------------+
1164 | **Domain** | **Scale - Reference** | **Scale - 1** |
1165 +=====================+=======================+=================+
1166 | ``JMh`` | ``J`` : 100 | ``J`` : 1 |
1167 | | | |
1168 | | ``M`` : 100 | ``M`` : 1 |
1169 | | | |
1170 | | ``h`` : 360 | ``h`` : 1 |
1171 +---------------------+-----------------------+-----------------+
1173 +---------------------+-----------------------+-----------------+
1174 | **Range** | **Scale - Reference** | **Scale - 1** |
1175 +=====================+=======================+=================+
1176 | ``specification.J`` | 100 | 1 |
1177 +---------------------+-----------------------+-----------------+
1178 | ``specification.C`` | 100 | 1 |
1179 +---------------------+-----------------------+-----------------+
1180 | ``specification.h`` | 360 | 1 |
1181 +---------------------+-----------------------+-----------------+
1182 | ``specification.s`` | 100 | 1 |
1183 +---------------------+-----------------------+-----------------+
1184 | ``specification.Q`` | 100 | 1 |
1185 +---------------------+-----------------------+-----------------+
1186 | ``specification.M`` | 100 | 1 |
1187 +---------------------+-----------------------+-----------------+
1188 | ``specification.H`` | 400 | 1 |
1189 +---------------------+-----------------------+-----------------+
1191 Examples
1192 --------
1193 >>> import numpy as np
1194 >>> JMh = np.array([19.8799185, 46.3464159, 22.0133882])
1195 >>> JMh_Kim2009_to_Kim2009(JMh) # doctest: +ELLIPSIS
1196 CAM_Specification_Kim2009(J=19.8799184..., C=None, h=22.0133882..., s=None, \
1197Q=None, M=46.3464158..., H=None, HC=None)
1198 """
1200 J, M, h = tsplit(JMh)
1202 return CAM_Specification_Kim2009(J=J, M=M, h=h)
1205def XYZ_to_luminance(XYZ: Domain1) -> Range1:
1206 """
1207 Convert specified *CIE XYZ* tristimulus values to *luminance* :math:`Y`.
1209 Extract the Y component from *CIE XYZ* tristimulus values, which
1210 represents the *luminance* of the colour stimulus.
1212 Parameters
1213 ----------
1214 XYZ
1215 *CIE XYZ* tristimulus values.
1217 Returns
1218 -------
1219 :class:`numpy.ndarray`
1220 *Luminance* :math:`Y`.
1222 Notes
1223 -----
1224 +------------+-----------------------+---------------+
1225 | **Domain** | **Scale - Reference** | **Scale - 1** |
1226 +============+=======================+===============+
1227 | ``XYZ`` | 1 | 1 |
1228 +------------+-----------------------+---------------+
1230 +-----------+-----------------------+---------------+
1231 | **Range** | **Scale - Reference** | **Scale - 1** |
1232 +===========+=======================+===============+
1233 | ``Y`` | 1 | 1 |
1234 +-----------+-----------------------+---------------+
1236 Examples
1237 --------
1238 >>> import numpy as np
1239 >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
1240 >>> XYZ_to_luminance(XYZ) # doctest: +ELLIPSIS
1241 0.1219722...
1242 """
1244 _X, Y, _Z = tsplit(XYZ)
1246 return Y
1249def RGB_luminance_to_RGB(Y: Domain1) -> Range1:
1250 """
1251 Convert from *luminance* :math:`Y` to *RGB*.
1253 Parameters
1254 ----------
1255 Y
1256 *Luminance* :math:`Y`.
1258 Returns
1259 -------
1260 :class:`numpy.ndarray`
1261 *RGB*.
1263 Notes
1264 -----
1265 +------------+-----------------------+---------------+
1266 | **Domain** | **Scale - Reference** | **Scale - 1** |
1267 +============+=======================+===============+
1268 | ``Y`` | 1 | 1 |
1269 +------------+-----------------------+---------------+
1271 +-----------+-----------------------+---------------+
1272 | **Range** | **Scale - Reference** | **Scale - 1** |
1273 +===========+=======================+===============+
1274 | ``RGB`` | 1 | 1 |
1275 +-----------+-----------------------+---------------+
1277 Examples
1278 --------
1279 >>> RGB_luminance_to_RGB(0.123014562384318) # doctest: +ELLIPSIS
1280 array([ 0.1230145..., 0.1230145..., 0.1230145...])
1281 """
1283 Y = as_float_array(Y)
1285 return tstack([Y, Y, Y])
1288def CCT_D_uv_to_mired(CCT_D_uv: ArrayLike) -> NDArrayFloat:
1289 """
1290 Convert correlated colour temperature :math:`T_{cp}` and
1291 :math:`\\Delta_{uv}` to micro reciprocal degree (mired).
1293 Parameters
1294 ----------
1295 CCT_D_uv
1296 Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`.
1298 Returns
1299 -------
1300 :class:`numpy.ndarray`
1301 Micro reciprocal degree (mired).
1303 Examples
1304 --------
1305 >>> CCT_D_uv = np.array([6500.0081378199056, 0.008333331244225])
1306 >>> CCT_D_uv_to_mired(CCT_D_uv) # doctest: +ELLIPSIS
1307 153.8459612...
1308 """
1310 CCT, _D_uv = tsplit(CCT_D_uv)
1312 return CCT_to_mired(CCT)
1315def mired_to_CCT_D_uv(mired: ArrayLike) -> NDArrayFloat:
1316 """
1317 Convert specified micro reciprocal degree (mired) to correlated colour
1318 temperature :math:`T_{cp}` and :math:`\\Delta_{uv}`.
1320 Parameters
1321 ----------
1322 mired
1323 Micro reciprocal degree (mired).
1325 Returns
1326 -------
1327 :class:`numpy.ndarray`
1328 Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`.
1330 Examples
1331 --------
1332 >>> mired = 153.84596123527297
1333 >>> mired_to_CCT_D_uv(mired) # doctest: +ELLIPSIS
1334 array([ 6500.0081378..., 0. ])
1335 """
1337 mired = as_float_array(mired)
1339 return tstack([mired_to_CCT(mired), zeros(mired.shape)])
1342_ILLUMINANT_DEFAULT: str = "D65"
1343"""Default automatic colour conversion graph illuminant name."""
1345_CCS_ILLUMINANT_DEFAULT: NDArrayFloat = CCS_ILLUMINANTS[
1346 "CIE 1931 2 Degree Standard Observer"
1347][_ILLUMINANT_DEFAULT]
1348"""
1349Default automatic colour conversion graph illuminant *CIE xy* chromaticity
1350coordinates.
1351"""
1353_TVS_ILLUMINANT_DEFAULT: NDArrayFloat = xy_to_XYZ(_CCS_ILLUMINANT_DEFAULT)
1354"""
1355Default automatic colour conversion graph illuminant *CIE XYZ* tristimulus
1356values.
1357"""
1359_RGB_COLOURSPACE_DEFAULT: RGB_Colourspace = RGB_COLOURSPACE_sRGB
1360"""Default automatic colour conversion graph *RGB* colourspace."""
1362_CAM_KWARGS_CIECAM02_sRGB: dict = CAM_KWARGS_CIECAM02_sRGB.copy()
1363"""
1364Default parameter values for the *CIECAM02* colour appearance model usage in
1365the context of *sRGB*.
1367Warnings
1368--------
1369The *CIE XYZ* tristimulus values of reference white :math:`XYZ_w` is adjusted
1370for the domain-range scale **'1'**.
1371"""
1373_CAM_KWARGS_CIECAM02_sRGB["XYZ_w"] = _CAM_KWARGS_CIECAM02_sRGB["XYZ_w"] / 100
1375CONVERSION_SPECIFICATIONS_DATA: List[tuple] = [
1376 # Colorimetry
1377 ("Spectral Distribution", "CIE XYZ", sd_to_XYZ),
1378 ("CIE XYZ", "Spectral Distribution", XYZ_to_sd),
1379 ("Spectral Distribution", "Luminous Flux", luminous_flux),
1380 ("Spectral Distribution", "Luminous Efficiency", luminous_efficiency),
1381 ("Spectral Distribution", "Luminous Efficacy", luminous_efficacy),
1382 ("CIE XYZ", "Luminance", XYZ_to_luminance),
1383 ("Luminance", "Lightness", lightness),
1384 ("Lightness", "Luminance", luminance),
1385 ("CIE XYZ", "Whiteness", partial(whiteness, XYZ_0=_TVS_ILLUMINANT_DEFAULT)),
1386 ("CIE XYZ", "Yellowness", yellowness),
1387 (
1388 "CIE xy",
1389 "Colorimetric Purity",
1390 partial(colorimetric_purity, xy_n=_CCS_ILLUMINANT_DEFAULT),
1391 ),
1392 (
1393 "CIE xy",
1394 "Complementary Wavelength",
1395 partial(complementary_wavelength, xy_n=_CCS_ILLUMINANT_DEFAULT),
1396 ),
1397 (
1398 "CIE xy",
1399 "Dominant Wavelength",
1400 partial(dominant_wavelength, xy_n=_CCS_ILLUMINANT_DEFAULT),
1401 ),
1402 (
1403 "CIE xy",
1404 "Excitation Purity",
1405 partial(excitation_purity, xy_n=_CCS_ILLUMINANT_DEFAULT),
1406 ),
1407 ("Wavelength", "CIE XYZ", wavelength_to_XYZ),
1408 # Colour Models
1409 ("CIE XYZ", "CIE xyY", XYZ_to_xyY),
1410 ("CIE xyY", "CIE XYZ", xyY_to_XYZ),
1411 ("CIE xyY", "CIE xy", xyY_to_xy),
1412 ("CIE xy", "CIE xyY", xy_to_xyY),
1413 ("CIE XYZ", "CIE xy", XYZ_to_xy),
1414 ("CIE xy", "CIE XYZ", xy_to_XYZ),
1415 ("CIE XYZ", "CIE Lab", XYZ_to_Lab),
1416 ("CIE Lab", "CIE XYZ", Lab_to_XYZ),
1417 ("CIE XYZ", "CIE Luv", XYZ_to_Luv),
1418 ("CIE Luv", "CIE XYZ", Luv_to_XYZ),
1419 ("CIE Luv", "CIE Luv uv", Luv_to_uv),
1420 ("CIE Luv uv", "CIE Luv", uv_to_Luv),
1421 ("CIE Luv uv", "CIE xy", Luv_uv_to_xy),
1422 ("CIE xy", "CIE Luv uv", xy_to_Luv_uv),
1423 ("CIE XYZ", "CIE UCS", XYZ_to_UCS),
1424 ("CIE UCS", "CIE XYZ", UCS_to_XYZ),
1425 ("CIE UCS", "CIE UCS uv", UCS_to_uv),
1426 ("CIE UCS uv", "CIE UCS", uv_to_UCS),
1427 ("CIE UCS uv", "CIE xy", UCS_uv_to_xy),
1428 ("CIE xy", "CIE UCS uv", xy_to_UCS_uv),
1429 ("CIE XYZ", "CIE UVW", XYZ_to_UVW),
1430 ("CIE UVW", "CIE XYZ", UVW_to_XYZ),
1431 ("CIE XYZ", "DIN99", XYZ_to_DIN99),
1432 ("DIN99", "CIE XYZ", DIN99_to_XYZ),
1433 ("CIE XYZ", "hdr-CIELAB", XYZ_to_hdr_CIELab),
1434 ("hdr-CIELAB", "CIE XYZ", hdr_CIELab_to_XYZ),
1435 (
1436 "CIE XYZ",
1437 "Hunter Lab",
1438 partial(
1439 XYZ_to_Hunter_Lab,
1440 XYZ_n=TVS_ILLUMINANTS_HUNTERLAB["CIE 1931 2 Degree Standard Observer"][
1441 "D65"
1442 ].XYZ_n
1443 / 100,
1444 ),
1445 ),
1446 (
1447 "Hunter Lab",
1448 "CIE XYZ",
1449 partial(
1450 Hunter_Lab_to_XYZ,
1451 XYZ_n=TVS_ILLUMINANTS_HUNTERLAB["CIE 1931 2 Degree Standard Observer"][
1452 "D65"
1453 ].XYZ_n
1454 / 100,
1455 ),
1456 ),
1457 (
1458 "CIE XYZ",
1459 "Hunter Rdab",
1460 partial(
1461 XYZ_to_Hunter_Rdab,
1462 XYZ_n=TVS_ILLUMINANTS_HUNTERLAB["CIE 1931 2 Degree Standard Observer"][
1463 "D65"
1464 ].XYZ_n
1465 / 100,
1466 ),
1467 ),
1468 (
1469 "Hunter Rdab",
1470 "CIE XYZ",
1471 partial(
1472 Hunter_Rdab_to_XYZ,
1473 XYZ_n=TVS_ILLUMINANTS_HUNTERLAB["CIE 1931 2 Degree Standard Observer"][
1474 "D65"
1475 ].XYZ_n
1476 / 100,
1477 ),
1478 ),
1479 ("CIE XYZ", "ICaCb", XYZ_to_ICaCb),
1480 ("ICaCb", "CIE XYZ", ICaCb_to_XYZ),
1481 ("CIE XYZ", "ICtCp", XYZ_to_ICtCp),
1482 ("ICtCp", "CIE XYZ", ICtCp_to_XYZ),
1483 ("CIE XYZ", "IgPgTg", XYZ_to_IgPgTg),
1484 ("IgPgTg", "CIE XYZ", IgPgTg_to_XYZ),
1485 ("CIE XYZ", "IPT", XYZ_to_IPT),
1486 ("IPT", "CIE XYZ", IPT_to_XYZ),
1487 ("CIE XYZ", "IPT Ragoo 2021", XYZ_to_IPT_Ragoo2021),
1488 ("IPT Ragoo 2021", "CIE XYZ", IPT_Ragoo2021_to_XYZ),
1489 ("CIE XYZ", "Jzazbz", XYZ_to_Jzazbz),
1490 ("Jzazbz", "CIE XYZ", Jzazbz_to_XYZ),
1491 ("CIE XYZ", "hdr-IPT", XYZ_to_hdr_IPT),
1492 ("hdr-IPT", "CIE XYZ", hdr_IPT_to_XYZ),
1493 ("CIE XYZ", "OSA UCS", XYZ_to_OSA_UCS),
1494 ("OSA UCS", "CIE XYZ", OSA_UCS_to_XYZ),
1495 ("CIE XYZ", "Oklab", XYZ_to_Oklab),
1496 ("Oklab", "CIE XYZ", Oklab_to_XYZ),
1497 ("CIE XYZ", "ProLab", XYZ_to_ProLab),
1498 ("ProLab", "CIE XYZ", ProLab_to_XYZ),
1499 ("CIE XYZ", "sUCS", XYZ_to_sUCS),
1500 ("sUCS", "CIE XYZ", sUCS_to_XYZ),
1501 ("CIE XYZ", "Yrg", XYZ_to_Yrg),
1502 ("Yrg", "CIE XYZ", Yrg_to_XYZ),
1503 ("CIE 1931", "CIE XYZ", xyY_to_XYZ),
1504 ("CIE XYZ", "CIE 1931", XYZ_to_xyY),
1505 ("CIE 1960 UCS", "CIE XYZ", CIE1960UCS_to_XYZ),
1506 ("CIE XYZ", "CIE 1960 UCS", XYZ_to_CIE1960UCS),
1507 ("CIE 1976 UCS", "CIE XYZ", CIE1976UCS_to_XYZ),
1508 ("CIE XYZ", "CIE 1976 UCS", XYZ_to_CIE1976UCS),
1509 # RGB Colour Models
1510 ("CIE XYZ", "RGB", partial(XYZ_to_RGB, colourspace=_RGB_COLOURSPACE_DEFAULT)),
1511 ("RGB", "CIE XYZ", partial(RGB_to_XYZ, colourspace=_RGB_COLOURSPACE_DEFAULT)),
1512 (
1513 "RGB",
1514 "Scene-Referred RGB",
1515 partial(
1516 RGB_to_RGB,
1517 input_colourspace=_RGB_COLOURSPACE_DEFAULT,
1518 output_colourspace=_RGB_COLOURSPACE_DEFAULT,
1519 ),
1520 ),
1521 (
1522 "Scene-Referred RGB",
1523 "RGB",
1524 partial(
1525 RGB_to_RGB,
1526 input_colourspace=_RGB_COLOURSPACE_DEFAULT,
1527 output_colourspace=_RGB_COLOURSPACE_DEFAULT,
1528 ),
1529 ),
1530 ("RGB", "HSV", RGB_to_HSV),
1531 ("HSV", "RGB", HSV_to_RGB),
1532 ("RGB", "HSL", RGB_to_HSL),
1533 ("HSL", "RGB", HSL_to_RGB),
1534 ("RGB", "HCL", RGB_to_HCL),
1535 ("HCL", "RGB", HCL_to_RGB),
1536 ("RGB", "IHLS", RGB_to_IHLS),
1537 ("IHLS", "RGB", IHLS_to_RGB),
1538 ("CMY", "RGB", CMY_to_RGB),
1539 ("RGB", "CMY", RGB_to_CMY),
1540 ("CMY", "CMYK", CMY_to_CMYK),
1541 ("CMYK", "CMY", CMYK_to_CMY),
1542 (
1543 "RGB",
1544 "RGB Luminance",
1545 partial(
1546 RGB_luminance,
1547 primaries=_RGB_COLOURSPACE_DEFAULT.primaries,
1548 whitepoint=_RGB_COLOURSPACE_DEFAULT.whitepoint,
1549 ),
1550 ),
1551 ("RGB Luminance", "RGB", RGB_luminance_to_RGB),
1552 ("RGB", "Prismatic", RGB_to_Prismatic),
1553 ("Prismatic", "RGB", Prismatic_to_RGB),
1554 ("Output-Referred RGB", "YCbCr", RGB_to_YCbCr),
1555 ("YCbCr", "Output-Referred RGB", YCbCr_to_RGB),
1556 ("RGB", "YcCbcCrc", RGB_to_YcCbcCrc),
1557 ("YcCbcCrc", "RGB", YcCbcCrc_to_RGB),
1558 ("Output-Referred RGB", "YCoCg", RGB_to_YCoCg),
1559 ("YCoCg", "Output-Referred RGB", YCoCg_to_RGB),
1560 ("RGB", "Output-Referred RGB", cctf_encoding),
1561 ("Output-Referred RGB", "RGB", cctf_decoding),
1562 ("Scene-Referred RGB", "Output-Referred RGB", cctf_encoding),
1563 ("Output-Referred RGB", "Scene-Referred RGB", cctf_decoding),
1564 ("CIE XYZ", "sRGB", XYZ_to_sRGB),
1565 ("sRGB", "CIE XYZ", sRGB_to_XYZ),
1566 # Colour Notation Systems
1567 ("Output-Referred RGB", "Hexadecimal", RGB_to_HEX),
1568 ("Hexadecimal", "Output-Referred RGB", HEX_to_RGB),
1569 ("CSS Color 3", "Output-Referred RGB", keyword_to_RGB_CSSColor3),
1570 ("CIE xyY", "Munsell Colour", xyY_to_munsell_colour),
1571 ("Munsell Colour", "CIE xyY", munsell_colour_to_xyY),
1572 ("Luminance", "Munsell Value", munsell_value),
1573 ("Munsell Value", "Luminance", partial(luminance, method="ASTM D1535")),
1574 # Colour Quality
1575 ("Spectral Distribution", "CRI", colour_rendering_index),
1576 ("Spectral Distribution", "CQS", colour_quality_scale),
1577 # Colour Temperature
1578 ("CCT", "CIE UCS uv", CCT_to_uv),
1579 ("CIE UCS uv", "CCT", uv_to_CCT),
1580 ("CCT", "Mired", CCT_D_uv_to_mired),
1581 ("Mired", "CCT", mired_to_CCT_D_uv),
1582 # Advanced Colorimetry
1583 (
1584 "CIE XYZ",
1585 "ATD95",
1586 partial(
1587 XYZ_to_ATD95,
1588 XYZ_0=_TVS_ILLUMINANT_DEFAULT,
1589 Y_0=80 * 0.2,
1590 k_1=0,
1591 k_2=(15 + 50) / 2,
1592 ),
1593 ),
1594 ("CIE XYZ", "CIECAM02", partial(XYZ_to_CIECAM02, **_CAM_KWARGS_CIECAM02_sRGB)),
1595 ("CIECAM02", "CIE XYZ", partial(CIECAM02_to_XYZ, **_CAM_KWARGS_CIECAM02_sRGB)),
1596 ("CIECAM02", "CIECAM02 JMh", CIECAM02_to_JMh_CIECAM02),
1597 ("CIECAM02 JMh", "CIECAM02", JMh_CIECAM02_to_CIECAM02),
1598 ("CIE XYZ", "CAM16", partial(XYZ_to_CAM16, **_CAM_KWARGS_CIECAM02_sRGB)),
1599 ("CAM16", "CIE XYZ", partial(CAM16_to_XYZ, **_CAM_KWARGS_CIECAM02_sRGB)),
1600 ("CAM16", "CAM16 JMh", CAM16_to_JMh_CAM16),
1601 ("CAM16 JMh", "CAM16", JMh_CAM16_to_CAM16),
1602 ("CIE XYZ", "CIECAM16", partial(XYZ_to_CIECAM16, **_CAM_KWARGS_CIECAM02_sRGB)),
1603 ("CIECAM16", "CIE XYZ", partial(CIECAM16_to_XYZ, **_CAM_KWARGS_CIECAM02_sRGB)),
1604 ("CIECAM16", "CIECAM16 JMh", CIECAM16_to_JMh_CIECAM16),
1605 ("CIECAM16 JMh", "CIECAM16", JMh_CIECAM16_to_CIECAM16),
1606 (
1607 "CIE XYZ",
1608 "Hellwig 2022",
1609 partial(XYZ_to_Hellwig2022, **_CAM_KWARGS_CIECAM02_sRGB),
1610 ),
1611 (
1612 "Hellwig 2022",
1613 "CIE XYZ",
1614 partial(Hellwig2022_to_XYZ, **_CAM_KWARGS_CIECAM02_sRGB),
1615 ),
1616 ("Hellwig 2022", "Hellwig 2022 JMh", Hellwig2022_to_JMh_Hellwig2022),
1617 ("Hellwig 2022 JMh", "Hellwig 2022", JMh_Hellwig2022_to_Hellwig2022),
1618 (
1619 "CIE XYZ",
1620 "Kim 2009",
1621 partial(XYZ_to_Kim2009, XYZ_w=_TVS_ILLUMINANT_DEFAULT, L_A=80 * 0.2),
1622 ),
1623 (
1624 "Kim 2009",
1625 "CIE XYZ",
1626 partial(Kim2009_to_XYZ, XYZ_w=_TVS_ILLUMINANT_DEFAULT, L_A=80 * 0.2),
1627 ),
1628 ("Kim 2009", "Kim 2009 JMh", Kim2009_to_JMh_Kim2009),
1629 ("Kim 2009 JMh", "Kim 2009", JMh_Kim2009_to_Kim2009),
1630 (
1631 "CIE XYZ",
1632 "Hunt",
1633 partial(
1634 XYZ_to_Hunt,
1635 XYZ_w=_TVS_ILLUMINANT_DEFAULT,
1636 XYZ_b=_TVS_ILLUMINANT_DEFAULT,
1637 L_A=80 * 0.2,
1638 CCT_w=6504,
1639 ),
1640 ),
1641 (
1642 "CIE XYZ",
1643 "LLAB",
1644 partial(XYZ_to_LLAB, XYZ_0=_TVS_ILLUMINANT_DEFAULT, Y_b=80 * 0.2, L=80),
1645 ),
1646 (
1647 "CIE XYZ",
1648 "Nayatani95",
1649 partial(
1650 XYZ_to_Nayatani95,
1651 XYZ_n=_TVS_ILLUMINANT_DEFAULT,
1652 Y_o=0.2,
1653 E_o=1000,
1654 E_or=1000,
1655 ),
1656 ),
1657 ("CIE XYZ", "RLAB", partial(XYZ_to_RLAB, XYZ_n=_TVS_ILLUMINANT_DEFAULT, Y_n=20)),
1658 ("CIE XYZ", "sCAM", partial(XYZ_to_sCAM, **_CAM_KWARGS_CIECAM02_sRGB)),
1659 ("sCAM", "CIE XYZ", partial(sCAM_to_XYZ, **_CAM_KWARGS_CIECAM02_sRGB)),
1660 ("sCAM", "sCAM JMh", sCAM_to_JMh_sCAM),
1661 ("sCAM JMh", "sCAM", JMh_sCAM_to_sCAM),
1662 (
1663 "CIE XYZ",
1664 "ZCAM",
1665 partial(
1666 XYZ_to_ZCAM, XYZ_w=_TVS_ILLUMINANT_DEFAULT, L_A=64 / np.pi * 0.2, Y_b=20
1667 ),
1668 ),
1669 (
1670 "ZCAM",
1671 "CIE XYZ",
1672 partial(
1673 ZCAM_to_XYZ, XYZ_w=_TVS_ILLUMINANT_DEFAULT, L_A=64 / np.pi * 0.2, Y_b=20
1674 ),
1675 ),
1676 ("ZCAM", "ZCAM JMh", ZCAM_to_JMh_ZCAM),
1677 ("ZCAM JMh", "ZCAM", JMh_ZCAM_to_ZCAM),
1678 ("CIECAM02 JMh", "CAM02LCD", JMh_CIECAM02_to_CAM02LCD),
1679 ("CAM02LCD", "CIECAM02 JMh", CAM02LCD_to_JMh_CIECAM02),
1680 ("CIECAM02 JMh", "CAM02SCD", JMh_CIECAM02_to_CAM02SCD),
1681 ("CAM02SCD", "CIECAM02 JMh", CAM02SCD_to_JMh_CIECAM02),
1682 ("CIECAM02 JMh", "CAM02UCS", JMh_CIECAM02_to_CAM02UCS),
1683 ("CAM02UCS", "CIECAM02 JMh", CAM02UCS_to_JMh_CIECAM02),
1684 ("CAM16 JMh", "CAM16LCD", JMh_CAM16_to_CAM16LCD),
1685 ("CAM16LCD", "CAM16 JMh", CAM16LCD_to_JMh_CAM16),
1686 ("CAM16 JMh", "CAM16SCD", JMh_CAM16_to_CAM16SCD),
1687 ("CAM16SCD", "CAM16 JMh", CAM16SCD_to_JMh_CAM16),
1688 ("CAM16 JMh", "CAM16UCS", JMh_CAM16_to_CAM16UCS),
1689 ("CAM16UCS", "CAM16 JMh", CAM16UCS_to_JMh_CAM16),
1690]
1691"""
1692Automatic colour conversion graph specifications data describing two nodes and
1693the edge in the graph.
1694"""
1697# Programmatically defining the colourspace models polar conversions.
1698def _format_node_name(name: str) -> str:
1699 """
1700 Format the specified name by applying a series of substitutions.
1702 This function transforms node names according to predefined patterns,
1703 typically converting underscores to hyphens and applying other naming
1704 conventions used in the colourspace models polar conversions system.
1706 Parameters
1707 ----------
1708 name
1709 The node name to format.
1711 Returns
1712 -------
1713 :class:`str`
1714 The formatted node name with substitutions applied.
1715 """
1717 for pattern, substitution in [
1718 ("hdr_", "hdr-"),
1719 ("-CIELab", "-CIELAB"),
1720 ("_", " "),
1721 ("^Lab", "CIE Lab"),
1722 ("^LCHab", "CIE LCHab"),
1723 ("^Luv", "CIE Luv"),
1724 ("^LCHuv", "CIE LCHuv"),
1725 ("Ragoo2021", "Ragoo 2021"),
1726 ]:
1727 name = re.sub(pattern, substitution, name)
1729 return name
1732for _Jab, _JCh in COLOURSPACE_MODELS_POLAR_CONVERSIONS:
1733 _module = sys.modules["colour.models"]
1734 _Jab_name = _format_node_name(_Jab)
1735 _JCh_name = _format_node_name(_JCh)
1736 CONVERSION_SPECIFICATIONS_DATA.append(
1737 (_Jab_name, _JCh_name, getattr(_module, f"{_Jab}_to_{_JCh}"))
1738 )
1739 CONVERSION_SPECIFICATIONS_DATA.append(
1740 (_JCh_name, _Jab_name, getattr(_module, f"{_JCh}_to_{_Jab}"))
1741 )
1743del _format_node_name, _JCh, _Jab, _module, _Jab_name, _JCh_name
1745CONVERSION_SPECIFICATIONS: list = [
1746 Conversion_Specification(*specification)
1747 for specification in CONVERSION_SPECIFICATIONS_DATA
1748]
1749"""
1750Automatic colour conversion graph specifications describing two nodes and
1751the edge in the graph.
1752"""
1754CONVERSION_GRAPH_NODE_LABELS: dict = {
1755 specification[0].lower(): specification[0]
1756 for specification in CONVERSION_SPECIFICATIONS_DATA
1757}
1758"""Automatic colour conversion graph node labels."""
1760CONVERSION_GRAPH_NODE_LABELS.update(
1761 {
1762 specification[1].lower(): specification[1]
1763 for specification in CONVERSION_SPECIFICATIONS_DATA
1764 }
1765)
1768@required("NetworkX")
1769def _build_graph() -> networkx.DiGraph: # pyright: ignore # noqa: F821
1770 """
1771 Build the automatic colour conversion graph.
1773 Returns
1774 -------
1775 :class:`networkx.DiGraph`
1776 Automatic colour conversion graph.
1777 """
1779 import networkx as nx # noqa: PLC0415
1781 graph = nx.DiGraph()
1783 for specification in CONVERSION_SPECIFICATIONS:
1784 graph.add_edge(
1785 specification.source,
1786 specification.target,
1787 conversion_function=specification.conversion_function,
1788 )
1790 return graph
1793CONVERSION_GRAPH: nx.DiGraph | None = None # pyright: ignore # noqa: F821
1794"""Automatic colour conversion graph."""
1797@required("NetworkX")
1798def conversion_path(source: str, target: str) -> List[Callable]:
1799 """
1800 Generate the conversion path from the source node to the target node in
1801 the automatic colour conversion graph.
1803 Parameters
1804 ----------
1805 source
1806 Source node.
1807 target
1808 Target node.
1810 Returns
1811 -------
1812 :class:`list`
1813 Conversion path from the source node to the target node, i.e., a
1814 list of conversion function callables.
1816 Examples
1817 --------
1818 >>> conversion_path("cie lab", "cct")
1819 ... # doctest: +ELLIPSIS
1820 [<function Lab_to_XYZ at 0x...>, <function XYZ_to_UCS at 0x...>, \
1821<function UCS_to_uv at 0x...>, <function uv_to_CCT at 0x...>]
1822 """
1824 import networkx as nx # noqa: PLC0415
1826 global CONVERSION_GRAPH # noqa: PLW0603
1828 if CONVERSION_GRAPH is None:
1829 # Updating the :attr:`CONVERSION_GRAPH` attributes.
1830 colour.graph.CONVERSION_GRAPH = CONVERSION_GRAPH = _build_graph()
1832 path = nx.shortest_path(cast("nx.DiGraph", CONVERSION_GRAPH), source, target)
1834 return [
1835 CONVERSION_GRAPH.get_edge_data(a, b)["conversion_function"] # pyright: ignore
1836 for a, b in itertools.pairwise(path)
1837 ]
1840def _lower_order_function(callable_: Callable) -> Callable:
1841 """
1842 Extract the lower-order function from the specified callable, such as the
1843 underlying function wrapped by a partial object.
1845 Parameters
1846 ----------
1847 callable_
1848 Callable from which to extract the lower-order function.
1850 Returns
1851 -------
1852 Callable
1853 Lower-order function if the callable is a partial object, otherwise
1854 the original callable.
1855 """
1857 return callable_.func if isinstance(callable_, partial) else callable_
1860def describe_conversion_path(
1861 source: str,
1862 target: str,
1863 mode: Literal["Short", "Long", "Extended"] | str = "Short",
1864 width: int = 79,
1865 padding: int = 3,
1866 print_callable: Callable = print,
1867 **kwargs: Any,
1868) -> None:
1869 """
1870 Describe the conversion path from the specified source colour
1871 representation to the specified target colour representation using the
1872 automatic colour conversion graph.
1874 Parameters
1875 ----------
1876 source
1877 Source colour representation, i.e., the source node in the automatic
1878 colour conversion graph.
1879 target
1880 Target colour representation, i.e., the target node in the automatic
1881 colour conversion graph.
1882 mode
1883 Verbose mode: *Short* describes the conversion path, *Long* provides
1884 details about the arguments, definitions signatures and output
1885 values, *Extended* appends the definitions' documentation.
1886 width
1887 Message box width.
1888 padding
1889 Padding on each side of the message.
1890 print_callable
1891 Callable used to print the message box.
1893 Other Parameters
1894 ----------------
1895 kwargs
1896 {:func:`colour.convert`},
1897 See the documentation of the previously listed definition.
1899 Raises
1900 ------
1901 ValueError
1902 If the mode is not one of the supported values.
1903 NetworkXNoPath
1904 If no conversion path exists between the source and target colour
1905 representations.
1907 Examples
1908 --------
1909 >>> describe_conversion_path("Spectral Distribution", "sRGB", width=75)
1910 ===========================================================================
1911 * *
1912 * [ Conversion Path ] *
1913 * *
1914 * "sd_to_XYZ" --> "XYZ_to_sRGB" *
1915 * *
1916 ===========================================================================
1917 """
1919 try: # pragma: no cover
1920 signature_inspection = inspect.signature
1921 except AttributeError: # pragma: no cover
1922 signature_inspection = inspect.getfullargspec
1924 source, target = source.lower(), target.lower()
1925 mode = validate_method(
1926 mode,
1927 ("Short", "Long", "Extended"),
1928 '"{0}" mode is invalid, it must be one of {1}!',
1929 )
1931 width = (79 + 2 + 2 * 3 - 4) if mode == "extended" else width
1933 conversion_functions = conversion_path(source, target)
1935 joined_conversion_path = " --> ".join(
1936 [
1937 f'"{_lower_order_function(conversion_function).__name__}"'
1938 for conversion_function in conversion_functions
1939 ]
1940 )
1942 message_box(
1943 f"[ Conversion Path ]\n\n{joined_conversion_path}",
1944 width,
1945 padding,
1946 print_callable,
1947 )
1949 for conversion_function in conversion_functions:
1950 conversion_function_name = _lower_order_function(conversion_function).__name__
1952 # Filtering compatible keyword arguments passed directly and
1953 # irrespective of any conversion function name.
1954 filtered_kwargs = filter_kwargs(conversion_function, **kwargs)
1956 # Filtering keyword arguments passed as dictionary with the
1957 # conversion function name.
1958 filtered_kwargs.update(kwargs.get(conversion_function_name, {}))
1960 return_value = filtered_kwargs.pop("return", None)
1962 if mode in ("long", "extended"):
1963 signature = pformat(
1964 signature_inspection(_lower_order_function(conversion_function))
1965 )
1966 message = (
1967 f'[ "{_lower_order_function(conversion_function).__name__}" ]\n\n'
1968 f"[ Signature ]\n\n"
1969 f"{signature}"
1970 )
1972 if filtered_kwargs:
1973 message += f"\n\n[ Filtered Arguments ]\n\n{pformat(filtered_kwargs)}"
1975 if mode in ("extended",):
1976 docstring = textwrap.dedent(
1977 str(_lower_order_function(conversion_function).__doc__)
1978 ).strip()
1979 message += f"\n\n[ Documentation ]\n\n {docstring}"
1981 if return_value is not None:
1982 message += f"\n\n[ Conversion Output ]\n\n{return_value}"
1984 message_box(message, width, padding, print_callable)
1987def convert(
1988 a: Any,
1989 source: str,
1990 target: str,
1991 *,
1992 from_reference_scale: bool = False,
1993 to_reference_scale: bool = False,
1994 **kwargs: Any,
1995) -> Any:
1996 """
1997 Convert specified object :math:`a` from source colour representation to
1998 target colour representation using the automatic colour conversion
1999 graph.
2001 The conversion is performed by finding the shortest path in a
2002 `NetworkX <https://networkx.github.io>`__ :class:`DiGraph` class
2003 instance.
2005 The conversion path adopts the **'1'** domain-range scale and the
2006 object :math:`a` is expected to be *soft* normalised accordingly. For
2007 example, *CIE XYZ* tristimulus values arguments for use with the
2008 *CAM16* colour appearance model should be in domain `[0, 1]` instead
2009 of the domain `[0, 100]` used with the **'Reference'** domain-range
2010 scale. The arguments are typically converted as follows:
2012 - *Scalars* in domain-range `[0, 10]`, e.g *Munsell Value* are
2013 scaled by *10*.
2014 - *Percentages* in domain-range `[0, 100]` are scaled by *100*.
2015 - *Degrees* in domain-range `[0, 360]` are scaled by *360*.
2016 - *Integers* in domain-range `[0, 2**n -1]` where `n` is the bit
2017 depth are scaled by *2**n -1*.
2019 The ``from_reference_scale`` and ``to_reference_scale`` parameters enable
2020 automatic scaling of input and output values based on the scale metadata in
2021 the function type annotations (PEP 593 ``Annotated`` hints).
2023 See the `Domain-Range Scales <../basics.html#domain-range-scales>`__
2024 page for more information.
2026 Parameters
2027 ----------
2028 a
2029 Object :math:`a` to convert. If :math:`a` represents a
2030 reflectance, transmittance or absorptance value, the expectation
2031 is that it is viewed under *CIE Standard Illuminant D Series*
2032 *D65*. The illuminant can be changed on a per-definition basis
2033 along the conversion path.
2034 source
2035 Source colour representation, i.e., the source node in the
2036 automatic colour conversion graph.
2037 target
2038 Target colour representation, i.e., the target node in the
2039 automatic colour conversion graph.
2040 from_reference_scale
2041 If *True*, the input value :math:`a` is assumed to be in reference
2042 scale (e.g., [0, 100] for CIE XYZ) and will be automatically scaled
2043 to scale-1 before conversion using the scale metadata from the first
2044 conversion function's type annotations.
2045 to_reference_scale
2046 If *True*, the output value will be automatically scaled from scale-1
2047 to reference scale (e.g., [0, 100] for CIE XYZ) after conversion using
2048 the scale metadata from the last conversion function's type annotations.
2050 Other Parameters
2051 ----------------
2052 kwargs
2053 See the documentation of the supported conversion definitions.
2055 Arguments for the conversion definitions are passed as keyword
2056 arguments whose names are those of the conversion definitions and
2057 values set as dictionaries. For example, in the conversion from
2058 spectral distribution to *sRGB* colourspace, passing arguments to
2059 the :func:`colour.sd_to_XYZ` definition is done as follows::
2061 convert(
2062 sd,
2063 "Spectral Distribution",
2064 "sRGB",
2065 sd_to_XYZ={"illuminant": SDS_ILLUMINANTS["FL2"]},
2066 )
2068 It is also possible to pass keyword arguments directly to the
2069 various conversion definitions irrespective of their name. This is
2070 ``dangerous`` and could cause unexpected behaviour, consider the
2071 following conversion::
2073 convert(sd, "Spectral Distribution", "sRGB", "illuminant": \
2074SDS_ILLUMINANTS["FL2"])
2076 Because both the :func:`colour.sd_to_XYZ` and
2077 :func:`colour.XYZ_to_sRGB` definitions have an *illuminant*
2078 argument, `SDS_ILLUMINANTS["FL2"]` will be passed to both of them
2079 and will raise an exception in the :func:`colour.XYZ_to_sRGB`
2080 definition. This will be addressed in the future by either
2081 catching the exception and trying a new time without the keyword
2082 argument or more elegantly via type checking.
2084 With that in mind, this mechanism offers some good benefits: For
2085 example, it allows defining a conversion from *CIE XYZ*
2086 colourspace to *n* different colour models while passing an
2087 illuminant argument but without having to explicitly define all
2088 the explicit conversion definition arguments::
2090 a = np.array([0.20654008, 0.12197225, 0.05136952])
2091 illuminant = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"]["D65"]
2092 for model in ("CIE xyY", "CIE Lab"):
2093 convert(a, "CIE XYZ", model, illuminant=illuminant)
2095 Instead of::
2097 for model in ("CIE xyY", "CIE Lab"):
2098 convert(
2099 a,
2100 "CIE XYZ",
2101 model,
2102 XYZ_to_xyY={"illuminant": illuminant},
2103 XYZ_to_Lab={"illuminant": illuminant},
2104 )
2106 Mixing both approaches is possible for the brevity benefits. It is
2107 made possible because the keyword arguments directly passed are
2108 filtered first and then the resulting dict is updated with the
2109 explicit conversion definition arguments::
2111 illuminant = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"]["D65"]
2112 convert(sd, "Spectral Distribution", "sRGB", "illuminant": \
2113SDS_ILLUMINANTS["FL2"], XYZ_to_sRGB={"illuminant": illuminant})
2115 For inspection purposes, verbose is enabled by passing arguments
2116 to the :func:`colour.describe_conversion_path` definition via the
2117 ``verbose`` keyword argument as follows::
2119 convert(sd, "Spectral Distribution", "sRGB", verbose={"mode": "Long"})
2121 Returns
2122 -------
2123 Any
2124 Converted object :math:`a`.
2126 Raises
2127 ------
2128 NetworkXNoPath
2129 If no conversion path exists between the source and target colour
2130 representations.
2132 Warnings
2133 --------
2134 The domain-range scale is **'1'** and cannot be changed.
2136 Notes
2137 -----
2138 - The **RGB** colour representation is assumed to be linear and
2139 representing *scene-referred* imagery, i.e., **Scene-Referred
2140 RGB** representation. To encode such *RGB* values as
2141 *output-referred* (*display-referred*) imagery, i.e., encode the
2142 *RGB* values using an encoding colour component transfer function
2143 (Encoding CCTF) / opto-electronic transfer function (OETF), the
2144 **Output-Referred RGB** representation must be used::
2146 convert(RGB, "Scene-Referred RGB", "Output-Referred RGB")
2148 Likewise, encoded *output-referred* *RGB* values can be decoded
2149 with the **Scene-Referred RGB** representation::
2151 convert(RGB, "Output-Referred RGB", "Scene-Referred RGB")
2153 - The following defaults have been adopted:
2155 - The default illuminant for the computation is *CIE Standard
2156 Illuminant D Series* *D65*. It can be changed on a
2157 per-definition basis along the conversion path. Note that the
2158 conversion from spectral to *CIE XYZ* tristimulus values
2159 remains unchanged.
2160 - The default *RGB* colourspace primaries and whitepoint are
2161 that of the *BT.709*/*sRGB* colourspace. They can be changed
2162 on a per-definition basis along the conversion path.
2163 - When using **sRGB** as a source or target colour
2164 representation, the convenient :func:`colour.sRGB_to_XYZ` and
2165 :func:`colour.XYZ_to_sRGB` definitions are used,
2166 respectively. Thus, decoding and encoding using the sRGB
2167 electro-optical transfer function (EOTF) and its inverse will
2168 be applied by default.
2169 - Most of the colour appearance models have defaults set
2170 according to *IEC 61966-2-1:1999* viewing conditions, i.e.,
2171 *sRGB* 64 Lux ambient illumination, 80 :math:`cd/m^2`,
2172 adapting field luminance about 20% of a white object in the
2173 scene.
2175 Examples
2176 --------
2177 >>> import numpy as np
2178 >>> from colour import SDS_COLOURCHECKERS, SDS_ILLUMINANTS
2179 >>> sd = SDS_COLOURCHECKERS["ColorChecker N Ohta"]["dark skin"]
2180 >>> convert(
2181 ... sd,
2182 ... "Spectral Distribution",
2183 ... "sRGB",
2184 ... verbose={"mode": "Short", "width": 75},
2185 ... )
2186 ... # doctest: +ELLIPSIS
2187 ===========================================================================
2188 * *
2189 * [ Conversion Path ] *
2190 * *
2191 * "sd_to_XYZ" --> "XYZ_to_sRGB" *
2192 * *
2193 ===========================================================================
2194 array([ 0.4903477..., 0.3018587..., 0.2358768...])
2195 >>> illuminant = SDS_ILLUMINANTS["FL2"]
2196 >>> convert(
2197 ... sd,
2198 ... "Spectral Distribution",
2199 ... "sRGB",
2200 ... sd_to_XYZ={"illuminant": illuminant},
2201 ... )
2202 ... # doctest: +ELLIPSIS
2203 array([ 0.4792457..., 0.3167696..., 0.1736272...])
2204 >>> a = np.array([0.45675795, 0.30986982, 0.24861924])
2205 >>> convert(a, "Output-Referred RGB", "CAM16UCS")
2206 ... # doctest: +ELLIPSIS
2207 array([ 0.3999481..., 0.0920655..., 0.0812752...])
2208 >>> a = np.array([0.39994811, 0.09206558, 0.08127526])
2209 >>> convert(a, "CAM16UCS", "sRGB", verbose={"mode": "Short", "width": 75})
2210 ... # doctest: +ELLIPSIS
2211 ===========================================================================
2212 * *
2213 * [ Conversion Path ] *
2214 * *
2215 * "UCS_Li2017_to_JMh_CAM16" --> "JMh_CAM16_to_CAM16" --> *
2216 * "CAM16_to_XYZ" --> "XYZ_to_sRGB" *
2217 * *
2218 ===========================================================================
2219 array([ 0.4567576..., 0.3098826..., 0.2486222...])
2220 """
2222 source, target = source.lower(), target.lower()
2224 conversion_path_list = conversion_path(source, target)
2226 verbose_kwargs = copy(kwargs)
2227 for i, conversion_function in enumerate(conversion_path_list):
2228 conversion_function_name = _lower_order_function(conversion_function).__name__
2230 # Scale input from reference to scale-1 on first iteration
2231 if i == 0 and from_reference_scale:
2232 metadata = get_domain_range_scale_metadata(
2233 _lower_order_function(conversion_function)
2234 )
2235 if (
2236 metadata["domain"]
2237 and (scale := next(iter(metadata["domain"].values()))) is not None
2238 ):
2239 a = a / as_float_array(scale)
2241 # Filtering compatible keyword arguments passed directly and
2242 # irrespective of any conversion function name.
2243 filtered_kwargs = filter_kwargs(conversion_function, **kwargs)
2245 # Filtering keyword arguments passed as dictionary with the
2246 # conversion function name.
2247 filtered_kwargs.update(kwargs.get(conversion_function_name, {}))
2249 with domain_range_scale("1"):
2250 a = conversion_function(a, **filtered_kwargs)
2252 # Scale output from scale-1 to reference on last iteration
2253 if i == len(conversion_path_list) - 1 and to_reference_scale:
2254 metadata = get_domain_range_scale_metadata(
2255 _lower_order_function(conversion_function)
2256 )
2257 if (scale := metadata["range"]) is not None:
2258 a = a * as_float_array(scale)
2260 if conversion_function_name in verbose_kwargs:
2261 verbose_kwargs[conversion_function_name]["return"] = a
2262 else:
2263 verbose_kwargs[conversion_function_name] = {"return": a}
2265 if "verbose" in verbose_kwargs:
2266 verbose_kwargs.update(verbose_kwargs.pop("verbose"))
2267 describe_conversion_path(source, target, **verbose_kwargs)
2269 return a