Coverage for models/jzazbz.py: 48%

1"""

2:math:`J_za_zb_z` Colourspace

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

4

5 Define the :math:`J_za_zb_z` colourspace transformations.

6

7- :func:`colour.models.IZAZBZ_METHODS`

8- :func:`colour.models.XYZ_to_Izazbz`

9- :func:`colour.models.Izazbz_to_XYZ`

10- :func:`colour.XYZ_to_Jzazbz`

11- :func:`colour.Jzazbz_to_XYZ`

12

13 References

14----------

15- :cite:`Safdar2017` : Safdar, M., Cui, G., Kim, Y. J., & Luo, M. R. (2017).

16 Perceptually uniform color space for image signals including high dynamic

17 range and wide gamut. Optics Express, 25(13), 15131.

18 doi:10.1364/OE.25.015131

19- :cite:`Safdar2021` : Safdar, M., Hardeberg, J. Y., & Ronnier Luo, M.

20 (2021). ZCAM, a colour appearance model based on a high dynamic range

21 uniform colour space. Optics Express, 29(4), 6036. doi:10.1364/OE.413659

22"""

23

24 from __future__ import annotations

25

26 import typing

27

28 import numpy as np

29

30 from colour.algebra import vecmul

31

32 if typing.TYPE_CHECKING:

33 from colour.hints import ArrayLike, Domain1, Literal, NDArrayFloat, Range1

34

35 from colour.models.rgb.transfer_functions import eotf_inverse_ST2084, eotf_ST2084

36 from colour.models.rgb.transfer_functions.st_2084 import CONSTANTS_ST2084

37 from colour.utilities import (

38 Structure,

39 as_float_array,

40 domain_range_scale,

41 optional,

42 tsplit,

43 tstack,

44 validate_method,

45)

46 from colour.utilities.documentation import DocstringTuple, is_documentation_building

47

48 __author__ = "Colour Developers"

49 __copyright__ = "Copyright 2013 Colour Developers"

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

51 __maintainer__ = "Colour Developers"

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

53 __status__ = "Production"

54

55 __all__ = [

56 "CONSTANTS_JZAZBZ_SAFDAR2017",

57 "CONSTANTS_JZAZBZ_SAFDAR2021",

58 "MATRIX_JZAZBZ_XYZ_TO_LMS",

59 "MATRIX_JZAZBZ_LMS_TO_XYZ",

60 "MATRIX_JZAZBZ_LMS_P_TO_IZAZBZ_SAFDAR2017",

61 "MATRIX_JZAZBZ_IZAZBZ_TO_LMS_P_SAFDAR2017",

62 "MATRIX_JZAZBZ_LMS_P_TO_IZAZBZ_SAFDAR2021",

63 "MATRIX_JZAZBZ_IZAZBZ_TO_LMS_P_SAFDAR2021",

64 "IZAZBZ_METHODS",

65 "XYZ_to_Izazbz",

66 "Izazbz_to_XYZ",

67 "XYZ_to_Jzazbz",

68 "Jzazbz_to_XYZ",

69]

70

71 CONSTANTS_JZAZBZ_SAFDAR2017: Structure = Structure(

72 b=1.15, g=0.66, d=-0.56, d_0=1.6295499532821566 * 10**-11

73)

74 CONSTANTS_JZAZBZ_SAFDAR2017.update(CONSTANTS_ST2084)

75 CONSTANTS_JZAZBZ_SAFDAR2017.m_2 = 1.7 * 2523 / 2**5

76"""

77 Constants for :math:`J_za_zb_z` colourspace and its variant of the perceptual

78 quantizer (PQ) from Dolby Laboratories.

79

80 Notes

81-----

82- The :math:`m2` constant, i.e., the power factor has been re-optimized during

83 the development of the :math:`J_za_zb_z` colourspace.

84"""

85

86 CONSTANTS_JZAZBZ_SAFDAR2021: Structure = Structure(**CONSTANTS_JZAZBZ_SAFDAR2017)

87 CONSTANTS_JZAZBZ_SAFDAR2021.d_0 = 3.7035226210190005 * 10**-11

