Coverage for colour/adaptation/zhai2018.py: 100%

36 statements  

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

1""" 

2Zhai and Luo (2018) Chromatic Adaptation Model 

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

4 

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

6corresponding colours under different viewing conditions. 

7 

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

9 

10References 

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 

17from __future__ import annotations 

18 

19import typing 

20 

21import numpy as np 

22 

23from colour.adaptation import CHROMATIC_ADAPTATION_TRANSFORMS 

24from colour.algebra import vecmul 

25 

26if typing.TYPE_CHECKING: 

27 from colour.hints import Literal 

28 

29from colour.hints import ( # noqa: TC001 

30 ArrayLike, 

31 Domain100, 

32 Range100, 

33) 

34from 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 

55def 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)