Coverage for models/cie_luv.py: 72%

79 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-16 22:49 +1300

1""" 

2CIE L*u*v* Colourspace 

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

4 

5Define the *CIE L\\*u\\*v\\** colourspace transformations. 

6 

7- :func:`colour.XYZ_to_Luv` 

8- :func:`colour.Luv_to_XYZ` 

9- :func:`colour.Luv_to_uv` 

10- :func:`colour.uv_to_Luv` 

11- :func:`colour.Luv_uv_to_xy` 

12- :func:`colour.xy_to_Luv_uv` 

13- :func:`colour.XYZ_to_CIE1976UCS` 

14- :func:`colour.CIE1976UCS_to_XYZ` 

15 

16References 

17---------- 

18- :cite:`CIETC1-482004j` : CIE TC 1-48. (2004). CIE 1976 uniform 

19 chromaticity scale diagram (UCS diagram). In CIE 015:2004 Colorimetry, 3rd 

20 Edition (p. 24). ISBN:978-3-901906-33-6 

21- :cite:`CIETC1-482004m` : CIE TC 1-48. (2004). CIE 1976 uniform colour 

22 spaces. In CIE 015:2004 Colorimetry, 3rd Edition (p. 24). 

23 ISBN:978-3-901906-33-6 

24- :cite:`Wikipedia2007b` : Wikipedia. (2007). CIELUV. Retrieved February 24, 

25 2014, from http://en.wikipedia.org/wiki/CIELUV 

26- :cite:`Wikipedia2007d` : Wikipedia. (2007). The reverse transformation. 

27 Retrieved February 24, 2014, from 

28 http://en.wikipedia.org/wiki/CIELUV#The_reverse_transformation 

29""" 

30 

31from __future__ import annotations 

32 

33import numpy as np 

34 

35from colour.algebra import sdiv, sdiv_mode 

36from colour.colorimetry import CCS_ILLUMINANTS, lightness_CIE1976, luminance_CIE1976 

37from colour.hints import ( # noqa: TC001 

38 Annotated, 

39 ArrayLike, 

40 Domain1, 

41 Domain100, 

42 NDArrayFloat, 

43 Range1, 

44 Range100, 

45) 

46from colour.models import xy_to_xyY, xyY_to_XYZ 

47from colour.utilities import ( 

48 domain_range_scale, 

49 from_range_1, 

50 from_range_100, 

51 get_domain_range_scale, 

52 optional, 

53 to_domain_1, 

54 to_domain_100, 

55 tsplit, 

56 tstack, 

57) 

58 

59__author__ = "Colour Developers" 

60__copyright__ = "Copyright 2013 Colour Developers" 

61__license__ = "BSD-3-Clause - https://opensource.org/licenses/BSD-3-Clause" 

62__maintainer__ = "Colour Developers" 

63__email__ = "colour-developers@colour-science.org" 

64__status__ = "Production" 

65 

66__all__ = [ 

67 "XYZ_to_Luv", 

68 "Luv_to_XYZ", 

69 "Luv_to_uv", 

70 "uv_to_Luv", 

71 "Luv_uv_to_xy", 

72 "xy_to_Luv_uv", 

73 "XYZ_to_CIE1976UCS", 

74 "CIE1976UCS_to_XYZ", 

75] 

76 

77 

78def XYZ_to_Luv( 

79 XYZ: Domain1, 

80 illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][ 

81 "D65" 

82 ], 

83) -> Range100: 