88""":math:`J_za_zb_z` colourspace constants for the *ZCAM* colour appearance model."""

89

90 MATRIX_JZAZBZ_XYZ_TO_LMS: NDArrayFloat = np.array(

91 [

92 [0.41478972, 0.579999, 0.0146480],

93 [-0.2015100, 1.120649, 0.0531008],

94 [-0.0166008, 0.264800, 0.6684799],

95 ]

96)

97"""

98:math:`J_za_zb_z` *CIE XYZ* tristimulus values to normalised cone responses

99 matrix.

100"""

101

102 MATRIX_JZAZBZ_LMS_TO_XYZ: NDArrayFloat = np.linalg.inv(MATRIX_JZAZBZ_XYZ_TO_LMS)

103"""

104:math:`J_za_zb_z` normalised cone responses to *CIE XYZ* tristimulus values

105 matrix.

106"""

107

108 MATRIX_JZAZBZ_LMS_P_TO_IZAZBZ_SAFDAR2017: NDArrayFloat = np.array(

109 [

110 [0.500000, 0.500000, 0.000000],

111 [3.524000, -4.066708, 0.542708],

112 [0.199076, 1.096799, -1.295875],

113 ]

114)

115"""

116:math:`LMS_p` *SMPTE ST 2084:2014* encoded normalised cone responses to

117:math:`I_za_zb_z` intermediate colourspace matrix.

118"""

119

120 MATRIX_JZAZBZ_IZAZBZ_TO_LMS_P_SAFDAR2017: NDArrayFloat = np.linalg.inv(

121 MATRIX_JZAZBZ_LMS_P_TO_IZAZBZ_SAFDAR2017

122)

123"""

124:math:`I_za_zb_z` intermediate colourspace to :math:`LMS_p`

125*SMPTE ST 2084:2014* encoded normalised cone responses matrix.

126"""

127

128 MATRIX_JZAZBZ_LMS_P_TO_IZAZBZ_SAFDAR2021: NDArrayFloat = np.array(

129 [

130 [0.000000, 1.000000, 0.000000],

131 [3.524000, -4.066708, 0.542708],

132 [0.199076, 1.096799, -1.295875],

133 ]

134)

135"""

136:math:`LMS_p` *SMPTE ST 2084:2014* encoded normalised cone responses to

137:math:`I_za_zb_z` intermediate colourspace matrix.

138

139 References

140----------

141:cite:`Safdar2021`

142"""

143

144 MATRIX_JZAZBZ_IZAZBZ_TO_LMS_P_SAFDAR2021: NDArrayFloat = np.linalg.inv(

145 MATRIX_JZAZBZ_LMS_P_TO_IZAZBZ_SAFDAR2021

146)

147"""

148:math:`I_za_zb_z` intermediate colourspace to :math:`LMS_p`

149*SMPTE ST 2084:2014* encoded normalised cone responses matrix.

150

151 References

152----------

153:cite:`Safdar2021`

154"""

155

156 IZAZBZ_METHODS: tuple = ("Safdar 2017", "Safdar 2021", "ZCAM")

157 if is_documentation_building(): # pragma: no cover

158 IZAZBZ_METHODS = DocstringTuple(IZAZBZ_METHODS)

159 IZAZBZ_METHODS.__doc__ = """

160 Supported :math:`I_za_zb_z` computation methods.

161

162 References

163----------

164:cite:`Safdar2017`, :cite:`Safdar2021`

165"""

166

167

168 def XYZ_to_Izazbz(

169 XYZ_D65: ArrayLike,

170 constants: Structure | None = None,

171 method: (Literal["Safdar 2017", "Safdar 2021", "ZCAM"] | str) = "Safdar 2017",

172) -> Range1:

