Coverage for models/cam16_ucs.py: 30%

1"""

2 CAM16-LCD, CAM16-SCD, and CAM16-UCS Colourspaces - Li et al. (2017)

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

4

5 Define the *Li, Li, Wang, Zu, Luo, Cui, Melgosa, Brill and Pointer (2017)*

6*CAM16-LCD*, *CAM16-SCD*, and *CAM16-UCS* colourspaces transformations.

7

8- :func:`colour.JMh_CAM16_to_CAM16LCD`

9- :func:`colour.CAM16LCD_to_JMh_CAM16`

10- :func:`colour.JMh_CAM16_to_CAM16SCD`

11- :func:`colour.CAM16SCD_to_JMh_CAM16`

12- :func:`colour.JMh_CAM16_to_CAM16UCS`

13- :func:`colour.CAM16UCS_to_JMh_CAM16`

14- :func:`colour.XYZ_to_CAM16LCD`

15- :func:`colour.CAM16LCD_to_XYZ`

16- :func:`colour.XYZ_to_CAM16SCD`

17- :func:`colour.CAM16SCD_to_XYZ`

18- :func:`colour.XYZ_to_CAM16UCS`

19- :func:`colour.CAM16UCS_to_XYZ`

20

21 References

22----------

23- :cite:`Li2017` : Li, C., Li, Z., Wang, Z., Xu, Y., Luo, M. R., Cui, G.,

24 Melgosa, M., Brill, M. H., & Pointer, M. (2017). Comprehensive color

25 solutions: CAM16, CAT16, and CAM16-UCS. Color Research & Application,

26 42(6), 703-718. doi:10.1002/col.22131

27"""

28

29 from __future__ import annotations

30

31 import re

32 import typing

33 from functools import partial

34

35 if typing.TYPE_CHECKING:

36 from colour.hints import (

37 Any,

38 ArrayLike,

39 Callable,

40 Domain1,

41 Domain100,

42 Range1,

43 Range100,

44 )

45

46 from colour.hints import ArrayLike, NDArrayFloat, cast

47 from colour.models.cam02_ucs import (

48 COEFFICIENTS_UCS_LUO2006,

49 CAM02LCD_to_JMh_CIECAM02,

50 CAM02LCD_to_XYZ,

51 CAM02SCD_to_JMh_CIECAM02,

52 CAM02SCD_to_XYZ,

53 CAM02UCS_to_JMh_CIECAM02,

54 CAM02UCS_to_XYZ,

55 JMh_CIECAM02_to_CAM02LCD,

56 JMh_CIECAM02_to_CAM02SCD,

57 JMh_CIECAM02_to_CAM02UCS,

58 JMh_CIECAM02_to_UCS_Luo2006,

59 UCS_Luo2006_to_JMh_CIECAM02,

60 XYZ_to_CAM02LCD,

61 XYZ_to_CAM02SCD,

62 XYZ_to_CAM02UCS,

63)

64 from colour.utilities import (

65 as_float_array,

66 copy_definition,

67 get_domain_range_scale,

68 optional,

69 tsplit,

70 tstack,

71)

72

73 __author__ = "Colour Developers"

74 __copyright__ = "Copyright 2013 Colour Developers"

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

76 __maintainer__ = "Colour Developers"

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

78 __status__ = "Production"

79

80 __all__ = [

81 "JMh_CAM16_to_UCS_Li2017",

82 "UCS_Li2017_to_JMh_CAM16",

83 "JMh_CAM16_to_CAM16LCD",

84 "CAM16LCD_to_JMh_CAM16",

85 "JMh_CAM16_to_CAM16SCD",

86 "CAM16SCD_to_JMh_CAM16",

87 "JMh_CAM16_to_CAM16UCS",

88 "CAM16UCS_to_JMh_CAM16",

89 "XYZ_to_UCS_Li2017",

90 "UCS_Li2017_to_XYZ",

91 "XYZ_to_CAM16LCD",

92 "CAM16LCD_to_XYZ",

93 "XYZ_to_CAM16SCD",

94 "CAM16SCD_to_XYZ",

95 "XYZ_to_CAM16UCS",

96 "CAM16UCS_to_XYZ",

97]

98

99

100 def _UCS_Luo2006_callable_to_UCS_Li2017_docstring(callable_: Callable) -> str:

101 """

102 Convert the docstring of the specified *Luo et al. (2006)* callable to

103 conform to *Li et al. (2017)* conventions.

104

105 Parameters

106 ----------

107 callable_

108 Callable whose docstring will be adapted.

109

110 Returns

111 -------

112 :class:`str`

113 Converted docstring following *Li et al. (2017)* conventions.

114 """

115

116 docstring = callable_.__doc__

117 # NOTE: Required for optimised python launch.

118 docstring = optional(docstring, "")

