Coverage for models/rgb/rgb_colourspace.py: 72%
243 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"""
2RGB Colourspace and Transformations
3===================================
5Define the :class:`colour.RGB_Colourspace` class for the *RGB* colourspaces
6datasets from :mod:`colour.models.datasets.aces_rgb`, etc... and the following
7*RGB* colourspace transformations or helper definitions:
9- :func:`colour.XYZ_to_RGB`
10- :func:`colour.RGB_to_XYZ`
11- :func:`colour.matrix_RGB_to_RGB`
12- :func:`colour.RGB_to_RGB`
14References
15----------
16- :cite:`InternationalElectrotechnicalCommission1999a` : International
17 Electrotechnical Commission. (1999). IEC 61966-2-1:1999 - Multimedia
18 systems and equipment - Colour measurement and management - Part 2-1:
19 Colour management - Default RGB colour space - sRGB (p. 51).
20 https://webstore.iec.ch/publication/6169
21- :cite:`Panasonic2014a` : Panasonic. (2014). VARICAM V-Log/V-Gamut (pp.
22 1-7).
23 http://pro-av.panasonic.net/en/varicam/common/pdf/VARICAM_V-Log_V-Gamut.pdf
24"""
26from __future__ import annotations
28import typing
29from copy import deepcopy
31import numpy as np
33from colour.adaptation import matrix_chromatic_adaptation_VonKries
34from colour.algebra import vecmul
36if typing.TYPE_CHECKING:
37 from colour.hints import (
38 Any,
39 ArrayLike,
40 Callable,
41 Domain1,
42 LiteralChromaticAdaptationTransform,
43 LiteralRGBColourspace,
44 NDArrayFloat,
45 Range1,
46 )
48from colour.hints import cast
49from colour.models import xy_to_xyY, xy_to_XYZ, xyY_to_XYZ
50from colour.models.rgb import chromatically_adapted_primaries, normalised_primary_matrix
51from colour.utilities import (
52 as_float_array,
53 attest,
54 domain_range_scale,
55 filter_kwargs,
56 from_range_1,
57 multiline_repr,
58 multiline_str,
59 optional,
60 to_domain_1,
61 usage_warning,
62 validate_method,
63)
65__author__ = "Colour Developers"
66__copyright__ = "Copyright 2013 Colour Developers"
67__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
68__maintainer__ = "Colour Developers"
69__email__ = "colour-developers@colour-science.org"
70__status__ = "Production"
72__all__ = [
73 "RGB_Colourspace",
74 "XYZ_to_RGB",
75 "RGB_to_XYZ",
76 "matrix_RGB_to_RGB",
77 "RGB_to_RGB",
78]
81class RGB_Colourspace:
82 """
83 Implement support for *RGB* colourspace datasets from modules including
84 :mod:`colour.models.datasets.aces_rgb`.
86 Colour science literature related to *RGB* colourspaces and encodings
87 defines datasets using different degrees of precision or rounding. While
88 instances where a whitepoint differs from its canonical agreed value are
89 rare, normalised primary matrices are commonly rounded at different
90 decimal places. This can yield large discrepancies in computations.
92 Such an occurrence is the *V-Gamut* colourspace white paper, which
93 defines the *V-Gamut* to *ITU-R BT.709* conversion matrix as follows::
95 [[ 1.806576 -0.695697 -0.110879]
96 [-0.170090 1.305955 -0.135865]
97 [-0.025206 -0.154468 1.179674]]
99 Computing this matrix using *ITU-R BT.709* colourspace derived
100 normalised primary matrix yields::
102 [[ 1.8065736 -0.6956981 -0.1108786]
103 [-0.1700890 1.3059548 -0.1358648]
104 [-0.0252057 -0.1544678 1.1796737]]
106 The latter matrix is almost equal to the former, however performing the
107 same computation using *IEC 61966-2-1:1999* *sRGB* colourspace
108 normalised primary matrix introduces severe disparities::
110 [[ 1.8063853 -0.6956147 -0.1109453]
111 [-0.1699311 1.3058387 -0.1358616]
112 [-0.0251630 -0.1544899 1.1797117]]
114 To provide support for both literature-defined datasets and accurate
115 computations enabling transformations without loss of precision, the
116 :class:`colour.RGB_Colourspace` class provides two sets of transformation
117 matrices:
119 - Instantiation transformation matrices
120 - Derived transformation matrices
122 Upon instantiation, the :class:`colour.RGB_Colourspace` class stores the
123 specified ``matrix_RGB_to_XYZ`` and ``matrix_XYZ_to_RGB`` arguments and
124 also computes their derived counterparts using the ``primaries`` and
125 ``whitepoint`` arguments.
127 Whether instantiation or derived matrices are used in subsequent
128 computations depends on the
129 :attr:`colour.RGB_Colourspace.use_derived_matrix_RGB_to_XYZ` and
130 :attr:`colour.RGB_Colourspace.use_derived_matrix_XYZ_to_RGB` attribute
131 values.
133 Parameters
134 ----------
135 name
136 *RGB* colourspace name.
137 primaries
138 *RGB* colourspace primaries.
139 whitepoint
140 *RGB* colourspace whitepoint.
141 whitepoint_name
142 *RGB* colourspace whitepoint name.
143 matrix_RGB_to_XYZ
144 Transformation matrix from colourspace to *CIE XYZ* tristimulus
145 values.
146 matrix_XYZ_to_RGB
147 Transformation matrix from *CIE XYZ* tristimulus values to
148 colourspace.
149 cctf_encoding
150 Encoding colour component transfer function (Encoding CCTF) /
151 opto-electronic transfer function (OETF) that maps estimated
152 tristimulus values in a scene to :math:`R'G'B'` video component
153 signal value.
154 cctf_decoding
155 Decoding colour component transfer function (Decoding CCTF) /
156 electro-optical transfer function (EOTF) that maps an
157 :math:`R'G'B'` video component signal value to tristimulus values at
158 the display.
159 use_derived_matrix_RGB_to_XYZ
160 Whether to use the instantiation time normalised primary matrix or to
161 use a computed derived normalised primary matrix.
162 use_derived_matrix_XYZ_to_RGB
163 Whether to use the instantiation time inverse normalised primary
164 matrix or to use a computed derived inverse normalised primary
165 matrix.
167 Attributes
168 ----------
169 - :attr:`~colour.RGB_Colourspace.name`
170 - :attr:`~colour.RGB_Colourspace.primaries`
171 - :attr:`~colour.RGB_Colourspace.whitepoint`
172 - :attr:`~colour.RGB_Colourspace.whitepoint_name`
173 - :attr:`~colour.RGB_Colourspace.matrix_RGB_to_XYZ`
174 - :attr:`~colour.RGB_Colourspace.matrix_XYZ_to_RGB`
175 - :attr:`~colour.RGB_Colourspace.cctf_encoding`
176 - :attr:`~colour.RGB_Colourspace.cctf_decoding`
177 - :attr:`~colour.RGB_Colourspace.use_derived_matrix_RGB_to_XYZ`
178 - :attr:`~colour.RGB_Colourspace.use_derived_matrix_XYZ_to_RGB`
180 Methods
181 -------
182 - :attr:`~colour.RGB_Colourspace.__init__`
183 - :attr:`~colour.RGB_Colourspace.__str__`
184 - :attr:`~colour.RGB_Colourspace.__repr__`
185 - :attr:`~colour.RGB_Colourspace.use_derived_transformation_matrices`
186 - :attr:`~colour.RGB_Colourspace.chromatically_adapt`
187 - :attr:`~colour.RGB_Colourspace.copy`
189 Notes
190 -----
191 - The normalised primary matrix defined by
192 :attr:`colour.RGB_Colourspace.matrix_RGB_to_XYZ` property is treated
193 as the prime matrix from which the inverse will be calculated as
194 required by the internal derivation mechanism. This behaviour has
195 been chosen in accordance with literature where commonly a *RGB*
196 colourspace is defined by its normalised primary matrix as it is
197 directly computed from the chosen primaries and whitepoint.
199 References
200 ----------
201 :cite:`InternationalElectrotechnicalCommission1999a`,
202 :cite:`Panasonic2014a`
204 Examples
205 --------
206 >>> p = np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700])
207 >>> whitepoint = np.array([0.32168, 0.33767])
208 >>> matrix_RGB_to_XYZ = np.identity(3)
209 >>> matrix_XYZ_to_RGB = np.identity(3)
210 >>> colourspace = RGB_Colourspace(
211 ... "RGB Colourspace",
212 ... p,
213 ... whitepoint,
214 ... "ACES",
215 ... matrix_RGB_to_XYZ,
216 ... matrix_XYZ_to_RGB,
217 ... )
218 >>> colourspace.matrix_RGB_to_XYZ
219 array([[ 1., 0., 0.],
220 [ 0., 1., 0.],
221 [ 0., 0., 1.]])
222 >>> colourspace.matrix_XYZ_to_RGB
223 array([[ 1., 0., 0.],
224 [ 0., 1., 0.],
225 [ 0., 0., 1.]])
226 >>> colourspace.use_derived_transformation_matrices(True)
227 >>> colourspace.matrix_RGB_to_XYZ # doctest: +ELLIPSIS
228 array([[ 9.5255239...e-01, 0.0000000...e+00, 9.3678631...e-05],
229 [ 3.4396645...e-01, 7.2816609...e-01, -7.2132546...e-02],
230 [ 0.0000000...e+00, 0.0000000...e+00, 1.0088251...e+00]])
231 >>> colourspace.matrix_XYZ_to_RGB # doctest: +ELLIPSIS
232 array([[ 1.0498110...e+00, 0.0000000...e+00, -9.7484540...e-05],
233 [ -4.9590302...e-01, 1.3733130...e+00, 9.8240036...e-02],
234 [ 0.0000000...e+00, 0.0000000...e+00, 9.9125201...e-01]])
235 >>> colourspace.use_derived_matrix_RGB_to_XYZ = False
236 >>> colourspace.matrix_RGB_to_XYZ
237 array([[ 1., 0., 0.],
238 [ 0., 1., 0.],
239 [ 0., 0., 1.]])
240 >>> colourspace.use_derived_matrix_XYZ_to_RGB = False
241 >>> colourspace.matrix_XYZ_to_RGB
242 array([[ 1., 0., 0.],
243 [ 0., 1., 0.],
244 [ 0., 0., 1.]])
245 """
247 def __init__(
248 self,
249 name: str,
250 primaries: ArrayLike,
251 whitepoint: ArrayLike,
252 whitepoint_name: str | None = None,
253 matrix_RGB_to_XYZ: ArrayLike | None = None,
254 matrix_XYZ_to_RGB: ArrayLike | None = None,
255 cctf_encoding: Callable | None = None,
256 cctf_decoding: Callable | None = None,
257 use_derived_matrix_RGB_to_XYZ: bool = False,
258 use_derived_matrix_XYZ_to_RGB: bool = False,
259 ) -> None:
260 self._derived_matrix_RGB_to_XYZ: NDArrayFloat = np.array([])
261 self._derived_matrix_XYZ_to_RGB: NDArrayFloat = np.array([])
263 self._name: str = f"{self.__class__.__name__} ({id(self)})"
264 self.name = name
265 self._primaries: NDArrayFloat = np.array([])
266 self.primaries = primaries
267 self._whitepoint: NDArrayFloat = np.array([])
268 self.whitepoint = whitepoint
269 self._whitepoint_name: str | None = None
270 self.whitepoint_name = whitepoint_name
271 self._matrix_RGB_to_XYZ: NDArrayFloat | None = None
272 self.matrix_RGB_to_XYZ = matrix_RGB_to_XYZ
273 self._matrix_XYZ_to_RGB: NDArrayFloat | None = None
274 self.matrix_XYZ_to_RGB = matrix_XYZ_to_RGB
275 self._cctf_encoding: Callable | None = None
276 self.cctf_encoding = cctf_encoding
277 self._cctf_decoding: Callable | None = None
278 self.cctf_decoding = cctf_decoding
279 self._use_derived_matrix_RGB_to_XYZ: bool = False
280 self.use_derived_matrix_RGB_to_XYZ = use_derived_matrix_RGB_to_XYZ
281 self._use_derived_matrix_XYZ_to_RGB: bool = False
282 self.use_derived_matrix_XYZ_to_RGB = use_derived_matrix_XYZ_to_RGB
284 @property
285 def name(self) -> str:
286 """
287 Getter and setter for the *RGB* colourspace name.
289 Parameters
290 ----------
291 value
292 Value to set the name with.
294 Returns
295 -------
296 :class:`str`
297 *RGB* colourspace name.
298 """
300 return self._name
302 @name.setter
303 def name(self, value: str) -> None:
304 """Setter for the **self.name** property."""
306 attest(
307 isinstance(value, str),
308 f'"name" property: "{value}" type is not "str"!',
309 )
311 self._name = value
313 @property
314 def primaries(self) -> NDArrayFloat:
315 """
316 Getter and setter for the *RGB* colourspace primaries.
318 Parameters
319 ----------
320 value
321 Value to set the primaries with.
323 Returns
324 -------
325 :class:`numpy.ndarray`
326 *RGB* colourspace primaries.
327 """
329 return self._primaries
331 @primaries.setter
332 def primaries(self, value: ArrayLike) -> None:
333 """Setter for the **self.primaries** property."""
335 attest(
336 isinstance(value, (tuple, list, np.ndarray, np.matrix)),
337 f'"primaries" property: "{value!r}" is not a "tuple", '
338 f'"list", "ndarray" or "matrix" instance!',
339 )
341 value = as_float_array(value)
343 value = np.reshape(value, (3, 2))
345 self._primaries = value
347 self._derived_matrix_XYZ_to_RGB = np.array([])
348 self._derived_matrix_RGB_to_XYZ = np.array([])
350 @property
351 def whitepoint(self) -> NDArrayFloat:
352 """
353 Getter and setter for the *RGB* colourspace whitepoint.
355 Parameters
356 ----------
357 value
358 Value to set the whitepoint with.
360 Returns
361 -------
362 :class:`numpy.ndarray`
363 *RGB* colourspace whitepoint.
364 """
366 return self._whitepoint
368 @whitepoint.setter
369 def whitepoint(self, value: ArrayLike) -> None:
370 """Setter for the **self.whitepoint** property."""
372 attest(
373 isinstance(value, (tuple, list, np.ndarray, np.matrix)),
374 f'"whitepoint" property: "{value!r}" is not a "tuple", '
375 f'"list", "ndarray" or "matrix" instance!',
376 )
378 value = as_float_array(value)
380 self._whitepoint = value
381 self._derived_matrix_XYZ_to_RGB = np.array([])
382 self._derived_matrix_RGB_to_XYZ = np.array([])
384 @property
385 def whitepoint_name(self) -> str | None:
386 """
387 Getter and setter for the *RGB* colourspace whitepoint name.
389 Define or retrieve the name identifier for the reference illuminant
390 (whitepoint) used by this *RGB* colourspace. This property allows
391 tracking of standardized illuminant names such as 'D65', 'D50', or
392 custom whitepoint identifiers.
394 Parameters
395 ----------
396 value
397 Name identifier to set for the *RGB* colourspace whitepoint. Can
398 be a standard illuminant name or custom identifier.
400 Returns
401 -------
402 :class:`str` or :py:data:`None`
403 *RGB* colourspace whitepoint name identifier. Returns
404 :py:data:`None` if no name has been specified.
405 """
407 return self._whitepoint_name
409 @whitepoint_name.setter
410 def whitepoint_name(self, value: str | None) -> None:
411 """Setter for the **self.whitepoint_name** property."""
413 if value is not None:
414 attest(
415 isinstance(value, str),
416 f'"whitepoint_name" property: "{value}" type is not "str"!',
417 )
419 self._whitepoint_name = value
421 @property
422 def matrix_RGB_to_XYZ(self) -> NDArrayFloat:
423 """
424 Getter and setter for the transformation matrix from RGB colourspace
425 to *CIE XYZ* tristimulus values.
427 Parameters
428 ----------
429 value
430 Transformation matrix from RGB colourspace to *CIE XYZ*
431 tristimulus values.
433 Returns
434 -------
435 :class:`numpy.ndarray`
436 Transformation matrix from RGB colourspace to *CIE XYZ*
437 tristimulus values.
438 """
440 if self._matrix_RGB_to_XYZ is None or self._use_derived_matrix_RGB_to_XYZ:
441 if self._derived_matrix_RGB_to_XYZ.size == 0:
442 self._derive_transformation_matrices()
444 return self._derived_matrix_RGB_to_XYZ
446 return self._matrix_RGB_to_XYZ
448 @matrix_RGB_to_XYZ.setter
449 def matrix_RGB_to_XYZ(self, value: ArrayLike | None) -> None:
450 """Setter for the **self.matrix_RGB_to_XYZ** property."""
452 if value is not None:
453 attest(
454 isinstance(value, (tuple, list, np.ndarray, np.matrix)),
455 f'"matrix_RGB_to_XYZ" property: "{value!r}" is not a "tuple", '
456 f'"list", "ndarray" or "matrix" instance!',
457 )
459 value = as_float_array(value)
461 self._matrix_RGB_to_XYZ = value
463 @property
464 def matrix_XYZ_to_RGB(self) -> NDArrayFloat:
465 """
466 Getter and setter for the transformation matrix from *CIE XYZ*
467 tristimulus values to RGB colourspace.
469 Parameters
470 ----------
471 value
472 Transformation matrix from *CIE XYZ* tristimulus values to the
473 colourspace.
475 Returns
476 -------
477 :class:`numpy.ndarray`
478 Transformation matrix from *CIE XYZ* tristimulus values to the
479 colourspace.
480 """
482 if self._matrix_XYZ_to_RGB is None or self._use_derived_matrix_XYZ_to_RGB:
483 if self._derived_matrix_XYZ_to_RGB.size == 0:
484 self._derive_transformation_matrices()
486 return self._derived_matrix_XYZ_to_RGB
488 return self._matrix_XYZ_to_RGB
490 @matrix_XYZ_to_RGB.setter
491 def matrix_XYZ_to_RGB(self, value: ArrayLike | None) -> None:
492 """Setter for the **self.matrix_XYZ_to_RGB** property."""
494 if value is not None:
495 attest(
496 isinstance(value, (tuple, list, np.ndarray, np.matrix)),
497 f'"matrix_XYZ_to_RGB" property: "{value!r}" is not a "tuple", '
498 f'"list", "ndarray" or "matrix" instance!',
499 )
501 value = as_float_array(value)
503 self._matrix_XYZ_to_RGB = value
505 @property
506 def cctf_encoding(self) -> Callable | None:
507 """
508 Getter and setter for the encoding colour component transfer
509 function (Encoding CCTF) / opto-electronic transfer function (OETF).
511 Parameters
512 ----------
513 value
514 Encoding colour component transfer function (Encoding CCTF) /
515 opto-electronic transfer function (OETF).
517 Returns
518 -------
519 Callable or :py:data:`None`
520 Encoding colour component transfer function (Encoding CCTF) /
521 opto-electronic transfer function (OETF).
522 """
524 return self._cctf_encoding
526 @cctf_encoding.setter
527 def cctf_encoding(self, value: Callable | None) -> None:
528 """Setter for the **self.cctf_encoding** property."""
530 if value is not None:
531 attest(
532 callable(value),
533 f'"cctf_encoding" property: "{value}" is not callable!',
534 )
536 self._cctf_encoding = value
538 @property
539 def cctf_decoding(self) -> Callable | None:
540 """
541 Getter and setter for the decoding colour component transfer
542 function (Decoding CCTF) / electro-optical transfer function
543 (EOTF).
545 Parameters
546 ----------
547 value
548 Decoding colour component transfer function (Decoding CCTF) /
549 electro-optical transfer function (EOTF).
551 Returns
552 -------
553 Callable or :py:data:`None`
554 Decoding colour component transfer function (Decoding CCTF) /
555 electro-optical transfer function (EOTF).
556 """
558 return self._cctf_decoding
560 @cctf_decoding.setter
561 def cctf_decoding(self, value: Callable | None) -> None:
562 """Setter for the **self.cctf_decoding** property."""
564 if value is not None:
565 attest(
566 callable(value),
567 f'"cctf_decoding" property: "{value}" is not callable!',
568 )
570 self._cctf_decoding = value
572 @property
573 def use_derived_matrix_RGB_to_XYZ(self) -> bool:
574 """
575 Getter and setter for whether to use the instantiation time normalised
576 primary matrix or to use a computed derived normalised primary matrix.
578 Control whether the RGB to XYZ transformation uses the pre-computed
579 normalised primary matrix from instantiation or derives it dynamically
580 from the current primaries and whitepoint.
582 Parameters
583 ----------
584 value
585 Whether to use the instantiation time normalised primary matrix or
586 to use a computed derived normalised primary matrix.
588 Returns
589 -------
590 :class:`bool`
591 Whether to use the instantiation time normalised primary matrix or
592 to use a computed derived normalised primary matrix.
593 """
595 return self._use_derived_matrix_RGB_to_XYZ
597 @use_derived_matrix_RGB_to_XYZ.setter
598 def use_derived_matrix_RGB_to_XYZ(self, value: bool) -> None:
599 """Setter for the **self.use_derived_matrix_RGB_to_XYZ** property."""
601 attest(
602 isinstance(value, (bool, np.bool_)),
603 f'"use_derived_matrix_RGB_to_XYZ" property: "{value}" is not a "bool"!',
604 )
606 self._use_derived_matrix_RGB_to_XYZ = value
608 @property
609 def use_derived_matrix_XYZ_to_RGB(self) -> bool:
610 """
611 Getter and setter for whether to use the instantiation time inverse
612 normalised primary matrix or to compute a derived inverse normalised
613 primary matrix.
615 Control whether the XYZ to RGB transformation uses the pre-computed
616 inverse matrix from instantiation or derives it dynamically from the
617 current primary matrix.
619 Parameters
620 ----------
621 value
622 Whether to use the instantiation time inverse normalised primary
623 matrix or to compute a derived inverse normalised primary matrix.
625 Returns
626 -------
627 :class:`bool`
628 Whether to use the instantiation time inverse normalised primary
629 matrix or to compute a derived inverse normalised primary matrix.
630 """
632 return self._use_derived_matrix_XYZ_to_RGB
634 @use_derived_matrix_XYZ_to_RGB.setter
635 def use_derived_matrix_XYZ_to_RGB(self, value: bool) -> None:
636 """Setter for the **self.use_derived_matrix_XYZ_to_RGB** property."""
638 attest(
639 isinstance(value, (bool, np.bool_)),
640 f'"use_derived_matrix_XYZ_to_RGB" property: "{value}" is not a "bool"!',
641 )
643 self._use_derived_matrix_XYZ_to_RGB = value
645 def __str__(self) -> str:
646 """
647 Generate a formatted string representation of the *RGB* colourspace.
649 Returns
650 -------
651 :class:`str`
652 Formatted string representation displaying colourspace properties
653 including primaries, whitepoint, encoding/decoding CCTFs, and
654 normalised primary matrices.
656 Examples
657 --------
658 >>> p = np.array(
659 ... [0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700]
660 ... )
661 >>> whitepoint = np.array([0.32168, 0.33767])
662 >>> matrix_RGB_to_XYZ = np.identity(3)
663 >>> matrix_XYZ_to_RGB = np.identity(3)
664 >>> cctf_encoding = lambda x: x
665 >>> cctf_decoding = lambda x: x
666 >>> print(
667 ... RGB_Colourspace(
668 ... "RGB Colourspace",
669 ... p,
670 ... whitepoint,
671 ... "ACES",
672 ... matrix_RGB_to_XYZ,
673 ... matrix_XYZ_to_RGB,
674 ... cctf_encoding,
675 ... cctf_decoding,
676 ... )
677 ... )
678 ... # doctest: +ELLIPSIS
679 RGB Colourspace
680 ---------------
681 <BLANKLINE>
682 Primaries : [[ 7.34700000e-01 2.65300000e-01]
683 [ 0.00000000e+00 1.00000000e+00]
684 [ 1.00000000e-04 -7.70000000e-02]]
685 Whitepoint : [ 0.32168 0.33767]
686 Whitepoint Name : ACES
687 Encoding CCTF : <function <lambda> at 0x...>
688 Decoding CCTF : <function <lambda> at 0x...>
689 NPM : [[ 1. 0. 0.]
690 [ 0. 1. 0.]
691 [ 0. 0. 1.]]
692 NPM -1 : [[ 1. 0. 0.]
693 [ 0. 1. 0.]
694 [ 0. 0. 1.]]
695 Derived NPM : \
696[[ 9.5255239...e-01 0.0000000...e+00 9.3678631...e-05]
697 \
698 [ 3.4396645...e-01 7.2816609...e-01 -7.2132546...e-02]
699 \
700 [ 0.0000000...e+00 0.0000000...e+00 1.0088251...e+00]]
701 Derived NPM -1 : \
702[[ 1.0498110...e+00 0.0000000...e+00 -9.7484540...e-05]
703 \
704 [ -4.9590302...e-01 1.3733130...e+00 9.8240036...e-02]
705 \
706 [ 0.0000000...e+00 0.0000000...e+00 9.9125201...e-01]]
707 Use Derived NPM : False
708 Use Derived NPM -1 : False
709 """
710 if self._derived_matrix_XYZ_to_RGB.size == 0:
711 self._derive_transformation_matrices()
713 return multiline_str(
714 self,
715 [
716 {"name": "name", "section": True},
717 {"line_break": True},
718 {"name": "primaries", "label": "Primaries"},
719 {"name": "whitepoint", "label": "Whitepoint"},
720 {"name": "whitepoint_name", "label": "Whitepoint Name"},
721 {"name": "cctf_encoding", "label": "Encoding CCTF"},
722 {"name": "cctf_decoding", "label": "Decoding CCTF"},
723 {"name": "_matrix_RGB_to_XYZ", "label": "NPM"},
724 {"name": "_matrix_XYZ_to_RGB", "label": "NPM -1"},
725 {
726 "name": "_derived_matrix_RGB_to_XYZ",
727 "label": "Derived NPM",
728 },
729 {
730 "name": "_derived_matrix_XYZ_to_RGB",
731 "label": "Derived NPM -1",
732 },
733 {
734 "name": "use_derived_matrix_RGB_to_XYZ",
735 "label": "Use Derived NPM",
736 },
737 {
738 "name": "use_derived_matrix_XYZ_to_RGB",
739 "label": "Use Derived NPM -1",
740 },
741 ],
742 )
744 def __repr__(self) -> str:
745 """
746 Return an evaluable string representation of the *RGB* colourspace.
748 The representation includes all parameters needed to reconstruct the
749 colourspace instance.
751 Returns
752 -------
753 :class:`str`
754 Evaluable string representation.
756 Examples
757 --------
758 >>> from colour.models import linear_function
759 >>> p = np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700])
760 >>> whitepoint = np.array([0.32168, 0.33767])
761 >>> matrix_RGB_to_XYZ = np.identity(3)
762 >>> matrix_XYZ_to_RGB = np.identity(3)
763 >>> RGB_Colourspace(
764 ... "RGB Colourspace",
765 ... p,
766 ... whitepoint,
767 ... "ACES",
768 ... matrix_RGB_to_XYZ,
769 ... matrix_XYZ_to_RGB,
770 ... linear_function,
771 ... linear_function,
772 ... )
773 ... # doctest: +ELLIPSIS
774 RGB_Colourspace('RGB Colourspace',
775 [[ 7.34700000e-01, 2.65300000e-01],
776 [ 0.00000000e+00, 1.00000000e+00],
777 [ 1.00000000e-04, -7.70000000e-02]],
778 [ 0.32168, 0.33767],
779 'ACES',
780 [[ 1., 0., 0.],
781 [ 0., 1., 0.],
782 [ 0., 0., 1.]],
783 [[ 1., 0., 0.],
784 [ 0., 1., 0.],
785 [ 0., 0., 1.]],
786 linear_function,
787 linear_function,
788 False,
789 False)
790 """
792 return multiline_repr(
793 self,
794 [
795 {"name": "name"},
796 {"name": "primaries"},
797 {"name": "whitepoint"},
798 {"name": "whitepoint_name"},
799 {"name": "matrix_RGB_to_XYZ"},
800 {"name": "matrix_XYZ_to_RGB"},
801 {
802 "name": "cctf_encoding",
803 "formatter": lambda x: ( # noqa: ARG005
804 None
805 if self.cctf_encoding is None
806 else (
807 self.cctf_encoding.__name__
808 if hasattr(self.cctf_encoding, "__name__")
809 else str(self.cctf_encoding)
810 )
811 ),
812 },
813 {
814 "name": "cctf_decoding",
815 "formatter": lambda x: ( # noqa: ARG005
816 None
817 if self.cctf_decoding is None
818 else (
819 self.cctf_decoding.__name__
820 if hasattr(self.cctf_decoding, "__name__")
821 else str(self.cctf_decoding)
822 )
823 ),
824 },
825 {"name": "use_derived_matrix_RGB_to_XYZ"},
826 {"name": "use_derived_matrix_XYZ_to_RGB"},
827 ],
828 )
830 def _derive_transformation_matrices(self) -> None:
831 """
832 Derive transformation matrices from the RGB colourspace specification.
834 Compute the normalised primary matrix and its inverse matrix that are
835 used for transformations between the RGB colourspace and CIE XYZ
836 tristimulus values.
837 """
839 if self._primaries is not None and self._whitepoint is not None:
840 npm = normalised_primary_matrix(self._primaries, self._whitepoint)
842 self._derived_matrix_RGB_to_XYZ = npm
843 self._derived_matrix_XYZ_to_RGB = np.linalg.inv(npm)
845 def use_derived_transformation_matrices(self, usage: bool = True) -> None:
846 """
847 Enable or disable usage of both derived transformation matrices,
848 the normalised primary matrix and its inverse in subsequent
849 computations.
851 Parameters
852 ----------
853 usage
854 Whether to use the derived transformation matrices.
855 """
857 self.use_derived_matrix_RGB_to_XYZ = usage
858 self.use_derived_matrix_XYZ_to_RGB = usage
860 def chromatically_adapt(
861 self,
862 whitepoint: ArrayLike,
863 whitepoint_name: str | None = None,
864 chromatic_adaptation_transform: (
865 LiteralChromaticAdaptationTransform | str
866 ) = "CAT02",
867 ) -> RGB_Colourspace:
868 """
869 Chromatically adapt the *RGB* colourspace *primaries* :math:`xy`
870 chromaticity coordinates from *RGB* colourspace whitepoint to the
871 specified reference whitepoint.
873 Parameters
874 ----------
875 whitepoint
876 Reference illuminant / whitepoint :math:`xy` chromaticity
877 coordinates.
878 whitepoint_name
879 Reference illuminant / whitepoint name.
880 chromatic_adaptation_transform
881 *Chromatic adaptation* transform.
883 Returns
884 -------
885 :class:`colour.RGB_Colourspace`
886 Chromatically adapted *RGB* colourspace.
888 Examples
889 --------
890 >>> p = np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700])
891 >>> w_t = np.array([0.32168, 0.33767])
892 >>> w_r = np.array([0.31270, 0.32900])
893 >>> colourspace = RGB_Colourspace("RGB Colourspace", p, w_t, "D65")
894 >>> print(colourspace.chromatically_adapt(w_r, "D50", "Bradford"))
895 ... # doctest: +ELLIPSIS
896 RGB Colourspace - Chromatically Adapted to 'D50'
897 ------------------------------------------------
898 <BLANKLINE>
899 Primaries : [[ 0.73485524 0.26422533]
900 [-0.00617091 1.01131496]
901 [ 0.01596756 -0.0642355 ]]
902 Whitepoint : [ 0.3127 0.329 ]
903 Whitepoint Name : D50
904 Encoding CCTF : None
905 Decoding CCTF : None
906 NPM : None
907 NPM -1 : None
908 Derived NPM : [[ 0.93827985 -0.00445145 0.01662752]
909 [ 0.33736889 0.72952157 -0.06689046]
910 [ 0.00117395 -0.00371071 1.09159451]]
911 Derived NPM -1 : [[ 1.06349549 0.00640891 -0.01580679]
912 [-0.49207413 1.36822341 0.09133709]
913 [-0.00281646 0.00464417 0.91641857]]
914 Use Derived NPM : True
915 Use Derived NPM -1 : True
916 """
918 colourspace = self.copy()
920 colourspace.primaries = chromatically_adapted_primaries(
921 colourspace.primaries,
922 colourspace.whitepoint,
923 whitepoint,
924 chromatic_adaptation_transform,
925 )
926 colourspace.whitepoint = whitepoint
927 colourspace.whitepoint_name = whitepoint_name
929 colourspace._matrix_RGB_to_XYZ = None # noqa: SLF001
930 colourspace._matrix_XYZ_to_RGB = None # noqa: SLF001
931 colourspace._derive_transformation_matrices() # noqa: SLF001
932 colourspace.use_derived_transformation_matrices()
934 colourspace.name = (
935 f"{colourspace.name} - Chromatically Adapted to "
936 f"{cast('str', optional(whitepoint_name, whitepoint))!r}"
937 )
939 return colourspace
941 def copy(self) -> RGB_Colourspace:
942 """
943 Create a deep copy of the *RGB* colourspace instance.
945 Generate an independent copy of this *RGB* colourspace with all
946 attributes duplicated, including primaries, whitepoint, matrices,
947 and transfer functions.
949 Returns
950 -------
951 :class:`colour.RGB_Colourspace`
952 Independent deep copy of the *RGB* colourspace instance.
953 """
955 return deepcopy(self)
958def XYZ_to_RGB(
959 XYZ: Domain1,
960 colourspace: RGB_Colourspace | LiteralRGBColourspace | str,
961 illuminant: ArrayLike | None = None,
962 chromatic_adaptation_transform: (
963 LiteralChromaticAdaptationTransform | str | None
964 ) = "CAT02",
965 apply_cctf_encoding: bool = False,
966 *args: Any,
967 **kwargs: Any,
968) -> Range1:
969 """
970 Convert from *CIE XYZ* tristimulus values to *RGB* colourspace array.
972 Parameters
973 ----------
974 XYZ
975 *CIE XYZ* tristimulus values.
976 colourspace
977 Output *RGB* colourspace.
978 illuminant
979 *CIE xy* chromaticity coordinates or *CIE xyY* colourspace array of
980 the *illuminant* for the input *CIE XYZ* tristimulus values.
981 chromatic_adaptation_transform
982 *Chromatic adaptation* transform. If *None*, no chromatic adaptation
983 is performed.
984 apply_cctf_encoding
985 Apply the *RGB* colourspace encoding colour component transfer
986 function / opto-electronic transfer function.
988 Other Parameters
989 ----------------
990 args
991 Arguments for deprecation management.
992 kwargs
993 Keywords arguments for deprecation management.
995 Returns
996 -------
997 :class:`numpy.ndarray`
998 *RGB* colourspace array.
1000 Notes
1001 -----
1002 +--------------------+-----------------------+---------------+
1003 | **Domain** | **Scale - Reference** | **Scale - 1** |
1004 +====================+=======================+===============+
1005 | ``XYZ`` | 1 | 1 |
1006 +--------------------+-----------------------+---------------+
1007 | ``illuminant_XYZ`` | 1 | 1 |
1008 +--------------------+-----------------------+---------------+
1009 | ``illuminant_RGB`` | 1 | 1 |
1010 +--------------------+-----------------------+---------------+
1012 +--------------------+-----------------------+---------------+
1013 | **Range** | **Scale - Reference** | **Scale - 1** |
1014 +====================+=======================+===============+
1015 | ``RGB`` | 1 | 1 |
1016 +--------------------+-----------------------+---------------+
1018 Examples
1019 --------
1020 >>> from colour.models import RGB_COLOURSPACE_sRGB
1021 >>> XYZ = np.array([0.21638819, 0.12570000, 0.03847493])
1022 >>> illuminant = np.array([0.34570, 0.35850])
1023 >>> XYZ_to_RGB(XYZ, RGB_COLOURSPACE_sRGB, illuminant, "Bradford")
1024 ... # doctest: +ELLIPSIS
1025 array([ 0.4559528..., 0.0304078..., 0.0408731...])
1026 >>> XYZ_to_RGB(XYZ, "sRGB", illuminant, "Bradford")
1027 ... # doctest: +ELLIPSIS
1028 array([ 0.4559528..., 0.0304078..., 0.0408731...])
1029 """
1031 from colour.models import RGB_COLOURSPACES # noqa: PLC0415
1033 XYZ = to_domain_1(XYZ)
1035 if not isinstance(colourspace, (RGB_Colourspace, str)):
1036 usage_warning(
1037 'The "colour.XYZ_to_RGB" definition signature has changed with '
1038 '"Colour 0.4.3". The used call arguments are deprecated, '
1039 "please refer to the documentation for more information about the "
1040 "new signature."
1041 )
1042 illuminant_XYZ = kwargs.pop("illuminant_XYZ", colourspace)
1043 illuminant_RGB = kwargs.pop("illuminant_RGB", illuminant)
1044 matrix_XYZ_to_RGB = kwargs.pop(
1045 "matrix_XYZ_to_RGB", chromatic_adaptation_transform
1046 )
1047 chromatic_adaptation_transform = kwargs.pop(
1048 "chromatic_adaptation_transform",
1049 (
1050 apply_cctf_encoding
1051 if not isinstance(apply_cctf_encoding, bool)
1052 else "CAT02"
1053 ),
1054 )
1055 cctf_encoding = kwargs.pop("cctf_encoding", args[0] if len(args) == 1 else None)
1056 apply_cctf_encoding = True
1057 else:
1058 if isinstance(colourspace, str):
1059 colourspace = validate_method(
1060 colourspace,
1061 tuple(RGB_COLOURSPACES),
1062 '"{0}" "RGB" colourspace is invalid, it must be one of {1}!',
1063 )
1064 colourspace = RGB_COLOURSPACES[colourspace]
1066 illuminant_XYZ = optional(
1067 illuminant,
1068 colourspace.whitepoint, # pyright: ignore
1069 )
1070 illuminant_RGB = colourspace.whitepoint # pyright: ignore
1071 matrix_XYZ_to_RGB = colourspace.matrix_XYZ_to_RGB # pyright: ignore
1072 cctf_encoding = colourspace.cctf_encoding # pyright: ignore
1074 if chromatic_adaptation_transform is not None:
1075 M_CAT = matrix_chromatic_adaptation_VonKries(
1076 xyY_to_XYZ(xy_to_xyY(illuminant_XYZ)),
1077 xyY_to_XYZ(xy_to_xyY(illuminant_RGB)),
1078 transform=chromatic_adaptation_transform,
1079 )
1081 XYZ = vecmul(M_CAT, XYZ)
1083 RGB = vecmul(matrix_XYZ_to_RGB, XYZ)
1085 if apply_cctf_encoding and cctf_encoding is not None:
1086 with domain_range_scale("ignore"):
1087 RGB = cctf_encoding(RGB)
1089 return from_range_1(RGB)
1092def RGB_to_XYZ(
1093 RGB: Domain1,
1094 colourspace: RGB_Colourspace | LiteralRGBColourspace | str,
1095 illuminant: ArrayLike | None = None,
1096 chromatic_adaptation_transform: (
1097 LiteralChromaticAdaptationTransform | str | None
1098 ) = "CAT02",
1099 apply_cctf_decoding: bool = False,
1100 *args: Any,
1101 **kwargs: Any,
1102) -> Range1:
1103 """
1104 Convert specified *RGB* colourspace array to *CIE XYZ* tristimulus values.
1106 Parameters
1107 ----------
1108 RGB
1109 *RGB* colourspace array.
1110 colourspace
1111 Input *RGB* colourspace.
1112 illuminant
1113 *CIE xy* chromaticity coordinates or *CIE xyY* colourspace array of
1114 the *illuminant* for the output *CIE XYZ* tristimulus values.
1115 chromatic_adaptation_transform
1116 *Chromatic adaptation* transform, if *None* no chromatic adaptation
1117 is performed.
1118 apply_cctf_decoding
1119 Apply the *RGB* colourspace decoding colour component transfer
1120 function / opto-electronic transfer function.
1122 Other Parameters
1123 ----------------
1124 args
1125 Arguments for deprecation management.
1126 kwargs
1127 Keywords arguments for deprecation management.
1129 Returns
1130 -------
1131 :class:`numpy.ndarray`
1132 *CIE XYZ* tristimulus values.
1134 Notes
1135 -----
1136 +--------------------+-----------------------+---------------+
1137 | **Domain** | **Scale - Reference** | **Scale - 1** |
1138 +====================+=======================+===============+
1139 | ``RGB`` | 1 | 1 |
1140 +--------------------+-----------------------+---------------+
1141 | ``illuminant_XYZ`` | 1 | 1 |
1142 +--------------------+-----------------------+---------------+
1143 | ``illuminant_RGB`` | 1 | 1 |
1144 +--------------------+-----------------------+---------------+
1146 +--------------------+-----------------------+---------------+
1147 | **Range** | **Scale - Reference** | **Scale - 1** |
1148 +====================+=======================+===============+
1149 | ``XYZ`` | 1 | 1 |
1150 +--------------------+-----------------------+---------------+
1152 Examples
1153 --------
1154 >>> from colour.models import RGB_COLOURSPACE_sRGB
1155 >>> RGB = np.array([0.45595571, 0.03039702, 0.04087245])
1156 >>> illuminant = np.array([0.34570, 0.35850])
1157 >>> RGB_to_XYZ(RGB, RGB_COLOURSPACE_sRGB, illuminant, "Bradford")
1158 ... # doctest: +ELLIPSIS
1159 array([ 0.2163881..., 0.1257 , 0.0384749...])
1160 >>> RGB_to_XYZ(RGB, "sRGB", illuminant, "Bradford")
1161 ... # doctest: +ELLIPSIS
1162 array([ 0.2163881..., 0.1257 , 0.0384749...])
1163 """
1165 from colour.models import RGB_COLOURSPACES # noqa: PLC0415
1167 RGB = to_domain_1(RGB)
1169 if not isinstance(colourspace, (RGB_Colourspace, str)):
1170 usage_warning(
1171 'The "colour.RGB_to_XYZ" definition signature has changed with '
1172 '"Colour 0.4.3". The used call arguments are deprecated, '
1173 "please refer to the documentation for more information about the "
1174 "new signature."
1175 )
1176 illuminant_RGB = kwargs.pop("illuminant_RGB", colourspace)
1177 illuminant_XYZ = kwargs.pop("illuminant_XYZ", illuminant)
1178 matrix_RGB_to_XYZ = kwargs.pop(
1179 "matrix_RGB_to_XYZ", chromatic_adaptation_transform
1180 )
1181 chromatic_adaptation_transform = kwargs.pop(
1182 "chromatic_adaptation_transform",
1183 (
1184 apply_cctf_decoding
1185 if not isinstance(apply_cctf_decoding, bool)
1186 else "CAT02"
1187 ),
1188 )
1189 cctf_decoding = kwargs.pop("cctf_decoding", args[0] if len(args) == 1 else None)
1190 apply_cctf_decoding = True
1191 else:
1192 if isinstance(colourspace, str):
1193 colourspace = validate_method(
1194 colourspace,
1195 tuple(RGB_COLOURSPACES),
1196 '"{0}" "RGB" colourspace is invalid, it must be one of {1}!',
1197 )
1198 colourspace = RGB_COLOURSPACES[colourspace]
1200 illuminant_XYZ = optional(
1201 illuminant,
1202 colourspace.whitepoint, # pyright: ignore
1203 )
1204 illuminant_RGB = colourspace.whitepoint # pyright: ignore
1205 matrix_RGB_to_XYZ = colourspace.matrix_RGB_to_XYZ # pyright: ignore
1206 cctf_decoding = colourspace.cctf_decoding # pyright: ignore
1208 if apply_cctf_decoding and cctf_decoding is not None:
1209 with domain_range_scale("ignore"):
1210 RGB = cctf_decoding(RGB)
1212 XYZ = vecmul(matrix_RGB_to_XYZ, RGB)
1214 if chromatic_adaptation_transform is not None:
1215 M_CAT = matrix_chromatic_adaptation_VonKries(
1216 xyY_to_XYZ(xy_to_xyY(illuminant_RGB)),
1217 xyY_to_XYZ(xy_to_xyY(illuminant_XYZ)),
1218 transform=chromatic_adaptation_transform,
1219 )
1221 XYZ = vecmul(M_CAT, XYZ)
1223 return from_range_1(XYZ)
1226def matrix_RGB_to_RGB(
1227 input_colourspace: RGB_Colourspace | LiteralRGBColourspace | str,
1228 output_colourspace: RGB_Colourspace | LiteralRGBColourspace | str,
1229 chromatic_adaptation_transform: (
1230 LiteralChromaticAdaptationTransform | str | None
1231 ) = "CAT02",
1232) -> NDArrayFloat:
1233 """
1234 Compute the matrix :math:`M` converting from the specified input *RGB*
1235 colourspace to the specified output *RGB* colourspace using the
1236 specified *chromatic adaptation* method.
1238 Parameters
1239 ----------
1240 input_colourspace
1241 *RGB* input colourspace.
1242 output_colourspace
1243 *RGB* output colourspace.
1244 chromatic_adaptation_transform
1245 *Chromatic adaptation* transform. If *None*, no chromatic
1246 adaptation is performed.
1248 Returns
1249 -------
1250 :class:`numpy.ndarray`
1251 Conversion matrix :math:`M`.
1253 Examples
1254 --------
1255 >>> from colour.models import (
1256 ... RGB_COLOURSPACE_sRGB,
1257 ... RGB_COLOURSPACE_PROPHOTO_RGB,
1258 ... )
1259 >>> matrix_RGB_to_RGB(RGB_COLOURSPACE_sRGB, RGB_COLOURSPACE_PROPHOTO_RGB)
1260 ... # doctest: +ELLIPSIS
1261 array([[ 0.5288241..., 0.3340609..., 0.1373616...],
1262 [ 0.0975294..., 0.8790074..., 0.0233981...],
1263 [ 0.0163599..., 0.1066124..., 0.8772485...]])
1264 >>> matrix_RGB_to_RGB("sRGB", "ProPhoto RGB")
1265 ... # doctest: +ELLIPSIS
1266 array([[ 0.5288241..., 0.3340609..., 0.1373616...],
1267 [ 0.0975294..., 0.8790074..., 0.0233981...],
1268 [ 0.0163599..., 0.1066124..., 0.8772485...]])
1269 """
1271 from colour.models import RGB_COLOURSPACES # noqa: PLC0415
1273 if isinstance(input_colourspace, str):
1274 input_colourspace = validate_method(
1275 input_colourspace,
1276 tuple(RGB_COLOURSPACES),
1277 '"{0}" "RGB" colourspace is invalid, it must be one of {1}!',
1278 )
1279 input_colourspace = cast("RGB_Colourspace", RGB_COLOURSPACES[input_colourspace])
1281 if isinstance(output_colourspace, str):
1282 output_colourspace = validate_method(
1283 output_colourspace,
1284 tuple(RGB_COLOURSPACES),
1285 '"{0}" "RGB" colourspace is invalid, it must be one of {1}!',
1286 )
1287 output_colourspace = cast(
1288 "RGB_Colourspace", RGB_COLOURSPACES[output_colourspace]
1289 )
1291 M = input_colourspace.matrix_RGB_to_XYZ
1293 if chromatic_adaptation_transform is not None:
1294 M_CAT = matrix_chromatic_adaptation_VonKries(
1295 xy_to_XYZ(input_colourspace.whitepoint),
1296 xy_to_XYZ(output_colourspace.whitepoint),
1297 chromatic_adaptation_transform,
1298 )
1300 M = np.matmul(M_CAT, input_colourspace.matrix_RGB_to_XYZ)
1302 return np.matmul(output_colourspace.matrix_XYZ_to_RGB, M)
1305def RGB_to_RGB(
1306 RGB: Domain1,
1307 input_colourspace: RGB_Colourspace | LiteralRGBColourspace | str,
1308 output_colourspace: RGB_Colourspace | LiteralRGBColourspace | str,
1309 chromatic_adaptation_transform: (
1310 LiteralChromaticAdaptationTransform | str | None
1311 ) = "CAT02",
1312 apply_cctf_decoding: bool = False,
1313 apply_cctf_encoding: bool = False,
1314 **kwargs: Any,
1315) -> Range1:
1316 """
1317 Convert *RGB* colourspace array from the specified input *RGB* colourspace to
1318 specified output *RGB* colourspace using the specified *chromatic adaptation*
1319 method.
1321 Parameters
1322 ----------
1323 RGB
1324 *RGB* colourspace array.
1325 input_colourspace
1326 *RGB* input colourspace.
1327 output_colourspace
1328 *RGB* output colourspace.
1329 chromatic_adaptation_transform
1330 *Chromatic adaptation* transform, if *None* no chromatic adaptation
1331 is performed.
1332 apply_cctf_decoding
1333 Apply the input colourspace decoding colour component transfer
1334 function / electro-optical transfer function.
1335 apply_cctf_encoding
1336 Apply the output colourspace encoding colour component transfer
1337 function / opto-electronic transfer function.
1339 Other Parameters
1340 ----------------
1341 kwargs
1342 Keywords arguments for the colour component transfer functions.
1344 Returns
1345 -------
1346 :class:`numpy.ndarray`
1347 *RGB* colourspace array.
1349 Notes
1350 -----
1351 +--------------------+-----------------------+---------------+
1352 | **Domain** | **Scale - Reference** | **Scale - 1** |
1353 +====================+=======================+===============+
1354 | ``RGB`` | 1 | 1 |
1355 +--------------------+-----------------------+---------------+
1357 +--------------------+-----------------------+---------------+
1358 | **Range** | **Scale - Reference** | **Scale - 1** |
1359 +====================+=======================+===============+
1360 | ``RGB`` | 1 | 1 |
1361 +--------------------+-----------------------+---------------+
1363 Examples
1364 --------
1365 >>> from colour.models import (
1366 ... RGB_COLOURSPACE_sRGB,
1367 ... RGB_COLOURSPACE_PROPHOTO_RGB,
1368 ... )
1369 >>> RGB = np.array([0.45595571, 0.03039702, 0.04087245])
1370 >>> RGB_to_RGB(RGB, RGB_COLOURSPACE_sRGB, RGB_COLOURSPACE_PROPHOTO_RGB)
1371 ... # doctest: +ELLIPSIS
1372 array([ 0.2568891..., 0.0721446..., 0.0465553...])
1373 >>> RGB_to_RGB(RGB, "sRGB", "ProPhoto RGB")
1374 ... # doctest: +ELLIPSIS
1375 array([ 0.2568891..., 0.0721446..., 0.0465553...])
1376 """
1378 from colour.models import RGB_COLOURSPACES # noqa: PLC0415
1380 if isinstance(input_colourspace, str):
1381 input_colourspace = validate_method(
1382 input_colourspace,
1383 tuple(RGB_COLOURSPACES),
1384 '"{0}" "RGB" colourspace is invalid, it must be one of {1}!',
1385 )
1386 input_colourspace = cast("RGB_Colourspace", RGB_COLOURSPACES[input_colourspace])
1388 if isinstance(output_colourspace, str):
1389 output_colourspace = validate_method(
1390 output_colourspace,
1391 tuple(RGB_COLOURSPACES),
1392 '"{0}" "RGB" colourspace is invalid, it must be one of {1}!',
1393 )
1394 output_colourspace = cast(
1395 "RGB_Colourspace", RGB_COLOURSPACES[output_colourspace]
1396 )
1398 RGB = to_domain_1(RGB)
1400 if apply_cctf_decoding and input_colourspace.cctf_decoding is not None:
1401 with domain_range_scale("ignore"):
1402 RGB = input_colourspace.cctf_decoding(
1403 RGB, **filter_kwargs(input_colourspace.cctf_decoding, **kwargs)
1404 )
1406 M = matrix_RGB_to_RGB(
1407 input_colourspace, output_colourspace, chromatic_adaptation_transform
1408 )
1410 RGB = vecmul(M, RGB)
1412 if apply_cctf_encoding and output_colourspace.cctf_encoding is not None:
1413 with domain_range_scale("ignore"):
1414 RGB = output_colourspace.cctf_encoding(
1415 RGB,
1416 **filter_kwargs(output_colourspace.cctf_encoding, **kwargs),
1417 )
1419 return from_range_1(RGB)