Coverage for adaptation/cmccat2000.py: 56%

1"""

2 CMCCAT2000 Chromatic Adaptation Model

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

4

5 Define the *CMCCAT2000* chromatic adaptation model for predicting

6 corresponding colours under different viewing conditions.

7

8- :class:`colour.adaptation.InductionFactors_CMCCAT2000`

9- :class:`colour.VIEWING_CONDITIONS_CMCCAT2000`

10- :func:`colour.adaptation.chromatic_adaptation_forward_CMCCAT2000`

11- :func:`colour.adaptation.chromatic_adaptation_inverse_CMCCAT2000`

12- :func:`colour.adaptation.chromatic_adaptation_CMCCAT2000`

13

14 References

15----------

16- :cite:`Li2002a` : Li, C., Luo, M. R., Rigg, B., & Hunt, R. W. G. (2002).

17 CMC 2000 chromatic adaptation transform: CMCCAT2000. Color Research &

18 Application, 27(1), 49-58. doi:10.1002/col.10005

19- :cite:`Westland2012k` : Westland, S., Ripamonti, C., & Cheung, V. (2012).

20 CMCCAT2000. In Computational Colour Science Using MATLAB (2nd ed., pp.

21 83-86). ISBN:978-0-470-66569-5

22"""

23

24 from __future__ import annotations

25

26 import typing

27 from dataclasses import dataclass

28

29 import numpy as np

30

31 from colour.adaptation import CAT_CMCCAT2000

32 from colour.algebra import vecmul

33

34 if typing.TYPE_CHECKING:

35 from colour.hints import Literal

36

37 from colour.hints import ( # noqa: TC001

38 ArrayLike,

39 Domain100,

40 NDArrayFloat,

41 Range100,

42)

43 from colour.utilities import (

44 CanonicalMapping,

45 MixinDataclassIterable,

46 as_float_array,

47 from_range_100,

48 to_domain_100,

49 validate_method,

50)

51

52 __author__ = "Colour Developers"

53 __copyright__ = "Copyright 2013 Colour Developers"

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

55 __maintainer__ = "Colour Developers"

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

57 __status__ = "Production"

58

59 __all__ = [

60 "CAT_INVERSE_CMCCAT2000",

61 "InductionFactors_CMCCAT2000",

62 "VIEWING_CONDITIONS_CMCCAT2000",

63 "chromatic_adaptation_forward_CMCCAT2000",

64 "chromatic_adaptation_inverse_CMCCAT2000",

65 "chromatic_adaptation_CMCCAT2000",

66]

67

68 CAT_INVERSE_CMCCAT2000: NDArrayFloat = np.linalg.inv(CAT_CMCCAT2000)

69"""

70 Inverse *CMCCAT2000* chromatic adaptation transform.

71

72 CAT_INVERSE_CMCCAT2000

73"""

74

75

76@dataclass(frozen=True)

77 class InductionFactors_CMCCAT2000(MixinDataclassIterable):

78 """

79 Define the *CMCCAT2000* chromatic adaptation model induction factors.

80

81 Parameters

82 ----------

83 F

84 :math:`F` surround condition factor that modulates the degree of

85 adaptation based on the viewing environment.

86

87 References

88 ----------

89 :cite:`Li2002a`, :cite:`Westland2012k`

90 """

91

92 F: float

93

94

95 VIEWING_CONDITIONS_CMCCAT2000: CanonicalMapping = CanonicalMapping(

96 {

97 "Average": InductionFactors_CMCCAT2000(1),

98 "Dim": InductionFactors_CMCCAT2000(0.8),

99 "Dark": InductionFactors_CMCCAT2000(0.8),

100 }

101)

102 VIEWING_CONDITIONS_CMCCAT2000.__doc__ = """

103 Define the reference *CMCCAT2000* chromatic adaptation model viewing

104 conditions.

105

106 The viewing conditions include three standard surround conditions with

107 their corresponding induction factors:

108

109- *Average*: Induction factor of 1.0

110- *Dim*: Induction factor of 0.8

111- *Dark*: Induction factor of 0.8

112

113 These values represent the standard viewing conditions used in the

114*CMCCAT2000* chromatic adaptation transform.

115

116 References

117----------

118:cite:`Li2002a`, :cite:`Westland2012k`

119"""