173 """

174 Convert from *CIE XYZ* tristimulus values to :math:`I_za_zb_z`

175 colourspace.

176

177 Parameters

178 ----------

179 XYZ_D65

180 *CIE XYZ* tristimulus values under

181 *CIE Standard Illuminant D Series D65*.

182 constants

183 :math:`J_za_zb_z` colourspace constants.

184 method

185 Computation method, *Safdar 2021* and *ZCAM* methods are equivalent.

186

187 Returns

188 -------

189 :class:`numpy.ndarray`

190 :math:`I_za_zb_z` colourspace array where :math:`I_z` is the

191 achromatic response, :math:`a_z` is redness-greenness and

192 :math:`b_z` is yellowness-blueness.

193

194 Warnings

195 --------

196 The underlying *SMPTE ST 2084:2014* transfer function is an absolute

197 transfer function.

198

199 Notes

200 -----

201 - The underlying *SMPTE ST 2084:2014* transfer function is an

202 absolute transfer function, thus the domain and range values for

203 the *Reference* and *1* scales are only indicative that the data

204 is not affected by scale transformations. The effective domain of

205 *SMPTE ST 2084:2014* inverse electro-optical transfer function

206 (EOTF) is [0.0001, 10000].

207

208 +------------+-----------------------+------------------+

209 | **Domain** | **Scale - Reference** | **Scale - 1** |

210 +============+=======================+==================+

211 | ``XYZ`` | ``UN`` | ``UN`` |

212 +------------+-----------------------+------------------+

213

214 +------------+-----------------------+------------------+

215 | **Range** | **Scale - Reference** | **Scale - 1** |

216 +============+=======================+==================+

217 | ``Izazbz`` | 1 | 1 |

218 +------------+-----------------------+------------------+

219

220 References

221 ----------

222 :cite:`Safdar2017`, :cite:`Safdar2021`

223

224 Examples

225 --------

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

227 >>> XYZ_to_Izazbz(XYZ) # doctest: +ELLIPSIS

228 array([ 0.0120779..., 0.0092430..., 0.0052600...])

229 """

230

231 X_D65, Y_D65, Z_D65 = tsplit(as_float_array(XYZ_D65))

232

233 method = validate_method(method, IZAZBZ_METHODS)

234

235 constants = optional(

236 constants,

237 (

238 CONSTANTS_JZAZBZ_SAFDAR2017

239 if method == "safdar 2017"

240 else CONSTANTS_JZAZBZ_SAFDAR2021

241 ),

242 )

243

244 X_p_D65 = constants.b * X_D65 - (constants.b - 1) * Z_D65

245 Y_p_D65 = constants.g * Y_D65 - (constants.g - 1) * X_D65

246

247 XYZ_p_D65 = tstack([X_p_D65, Y_p_D65, Z_D65])

248

249 LMS = vecmul(MATRIX_JZAZBZ_XYZ_TO_LMS, XYZ_p_D65)

250

251 with domain_range_scale("ignore"):

252 LMS_p = eotf_inverse_ST2084(LMS, 10000, constants)

253

254 if method == "safdar 2017":

255 Izazbz = vecmul(MATRIX_JZAZBZ_LMS_P_TO_IZAZBZ_SAFDAR2017, LMS_p)

256 else:

257 Izazbz = vecmul(MATRIX_JZAZBZ_LMS_P_TO_IZAZBZ_SAFDAR2021, LMS_p)

258 Izazbz[..., 0] -= constants.d_0

259

260 return Izazbz

261

262

263 def Izazbz_to_XYZ(

264 Izazbz: Domain1,

265 constants: Structure | None = None,

266 method: (Literal["Safdar 2017", "Safdar 2021", "ZCAM"] | str) = "Safdar 2017",

267) -> NDArrayFloat:

