Coverage for colour/recovery/__init__.py: 100%

32 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-15 19:01 +1300

1""" 

2References 

3---------- 

4- :cite:`Jakob2019` : Jakob, W., & Hanika, J. (2019). A Low-Dimensional 

5 Function Space for Efficient Spectral Upsampling. Computer Graphics Forum, 

6 38(2), 147-155. doi:10.1111/cgf.13626 

7- :cite:`Mallett2019` : Mallett, I., & Yuksel, C. (2019). Spectral Primary 

8 Decomposition for Rendering with sRGB Reflectance. Eurographics Symposium 

9 on Rendering - DL-Only and Industry Track, 7 pages. doi:10.2312/SR.20191216 

10- :cite:`Meng2015c` : Meng, J., Simon, F., Hanika, J., & Dachsbacher, C. 

11 (2015). Physically Meaningful Rendering using Tristimulus Colours. Computer 

12 Graphics Forum, 34(4), 31-40. doi:10.1111/cgf.12676 

13- :cite:`Otsu2018` : Otsu, H., Yamamoto, M., & Hachisuka, T. (2018). 

14 Reproducing Spectral Reflectances From Tristimulus Colours. Computer 

15 Graphics Forum, 37(6), 370-381. doi:10.1111/cgf.13332 

16- :cite:`Smits1999a` : Smits, B. (1999). An RGB-to-Spectrum Conversion for 

17 Reflectances. Journal of Graphics Tools, 4(4), 11-22. 

18 doi:10.1080/10867651.1999.10487511 

19""" 

20 

21from __future__ import annotations 

22 

23import typing 

24 

25if typing.TYPE_CHECKING: 

26 from colour.colorimetry import SpectralDistribution 

27 from colour.hints import Any, ArrayLike, Literal 

28 

29from colour.utilities import ( 

30 CanonicalMapping, 

31 as_float_array, 

32 filter_kwargs, 

33 validate_method, 

34) 

35 

36from . import datasets 

37from .datasets import * # noqa: F403 

38from .jakob2019 import ( 

39 LUT3D_Jakob2019, 

40 XYZ_to_sd_Jakob2019, 

41 find_coefficients_Jakob2019, 

42 sd_Jakob2019, 

43) 

44from .jiang2013 import ( 

45 PCA_Jiang2013, 

46 RGB_to_msds_camera_sensitivities_Jiang2013, 

47 RGB_to_sd_camera_sensitivity_Jiang2013, 

48) 

49from .mallett2019 import ( 

50 RGB_to_sd_Mallett2019, 

51 spectral_primary_decomposition_Mallett2019, 

52) 

53from .meng2015 import XYZ_to_sd_Meng2015 

54from .otsu2018 import ( 

55 Dataset_Otsu2018, 

56 Tree_Otsu2018, 

57 XYZ_to_sd_Otsu2018, 

58) 

59from .smits1999 import RGB_to_sd_Smits1999 

60 

61__all__ = datasets.__all__ 

62__all__ += [ 

63 "LUT3D_Jakob2019", 

64 "XYZ_to_sd_Jakob2019", 

65 "find_coefficients_Jakob2019", 

66 "sd_Jakob2019", 

67] 

68__all__ += [ 

69 "PCA_Jiang2013", 

70 "RGB_to_msds_camera_sensitivities_Jiang2013", 

71 "RGB_to_sd_camera_sensitivity_Jiang2013", 

72] 

73__all__ += [ 

74 "RGB_to_sd_Mallett2019", 

75 "spectral_primary_decomposition_Mallett2019", 

76] 

77__all__ += [ 

78 "XYZ_to_sd_Meng2015", 

79] 

80__all__ += [ 

81 "Dataset_Otsu2018", 

82 "Tree_Otsu2018", 

83 "XYZ_to_sd_Otsu2018", 

84] 

85__all__ += [ 

86 "RGB_to_sd_Smits1999", 

87] 

88 