120

121

122 def chromatic_adaptation_forward_CMCCAT2000(

123 XYZ: Domain100,

124 XYZ_w: Domain100,

125 XYZ_wr: Domain100,

126 L_A1: ArrayLike,

127 L_A2: ArrayLike,

128 surround: InductionFactors_CMCCAT2000 = VIEWING_CONDITIONS_CMCCAT2000["Average"],

129) -> Range100:

130 """

131 Adapt the specified stimulus *CIE XYZ* tristimulus values from test

132 viewing conditions to reference viewing conditions using the

133 *CMCCAT2000* forward chromatic adaptation model.

134

135 Parameters

136 ----------

137 XYZ

138 *CIE XYZ* tristimulus values of the stimulus to adapt.

139 XYZ_w

140 Test viewing condition *CIE XYZ* tristimulus values of the

141 whitepoint.

142 XYZ_wr

143 Reference viewing condition *CIE XYZ* tristimulus values of the

144 whitepoint.

145 L_A1

146 Luminance of test adapting field :math:`L_{A1}` in

147 :math:`cd/m^2`.

148 L_A2

149 Luminance of reference adapting field :math:`L_{A2}` in

150 :math:`cd/m^2`.

151 surround

152 Surround viewing conditions induction factors.

153

154 Returns

155 -------

156 :class:`numpy.ndarray`

157 *CIE XYZ* tristimulus values of the stimulus corresponding colour.

158

159 Notes

160 -----

161 +------------+-----------------------+---------------+

162 | **Domain** | **Scale - Reference** | **Scale - 1** |

163 +============+=======================+===============+

164 | ``XYZ`` | 100 | 1 |

165 +------------+-----------------------+---------------+

166 | ``XYZ_w`` | 100 | 1 |

167 +------------+-----------------------+---------------+

168 | ``XYZ_wr`` | 100 | 1 |

169 +------------+-----------------------+---------------+

170

171 +------------+-----------------------+---------------+

172 | **Range** | **Scale - Reference** | **Scale - 1** |

173 +============+=======================+===============+

174 | ``XYZ_c`` | 100 | 1 |

175 +------------+-----------------------+---------------+

176

177 References

178 ----------

179 :cite:`Li2002a`, :cite:`Westland2012k`

180

181 Examples

182 --------

183 >>> XYZ = np.array([22.48, 22.74, 8.54])

184 >>> XYZ_w = np.array([111.15, 100.00, 35.20])

185 >>> XYZ_wr = np.array([94.81, 100.00, 107.30])

186 >>> L_A1 = 200

187 >>> L_A2 = 200

188 >>> chromatic_adaptation_forward_CMCCAT2000(XYZ, XYZ_w, XYZ_wr, L_A1, L_A2)

189 ... # doctest: +ELLIPSIS

190 array([ 19.5269832..., 23.0683396..., 24.9717522...])

191 """

192

193 XYZ = to_domain_100(XYZ)

194 XYZ_w = to_domain_100(XYZ_w)

195 XYZ_wr = to_domain_100(XYZ_wr)

196 L_A1 = as_float_array(L_A1)

197 L_A2 = as_float_array(L_A2)

198

199 RGB = vecmul(CAT_CMCCAT2000, XYZ)

200 RGB_w = vecmul(CAT_CMCCAT2000, XYZ_w)

201 RGB_wr = vecmul(CAT_CMCCAT2000, XYZ_wr)

202

203 D = surround.F * (

204 0.08 * np.log10(0.5 * (L_A1 + L_A2))

205 + 0.76

206 - 0.45 * (L_A1 - L_A2) / (L_A1 + L_A2)

207 )

208

209 D = np.clip(D, 0, 1)

210 a = D * XYZ_w[..., 1] / XYZ_wr[..., 1]

211

212 RGB_c = RGB * (a[..., None] * (RGB_wr / RGB_w) + 1 - D[..., None])

213 XYZ_c = vecmul(CAT_INVERSE_CMCCAT2000, RGB_c)

214

215 return from_range_100(XYZ_c)

216

217