268 """

269 Convert from :math:`I_za_zb_z` colourspace to *CIE XYZ* tristimulus

270 values.

271

272 Parameters

273 ----------

274 Izazbz

275 :math:`I_za_zb_z` colourspace array where :math:`I_z` is the

276 achromatic response, :math:`a_z` is redness-greenness and

277 :math:`b_z` is yellowness-blueness.

278 constants

279 :math:`J_za_zb_z` colourspace constants.

280 method

281 Computation method, *Safdar 2021* and *ZCAM* methods are equivalent.

282

283 Returns

284 -------

285 :class:`numpy.ndarray`

286 *CIE XYZ* tristimulus values under

287 *CIE Standard Illuminant D Series D65*.

288

289 Warnings

290 --------

291 The underlying *SMPTE ST 2084:2014* transfer function is an absolute

292 transfer function.

293

294 Notes

295 -----

296 - The underlying *SMPTE ST 2084:2014* transfer function is an

297 absolute transfer function, thus the domain and range values for

298 the *Reference* and *1* scales are only indicative that the data

299 is not affected by scale transformations. The effective domain of

300 *SMPTE ST 2084:2014* inverse electro-optical transfer function

301 (EOTF) is [0.0001, 10000].

302

303 +------------+-----------------------+------------------+

304 | **Domain** | **Scale - Reference** | **Scale - 1** |

305 +============+=======================+==================+

306 | ``Izazbz`` | 1 | 1 |

307 +------------+-----------------------+------------------+

308

309 +------------+-----------------------+------------------+

310 | **Range** | **Scale - Reference** | **Scale - 1** |

311 +============+=======================+==================+

312 | ``XYZ`` | ``UN`` | ``UN`` |

313 +------------+-----------------------+------------------+

314

315 References

316 ----------

317 :cite:`Safdar2017`, :cite:`Safdar2021`

318

319 Examples

320 --------

321 >>> Izazbz = np.array([0.01207793, 0.00924302, 0.00526007])

322 >>> Izazbz_to_XYZ(Izazbz) # doctest: +ELLIPSIS

323 array([ 0.2065401..., 0.1219723..., 0.0513696...])

324 """

325

326 Izazbz = as_float_array(Izazbz)

327

328 method = validate_method(method, IZAZBZ_METHODS)

329

330 constants = optional(

331 constants,

332 (

333 CONSTANTS_JZAZBZ_SAFDAR2017

334 if method == "safdar 2017"

335 else CONSTANTS_JZAZBZ_SAFDAR2021

336 ),

337 )

338

339 if method == "safdar 2017":

340 LMS_p = vecmul(MATRIX_JZAZBZ_IZAZBZ_TO_LMS_P_SAFDAR2017, Izazbz)

341 else:

342 Izazbz[..., 0] += constants.d_0

343 LMS_p = vecmul(MATRIX_JZAZBZ_IZAZBZ_TO_LMS_P_SAFDAR2021, Izazbz)

344

345 with domain_range_scale("ignore"):

346 LMS = eotf_ST2084(LMS_p, 10000, constants)

347

348 X_p_D65, Y_p_D65, Z_p_D65 = tsplit(vecmul(MATRIX_JZAZBZ_LMS_TO_XYZ, LMS))

349

350 X_D65 = (X_p_D65 + (constants.b - 1) * Z_p_D65) / constants.b

351 Y_D65 = (Y_p_D65 + (constants.g - 1) * X_D65) / constants.g

352

353 return tstack([X_D65, Y_D65, Z_p_D65])

354

355

356 def XYZ_to_Jzazbz(

357 XYZ_D65: ArrayLike, constants: Structure = CONSTANTS_JZAZBZ_SAFDAR2017

358) -> Range1:

359 """

360 Convert from *CIE XYZ* tristimulus values to :math:`J_za_zb_z`

361 colourspace.

362

363 Parameters

364 ----------

365 XYZ_D65

366 *CIE XYZ* tristimulus values under

367 *CIE Standard Illuminant D Series D65*.

368 constants

369 :math:`J_za_zb_z` colourspace constants.

370

371 Returns

372 -------

373 :class:`numpy.ndarray`

374 :math:`J_za_zb_z` colourspace array where :math:`J_z` is

375 Lightness, :math:`a_z` is redness-greenness and :math:`b_z` is

376 yellowness-blueness.

377

378 Warnings

379 --------

380 The underlying *SMPTE ST 2084:2014* transfer function is an absolute

381 transfer function.

382

383 Notes

384 -----

385 - The underlying *SMPTE ST 2084:2014* transfer function is an

386 absolute transfer function, thus the domain and range values for

387 the *Reference* and *1* scales are only indicative that the data

388 is not affected by scale transformations. The effective domain of

389 *SMPTE ST 2084:2014* inverse electro-optical transfer function

390 (EOTF) is [0.0001, 10000].

391 domain of *SMPTE ST 2084:2014* inverse electro-optical transfer

392 function (EOTF) is [0.0001, 10000].

393

394 +------------+-----------------------+------------------+

395 | **Domain** | **Scale - Reference** | **Scale - 1** |

396 +============+=======================+==================+

397 | ``XYZ`` | ``UN`` | ``UN`` |

398 +------------+-----------------------+------------------+

399

400 +------------+-----------------------+------------------+

401 | **Range** | **Scale - Reference** | **Scale - 1** |

402 +============+=======================+==================+

403 | ``Jzazbz`` | 1 | 1 |

404 +------------+-----------------------+------------------+

405

406 References

407 ----------

408 :cite:`Safdar2017`

409

410 Examples

411 --------

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

413 >>> XYZ_to_Jzazbz(XYZ) # doctest: +ELLIPSIS

414 array([ 0.0053504..., 0.0092430..., 0.0052600...])

415 """