89XYZ_TO_SD_METHODS: CanonicalMapping = CanonicalMapping( 

90 { 

91 "Jakob 2019": XYZ_to_sd_Jakob2019, 

92 "Mallett 2019": RGB_to_sd_Mallett2019, 

93 "Meng 2015": XYZ_to_sd_Meng2015, 

94 "Otsu 2018": XYZ_to_sd_Otsu2018, 

95 "Smits 1999": RGB_to_sd_Smits1999, 

96 } 

97) 

98XYZ_TO_SD_METHODS.__doc__ = """ 

99Supported spectral distribution recovery methods. 

100 

101References 

102---------- 

103:cite:`Jakob2019`, :cite:`Mallett2019`, :cite:`Meng2015c`, 

104:cite:`Smits1999a` 

105""" 

106 

107 

108def XYZ_to_sd( 

109 XYZ: ArrayLike, 

110 method: ( 

111 Literal[ 

112 "Jakob 2019", 

113 "Mallett 2019", 

114 "Meng 2015", 

115 "Otsu 2018", 

116 "Smits 1999", 

117 ] 

118 | str 

119 ) = "Meng 2015", 

120 **kwargs: Any, 

121) -> SpectralDistribution: 

122 """ 

123 Recover the spectral distribution of the specified *CIE XYZ* tristimulus 

124 values using the specified method. 

125 

126 Parameters 

127 ---------- 

128 XYZ 

129 *CIE XYZ* tristimulus values to recover the spectral distribution 

130 from. 

131 method 

132 Computation method. 

133 

134 Other Parameters 

135 ---------------- 

136 additional_data 

137 {:func:`colour.recovery.XYZ_to_sd_Jakob2019`}, 

138 If *True*, ``error`` will be returned alongside the recovered 

139 spectral distribution. 

140 basis_functions 

141 {:func:`colour.recovery.RGB_to_sd_Mallett2019`}, 

142 Basis functions for the method. The default is to use the built-in 

143 *sRGB* basis functions, i.e., 

144 :attr:`colour.recovery.MSDS_BASIS_FUNCTIONS_sRGB_MALLETT2019`. 

145 clip 

146 {:func:`colour.recovery.XYZ_to_sd_Otsu2018`}, 

147 If *True*, the default, values below zero and above unity in the 

148 recovered spectral distributions will be clipped. This ensures that 

149 the returned reflectance is physical and conserves energy, but will 

150 cause noticeable colour differences in case of very saturated 

151 colours. 

152 cmfs 

153 {:func:`colour.recovery.XYZ_to_sd_Meng2015`}, 

154 Standard observer colour matching functions. 

155 dataset 

156 {:func:`colour.recovery.XYZ_to_sd_Otsu2018`}, 

157 Dataset to use for reconstruction. The default is to use the 

158 published data. 

159 illuminant 

160 {:func:`colour.recovery.XYZ_to_sd_Jakob2019`, 

161 :func:`colour.recovery.XYZ_to_sd_Meng2015`}, 

162 Illuminant spectral distribution, default to 

163 *CIE Standard Illuminant D65*. 

164 optimisation_kwargs 

165 {:func:`colour.recovery.XYZ_to_sd_Jakob2019`, 

166 :func:`colour.recovery.XYZ_to_sd_Meng2015`}, 

167 Parameters for :func:`scipy.optimize.minimize` and 

168 :func:`colour.recovery.find_coefficients_Jakob2019` definitions. 

169 

170 Returns 

171 ------- 

172 :class:`colour.SpectralDistribution` 

173 Recovered spectral distribution. 

174 

175 Notes 

176 ----- 

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

178 | **Domain** | **Scale - Reference** | **Scale - 1** | 

179 +============+=======================+===============+ 

180 | ``XYZ`` | 1 | 1 | 

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

182 

183 - *Smits (1999)* method will internally convert specified *CIE XYZ* 

184 tristimulus values to *sRGB* colourspace array assuming equal 

185 energy illuminant *E*. 

186 

187 References 

188 ---------- 

189 :cite:`Jakob2019`, :cite:`Mallett2019`, :cite:`Meng2015c`, 

190 :cite:`Otsu2018`, :cite:`Smits1999a` 

191 

192 Examples 

193 -------- 

194 *Jakob and Hanika (2019)* reflectance recovery: 

195 

196 >>> import numpy as np 

197 >>> from colour import MSDS_CMFS, SDS_ILLUMINANTS, SpectralShape 

198 >>> from colour.colorimetry import sd_to_XYZ_integration 

199 >>> from colour.utilities import numpy_print_options 

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

201 >>> cmfs = ( 

202 ... MSDS_CMFS["CIE 1931 2 Degree Standard Observer"] 

203 ... .copy() 

204 ... .align(SpectralShape(360, 780, 10)) 

205 ... ) 

206 >>> illuminant = SDS_ILLUMINANTS["D65"].copy().align(cmfs.shape) 

207 >>> sd = XYZ_to_sd(XYZ, method="Jakob 2019", cmfs=cmfs, illuminant=illuminant) 

208 >>> with numpy_print_options(suppress=True): 

209 ... sd # doctest: +ELLIPSIS 

210 SpectralDistribution([[ 360. , 0.4893773...], 

211 [ 370. , 0.3258214...], 

212 [ 380. , 0.2147792...], 

213 [ 390. , 0.1482413...], 

214 [ 400. , 0.1086169...], 

215 [ 410. , 0.0841255...], 

216 [ 420. , 0.0683114...], 

217 [ 430. , 0.0577144...], 

218 [ 440. , 0.0504267...], 

219 [ 450. , 0.0453552...], 

220 [ 460. , 0.0418520...], 

221 [ 470. , 0.0395259...], 

222 [ 480. , 0.0381430...], 

223 [ 490. , 0.0375741...], 

224 [ 500. , 0.0377685...], 

225 [ 510. , 0.0387432...], 

226 [ 520. , 0.0405871...], 

227 [ 530. , 0.0434783...], 

228 [ 540. , 0.0477225...], 

229 [ 550. , 0.0538256...], 

230 [ 560. , 0.0626314...], 

231 [ 570. , 0.0755869...], 

232 [ 580. , 0.0952675...], 

233 [ 590. , 0.1264265...], 

234 [ 600. , 0.1779272...], 

235 [ 610. , 0.2649393...], 

236 [ 620. , 0.4039779...], 

237 [ 630. , 0.5832105...], 

238 [ 640. , 0.7445440...], 

239 [ 650. , 0.8499970...], 

240 [ 660. , 0.9094792...], 

241 [ 670. , 0.9425378...], 

242 [ 680. , 0.9616376...], 

243 [ 690. , 0.9732481...], 

244 [ 700. , 0.9806562...], 

245 [ 710. , 0.9855873...], 

246 [ 720. , 0.9889903...], 

247 [ 730. , 0.9914117...], 

248 [ 740. , 0.9931801...], 

249 [ 750. , 0.9945009...], 

250 [ 760. , 0.9955066...], 

251 [ 770. , 0.9962855...], 

252 [ 780. , 0.9968976...]], 

253 SpragueInterpolator, 

254 {}, 

255 Extrapolator, 

256 {'method': 'Constant', 'left': None, 'right': None}) 

257 >>> sd_to_XYZ_integration(sd, cmfs, illuminant) / 100 # doctest: +ELLIPSIS 

258 array([ 0.2066217..., 0.1220128..., 0.0513958...]) 

259 

260 *Mallett and Yuksel (2019)* reflectance recovery: 

261 

262 >>> cmfs = ( 

263 ... MSDS_CMFS["CIE 1931 2 Degree Standard Observer"] 

264 ... .copy() 

265 ... .align(SPECTRAL_SHAPE_sRGB_MALLETT2019) 

266 ... ) 

267 >>> illuminant = SDS_ILLUMINANTS["D65"].copy().align(cmfs.shape) 

268 >>> sd = XYZ_to_sd(XYZ, method="Mallett 2019") 

269 >>> with numpy_print_options(suppress=True): 

270 ... sd # doctest: +ELLIPSIS 

271 SpectralDistribution([[ 380. , 0.1735531...], 

272 [ 385. , 0.1720357...], 

273 [ 390. , 0.1677721...], 

274 [ 395. , 0.1576605...], 

275 [ 400. , 0.1372829...], 

276 [ 405. , 0.1170849...], 

277 [ 410. , 0.0895694...], 

278 [ 415. , 0.0706232...], 

279 [ 420. , 0.0585765...], 

280 [ 425. , 0.0523959...], 

281 [ 430. , 0.0497598...], 

282 [ 435. , 0.0476057...], 

283 [ 440. , 0.0465079...], 

284 [ 445. , 0.0460337...], 

285 [ 450. , 0.0455839...], 

286 [ 455. , 0.0452872...], 

287 [ 460. , 0.0450981...], 

288 [ 465. , 0.0448895...], 

289 [ 470. , 0.0449257...], 

290 [ 475. , 0.0448987...], 

291 [ 480. , 0.0446834...], 

292 [ 485. , 0.0441372...], 

293 [ 490. , 0.0417137...], 

294 [ 495. , 0.0373832...], 

295 [ 500. , 0.0357657...], 

296 [ 505. , 0.0348263...], 

297 [ 510. , 0.0341953...], 

298 [ 515. , 0.0337683...], 

299 [ 520. , 0.0334979...], 

300 [ 525. , 0.0332991...], 

301 [ 530. , 0.0331909...], 

302 [ 535. , 0.0332181...], 

303 [ 540. , 0.0333387...], 

304 [ 545. , 0.0334970...], 

305 [ 550. , 0.0337381...], 

306 [ 555. , 0.0341847...], 

307 [ 560. , 0.0346447...], 

308 [ 565. , 0.0353993...], 

309 [ 570. , 0.0367367...], 

310 [ 575. , 0.0392007...], 

311 [ 580. , 0.0445902...], 

312 [ 585. , 0.0625633...], 

313 [ 590. , 0.2965381...], 

314 [ 595. , 0.4215576...], 

315 [ 600. , 0.4347139...], 

316 [ 605. , 0.4385134...], 

317 [ 610. , 0.4385184...], 

318 [ 615. , 0.4385249...], 

319 [ 620. , 0.4374694...], 

320 [ 625. , 0.4384672...], 

321 [ 630. , 0.4368251...], 

322 [ 635. , 0.4340867...], 

323 [ 640. , 0.4303219...], 

324 [ 645. , 0.4243257...], 

325 [ 650. , 0.4159482...], 

326 [ 655. , 0.4057443...], 

327 [ 660. , 0.3919874...], 

328 [ 665. , 0.3742784...], 

329 [ 670. , 0.3518421...], 

330 [ 675. , 0.3240127...], 

331 [ 680. , 0.2955145...], 

332 [ 685. , 0.2625658...], 

333 [ 690. , 0.2343423...], 

334 [ 695. , 0.2174830...], 

335 [ 700. , 0.2060461...], 

336 [ 705. , 0.1977437...], 

337 [ 710. , 0.1916846...], 

338 [ 715. , 0.1861020...], 

339 [ 720. , 0.1823908...], 

340 [ 725. , 0.1807923...], 

341 [ 730. , 0.1795571...], 

342 [ 735. , 0.1785623...], 

343 [ 740. , 0.1775758...], 

344 [ 745. , 0.1771614...], 

345 [ 750. , 0.1767431...], 

346 [ 755. , 0.1764319...], 

347 [ 760. , 0.1762597...], 

348 [ 765. , 0.1762209...], 

349 [ 770. , 0.1761803...], 

350 [ 775. , 0.1761195...], 

351 [ 780. , 0.1760763...]], 

352 SpragueInterpolator, 

353 {}, 

354 Extrapolator, 

355 {'method': 'Constant', 'left': None, 'right': None}) 

356 >>> sd_to_XYZ_integration(sd, cmfs, illuminant) / 100 

357 ... # doctest: +ELLIPSIS 

358 array([ 0.2065436..., 0.1219996..., 0.0513764...]) 

359 

360 *Meng (2015)* reflectance recovery: 

361 

362 >>> cmfs = ( 

363 ... MSDS_CMFS["CIE 1931 2 Degree Standard Observer"] 

364 ... .copy() 

365 ... .align(SpectralShape(360, 780, 10)) 

366 ... ) 

367 >>> illuminant = SDS_ILLUMINANTS["D65"].copy().align(cmfs.shape) 

368 >>> sd = XYZ_to_sd(XYZ, method="Meng 2015", cmfs=cmfs, illuminant=illuminant) 

369 >>> with numpy_print_options(suppress=True): 

370 ... sd # doctest: +SKIP 

371 SpectralDistribution([[ 360. , 0.0762005...], 

372 [ 370. , 0.0761792...], 

373 [ 380. , 0.0761363...], 

374 [ 390. , 0.0761194...], 

375 [ 400. , 0.0762539...], 

376 [ 410. , 0.0761671...], 

377 [ 420. , 0.0754649...], 

378 [ 430. , 0.0731519...], 

379 [ 440. , 0.0676701...], 

380 [ 450. , 0.0577800...], 

381 [ 460. , 0.0441993...], 

382 [ 470. , 0.0285064...], 

383 [ 480. , 0.0138728...], 

384 [ 490. , 0.0033585...], 

385 [ 500. , 0. ...], 

386 [ 510. , 0. ...], 

387 [ 520. , 0. ...], 

388 [ 530. , 0. ...], 

389 [ 540. , 0.0055767...], 

390 [ 550. , 0.0317581...], 

391 [ 560. , 0.0754491...], 

392 [ 570. , 0.1314115...], 

393 [ 580. , 0.1937649...], 

394 [ 590. , 0.2559311...], 

395 [ 600. , 0.3123173...], 

396 [ 610. , 0.3584966...], 

397 [ 620. , 0.3927335...], 

398 [ 630. , 0.4159458...], 

399 [ 640. , 0.4306660...], 

400 [ 650. , 0.4391040...], 

401 [ 660. , 0.4439497...], 

402 [ 670. , 0.4463618...], 

403 [ 680. , 0.4474625...], 

404 [ 690. , 0.4479868...], 

405 [ 700. , 0.4482116...], 

406 [ 710. , 0.4482800...], 

407 [ 720. , 0.4483472...], 

408 [ 730. , 0.4484251...], 

409 [ 740. , 0.4484633...], 

410 [ 750. , 0.4485071...], 

411 [ 760. , 0.4484969...], 

412 [ 770. , 0.4484853...], 

413 [ 780. , 0.4485134...]], 

414 SpragueInterpolator, 

415 {}, 

416 Extrapolator, 

417 {'method': 'Constant', 'left': None, 'right': None}) 

418 >>> sd_to_XYZ_integration(sd, cmfs, illuminant) / 100 # doctest: +ELLIPSIS 

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

420 

421 *Otsu, Yamamoto and Hachisuka (2018)* reflectance recovery: 

422 

423 >>> cmfs = ( 

424 ... MSDS_CMFS["CIE 1931 2 Degree Standard Observer"] 

425 ... .copy() 

426 ... .align(SPECTRAL_SHAPE_OTSU2018) 

427 ... ) 

428 >>> illuminant = SDS_ILLUMINANTS["D65"].copy().align(cmfs.shape) 

429 >>> sd = XYZ_to_sd(XYZ, method="Otsu 2018", cmfs=cmfs, illuminant=illuminant) 

430 >>> with numpy_print_options(suppress=True): 

431 ... sd # doctest: +ELLIPSIS 

432 SpectralDistribution([[ 380. , 0.0601939...], 

433 [ 390. , 0.0568063...], 

434 [ 400. , 0.0517429...], 

435 [ 410. , 0.0495841...], 

436 [ 420. , 0.0502007...], 

437 [ 430. , 0.0506489...], 

438 [ 440. , 0.0510020...], 

439 [ 450. , 0.0493782...], 

440 [ 460. , 0.0468046...], 

441 [ 470. , 0.0437132...], 

442 [ 480. , 0.0416957...], 

443 [ 490. , 0.0403783...], 

444 [ 500. , 0.0405197...], 

445 [ 510. , 0.0406031...], 

446 [ 520. , 0.0416912...], 

447 [ 530. , 0.0430956...], 

448 [ 540. , 0.0444474...], 

449 [ 550. , 0.0459336...], 

450 [ 560. , 0.0507631...], 

451 [ 570. , 0.0628967...], 

452 [ 580. , 0.0844661...], 

453 [ 590. , 0.1334277...], 

454 [ 600. , 0.2262428...], 

455 [ 610. , 0.3599330...], 

456 [ 620. , 0.4885571...], 

457 [ 630. , 0.5752546...], 

458 [ 640. , 0.6193023...], 

459 [ 650. , 0.6450744...], 

460 [ 660. , 0.6610548...], 

461 [ 670. , 0.6688673...], 

462 [ 680. , 0.6795426...], 

463 [ 690. , 0.6887933...], 

464 [ 700. , 0.7003469...], 

465 [ 710. , 0.7084128...], 

466 [ 720. , 0.7154674...], 

467 [ 730. , 0.7234334...]], 

468 SpragueInterpolator, 

469 {}, 

470 Extrapolator, 

471 {'method': 'Constant', 'left': None, 'right': None}) 

472 >>> sd_to_XYZ_integration(sd, cmfs, illuminant) / 100 # doctest: +ELLIPSIS 

473 array([ 0.2065494..., 0.1219712..., 0.0514002...]) 

474 

475 *Smits (1999)* reflectance recovery: 

476 

477 >>> cmfs = ( 

478 ... MSDS_CMFS["CIE 1931 2 Degree Standard Observer"] 

479 ... .copy() 

480 ... .align(SpectralShape(360, 780, 10)) 

481 ... ) 

482 >>> illuminant = SDS_ILLUMINANTS["E"].copy().align(cmfs.shape) 

483 >>> sd = XYZ_to_sd(XYZ, method="Smits 1999") 

484 >>> with numpy_print_options(suppress=True): 

485 ... sd # doctest: +ELLIPSIS 

486 SpectralDistribution([[ 380. , 0.0787830...], 

487 [ 417.7778 , 0.0622018...], 

488 [ 455.5556 , 0.0446206...], 

489 [ 493.3333 , 0.0352220...], 

490 [ 531.1111 , 0.0324149...], 

491 [ 568.8889 , 0.0330105...], 

492 [ 606.6667 , 0.3207115...], 

493 [ 644.4444 , 0.3836164...], 

494 [ 682.2222 , 0.3836164...], 

495 [ 720. , 0.3835649...]], 

496 LinearInterpolator, 

497 {}, 

498 Extrapolator, 

499 {'method': 'Constant', 'left': None, 'right': None}) 

500 >>> sd_to_XYZ_integration(sd, cmfs, illuminant) / 100 # doctest: +ELLIPSIS 

501 array([ 0.1894770..., 0.1126470..., 0.0474420...]) 

502 """ 

503 

504 a = as_float_array(XYZ) 

505 method = validate_method(method, tuple(XYZ_TO_SD_METHODS)) 

506 

507 function = XYZ_TO_SD_METHODS[method] 

508 

509 if function is RGB_to_sd_Smits1999: 

510 from colour.recovery.smits1999 import XYZ_to_RGB_Smits1999 # noqa: PLC0415 

511 

512 a = XYZ_to_RGB_Smits1999(XYZ) 

513 elif function is RGB_to_sd_Mallett2019: 

514 from colour.models import XYZ_to_sRGB # noqa: PLC0415 

515 

516 a = XYZ_to_sRGB(XYZ, apply_cctf_encoding=False) 

517 

518 return function(a, **filter_kwargs(function, **kwargs)) 

519 

520 

521__all__ += [ 

522 "XYZ_TO_SD_METHODS", 

523 "XYZ_to_sd", 

524]