Coverage for colour/temperature/robertson1968.py: 100%
87 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"""
2Robertson (1968) Correlated Colour Temperature
3==============================================
5Define the *Robertson (1968)* correlated colour temperature :math:`T_{cp}`
6computation objects.
8- :func:`colour.temperature.mired_to_CCT`: Convert micro reciprocal
9 degrees to correlated colour temperature :math:`T_{cp}`.
10- :func:`colour.temperature.CCT_to_mired`: Convert correlated colour
11 temperature :math:`T_{cp}` to micro reciprocal degrees.
12- :func:`colour.temperature.uv_to_CCT_Robertson1968`: Compute correlated
13 colour temperature :math:`T_{cp}` and :math:`\\Delta_{uv}` from
14 specified *CIE UCS* colourspace *uv* chromaticity coordinates using
15 the *Robertson (1968)* method.
16- :func:`colour.temperature.CCT_to_uv_Robertson1968`: Compute *CIE UCS*
17 colourspace *uv* chromaticity coordinates from specified correlated
18 colour temperature :math:`T_{cp}` and :math:`\\Delta_{uv}` using the
19 *Robertson (1968)* method.
21References
22----------
23- :cite:`Wyszecki2000x` : Wyszecki, Günther, & Stiles, W. S. (2000). Table
24 1(3.11) Isotemperature Lines. In Color Science: Concepts and Methods,
25 Quantitative Data and Formulae (p. 228). Wiley. ISBN:978-0-471-39918-6
26- :cite:`Wyszecki2000y` : Wyszecki, Günther, & Stiles, W. S. (2000).
27 DISTRIBUTION TEMPERATURE, COLOR TEMPERATURE, AND CORRELATED COLOR
28 TEMPERATURE. In Color Science: Concepts and Methods, Quantitative Data and
29 Formulae (pp. 224-229). Wiley. ISBN:978-0-471-39918-6
30"""
32from __future__ import annotations
34import typing
35from dataclasses import dataclass
37import numpy as np
39from colour.algebra import sdiv, sdiv_mode
41if typing.TYPE_CHECKING:
42 from colour.hints import ArrayLike, NDArrayFloat
44from colour.utilities import as_float_array, tsplit
46__author__ = "Colour Developers"
47__copyright__ = "Copyright 2013 Colour Developers"
48__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause"
49__maintainer__ = "Colour Developers"
50__email__ = "colour-developers@colour-science.org"
51__status__ = "Production"
53__all__ = [
54 "DATA_ISOTEMPERATURE_LINES_ROBERTSON1968",
55 "ISOTemperatureLine_Specification_Robertson1968",
56 "ISOTEMPERATURE_LINES_ROBERTSON1968",
57 "mired_to_CCT",
58 "CCT_to_mired",
59 "uv_to_CCT_Robertson1968",
60 "CCT_to_uv_Robertson1968",
61]
63DATA_ISOTEMPERATURE_LINES_ROBERTSON1968: tuple = (
64 (0, 0.18006, 0.26352, -0.24341),
65 (10, 0.18066, 0.26589, -0.25479),
66 (20, 0.18133, 0.26846, -0.26876),
67 (30, 0.18208, 0.27119, -0.28539),
68 (40, 0.18293, 0.27407, -0.30470),
69 (50, 0.18388, 0.27709, -0.32675),
70 (60, 0.18494, 0.28021, -0.35156),
71 (70, 0.18611, 0.28342, -0.37915),
72 (80, 0.18740, 0.28668, -0.40955),
73 (90, 0.18880, 0.28997, -0.44278),
74 (100, 0.19032, 0.29326, -0.47888),
75 (125, 0.19462, 0.30141, -0.58204),
76 (150, 0.19962, 0.30921, -0.70471),
77 (175, 0.20525, 0.31647, -0.84901),
78 (200, 0.21142, 0.32312, -1.0182),
79 (225, 0.21807, 0.32909, -1.2168),
80 (250, 0.22511, 0.33439, -1.4512),
81 (275, 0.23247, 0.33904, -1.7298),
82 (300, 0.24010, 0.34308, -2.0637),
83 (325, 0.24792, 0.34655, -2.4681), # 0.24702 --> 0.24792 Bruce Lindbloom
84 (350, 0.25591, 0.34951, -2.9641),
85 (375, 0.26400, 0.35200, -3.5814),
86 (400, 0.27218, 0.35407, -4.3633),
87 (425, 0.28039, 0.35577, -5.3762),
88 (450, 0.28863, 0.35714, -6.7262),
89 (475, 0.29685, 0.35823, -8.5955),
90 (500, 0.30505, 0.35907, -11.324),
91 (525, 0.31320, 0.35968, -15.628),
92 (550, 0.32129, 0.36011, -23.325),
93 (575, 0.32931, 0.36038, -40.770),
94 (600, 0.33724, 0.36051, -116.45),
95)
96"""
97*Robertson (1968)* iso-temperature lines as a *tuple* as follows::
99 (
100 ('Reciprocal Megakelvin', 'CIE 1960 Chromaticity Coordinate *u*',
101 'CIE 1960 Chromaticity Coordinate *v*', 'Slope'),
102 ...,
103 ('Reciprocal Megakelvin', 'CIE 1960 Chromaticity Coordinate *u*',
104 'CIE 1960 Chromaticity Coordinate *v*', 'Slope'),
105 )
107Notes
108-----
109- A correction has been done by Lindbloom for *325* Megakelvin
110 temperature: 0.24702 --> 0.24792
112References
113----------
114:cite:`Wyszecki2000x`
115"""
118@dataclass
119class ISOTemperatureLine_Specification_Robertson1968:
120 """
121 Define a data structure for a *Robertson (1968)* iso-temperature line.
123 Parameters
124 ----------
125 r
126 Temperature :math:`r` in reciprocal mega-kelvin degrees.
127 u
128 *u* chromaticity coordinate of the temperature :math:`r`.
129 v
130 *v* chromaticity coordinate of the temperature :math:`r`.
131 t
132 Slope of the *v* chromaticity coordinate.
133 """
135 r: float
136 u: float
137 v: float
138 t: float
141ISOTEMPERATURE_LINES_ROBERTSON1968: list = [
142 ISOTemperatureLine_Specification_Robertson1968(*x)
143 for x in DATA_ISOTEMPERATURE_LINES_ROBERTSON1968
144]
147def mired_to_CCT(mired: ArrayLike) -> NDArrayFloat:
148 """
149 Convert specified micro reciprocal degree (mired) to correlated colour
150 temperature :math:`T_{cp}`.
152 Parameters
153 ----------
154 mired
155 Micro reciprocal degree.
157 Returns
158 -------
159 :class:`numpy.ndarray`
160 Correlated colour temperature :math:`T_{cp}`.
162 Examples
163 --------
164 >>> CCT_to_mired(153.84615384615384) # doctest: +ELLIPSIS
165 6500.0
166 """
168 mired = as_float_array(mired)
170 with sdiv_mode():
171 return sdiv(1.0e6, mired)
174def CCT_to_mired(CCT: ArrayLike) -> NDArrayFloat:
175 """
176 Convert specified correlated colour temperature :math:`T_{cp}` to micro
177 reciprocal degree (mired).
179 Parameters
180 ----------
181 CCT
182 Correlated colour temperature :math:`T_{cp}`.
184 Returns
185 -------
186 :class:`numpy.ndarray`
187 Micro reciprocal degree.
189 Examples
190 --------
191 >>> CCT_to_mired(6500) # doctest: +ELLIPSIS
192 153.8461538...
193 """
195 CCT = as_float_array(CCT)
197 with sdiv_mode():
198 return sdiv(1.0e6, CCT)
201def uv_to_CCT_Robertson1968(uv: ArrayLike) -> NDArrayFloat:
202 """
203 Compute the correlated colour temperature :math:`T_{cp}` and
204 :math:`\\Delta_{uv}` from the specified *CIE UCS* colourspace *uv*
205 chromaticity coordinates using *Robertson (1968)* method.
207 Parameters
208 ----------
209 uv
210 *CIE UCS* colourspace *uv* chromaticity coordinates.
212 Returns
213 -------
214 :class:`numpy.ndarray`
215 Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`.
217 References
218 ----------
219 :cite:`Wyszecki2000y`
221 Examples
222 --------
223 >>> uv = np.array([0.193741375998230, 0.315221043940594])
224 >>> uv_to_CCT_Robertson1968(uv) # doctest: +ELLIPSIS
225 array([ 6.5000162...e+03, 8.3333289...e-03])
226 """
228 uv = as_float_array(uv)
229 shape = uv.shape
230 uv = uv.reshape(-1, 2)
232 r_itl, u_itl, v_itl, t_itl = tsplit(
233 np.array(DATA_ISOTEMPERATURE_LINES_ROBERTSON1968)
234 )
236 # Normalized direction vectors
237 length = np.hypot(1.0, t_itl)
238 du_itl = 1.0 / length
239 dv_itl = t_itl / length
241 # Vectorized computation for all UV pairs at once
242 u, v = tsplit(uv)
243 u = u[:, np.newaxis] # Shape (N, 1)
244 v = v[:, np.newaxis] # Shape (N, 1)
246 # Compute distances for all UV pairs against all isotemperature lines
247 # Broadcasting: (N, 1) - (30,) = (N, 30)
248 uu = u - u_itl[1:] # Shape (N, 30)
249 vv = v - v_itl[1:] # Shape (N, 30)
250 dt = -uu * dv_itl[1:] + vv * du_itl[1:] # Shape (N, 30)
252 # Find the first crossing point for each UV pair
253 mask = dt <= 0
254 i = np.where(np.any(mask, axis=1), np.argmax(mask, axis=1) + 1, 30)
256 # Interpolation factor
257 idx = np.arange(len(i))
258 dt_current = -np.minimum(dt[idx, i - 1], 0.0)
259 dt_previous = dt[idx, i - 2]
260 f = np.where(
261 i == 1, 0.0, np.where(i > 1, dt_current / (dt_previous + dt_current), 0.0)
262 )
264 # Interpolate temperature
265 T = mired_to_CCT(r_itl[i - 1] * f + r_itl[i] * (1 - f))
267 # Interpolate uv position
268 u_i = u_itl[i - 1] * f + u_itl[i] * (1 - f)
269 v_i = v_itl[i - 1] * f + v_itl[i] * (1 - f)
271 # Interpolate direction vectors
272 du_i = du_itl[i] * (1 - f) + du_itl[i - 1] * f
273 dv_i = dv_itl[i] * (1 - f) + dv_itl[i - 1] * f
275 # Normalize interpolated direction
276 length_i = np.hypot(du_i, dv_i)
277 du_i /= length_i
278 dv_i /= length_i
280 # Calculate D_uv
281 uu = u.ravel() - u_i
282 vv = v.ravel() - v_i
283 D_uv = uu * du_i + vv * dv_i
285 result = np.stack([T, -D_uv], axis=-1)
287 return result.reshape(shape)
290def CCT_to_uv_Robertson1968(CCT_D_uv: ArrayLike) -> NDArrayFloat:
291 """
292 Return the *CIE UCS* colourspace *uv* chromaticity coordinates from the
293 specified correlated colour temperature :math:`T_{cp}` and
294 :math:`\\Delta_{uv}` using *Robertson (1968)* method.
296 Parameters
297 ----------
298 CCT_D_uv
299 Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`.
301 Returns
302 -------
303 :class:`numpy.ndarray`
304 *CIE UCS* colourspace *uv* chromaticity coordinates.
306 References
307 ----------
308 :cite:`Wyszecki2000y`
310 Examples
311 --------
312 >>> CCT_D_uv = np.array([6500.0081378199056, 0.008333331244225])
313 >>> CCT_to_uv_Robertson1968(CCT_D_uv) # doctest: +ELLIPSIS
314 array([ 0.1937413..., 0.3152210...])
315 """
317 CCT_D_uv = as_float_array(CCT_D_uv)
318 shape = CCT_D_uv.shape
319 CCT_D_uv = CCT_D_uv.reshape(-1, 2)
321 r_itl, u_itl, v_itl, t_itl = tsplit(
322 np.array(DATA_ISOTEMPERATURE_LINES_ROBERTSON1968)
323 )
325 # Precompute normalized direction vectors
326 length = np.hypot(1.0, t_itl)
327 du_itl = 1.0 / length
328 dv_itl = t_itl / length
330 # Vectorized computation for all CCT/D_uv pairs at once
331 CCT, D_uv = tsplit(CCT_D_uv)
332 r = CCT_to_mired(CCT)
334 # Find the isotemperature range containing r for all values
335 mask = r[:, np.newaxis] < r_itl[1:]
336 i = np.where(np.any(mask, axis=1), np.argmax(mask, axis=1), 29)
338 # Interpolation factor
339 f = (r_itl[i + 1] - r) / (r_itl[i + 1] - r_itl[i])
341 # Interpolate uv position on Planckian locus
342 u = u_itl[i] * f + u_itl[i + 1] * (1 - f)
343 v = v_itl[i] * f + v_itl[i + 1] * (1 - f)
345 # Interpolate direction vectors
346 du_i = du_itl[i] * f + du_itl[i + 1] * (1 - f)
347 dv_i = dv_itl[i] * f + dv_itl[i + 1] * (1 - f)
349 # Normalize interpolated direction
350 length_i = np.hypot(du_i, dv_i)
351 du_i /= length_i
352 dv_i /= length_i
354 # Offset by D_uv along the isotherm
355 u += du_i * -D_uv
356 v += dv_i * -D_uv
358 result = np.stack([u, v], axis=-1)
360 return result.reshape(shape)