Coverage for models/rgb/derivation.py: 49%
39 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 Derivation
3==========================
5Define objects for *RGB* colourspace derivation, primarily focused on
6calculating the normalised primary matrix from the specified *RGB* colourspace
7primaries and whitepoint.
9- :func:`colour.normalised_primary_matrix`
10- :func:`colour.chromatically_adapted_primaries`
11- :func:`colour.primaries_whitepoint`
12- :func:`colour.RGB_luminance_equation`
13- :func:`colour.RGB_luminance`
15References
16----------
17- :cite:`SocietyofMotionPictureandTelevisionEngineers1993a` : Society of
18 Motion Picture and Television Engineers. (1993). RP 177:1993 - Derivation
19 of Basic Television Color Equations. In RP 177:1993: Vol. RP 177:199. The
20 Society of Motion Picture and Television Engineers.
21 doi:10.5594/S9781614821915
22- :cite:`Trieu2015a` : Borer, T. (2017). Private Discussion with Mansencal,
23 T. and Shaw, N.
24"""
26from __future__ import annotations
28import typing
30import numpy as np
32from colour.adaptation import chromatic_adaptation_VonKries
34if typing.TYPE_CHECKING:
35 from colour.hints import LiteralChromaticAdaptationTransform, Tuple
37from colour.hints import ArrayLike, NDArrayFloat, Range1 # noqa: TC001
38from colour.models import XYZ_to_xy, XYZ_to_xyY, xy_to_XYZ
39from colour.utilities import as_float, as_float_array, ones, tsplit
41__author__ = "Colour Developers"
42__copyright__ = "Copyright 2013 Colour Developers"
43__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
44__maintainer__ = "Colour Developers"
45__email__ = "colour-developers@colour-science.org"
46__status__ = "Production"
48__all__ = [
49 "xy_to_z",
50 "normalised_primary_matrix",
51 "chromatically_adapted_primaries",
52 "primaries_whitepoint",
53 "RGB_luminance_equation",
54 "RGB_luminance",
55]
58def xy_to_z(xy: ArrayLike) -> float:
59 """
60 Return the *z* coordinate using the specified :math:`xy` chromaticity
61 coordinates.
63 Parameters
64 ----------
65 xy
66 :math:`xy` chromaticity coordinates.
68 Returns
69 -------
70 :class:`float`
71 *z* coordinate.
73 Examples
74 --------
75 >>> xy_to_z(np.array([0.25, 0.25]))
76 0.5
77 """
79 x, y = tsplit(xy)
81 return 1 - x - y
84def normalised_primary_matrix(
85 primaries: ArrayLike, whitepoint: ArrayLike
86) -> NDArrayFloat:
87 """
88 Compute the *Normalised Primary Matrix* (NPM) converting a *RGB*
89 colourspace array to *CIE XYZ* tristimulus values using the specified
90 *primaries* and *whitepoint* :math:`xy` chromaticity coordinates.
92 Parameters
93 ----------
94 primaries
95 Primaries :math:`xy` chromaticity coordinates.
96 whitepoint
97 Illuminant / whitepoint :math:`xy` chromaticity coordinates.
99 Returns
100 -------
101 :class:`numpy.ndarray`
102 *Normalised Primary Matrix* (NPM).
104 References
105 ----------
106 :cite:`SocietyofMotionPictureandTelevisionEngineers1993a`
108 Examples
109 --------
110 >>> p = np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700])
111 >>> w = np.array([0.32168, 0.33767])
112 >>> normalised_primary_matrix(p, w) # doctest: +ELLIPSIS
113 array([[ 9.5255239...e-01, 0.0000000...e+00, 9.3678631...e-05],
114 [ 3.4396645...e-01, 7.2816609...e-01, -7.2132546...e-02],
115 [ 0.0000000...e+00, 0.0000000...e+00, 1.0088251...e+00]])
116 """
118 primaries = np.reshape(primaries, (3, 2))
120 z = as_float_array(xy_to_z(primaries))[..., None]
121 primaries = np.transpose(np.hstack([primaries, z]))
123 whitepoint = xy_to_XYZ(whitepoint)
125 coefficients = np.dot(np.linalg.inv(primaries), whitepoint)
126 coefficients = np.diagflat(coefficients)
128 return np.dot(primaries, coefficients)
131def chromatically_adapted_primaries(
132 primaries: ArrayLike,
133 whitepoint_t: ArrayLike,
134 whitepoint_r: ArrayLike,
135 chromatic_adaptation_transform: (
136 LiteralChromaticAdaptationTransform | str
137 ) = "CAT02",
138) -> NDArrayFloat:
139 """
140 Chromatically adapt specified *primaries* :math:`xy` chromaticity
141 coordinates from test ``whitepoint_t`` to reference ``whitepoint_r``.
143 Parameters
144 ----------
145 primaries
146 Primaries :math:`xy` chromaticity coordinates.
147 whitepoint_t
148 Test illuminant / whitepoint :math:`xy` chromaticity coordinates.
149 whitepoint_r
150 Reference illuminant / whitepoint :math:`xy` chromaticity
151 coordinates.
152 chromatic_adaptation_transform
153 *Chromatic adaptation* transform.
155 Returns
156 -------
157 :class:`numpy.ndarray`
158 Chromatically adapted primaries :math:`xy` chromaticity
159 coordinates.
161 Examples
162 --------
163 >>> p = np.array([0.64, 0.33, 0.30, 0.60, 0.15, 0.06])
164 >>> w_t = np.array([0.31270, 0.32900])
165 >>> w_r = np.array([0.34570, 0.35850])
166 >>> chromatic_adaptation_transform = "Bradford"
167 >>> chromatically_adapted_primaries(p, w_t, w_r, chromatic_adaptation_transform)
168 ... # doctest: +ELLIPSIS
169 array([[ 0.6484414..., 0.3308533...],
170 [ 0.3211951..., 0.5978443...],
171 [ 0.1558932..., 0.0660492...]])
172 """
174 primaries = np.reshape(primaries, (3, 2))
176 XYZ_a = chromatic_adaptation_VonKries(
177 xy_to_XYZ(primaries),
178 xy_to_XYZ(whitepoint_t),
179 xy_to_XYZ(whitepoint_r),
180 chromatic_adaptation_transform,
181 )
183 return XYZ_to_xyY(XYZ_a)[..., 0:2]
186def primaries_whitepoint(npm: ArrayLike) -> Tuple[NDArrayFloat, NDArrayFloat]:
187 """
188 Compute the *primaries* and *whitepoint* :math:`xy` chromaticity
189 coordinates using the specified *Normalised Primary Matrix* (NPM).
191 Parameters
192 ----------
193 npm
194 *Normalised Primary Matrix*.
196 Returns
197 -------
198 :class:`tuple`
199 *Primaries* and *whitepoint* :math:`xy` chromaticity coordinates.
201 References
202 ----------
203 :cite:`Trieu2015a`
205 Examples
206 --------
207 >>> npm = np.array(
208 ... [
209 ... [9.52552396e-01, 0.00000000e00, 9.36786317e-05],
210 ... [3.43966450e-01, 7.28166097e-01, -7.21325464e-02],
211 ... [0.00000000e00, 0.00000000e00, 1.00882518e00],
212 ... ]
213 ... )
214 >>> p, w = primaries_whitepoint(npm)
215 >>> p # doctest: +ELLIPSIS
216 array([[ 7.3470000...e-01, 2.6530000...e-01],
217 [ 0.0000000...e+00, 1.0000000...e+00],
218 [ 1.0000000...e-04, -7.7000000...e-02]])
219 >>> w # doctest: +ELLIPSIS
220 array([ 0.32168, 0.33767])
221 """
223 npm = np.reshape(npm, (3, 3))
225 primaries = XYZ_to_xy(np.transpose(np.dot(npm, np.identity(3))))
226 whitepoint = np.squeeze(XYZ_to_xy(np.transpose(np.dot(npm, ones((3, 1))))))
228 # TODO: Investigate if we return an ndarray here with primaries and
229 # whitepoint stacked together.
230 return primaries, whitepoint
233def RGB_luminance_equation(primaries: ArrayLike, whitepoint: ArrayLike) -> str:
234 """
235 Return the *luminance equation* from the specified *primaries* and
236 *whitepoint*.
238 Parameters
239 ----------
240 primaries
241 Primaries chromaticity coordinates.
242 whitepoint
243 Illuminant / whitepoint chromaticity coordinates.
245 Returns
246 -------
247 :class:`str`
248 *Luminance* equation.
250 Examples
251 --------
252 >>> p = np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700])
253 >>> whitepoint = np.array([0.32168, 0.33767])
254 >>> RGB_luminance_equation(p, whitepoint) # doctest: +ELLIPSIS
255 'Y = 0.3439664...(R) + 0.7281660...(G) + -0.0721325...(B)'
256 """
258 return "Y = {}(R) + {}(G) + {}(B)".format(
259 *np.ravel(normalised_primary_matrix(primaries, whitepoint))[3:6]
260 )
263def RGB_luminance(
264 RGB: ArrayLike, primaries: ArrayLike, whitepoint: ArrayLike
265) -> Range1:
266 """
267 Calculate the *luminance* :math:`Y` of the specified *RGB* components using
268 the specified *primaries* and *whitepoint* chromaticity coordinates.
270 Parameters
271 ----------
272 RGB
273 *RGB* colour components array.
274 primaries
275 Primaries :math:`xy` chromaticity coordinates array.
276 whitepoint
277 Illuminant / whitepoint :math:`xy` chromaticity coordinates.
279 Returns
280 -------
281 :class:`numpy.ndarray`
282 *Luminance* :math:`Y`.
284 Notes
285 -----
286 +-----------+-----------------------+---------------+
287 | **Range** | **Scale - Reference** | **Scale - 1** |
288 +===========+=======================+===============+
289 | ``Y`` | 1 | 1 |
290 +-----------+-----------------------+---------------+
292 Examples
293 --------
294 >>> RGB = np.array([0.21959402, 0.06986677, 0.04703877])
295 >>> p = np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700])
296 >>> whitepoint = np.array([0.32168, 0.33767])
297 >>> RGB_luminance(RGB, p, whitepoint) # doctest: +ELLIPSIS
298 0.1230145...
299 """
301 Y = np.sum(normalised_primary_matrix(primaries, whitepoint)[1] * RGB, axis=-1)
303 return as_float(Y)