416

417 XYZ_D65 = as_float_array(XYZ_D65)

418

419 with domain_range_scale("ignore"):

420 I_z, a_z, b_z = tsplit(

421 XYZ_to_Izazbz(XYZ_D65, CONSTANTS_JZAZBZ_SAFDAR2017, "Safdar 2017")

422 )

423

424 J_z = ((1 + constants.d) * I_z) / (1 + constants.d * I_z) - constants.d_0

425

426 return tstack([J_z, a_z, b_z])

427

428

429 def Jzazbz_to_XYZ(

430 Jzazbz: Domain1, constants: Structure = CONSTANTS_JZAZBZ_SAFDAR2017

431) -> NDArrayFloat:

432 """

433 Convert from :math:`J_za_zb_z` colourspace to *CIE XYZ* tristimulus

434 values.

435

436 Parameters

437 ----------

438 Jzazbz

439 :math:`J_za_zb_z` colourspace array where :math:`J_z` is Lightness,

440 :math:`a_z` is redness-greenness and :math:`b_z` is

441 yellowness-blueness.

442 constants

443 :math:`J_za_zb_z` colourspace constants.

444

445 Returns

446 -------

447 :class:`numpy.ndarray`

448 *CIE XYZ* tristimulus values under

449 *CIE Standard Illuminant D Series D65*.

450

451 Warnings

452 --------

453 The underlying *SMPTE ST 2084:2014* transfer function is an absolute

454 transfer function.

455

456 Notes

457 -----

458 - The underlying *SMPTE ST 2084:2014* transfer function is an

459 absolute transfer function, thus the domain and range values for

460 the *Reference* and *1* scales are only indicative that the data

461 is not affected by scale transformations. The effective domain of

462 *SMPTE ST 2084:2014* inverse electro-optical transfer function

463 (EOTF) is [0.0001, 10000].

464

465 +------------+-----------------------+------------------+

466 | **Domain** | **Scale - Reference** | **Scale - 1** |

467 +============+=======================+==================+

468 | ``Jzazbz`` | 1 | 1 |

469 +------------+-----------------------+------------------+

470

471 +------------+-----------------------+------------------+

472 | **Range** | **Scale - Reference** | **Scale - 1** |

473 +============+=======================+==================+

474 | ``XYZ`` | ``UN`` | ``UN`` |

475 +------------+-----------------------+------------------+

476

477 References

478 ----------

479 :cite:`Safdar2017`

480

481 Examples

482 --------

483 >>> Jzazbz = np.array([0.00535048, 0.00924302, 0.00526007])

484 >>> Jzazbz_to_XYZ(Jzazbz) # doctest: +ELLIPSIS

485 array([ 0.2065402..., 0.1219723..., 0.0513696...])

486 """

487

488 J_z, a_z, b_z = tsplit(as_float_array(Jzazbz))

489

490 I_z = (J_z + constants.d_0) / (

491 1 + constants.d - constants.d * (J_z + constants.d_0)

492 )

493

494 with domain_range_scale("ignore"):

495 return Izazbz_to_XYZ(

496 tstack([I_z, a_z, b_z]), CONSTANTS_JZAZBZ_SAFDAR2017, "Safdar 2017"

497 )