Coverage for temperature/robertson1968.py: 70%

1"""

2 Robertson (1968) Correlated Colour Temperature

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

4

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

6 computation 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

21 References

22----------

23- :cite:`Wyszecki2000x` : Wyszecki, Gü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, Gü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

32 from __future__ import annotations

33

34 import typing

35 from dataclasses import dataclass

36

37 import numpy as np

38

39 from colour.algebra import sdiv, sdiv_mode

40

41 if typing.TYPE_CHECKING:

42 from colour.hints import ArrayLike, NDArrayFloat

43

44 from 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

63 DATA_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

107 Notes

108-----

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

110 temperature: 0.24702 --> 0.24792

111

112 References

113----------

114:cite:`Wyszecki2000x`

115"""

116

117

118@dataclass

119 class 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

141 ISOTEMPERATURE_LINES_ROBERTSON1968: list = [

142 ISOTemperatureLine_Specification_Robertson1968(*x)

143 for x in DATA_ISOTEMPERATURE_LINES_ROBERTSON1968

144]

145

146

147 def 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

174 def 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

201 def 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

290 def 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)