Coverage for adaptation/fairchild1990.py: 61%

1"""

2 Fairchild (1990) Chromatic Adaptation Model

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

4

5 Define the *Fairchild (1990)* chromatic adaptation model for predicting

6 corresponding colours under different viewing conditions.

7

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

9

10 References

11----------

12- :cite:`Fairchild1991a` : Fairchild, M. D. (1991). Formulation and testing

13 of an incomplete-chromatic-adaptation model. Color Research & Application,

14 16(4), 243-250. doi:10.1002/col.5080160406

15- :cite:`Fairchild2013s` : Fairchild, M. D. (2013). FAIRCHILD'S 1990 MODEL.

16 In Color Appearance Models (3rd ed., pp. 4418-4495). Wiley. ISBN:B00DAYO8E2

17"""

18

19 from __future__ import annotations

20

21 import numpy as np

22

23 from colour.adaptation import CAT_VON_KRIES

24 from colour.algebra import sdiv, sdiv_mode, spow, vecmul

25 from colour.hints import ( # noqa: TC001

26 ArrayLike,

27 Domain100,

28 NDArrayFloat,

29 Range100,

30)

31 from colour.utilities import (

32 as_float_array,

33 from_range_100,

34 ones,

35 row_as_diagonal,

36 to_domain_100,

37 tstack,

38)

39

40 __author__ = "Colour Developers"

41 __copyright__ = "Copyright 2013 Colour Developers"

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

43 __maintainer__ = "Colour Developers"

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

45 __status__ = "Production"

46

47 __all__ = [

48 "MATRIX_XYZ_TO_RGB_FAIRCHILD1990",

49 "MATRIX_RGB_TO_XYZ_FAIRCHILD1990",

50 "chromatic_adaptation_Fairchild1990",

51 "XYZ_to_RGB_Fairchild1990",

52 "RGB_to_XYZ_Fairchild1990",

53 "degrees_of_adaptation",

54]

55

56 MATRIX_XYZ_TO_RGB_FAIRCHILD1990: NDArrayFloat = CAT_VON_KRIES

57"""

58*Fairchild (1990)* colour appearance model *CIE XYZ* tristimulus values to cone

59 responses matrix.

60"""

61

62 MATRIX_RGB_TO_XYZ_FAIRCHILD1990: NDArrayFloat = np.linalg.inv(CAT_VON_KRIES)

63"""

64*Fairchild (1990)* colour appearance model cone responses to *CIE XYZ*

65 tristimulus values matrix.

66"""

67

68

69 def chromatic_adaptation_Fairchild1990(

70 XYZ_1: Domain100,

71 XYZ_n: Domain100,

72 XYZ_r: Domain100,

73 Y_n: ArrayLike,

74 discount_illuminant: bool = False,

75) -> Range100:

76 """

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

78 viewing conditions to reference viewing conditions using the

79 *Fairchild (1990)* chromatic adaptation model.

80

81 Parameters

82 ----------

83 XYZ_1

84 *CIE XYZ_1* tristimulus values of test sample / stimulus.

85 XYZ_n

86 Test viewing condition *CIE XYZ_n* tristimulus values of the

87 whitepoint.

88 XYZ_r

89 Reference viewing condition *CIE XYZ_r* tristimulus values of the

90 whitepoint.

91 Y_n

92 Luminance :math:`Y_n` of test adapting stimulus in :math:`cd/m^2`.

93 discount_illuminant

94 Truth value indicating if the illuminant should be discounted.

95

96 Returns

97 -------

98 :class:`numpy.ndarray`

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

100

101 Notes

102 -----

103 +------------+-----------------------+---------------+

104 | **Domain** | **Scale - Reference** | **Scale - 1** |

105 +============+=======================+===============+

106 | ``XYZ_1`` | 100 | 1 |

107 +------------+-----------------------+---------------+

108 | ``XYZ_n`` | 100 | 1 |

109 +------------+-----------------------+---------------+

110 | ``XYZ_r`` | 100 | 1 |

111 +------------+-----------------------+---------------+

112

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

114 | **Range** | **Scale - Reference** | **Scale - 1** |

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

116 | ``XYZ_2`` | 100 | 1 |

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

118

119 References

120 ----------

121 :cite:`Fairchild1991a`, :cite:`Fairchild2013s`

122

123 Examples

124 --------

125 >>> XYZ_1 = np.array([19.53, 23.07, 24.97])

126 >>> XYZ_n = np.array([111.15, 100.00, 35.20])

127 >>> XYZ_r = np.array([94.81, 100.00, 107.30])

128 >>> Y_n = 200

129 >>> chromatic_adaptation_Fairchild1990(XYZ_1, XYZ_n, XYZ_r, Y_n)

130 ... # doctest: +ELLIPSIS

131 array([ 23.3252634..., 23.3245581..., 76.1159375...])

132 """

133

134 XYZ_1 = to_domain_100(XYZ_1)

135 XYZ_n = to_domain_100(XYZ_n)

136 XYZ_r = to_domain_100(XYZ_r)

137 Y_n = as_float_array(Y_n)

138

139 LMS_1 = XYZ_to_RGB_Fairchild1990(XYZ_1)