84 """ 

85 Convert from *CIE XYZ* tristimulus values to *CIE L\\*u\\*v\\** 

86 colourspace. 

87 

88 Parameters 

89 ---------- 

90 XYZ 

91 *CIE XYZ* tristimulus values. 

92 illuminant 

93 Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY* 

94 colourspace array. 

95 

96 Returns 

97 ------- 

98 :class:`numpy.ndarray` 

99 *CIE L\\*u\\*v\\** colourspace array. 

100 

101 Notes 

102 ----- 

103 +----------------+-----------------------+-----------------+ 

104 | **Domain** | **Scale - Reference** | **Scale - 1** | 

105 +================+=======================+=================+ 

106 | ``XYZ`` | 1 | 1 | 

107 +----------------+-----------------------+-----------------+ 

108 | ``illuminant`` | 1 | 1 | 

109 +----------------+-----------------------+-----------------+ 

110 

111 +----------------+-----------------------+-----------------+ 

112 | **Range** | **Scale - Reference** | **Scale - 1** | 

113 +================+=======================+=================+ 

114 | ``Luv`` | 100 | 1 | 

115 +----------------+-----------------------+-----------------+ 

116 

117 References 

118 ---------- 

119 :cite:`CIETC1-482004m`, :cite:`Wikipedia2007b` 

120 

121 Examples 

122 -------- 

123 >>> import numpy as np 

124 >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) 

125 >>> XYZ_to_Luv(XYZ) # doctest: +ELLIPSIS 

126 array([ 41.5278752..., 96.8362605..., 17.7521014...]) 

127 """ 

128 

129 X, Y, Z = tsplit(to_domain_1(XYZ)) 

130 

131 X_r, Y_r, Z_r = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant))) 

132 

133 with domain_range_scale("ignore"): 

134 L = lightness_CIE1976(Y, Y_r) 

135 

136 X_Y_Z = X + 15 * Y + 3 * Z 

137 X_r_Y_r_Z_r = X_r + 15 * Y_r + 3 * Z_r 

138 

139 with sdiv_mode(): 

140 u = 13 * L * ((4 * sdiv(X, X_Y_Z)) - (4 * sdiv(X_r, X_r_Y_r_Z_r))) 

141 v = 13 * L * ((9 * sdiv(Y, X_Y_Z)) - (9 * sdiv(Y_r, X_r_Y_r_Z_r))) 

142 

143 Luv = tstack([L, u, v]) 

144 

145 return from_range_100(Luv) 

146 

147 

148def Luv_to_XYZ( 

149 Luv: Domain100, 

150 illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][ 

151 "D65" 

152 ], 

153) -> Range1: 

154 """ 

155 Convert from *CIE L\\*u\\*v\\** colourspace to *CIE XYZ* tristimulus 

156 values. 

157 

158 Parameters 

159 ---------- 

160 Luv 

161 *CIE L\\*u\\*v\\** colourspace array. 

162 illuminant 

163 Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY* 

164 colourspace array. 

165 

166 Returns 

167 ------- 

168 :class:`numpy.ndarray` 

169 *CIE XYZ* tristimulus values. 

170 

171 Notes 

172 ----- 

173 +----------------+-----------------------+-----------------+ 

174 | **Domain** | **Scale - Reference** | **Scale - 1** | 

175 +================+=======================+=================+ 

176 | ``Luv`` | 100 | 1 | 

177 +----------------+-----------------------+-----------------+ 

178 | ``illuminant`` | 1 | 1 | 

179 +----------------+-----------------------+-----------------+ 

180 

181 +----------------+-----------------------+-----------------+ 

182 | **Range** | **Scale - Reference** | **Scale - 1** | 

183 +================+=======================+=================+ 

184 | ``XYZ`` | 1 | 1 | 

185 +----------------+-----------------------+-----------------+ 

186 

187 References 

188 ---------- 

189 :cite:`CIETC1-482004m`, :cite:`Wikipedia2007b` 

190 

191 Examples 

192 -------- 

193 >>> import numpy as np 

194 >>> Luv = np.array([41.52787529, 96.83626054, 17.75210149]) 

195 >>> Luv_to_XYZ(Luv) # doctest: +ELLIPSIS 

196 array([ 0.2065400..., 0.1219722..., 0.0513695...]) 

197 """ 

198 

199 L, u, v = tsplit(to_domain_100(Luv)) 

200 

201 X_r, Y_r, Z_r = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant))) 

202 

203 with domain_range_scale("ignore"): 

204 Y = luminance_CIE1976(L, Y_r) 

205 

206 X_r_Y_r_Z_r = X_r + 15 * Y_r + 3 * Z_r 

207 

208 with sdiv_mode(): 

209 a_1 = u + 13 * L * (4 * sdiv(X_r, X_r_Y_r_Z_r)) 