119

120 docstring = docstring.replace("Luo et al. (2006)", "Li et al. (2017)")

121 docstring = docstring.replace("CIECAM02", "CAM16")

122 docstring = docstring.replace("CAM02", "CAM16")

123 docstring = docstring.replace("Luo2006b", "Li2017")

124

125 match = re.match("(.*)Examples", docstring, re.DOTALL)

126 if match is not None:

127 docstring = match.group(1)

128

129 return docstring

130

131

132 JMh_CAM16_to_UCS_Li2017 = copy_definition(

133 JMh_CIECAM02_to_UCS_Luo2006, "JMh_CAM16_to_UCS_Li2017"

134)

135 JMh_CAM16_to_UCS_Li2017.__doc__ = _UCS_Luo2006_callable_to_UCS_Li2017_docstring(

136 JMh_CIECAM02_to_UCS_Luo2006

137)

138

139 UCS_Li2017_to_JMh_CAM16 = copy_definition(

140 UCS_Luo2006_to_JMh_CIECAM02, "UCS_Li2017_to_JMh_CAM16"

141)

142 UCS_Li2017_to_JMh_CAM16.__doc__ = _UCS_Luo2006_callable_to_UCS_Li2017_docstring(

143 UCS_Luo2006_to_JMh_CIECAM02

144)

145

146 JMh_CAM16_to_CAM16LCD = partial(

147 JMh_CAM16_to_UCS_Li2017, coefficients=COEFFICIENTS_UCS_LUO2006["CAM02-LCD"]

148)

149 JMh_CAM16_to_CAM16LCD.__doc__ = _UCS_Luo2006_callable_to_UCS_Li2017_docstring(

150 JMh_CIECAM02_to_CAM02LCD

151)

152

153 CAM16LCD_to_JMh_CAM16 = partial(

154 UCS_Li2017_to_JMh_CAM16, coefficients=COEFFICIENTS_UCS_LUO2006["CAM02-LCD"]

155)

156 CAM16LCD_to_JMh_CAM16.__doc__ = _UCS_Luo2006_callable_to_UCS_Li2017_docstring(

157 CAM02LCD_to_JMh_CIECAM02

158)

159

160 JMh_CAM16_to_CAM16SCD = partial(

161 JMh_CAM16_to_UCS_Li2017, coefficients=COEFFICIENTS_UCS_LUO2006["CAM02-SCD"]

162)

163 JMh_CAM16_to_CAM16SCD.__doc__ = _UCS_Luo2006_callable_to_UCS_Li2017_docstring(

164 JMh_CIECAM02_to_CAM02SCD

165)

166

167 CAM16SCD_to_JMh_CAM16 = partial(

168 UCS_Li2017_to_JMh_CAM16, coefficients=COEFFICIENTS_UCS_LUO2006["CAM02-SCD"]

169)

170 CAM16SCD_to_JMh_CAM16.__doc__ = _UCS_Luo2006_callable_to_UCS_Li2017_docstring(

171 CAM02SCD_to_JMh_CIECAM02

172)

173

174 JMh_CAM16_to_CAM16UCS = partial(

175 JMh_CAM16_to_UCS_Li2017, coefficients=COEFFICIENTS_UCS_LUO2006["CAM02-UCS"]

176)

177 JMh_CAM16_to_CAM16UCS.__doc__ = _UCS_Luo2006_callable_to_UCS_Li2017_docstring(

178 JMh_CIECAM02_to_CAM02UCS

179)

180

181 CAM16UCS_to_JMh_CAM16 = partial(

182 UCS_Li2017_to_JMh_CAM16, coefficients=COEFFICIENTS_UCS_LUO2006["CAM02-UCS"]

183)

184 CAM16UCS_to_JMh_CAM16.__doc__ = _UCS_Luo2006_callable_to_UCS_Li2017_docstring(

185 CAM02UCS_to_JMh_CIECAM02

186)

187

188

189 def XYZ_to_UCS_Li2017(

190 XYZ: Domain1,

191 coefficients: ArrayLike,

192 **kwargs: Any,

193) -> Range100:

194 """

195 Convert from *CIE XYZ* tristimulus values to one of the *Li et al.

196 (2017)* *CAM16-LCD*, *CAM16-SCD*, or *CAM16-UCS* colourspaces

197 :math:`J'a'b'` array.

198

199 Parameters

200 ----------

201 XYZ

202 *CIE XYZ* tristimulus values.

203 coefficients

204 Coefficients of one of the *Li et al. (2017)* *CAM16-LCD*,

205 *CAM16-SCD*, or *CAM16-UCS* colourspaces.

206

207 Other Parameters

208 ----------------

209 kwargs

210 {:func:`colour.XYZ_to_CAM16`},

211 See the documentation of the previously listed definition. The

212 default viewing conditions are those of *IEC 61966-2-1:1999*,

213 i.e., *sRGB* 64 Lux ambient illumination, 80 :math:`cd/m^2`,

214 adapting field luminance about 20% of a white object in the

215 scene.

216

217 Returns

218 -------

219 :class:`numpy.ndarray`

220 *Li et al. (2017)* *CAM16-LCD*, *CAM16-SCD*, or *CAM16-UCS*

221 colourspaces :math:`J'a'b'` array.

222

223 Warnings

224 --------

225 The ``XYZ_w`` parameter for :func:`colour.XYZ_to_CAM16` definition must

226 be specified in the same domain-range scale as the ``XYZ`` parameter.

227

228 Notes

229 -----

230 +------------+------------------------+------------------+

231 | **Domain** | **Scale - Reference** | **Scale - 1** |

232 +============+========================+==================+

233 | ``XYZ`` | 1 | 1 |

234 +------------+------------------------+------------------+

235

236 +------------+------------------------+------------------+

237 | **Range** | **Scale - Reference** | **Scale - 1** |

238 +============+========================+==================+

239 | ``Jpapbp`` | 100 | 1 |

240 +------------+------------------------+------------------+

241

242 Examples

243 --------

244 >>> import numpy as np

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

246 >>> XYZ_to_UCS_Li2017(XYZ, COEFFICIENTS_UCS_LUO2006["CAM02-LCD"])

247 ... # doctest: +ELLIPSIS

248 array([ 46.0658603..., 41.0758649..., 14.5102582...])

249

250 >>> from colour.appearance import CAM_KWARGS_CIECAM02_sRGB

251 >>> XYZ_w = CAM_KWARGS_CIECAM02_sRGB["XYZ_w"]

252 >>> XYZ_to_UCS_Li2017(XYZ, COEFFICIENTS_UCS_LUO2006["CAM02-LCD"], XYZ_w=XYZ_w / 100)

253 ... # doctest: +ELLIPSIS

254 array([ 46.0658603..., 41.0758649..., 14.5102582...])

255 """

256

257 from colour.appearance import ( # noqa: PLC0415

258 CAM_KWARGS_CIECAM02_sRGB,

259 XYZ_to_CAM16,

260 )

261

262 domain_range_reference = get_domain_range_scale() == "reference"

263

264 settings = CAM_KWARGS_CIECAM02_sRGB.copy()

265 settings.update(**kwargs)

266 XYZ_w = kwargs.get("XYZ_w")

267 if XYZ_w is not None and domain_range_reference:

268 settings["XYZ_w"] = XYZ_w * 100

269

270 if domain_range_reference:

271 XYZ = as_float_array(XYZ) * 100

272

273 specification = XYZ_to_CAM16(XYZ, **settings)

274 JMh = tstack(

275 [

276 cast("NDArrayFloat", specification.J),

277 cast("NDArrayFloat", specification.M),

278 cast("NDArrayFloat", specification.h),

279 ]

280 )

281

282 return JMh_CAM16_to_UCS_Li2017(JMh, coefficients)

283

284

285 def UCS_Li2017_to_XYZ(

286 Jpapbp: Domain100,

287 coefficients: ArrayLike,

288 **kwargs: Any,

289) -> Range1:

290 """

291 Convert from one of the *Li et al. (2017)* *CAM16-LCD*, *CAM16-SCD*, or

292 *CAM16-UCS* colourspaces :math:`J'a'b'` array to *CIE XYZ* tristimulus

293 values.

294

295 Parameters

296 ----------

297 Jpapbp

298 *Li et al. (2017)* *CAM16-LCD*, *CAM16-SCD*, or *CAM16-UCS*

299 colourspaces :math:`J'a'b'` array.

300 coefficients

301 Coefficients of one of the *Li et al. (2017)* *CAM16-LCD*,

302 *CAM16-SCD*, or *CAM16-UCS* colourspaces.

303

304 Other Parameters

305 ----------------

306 kwargs

307 {:func:`colour.CAM16_to_XYZ`},

308 See the documentation of the previously listed definition. The

309 default viewing conditions are those of *IEC 61966-2-1:1999*,

310 i.e., *sRGB* 64 Lux ambient illumination, 80 :math:`cd/m^2`,

311 adapting field luminance about 20% of a white object in the

312 scene.

313

314 Returns

315 -------

316 :class:`numpy.ndarray`

317 *CIE XYZ* tristimulus values.

318

319 Warnings

320 --------

321 The ``XYZ_w`` parameter for :func:`colour.XYZ_to_CAM16` definition must

322 be specified in the same domain-range scale as the ``XYZ`` parameter.

323

324 Notes

325 -----

326 +------------+------------------------+------------------+

327 | **Domain** | **Scale - Reference** | **Scale - 1** |

328 +============+========================+==================+

329 | ``Jpapbp`` | 100 | 1 |

330 +------------+------------------------+------------------+

331

332 +------------+------------------------+------------------+

333 | **Range** | **Scale - Reference** | **Scale - 1** |

334 +============+========================+==================+

335 | ``XYZ`` | 1 | 1 |

336 +------------+------------------------+------------------+

337

338 Examples

339 --------

340 >>> import numpy as np

341 >>> Jpapbp = np.array([46.06586037, 41.07586491, 14.51025828])

342 >>> UCS_Li2017_to_XYZ(Jpapbp, COEFFICIENTS_UCS_LUO2006["CAM02-LCD"])

343 ... # doctest: +ELLIPSIS

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

345

346 >>> from colour.appearance import CAM_KWARGS_CIECAM02_sRGB

347 >>> XYZ_w = CAM_KWARGS_CIECAM02_sRGB["XYZ_w"]

348 >>> UCS_Li2017_to_XYZ(

349 ... Jpapbp, COEFFICIENTS_UCS_LUO2006["CAM02-LCD"], XYZ_w=XYZ_w / 100

350 ... )

351 ... # doctest: +ELLIPSIS

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

353 """