140 LMS_n = XYZ_to_RGB_Fairchild1990(XYZ_n)

141 LMS_r = XYZ_to_RGB_Fairchild1990(XYZ_r)

142

143 p_LMS = degrees_of_adaptation(LMS_1, Y_n, discount_illuminant=discount_illuminant)

144

145 a_LMS_1 = p_LMS / LMS_n

146 a_LMS_2 = p_LMS / LMS_r

147

148 A_1 = row_as_diagonal(a_LMS_1)

149 A_2 = row_as_diagonal(a_LMS_2)

150

151 LMSp_1 = vecmul(A_1, LMS_1)

152

153 c = 0.219 - 0.0784 * np.log10(Y_n)

154 C = row_as_diagonal(tstack([c, c, c]))

155

156 LMS_a = vecmul(C, LMSp_1)

157 LMSp_2 = vecmul(np.linalg.inv(C), LMS_a)

158

159 LMS_c = vecmul(np.linalg.inv(A_2), LMSp_2)

160 XYZ_c = RGB_to_XYZ_Fairchild1990(LMS_c)

161

162 return from_range_100(XYZ_c)

163

164

165 def XYZ_to_RGB_Fairchild1990(XYZ: ArrayLike) -> NDArrayFloat:

166 """

167 Convert from *CIE XYZ* tristimulus values to cone responses using the

168 *Fairchild (1990)* chromatic adaptation model.

169

170 Parameters

171 ----------

172 XYZ

173 *CIE XYZ* tristimulus values.

174

175 Returns

176 -------

177 :class:`numpy.ndarray`

178 Cone responses.

179

180 Examples

181 --------

182 >>> XYZ = np.array([19.53, 23.07, 24.97])

183 >>> XYZ_to_RGB_Fairchild1990(XYZ) # doctest: +ELLIPSIS

184 array([ 22.1231935..., 23.6054224..., 22.9279534...])

185 """

186

187 return vecmul(MATRIX_XYZ_TO_RGB_FAIRCHILD1990, XYZ)

188

189

190 def RGB_to_XYZ_Fairchild1990(RGB: ArrayLike) -> NDArrayFloat:

191 """

192 Convert cone responses to *CIE XYZ* tristimulus values.

193

194 Parameters

195 ----------

196 RGB

197 Cone responses.

198

199 Returns

200 -------

201 :class:`numpy.ndarray`

202 *CIE XYZ* tristimulus values.

203

204 Examples

205 --------

206 >>> RGB = np.array([22.12319350, 23.60542240, 22.92795340])

207 >>> RGB_to_XYZ_Fairchild1990(RGB) # doctest: +ELLIPSIS

208 array([ 19.53, 23.07, 24.97])

209 """

210

211 return vecmul(MATRIX_RGB_TO_XYZ_FAIRCHILD1990, RGB)

212

213

214 def degrees_of_adaptation(

215 LMS: ArrayLike,

216 Y_n: ArrayLike,

217 v: ArrayLike = 1 / 3,

218 discount_illuminant: bool = False,

219) -> NDArrayFloat:

220 """

221 Compute the degrees of adaptation :math:`p_L`, :math:`p_M` and

222 :math:`p_S`.

223

224 Parameters

225 ----------

226 LMS

227 Cone responses.

228 Y_n

229 Luminance :math:`Y_n` of test adapting stimulus in :math:`cd/m^2`.

230 v

231 Exponent :math:`v`.

232 discount_illuminant

233 Truth value indicating if the illuminant should be discounted.

234

235 Returns

236 -------

237 :class:`numpy.ndarray`

238 Degrees of adaptation :math:`p_L`, :math:`p_M` and :math:`p_S`.

239

240 Examples

241 --------

242 >>> LMS = np.array([20.00052060, 19.99978300, 19.99883160])

243 >>> Y_n = 31.83

244 >>> degrees_of_adaptation(LMS, Y_n) # doctest: +ELLIPSIS

245 array([ 0.9799324..., 0.9960035..., 1.0233041...])

246 >>> degrees_of_adaptation(LMS, Y_n, 1 / 3, True)

247 array([ 1., 1., 1.])

248 """

249

250 LMS = as_float_array(LMS)

251

252 if discount_illuminant:

253 return ones(LMS.shape)

254

255 Y_n = as_float_array(Y_n)

256 v = as_float_array(v)

257

258 # E illuminant.

259 LMS_E = vecmul(CAT_VON_KRIES, ones(LMS.shape))

260

261 Ye_n = spow(Y_n, v)

262

263 def m_E(x: NDArrayFloat, y: NDArrayFloat) -> NDArrayFloat:

264 """Compute the :math:`m_E` term."""

265

266 return (3 * x / y) / np.sum(x / y, axis=-1)[..., None]

267

268 def P_c(x: NDArrayFloat) -> NDArrayFloat:

269 """Compute the :math:`P_L`, :math:`P_M` or :math:`P_S` terms."""

270

271 return sdiv(

272 1 + Ye_n[..., None] + x,

273 1 + Ye_n[..., None] + sdiv(1, x),

274 )

275

276 with sdiv_mode():

277 return P_c(m_E(LMS, LMS_E))