210 d_1 = v + 13 * L * (9 * sdiv(Y_r, X_r_Y_r_Z_r)) 

211 

212 a = 1 / 3 * (52 * sdiv(L, a_1) - 1) 

213 b = -5 * Y 

214 c = -1 / 3 

215 d = Y * (39 * sdiv(L, d_1) - 5) 

216 

217 X = sdiv(d - b, a - c) 

218 

219 Z = X * a + b 

220 

221 XYZ = tstack([X, Y, Z]) 

222 

223 return from_range_1(XYZ) 

224 

225 

226def Luv_to_uv( 

227 Luv: Domain100, 

228 illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][ 

229 "D65" 

230 ], 

231) -> NDArrayFloat: 

232 """ 

233 Convert from *CIE L\\*u\\*v\\** colourspace to :math:`uv^p` chromaticity 

234 coordinates. 

235 

236 Parameters 

237 ---------- 

238 Luv 

239 *CIE L\\*u\\*v\\** colourspace array. 

240 illuminant 

241 Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY* 

242 colourspace array. 

243 

244 Returns 

245 ------- 

246 :class:`numpy.ndarray` 

247 :math:`uv^p` chromaticity coordinates. 

248 

249 Notes 

250 ----- 

251 +----------------+-----------------------+-----------------+ 

252 | **Domain** | **Scale - Reference** | **Scale - 1** | 

253 +================+=======================+=================+ 

254 | ``Luv`` | 100 | 1 | 

255 +----------------+-----------------------+-----------------+ 

256 | ``illuminant`` | 1 | 1 | 

257 +----------------+-----------------------+-----------------+ 

258 

259 References 

260 ---------- 

261 :cite:`CIETC1-482004j` 

262 

263 Examples 

264 -------- 

265 >>> import numpy as np 

266 >>> Luv = np.array([41.52787529, 96.83626054, 17.75210149]) 

267 >>> Luv_to_uv(Luv) # doctest: +ELLIPSIS 

268 array([ 0.3772021..., 0.5012026...]) 

269 """ 

270 

271 Luv = to_domain_100(Luv) 

272 

273 X, Y, Z = tsplit(Luv_to_XYZ(Luv, illuminant)) 

274 

275 X_Y_Z = X + 15 * Y + 3 * Z 

276 

277 with sdiv_mode(): 

278 return tstack([4 * sdiv(X, X_Y_Z), 9 * sdiv(Y, X_Y_Z)]) 

279 

280 

281def uv_to_Luv( 

282 uv: ArrayLike, 

283 illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][ 

284 "D65" 

285 ], 

286 L: Domain100 | None = None, 

287) -> Range100: 

288 """ 

289 Convert from :math:`uv^p` chromaticity coordinates to *CIE L\\*u\\*v\\** 

290 colourspace by extending the array's last dimension with the specified 

291 :math:`L^*` *Lightness*. 

292 

293 Parameters 

294 ---------- 

295 uv 

296 :math:`uv^p` chromaticity coordinates. 

297 illuminant 

298 Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY* 

299 colourspace array. 

300 L 

301 Optional :math:`L^*` *Lightness* value used to construct the 

302 intermediate *CIE XYZ* colourspace array, the default :math:`L^*` 

303 *Lightness* value is 100. 

304 

305 Returns 

306 ------- 

307 :class:`numpy.ndarray` 

308 *CIE L\\*u\\*v\\** colourspace array. 

309 

310 Notes 

311 ----- 

312 +----------------+-----------------------+-----------------+ 

313 | **Range** | **Scale - Reference** | **Scale - 1** | 

314 +================+=======================+=================+ 

315 | ``Luv`` | 100 | 1 | 

316 +----------------+-----------------------+-----------------+ 

317 | ``illuminant`` | 100 | 1 | 

318 +----------------+-----------------------+-----------------+ 

319 

320 References 

321 ---------- 

322 :cite:`CIETC1-482004j` 

323 

324 Examples 

325 -------- 

326 >>> import numpy as np 

327 >>> uv = np.array([0.37720213, 0.50120264]) 

328 >>> uv_to_Luv(uv, L=41.5278752) # doctest: +ELLIPSIS 

329 array([ 41.5278752..., 96.8362609..., 17.7521029...]) 

330 """ 

