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

1""" 

2Robertson (1968) Correlated Colour Temperature 

3============================================== 

4 

5Define the *Robertson (1968)* correlated colour temperature :math:`T_{cp}` 

6computation objects. 

7 

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. 

20 

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""" 

31 

32from __future__ import annotations 

33 

34import typing 

35from dataclasses import dataclass 

36 

37import numpy as np 

38 

39from colour.algebra import sdiv, sdiv_mode 

40 

41if typing.TYPE_CHECKING: 

42 from colour.hints import ArrayLike, NDArrayFloat 

43 

44from colour.utilities import as_float_array, tsplit 

45 

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" 

52 

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] 

62 

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:: 

98 

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 ) 

106 

107Notes 

108----- 

109- A correction has been done by Lindbloom for *325* Megakelvin 

110 temperature: 0.24702 --> 0.24792 

111 

112References 

113---------- 

114:cite:`Wyszecki2000x` 

115""" 

116 

117 

118@dataclass 

119class ISOTemperatureLine_Specification_Robertson1968: 

120 """ 

121 Define a data structure for a *Robertson (1968)* iso-temperature line. 

122 

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 """ 

134 

135 r: float 

136 u: float 

137 v: float 

138 t: float 

139 

140 

141ISOTEMPERATURE_LINES_ROBERTSON1968: list = [ 

142 ISOTemperatureLine_Specification_Robertson1968(*x) 

143 for x in DATA_ISOTEMPERATURE_LINES_ROBERTSON1968 

144] 

145 

146 

147def mired_to_CCT(mired: ArrayLike) -> NDArrayFloat: 

148 """ 

149 Convert specified micro reciprocal degree (mired) to correlated colour 

150 temperature :math:`T_{cp}`. 

151 

152 Parameters 

153 ---------- 

154 mired 

155 Micro reciprocal degree. 

156 

157 Returns 

158 ------- 

159 :class:`numpy.ndarray` 

160 Correlated colour temperature :math:`T_{cp}`. 

161 

162 Examples 

163 -------- 

164 >>> CCT_to_mired(153.84615384615384) # doctest: +ELLIPSIS 

165 6500.0 

166 """ 

167 

168 mired = as_float_array(mired) 

169 

170 with sdiv_mode(): 

171 return sdiv(1.0e6, mired) 

172 

173 

174def CCT_to_mired(CCT: ArrayLike) -> NDArrayFloat: 

175 """ 

176 Convert specified correlated colour temperature :math:`T_{cp}` to micro 

177 reciprocal degree (mired). 

178 

179 Parameters 

180 ---------- 

181 CCT 

182 Correlated colour temperature :math:`T_{cp}`. 

183 

184 Returns 

185 ------- 

186 :class:`numpy.ndarray` 

187 Micro reciprocal degree. 

188 

189 Examples 

190 -------- 

191 >>> CCT_to_mired(6500) # doctest: +ELLIPSIS 

192 153.8461538... 

193 """ 

194 

195 CCT = as_float_array(CCT) 

196 

197 with sdiv_mode(): 

198 return sdiv(1.0e6, CCT) 

199 

200 

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. 

206 

207 Parameters 

208 ---------- 

209 uv 

210 *CIE UCS* colourspace *uv* chromaticity coordinates. 

211 

212 Returns 

213 ------- 

214 :class:`numpy.ndarray` 

215 Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`. 

216 

217 References 

218 ---------- 

219 :cite:`Wyszecki2000y` 

220 

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 """ 

227 

228 uv = as_float_array(uv) 

229 shape = uv.shape 

230 uv = uv.reshape(-1, 2) 

231 

232 r_itl, u_itl, v_itl, t_itl = tsplit( 

233 np.array(DATA_ISOTEMPERATURE_LINES_ROBERTSON1968) 

234 ) 

235 

236 # Normalized direction vectors 

237 length = np.hypot(1.0, t_itl) 

238 du_itl = 1.0 / length 

239 dv_itl = t_itl / length 

240 

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) 

245 

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) 

251 

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) 

255 

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 ) 

263 

264 # Interpolate temperature 

265 T = mired_to_CCT(r_itl[i - 1] * f + r_itl[i] * (1 - f)) 

266 

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) 

270 

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 

274 

275 # Normalize interpolated direction 

276 length_i = np.hypot(du_i, dv_i) 

277 du_i /= length_i 

278 dv_i /= length_i 

279 

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 

284 

285 result = np.stack([T, -D_uv], axis=-1) 

286 

287 return result.reshape(shape) 

288 

289 

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. 

295 

296 Parameters 

297 ---------- 

298 CCT_D_uv 

299 Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`. 

300 

301 Returns 

302 ------- 

303 :class:`numpy.ndarray` 

304 *CIE UCS* colourspace *uv* chromaticity coordinates. 

305 

306 References 

307 ---------- 

308 :cite:`Wyszecki2000y` 

309 

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 """ 

316 

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) 

320 

321 r_itl, u_itl, v_itl, t_itl = tsplit( 

322 np.array(DATA_ISOTEMPERATURE_LINES_ROBERTSON1968) 

323 ) 

324 

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 

329 

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) 

333 

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) 

337 

338 # Interpolation factor 

339 f = (r_itl[i + 1] - r) / (r_itl[i + 1] - r_itl[i]) 

340 

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) 

344 

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) 

348 

349 # Normalize interpolated direction 

350 length_i = np.hypot(du_i, dv_i) 

351 du_i /= length_i 

352 dv_i /= length_i 

353 

354 # Offset by D_uv along the isotherm 

355 u += du_i * -D_uv 

356 v += dv_i * -D_uv 

357 

358 result = np.stack([u, v], axis=-1) 

359 

360 return result.reshape(shape)