218 def chromatic_adaptation_inverse_CMCCAT2000(

219 XYZ_c: Domain100,

220 XYZ_w: Domain100,

221 XYZ_wr: Domain100,

222 L_A1: ArrayLike,

223 L_A2: ArrayLike,

224 surround: InductionFactors_CMCCAT2000 = VIEWING_CONDITIONS_CMCCAT2000["Average"],

225) -> Range100:

226 """

227 Adapt the specified *CIE XYZ* tristimulus values from reference viewing

228 conditions to test viewing conditions using the inverse *CMCCAT2000*

229 chromatic adaptation model.

230

231 Parameters

232 ----------

233 XYZ_c

234 *CIE XYZ* tristimulus values of the adapted stimulus in the reference

235 viewing conditions.

236 XYZ_w

237 Test viewing condition *CIE XYZ* tristimulus values of the whitepoint.

238 XYZ_wr

239 Reference viewing condition *CIE XYZ* tristimulus values of the

240 whitepoint.

241 L_A1

242 Luminance of test adapting field :math:`L_{A1}` in :math:`cd/m^2`.

243 L_A2

244 Luminance of reference adapting field :math:`L_{A2}` in :math:`cd/m^2`.

245 surround

246 Surround viewing conditions induction factors.

247

248 Returns

249 -------

250 :class:`numpy.ndarray`

251 *CIE XYZ* tristimulus values of the stimulus adapted to the test

252 viewing conditions.

253

254 Notes

255 -----

256 +------------+-----------------------+---------------+

257 | **Domain** | **Scale - Reference** | **Scale - 1** |

258 +============+=======================+===============+

259 | ``XYZ_c`` | 100 | 1 |

260 +------------+-----------------------+---------------+

261 | ``XYZ_w`` | 100 | 1 |

262 +------------+-----------------------+---------------+

263 | ``XYZ_wr`` | 100 | 1 |

264 +------------+-----------------------+---------------+

265

266 +------------+-----------------------+---------------+

267 | **Range** | **Scale - Reference** | **Scale - 1** |

268 +============+=======================+===============+

269 | ``XYZ`` | 100 | 1 |

270 +------------+-----------------------+---------------+

271

272 References

273 ----------

274 :cite:`Li2002a`, :cite:`Westland2012k`

275

276 Examples

277 --------

278 >>> XYZ_c = np.array([19.53, 23.07, 24.97])

279 >>> XYZ_w = np.array([111.15, 100.00, 35.20])

280 >>> XYZ_wr = np.array([94.81, 100.00, 107.30])

281 >>> L_A1 = 200

282 >>> L_A2 = 200

283 >>> chromatic_adaptation_inverse_CMCCAT2000(XYZ_c, XYZ_w, XYZ_wr, L_A1, L_A2)

284 ... # doctest: +ELLIPSIS

285 array([ 22.4839876..., 22.7419485..., 8.5393392...])

286 """

287

288 XYZ_c = to_domain_100(XYZ_c)

289 XYZ_w = to_domain_100(XYZ_w)

290 XYZ_wr = to_domain_100(XYZ_wr)

291 L_A1 = as_float_array(L_A1)

292 L_A2 = as_float_array(L_A2)

293

294 RGB_c = vecmul(CAT_CMCCAT2000, XYZ_c)

295 RGB_w = vecmul(CAT_CMCCAT2000, XYZ_w)

296 RGB_wr = vecmul(CAT_CMCCAT2000, XYZ_wr)

297

298 D = surround.F * (

299 0.08 * np.log10(0.5 * (L_A1 + L_A2))

300 + 0.76

301 - 0.45 * (L_A1 - L_A2) / (L_A1 + L_A2)

302 )

303

304 D = np.clip(D, 0, 1)

305 a = D * XYZ_w[..., 1] / XYZ_wr[..., 1]

306

307 RGB = RGB_c / (a[..., None] * (RGB_wr / RGB_w) + 1 - D[..., None])

308 XYZ = vecmul(CAT_INVERSE_CMCCAT2000, RGB)

309

310 return from_range_100(XYZ)

311

312