354

355 from colour.appearance import ( # noqa: PLC0415

356 CAM16_to_XYZ,

357 CAM_KWARGS_CIECAM02_sRGB,

358 CAM_Specification_CAM16,

359 )

360

361 domain_range_reference = get_domain_range_scale() == "reference"

362

363 settings = CAM_KWARGS_CIECAM02_sRGB.copy()

364 settings.update(**kwargs)

365 XYZ_w = kwargs.get("XYZ_w")

366

367 if XYZ_w is not None and domain_range_reference:

368 settings["XYZ_w"] = XYZ_w * 100

369

370 J, M, h = tsplit(UCS_Li2017_to_JMh_CAM16(Jpapbp, coefficients))

371

372 specification = CAM_Specification_CAM16(J=J, M=M, h=h)

373

374 XYZ = CAM16_to_XYZ(specification, **settings)

375

376 if domain_range_reference:

377 XYZ /= 100

378

379 return XYZ

380

381

382 XYZ_to_CAM16LCD = partial(

383 XYZ_to_UCS_Li2017, coefficients=COEFFICIENTS_UCS_LUO2006["CAM02-LCD"]

384)

385 XYZ_to_CAM16LCD.__doc__ = _UCS_Luo2006_callable_to_UCS_Li2017_docstring(XYZ_to_CAM02LCD)

386 XYZ_to_CAM16LCD.__annotations__ = XYZ_to_UCS_Li2017.__annotations__.copy()

387

388 CAM16LCD_to_XYZ = partial(

389 UCS_Li2017_to_XYZ, coefficients=COEFFICIENTS_UCS_LUO2006["CAM02-LCD"]

390)

391 CAM16LCD_to_XYZ.__doc__ = _UCS_Luo2006_callable_to_UCS_Li2017_docstring(CAM02LCD_to_XYZ)

392

393 XYZ_to_CAM16SCD = partial(

394 XYZ_to_UCS_Li2017, coefficients=COEFFICIENTS_UCS_LUO2006["CAM02-SCD"]

395)

396 XYZ_to_CAM16SCD.__doc__ = _UCS_Luo2006_callable_to_UCS_Li2017_docstring(XYZ_to_CAM02SCD)

397 XYZ_to_CAM16SCD.__annotations__ = XYZ_to_UCS_Li2017.__annotations__.copy()

398

399 CAM16SCD_to_XYZ = partial(

400 UCS_Li2017_to_XYZ, coefficients=COEFFICIENTS_UCS_LUO2006["CAM02-SCD"]

401)

402 CAM16SCD_to_XYZ.__doc__ = _UCS_Luo2006_callable_to_UCS_Li2017_docstring(CAM02SCD_to_XYZ)

403

404 XYZ_to_CAM16UCS = partial(

405 XYZ_to_UCS_Li2017, coefficients=COEFFICIENTS_UCS_LUO2006["CAM02-UCS"]

406)

407 XYZ_to_CAM16UCS.__doc__ = _UCS_Luo2006_callable_to_UCS_Li2017_docstring(XYZ_to_CAM02UCS)

408 XYZ_to_CAM16UCS.__annotations__ = XYZ_to_UCS_Li2017.__annotations__.copy()

409

410 CAM16UCS_to_XYZ = partial(

411 UCS_Li2017_to_XYZ, coefficients=COEFFICIENTS_UCS_LUO2006["CAM02-UCS"]

412)

413 CAM16UCS_to_XYZ.__doc__ = _UCS_Luo2006_callable_to_UCS_Li2017_docstring(CAM02UCS_to_XYZ)