Coverage for adaptation/zhai2018.py: 58%

1"""

2 Zhai and Luo (2018) Chromatic Adaptation Model

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

4

5 Define the *Zhai and Luo (2018)* two-step chromatic adaptation for predicting

6 corresponding colours under different viewing conditions.

7

8- :func:`colour.adaptation.chromatic_adaptation_Zhai2018`

9

10 References

11----------

12- :cite:`Zhai2018` : Zhai, Q., & Luo, M. R. (2018). Study of chromatic

13 adaptation via neutral white matches on different viewing media. Optics

14 Express, 26(6), 7724. doi:10.1364/OE.26.007724

15"""

16

17 from __future__ import annotations

18

19 import typing

20

21 import numpy as np

22

23 from colour.adaptation import CHROMATIC_ADAPTATION_TRANSFORMS

24 from colour.algebra import vecmul

25

26 if typing.TYPE_CHECKING:

27 from colour.hints import Literal

28

29 from colour.hints import ( # noqa: TC001

30 ArrayLike,

31 Domain100,

32 Range100,

33)

34 from colour.utilities import (

35 as_float_array,

36 from_range_100,

37 get_domain_range_scale,

38 optional,

39 to_domain_100,

40 validate_method,

41)

42

43 __author__ = "Colour Developers"

44 __copyright__ = "Copyright 2013 Colour Developers"

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

46 __maintainer__ = "Colour Developers"

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

48 __status__ = "Production"

49

50 __all__ = [

51 "chromatic_adaptation_Zhai2018",

52]

53

54

55 def chromatic_adaptation_Zhai2018(

56 XYZ_b: Domain100,

57 XYZ_wb: Domain100,

58 XYZ_wd: Domain100,

59 D_b: ArrayLike = 1,

60 D_d: ArrayLike = 1,

61 XYZ_wo: ArrayLike | None = None,

62 transform: Literal["CAT02", "CAT16"] | str = "CAT02",

63) -> Range100:

64 """

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

66 viewing conditions to reference viewing conditions using the

67 *Zhai and Luo (2018)* chromatic adaptation model.

68

69 According to the definition of :math:`D`, a one-step chromatic adaptation

70 transform (CAT) such as CAT02 can only transform colours from an

71 incomplete adapted field into a complete adapted field. When CAT02 is

72 used to transform from incomplete to incomplete adaptation, :math:`D` has

73 no baseline level to refer to. *Smet et al. (2017)* proposed a two-step

74 CAT concept to replace existing one-step transforms such as CAT02,

75 providing a clearer definition of :math:`D`. A two-step CAT involves a

76 baseline illuminant (BI) representing the baseline state between the test

77 and reference illuminants. In the first step, the test colour is

78 transformed from the test illuminant to the baseline illuminant

79 (:math:`BI`), then subsequently transformed to the reference illuminant.

80 Degrees of adaptation under other illuminants are calculated relative to

81 the adaptation under the :math:`BI`. As :math:`D` approaches zero, the

82 observer's adaptation point moves towards the :math:`BI`. Therefore, the

83 chromaticity of the :math:`BI` is an intrinsic property of the human

84 visual system.

85

86 Parameters

87 ----------

88 XYZ_b

89 Sample colour :math:`XYZ_{\\beta}` tristimulus values under input

90 illuminant :math:`\\beta`.

91 XYZ_wb

92 Input illuminant :math:`\\beta` tristimulus values.

93 XYZ_wd

94 Output illuminant :math:`\\delta` tristimulus values.

95 D_b

96 Degree of adaptation :math:`D_{\\beta}` of input illuminant

97 :math:`\\beta`.

98 D_d

99 Degree of adaptation :math:`D_{\\delta}` of output illuminant

100 :math:`\\delta`.

101 XYZ_wo

102 Baseline illuminant (:math:`BI`) :math:`o` tristimulus values.

103 transform

104 Chromatic adaptation transform matrix.

105

106 Returns

107 -------

108 :class:`numpy.ndarray`

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

110

111 Notes

112 -----

113 +------------+-----------------------+---------------+

114 | **Domain** | **Scale - Reference** | **Scale - 1** |

115 +============+=======================+===============+

116 | ``XYZ_b`` | 100 | 1 |

117 +------------+-----------------------+---------------+

118 | ``XYZ_wb`` | 100 | 1 |

119 +------------+-----------------------+---------------+

120 | ``XYZ_wd`` | 100 | 1 |

121 +------------+-----------------------+---------------+

122 | ``XYZ_wo`` | 100 | 1 |

123 +------------+-----------------------+---------------+

124

125 +------------+-----------------------+---------------+

126 | **Range** | **Scale - Reference** | **Scale - 1** |

127 +============+=======================+===============+

128 | ``XYZ_d`` | 100 | 1 |

129 +------------+-----------------------+---------------+

130

131 References

132 ----------

133 :cite:`Zhai2018`

134

135 Examples

136 --------

137 >>> XYZ_b = np.array([48.900, 43.620, 6.250])

138 >>> XYZ_wb = np.array([109.850, 100, 35.585])

139 >>> XYZ_wd = np.array([95.047, 100, 108.883])

140 >>> D_b = 0.9407

141 >>> D_d = 0.9800

142 >>> XYZ_wo = np.array([100, 100, 100])

143 >>> chromatic_adaptation_Zhai2018(

144 ... XYZ_b, XYZ_wb, XYZ_wd, D_b, D_d, XYZ_wo

145 ... ) # doctest: +ELLIPSIS

146 array([ 39.1856164..., 42.1546179..., 19.2367203...])

147 >>> XYZ_d = np.array([39.18561644, 42.15461798, 19.23672036])

148 >>> chromatic_adaptation_Zhai2018(

149 ... XYZ_d, XYZ_wd, XYZ_wb, D_d, D_b, XYZ_wo

150 ... ) # doctest: +ELLIPSIS

151 array([ 48.9 , 43.62, 6.25])

152 """

153

154 XYZ_b = to_domain_100(XYZ_b)

155 XYZ_wb = to_domain_100(XYZ_wb)

156 XYZ_wd = to_domain_100(XYZ_wd)

157 XYZ_wo = to_domain_100(

158 optional(

159 XYZ_wo,

160 np.array([1, 1, 1])

161 if get_domain_range_scale() == "reference"

162 else np.array([0.01, 0.01, 0.01]),

163 )

164 )

165 D_b = as_float_array(D_b)

166 D_d = as_float_array(D_d)

167

168 Y_wb = XYZ_wb[..., 1][..., None]

169 Y_wd = XYZ_wd[..., 1][..., None]

170 Y_wo = XYZ_wo[..., 1][..., None]

171

172 transform = validate_method(transform, ("CAT02", "CAT16"))

173 M = CHROMATIC_ADAPTATION_TRANSFORMS[transform]

174

175 RGB_b = vecmul(M, XYZ_b)

176 RGB_wb = vecmul(M, XYZ_wb)

177 RGB_wd = vecmul(M, XYZ_wd)

178 RGB_wo = vecmul(M, XYZ_wo)

179

180 D_RGB_b = D_b * (Y_wb / Y_wo) * (RGB_wo / RGB_wb) + 1 - D_b

181 D_RGB_d = D_d * (Y_wd / Y_wo) * (RGB_wo / RGB_wd) + 1 - D_d

182

183 D_RGB = D_RGB_b / D_RGB_d

184

185 RGB_d = D_RGB * RGB_b

186

187 XYZ_d = vecmul(np.linalg.inv(M), RGB_d)

188

189 return from_range_100(XYZ_d)