313 def chromatic_adaptation_CMCCAT2000(

314 XYZ: Domain100,

315 XYZ_w: Domain100,

316 XYZ_wr: Domain100,

317 L_A1: ArrayLike,

318 L_A2: ArrayLike,

319 surround: InductionFactors_CMCCAT2000 = VIEWING_CONDITIONS_CMCCAT2000["Average"],

320 direction: Literal["Forward", "Inverse"] | str = "Forward",

321) -> Range100:

322 """

323 Adapt the specified stimulus *CIE XYZ* tristimulus values from test

324 viewing conditions to reference viewing conditions using the

325 *CMCCAT2000* chromatic adaptation model.

326

327 This definition provides a convenient wrapper around

328 :func:`colour.adaptation.chromatic_adaptation_forward_CMCCAT2000` and

329 :func:`colour.adaptation.chromatic_adaptation_inverse_CMCCAT2000`.

330

331 Parameters

332 ----------

333 XYZ

334 *CIE XYZ* tristimulus values of the stimulus to adapt.

335 XYZ_w

336 Source viewing condition *CIE XYZ* tristimulus values of the

337 whitepoint.

338 XYZ_wr

339 Target viewing condition *CIE XYZ* tristimulus values of the

340 whitepoint.

341 L_A1

342 Luminance of test adapting field :math:`L_{A1}` in :math:`cd/m^2`.

343 L_A2

344 Luminance of reference adapting field :math:`L_{A2}` in

345 :math:`cd/m^2`.

346 surround

347 Surround viewing conditions induction factors.

348 direction

349 Chromatic adaptation direction.

350

351 Returns

352 -------

353 :class:`numpy.ndarray`

354 *CIE XYZ* tristimulus values of the stimulus corresponding colour.

355

356 Notes

357 -----

358 +------------+-----------------------+---------------+

359 | **Domain** | **Scale - Reference** | **Scale - 1** |

360 +============+=======================+===============+

361 | ``XYZ`` | 100 | 1 |

362 +------------+-----------------------+---------------+

363 | ``XYZ_w`` | 100 | 1 |

364 +------------+-----------------------+---------------+

365 | ``XYZ_wr`` | 100 | 1 |

366 +------------+-----------------------+---------------+

367

368 +------------+-----------------------+---------------+

369 | **Range** | **Scale - Reference** | **Scale - 1** |

370 +============+=======================+===============+

371 | ``XYZ`` | 100 | 1 |

372 +------------+-----------------------+---------------+

373

374 References

375 ----------

376 :cite:`Li2002a`, :cite:`Westland2012k`

377

378 Examples

379 --------

380 >>> XYZ = np.array([22.48, 22.74, 8.54])

381 >>> XYZ_w = np.array([111.15, 100.00, 35.20])

382 >>> XYZ_wr = np.array([94.81, 100.00, 107.30])

383 >>> L_A1 = 200

384 >>> L_A2 = 200

385 >>> chromatic_adaptation_CMCCAT2000(

386 ... XYZ, XYZ_w, XYZ_wr, L_A1, L_A2, direction="Forward"

387 ... )

388 ... # doctest: +ELLIPSIS

389 array([ 19.5269832..., 23.0683396..., 24.9717522...])

390

391 Using the *CMCCAT2000* inverse model:

392

393 >>> XYZ = np.array([19.52698326, 23.06833960, 24.97175229])

394 >>> XYZ_w = np.array([111.15, 100.00, 35.20])

395 >>> XYZ_wr = np.array([94.81, 100.00, 107.30])

396 >>> L_A1 = 200

397 >>> L_A2 = 200

398 >>> chromatic_adaptation_CMCCAT2000(

399 ... XYZ, XYZ_w, XYZ_wr, L_A1, L_A2, direction="Inverse"

400 ... )

401 ... # doctest: +ELLIPSIS

402 array([ 22.48, 22.74, 8.54])

403 """

404

405 direction = validate_method(

406 direction,

407 ("Forward", "Inverse"),

408 '"{0}" direction is invalid, it must be one of {1}!',

409 )

410

411 if direction == "forward":

412 return chromatic_adaptation_forward_CMCCAT2000(

413 XYZ, XYZ_w, XYZ_wr, L_A1, L_A2, surround

414 )

415

416 return chromatic_adaptation_inverse_CMCCAT2000(

417 XYZ, XYZ_w, XYZ_wr, L_A1, L_A2, surround

418 )