331 

332 u, v = tsplit(uv) 

333 L = to_domain_100( 

334 optional(L, 100 if get_domain_range_scale() == "reference" else 1) 

335 ) 

336 

337 _X_r, Y_r, _Z_r = tsplit(xyY_to_XYZ(xy_to_xyY(illuminant))) 

338 

339 with domain_range_scale("ignore"): 

340 Y = luminance_CIE1976(L, Y_r) 

341 

342 with sdiv_mode(): 

343 X = sdiv(9 * Y * u, 4 * v) 

344 Z = sdiv(Y * (-3 * u - 20 * v + 12), 4 * v) 

345 

346 XYZ = tstack([X, np.resize(Y, u.shape), Z]) 

347 

348 return XYZ_to_Luv(from_range_1(XYZ), illuminant) 

349 

350 

351def Luv_uv_to_xy(uv: ArrayLike) -> NDArrayFloat: 

352 """ 

353 Convert from *CIE L\\*u\\*v\\** colourspace :math:`u'v'` chromaticity 

354 coordinates to *CIE xy* chromaticity coordinates. 

355 

356 Parameters 

357 ---------- 

358 uv 

359 *CIE L\\*u\\*v\\* u"v"* chromaticity coordinates. 

360 

361 Returns 

362 ------- 

363 :class:`numpy.ndarray` 

364 *CIE xy* chromaticity coordinates. 

365 

366 References 

367 ---------- 

368 :cite:`Wikipedia2007d` 

369 

370 Examples 

371 -------- 

372 >>> import numpy as np 

373 >>> uv = np.array([0.37720213, 0.50120264]) 

374 >>> Luv_uv_to_xy(uv) # doctest: +ELLIPSIS 

375 array([ 0.5436955..., 0.3210794...]) 

376 """ 

377 

378 u, v = tsplit(uv) 

379 

380 d = 6 * u - 16 * v + 12 

381 

382 with sdiv_mode(): 

383 return tstack([sdiv(9 * u, d), sdiv(4 * v, d)]) 

384 

385 

386def xy_to_Luv_uv(xy: ArrayLike) -> NDArrayFloat: 

387 """ 

388 Convert from *CIE xy* chromaticity coordinates to *CIE L\\*u\\*v\\** 

389 colourspace :math:`u'v'` chromaticity coordinates. 

390 

391 Parameters 

392 ---------- 

393 xy 

394 *CIE xy* chromaticity coordinates. 

395 

396 Returns 

397 ------- 

398 :class:`numpy.ndarray` 

399 *CIE L\\*u\\*v\\* u"v"* chromaticity coordinates. 

400 

401 References 

402 ---------- 

403 :cite:`Wikipedia2007b` 

404 

405 Examples 

406 -------- 

407 >>> import numpy as np 

408 >>> xy = np.array([0.54369558, 0.32107944]) 

409 >>> xy_to_Luv_uv(xy) # doctest: +ELLIPSIS 

410 array([ 0.3772021..., 0.5012026...]) 

411 """ 

412 

413 x, y = tsplit(xy) 

414 

415 d = -2 * x + 12 * y + 3 

416 

417 with sdiv_mode(): 

418 return tstack([sdiv(4 * x, d), sdiv(9 * y, d)]) 

419 

420 

421def XYZ_to_CIE1976UCS( 

422 XYZ: Domain1, 

423 illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][ 

424 "D65" 

425 ], 

426) -> Annotated[NDArrayFloat, (1, 1, 100)]: 

427 """ 

428 Convert from *CIE XYZ* tristimulus values to :math:`uv^pL^*` colourspace. 

429 

430 This colourspace combines the :math:`uv^p` chromaticity 

431 coordinates with the *Lightness* :math:`L^{*}` from the 

432 *CIE L*u*v** colourspace. 

433 

434 It is a convenient definition for use with the 

435 *CIE 1976 UCS Chromaticity Diagram*. 

436 

437 Parameters 

438 ---------- 

439 XYZ 

440 *CIE XYZ* tristimulus values. 

441 illuminant 

442 Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY* 

443 colourspace array. 

444 

445 Returns 

446 ------- 

447 :class:`numpy.ndarray` 

448 :math:`uv^pL^*` colourspace array. 

449 

450 Notes 

451 ----- 

452 +----------------+-----------------------+-----------------+ 

453 | **Domain** | **Scale - Reference** | **Scale - 1** | 

454 +================+=======================+=================+ 

455 | ``XYZ`` | 1 | 1 | 

456 +----------------+-----------------------+-----------------+ 

457 | ``illuminant`` | 1 | 1 | 

458 +----------------+-----------------------+-----------------+ 

459 

460 +----------------+-----------------------+-----------------+ 

461 | **Range** | **Scale - Reference** | **Scale - 1** | 

462 +================+=======================+=================+ 

463 | ``uvL`` | ``u`` : 1 | ``u`` : 1 | 

464 | | | | 

465 | | ``v`` : 1 | ``v`` : 1 | 

466 | | | | 

467 | | ``L`` : 100 | ``L`` : 1 | 

468 +----------------+-----------------------+-----------------+ 

469 

470 Examples 

471 -------- 

472 >>> import numpy as np 

473 >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) 

474 >>> XYZ_to_CIE1976UCS(XYZ) # doctest: +ELLIPSIS 

475 array([ 0.3772021..., 0.5012026..., 41.5278752...]) 

476 """ 

477 

478 Luv = XYZ_to_Luv(XYZ, illuminant) 

479 

480 L, _u, _v = tsplit(Luv) 

481 

482 u, v = tsplit(Luv_to_uv(Luv, illuminant)) 

483 

484 return tstack([u, v, L]) 

485 

486 

487def CIE1976UCS_to_XYZ( 

488 uvL: Annotated[ArrayLike, (1, 1, 100)], 

489 illuminant: ArrayLike = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][ 

490 "D65" 

491 ], 

492) -> Range1: 

493 """ 

494 Convert from :math:`uv^pL^*` colourspace to *CIE XYZ* tristimulus values. 

495 

496 This colourspace combines the :math:`uv^p` chromaticity 

497 coordinates with the *Lightness* :math:`L^{*}` from the 

498 *CIE L*u*v** colourspace. 

499 

500 It is a convenient definition for use with the 

501 *CIE 1976 UCS Chromaticity Diagram*. 

502 

503 Parameters 

504 ---------- 

505 uvL 

506 :math:`uv^pL^*` colourspace array. 

507 illuminant 

508 Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY* 

509 colourspace array. 

510 

511 Returns 

512 ------- 

513 :class:`numpy.ndarray` 

514 *CIE XYZ* tristimulus values. 

515 

516 Notes 

517 ----- 

518 +----------------+-----------------------+-----------------+ 

519 | **Domain** | **Scale - Reference** | **Scale - 1** | 

520 +================+=======================+=================+ 

521 | ``uvL`` | ``u`` : 1 | ``u`` : 1 | 

522 | | | | 

523 | | ``v`` : 1 | ``v`` : 1 | 

524 | | | | 

525 | | ``L`` : 100 | ``L`` : 1 | 

526 +----------------+-----------------------+-----------------+ 

527 | ``illuminant`` | 1 | 1 | 

528 +----------------+-----------------------+-----------------+ 

529 

530 +----------------+-----------------------+-----------------+ 

531 | **Range** | **Scale - Reference** | **Scale - 1** | 

532 +================+=======================+=================+ 

533 | ``XYZ`` | 1 | 1 | 

534 +----------------+-----------------------+-----------------+ 

535 

536 Examples 

537 -------- 

538 >>> import numpy as np 

539 >>> uvL = np.array([0.37720213, 0.50120264, 41.52787529]) 

540 >>> CIE1976UCS_to_XYZ(uvL) # doctest: +ELLIPSIS 

541 array([ 0.2065400..., 0.1219722..., 0.0513695...]) 

542 """ 

543 

544 u, v, L = tsplit(uvL) 

545 

546 _L, u, v = tsplit(uv_to_Luv(tstack([u, v]), illuminant, L)) 

547 

548 return Luv_to_XYZ(tstack([L, u, v]), illuminant)