Coverage for colour/appearance/rlab.py: 100%

62 statements  

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

1""" 

2RLAB Colour Appearance Model 

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

4 

5Define the *RLAB* colour appearance model for predicting perceptual colour 

6attributes under varying viewing conditions. 

7 

8- :attr:`colour.VIEWING_CONDITIONS_RLAB` 

9- :attr:`colour.D_FACTOR_RLAB` 

10- :class:`colour.CAM_Specification_RLAB` 

11- :func:`colour.XYZ_to_RLAB` 

12 

13References 

14---------- 

15- :cite:`Fairchild1996a` : Fairchild, M. D. (1996). Refinement of the RLAB 

16 color space. Color Research & Application, 21(5), 338-346. 

17 doi:10.1002/(SICI)1520-6378(199610)21:5<338::AID-COL3>3.0.CO;2-Z 

18- :cite:`Fairchild2013w` : Fairchild, M. D. (2013). The RLAB Model. In Color 

19 Appearance Models (3rd ed., pp. 5563-5824). Wiley. ISBN:B00DAYO8E2 

20""" 

21 

22from __future__ import annotations 

23 

24from dataclasses import dataclass, field 

25 

26import numpy as np 

27 

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

29from colour.appearance.hunt import MATRIX_XYZ_TO_HPE, XYZ_to_rgb 

30from colour.hints import Annotated, ArrayLike, Domain100, NDArrayFloat # noqa: TC001 

31from colour.utilities import ( 

32 CanonicalMapping, 

33 MixinDataclassArray, 

34 as_float, 

35 as_float_array, 

36 from_range_degrees, 

37 row_as_diagonal, 

38 to_domain_100, 

39 tsplit, 

40) 

41 

42__author__ = "Colour Developers" 

43__copyright__ = "Copyright 2013 Colour Developers" 

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

45__maintainer__ = "Colour Developers" 

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

47__status__ = "Production" 

48 

49__all__ = [ 

50 "MATRIX_R", 

51 "VIEWING_CONDITIONS_RLAB", 

52 "D_FACTOR_RLAB", 

53 "CAM_ReferenceSpecification_RLAB", 

54 "CAM_Specification_RLAB", 

55 "XYZ_to_RLAB", 

56] 

57 

58MATRIX_R: NDArrayFloat = np.array( 

59 [ 

60 [1.9569, -1.1882, 0.2313], 

61 [0.3612, 0.6388, 0.0000], 

62 [0.0000, 0.0000, 1.0000], 

63 ] 

64) 

65"""*RLAB* colour appearance model precomputed helper matrix.""" 

66 

67VIEWING_CONDITIONS_RLAB: CanonicalMapping = CanonicalMapping( 

68 {"Average": 1 / 2.3, "Dim": 1 / 2.9, "Dark": 1 / 3.5} 

69) 

70VIEWING_CONDITIONS_RLAB.__doc__ = """ 

71Define the reference *RLAB* colour appearance model viewing conditions. 

72 

73References 

74---------- 

75:cite:`Fairchild1996a`, :cite:`Fairchild2013w` 

76""" 

77 

78D_FACTOR_RLAB: CanonicalMapping = CanonicalMapping( 

79 { 

80 "Hard Copy Images": 1, 

81 "Soft Copy Images": 0, 

82 "Projected Transparencies, Dark Room": 0.5, 

83 } 

84) 

85D_FACTOR_RLAB.__doc__ = """ 

86Define the *RLAB* colour appearance model *Discounting-the-Illuminant* 

87factor values for the specified media types. 

88 

89References 

90---------- 

91:cite:`Fairchild1996a`, :cite:`Fairchild2013w` 

92 

93Aliases: 

94 

95- 'hard_cp_img': 'Hard Copy Images' 

96- 'soft_cp_img': 'Soft Copy Images' 

97- 'projected_dark': 'Projected Transparencies, Dark Room' 

98""" 

99D_FACTOR_RLAB["hard_cp_img"] = D_FACTOR_RLAB["Hard Copy Images"] 

100D_FACTOR_RLAB["soft_cp_img"] = D_FACTOR_RLAB["Soft Copy Images"] 

101D_FACTOR_RLAB["projected_dark"] = D_FACTOR_RLAB["Projected Transparencies, Dark Room"] 

102 

103 

104@dataclass 

105class CAM_ReferenceSpecification_RLAB(MixinDataclassArray): 

106 """ 

107 Define the *RLAB* colour appearance model reference specification. 

108 

109 This specification contains field names consistent with the *Fairchild 

110 (2013)* reference. 

111 

112 Parameters 

113 ---------- 

114 LR 

115 Correlate of *lightness* :math:`L^R`. 

116 CR 

117 Correlate of *achromatic chroma* :math:`C^R`. 

118 hR 

119 *Hue* angle :math:`h^R` in degrees. 

120 sR 

121 Correlate of *saturation* :math:`s^R`. 

122 HR 

123 *Hue* :math:`h` composition :math:`H^R`. 

124 aR 

125 Red-green chromatic response :math:`a^R`. 

126 bR 

127 Yellow-blue chromatic response :math:`b^R`. 

128 

129 References 

130 ---------- 

131 :cite:`Fairchild1996a`, :cite:`Fairchild2013w` 

132 """ 

133 

134 LR: float | NDArrayFloat | None = field(default_factory=lambda: None) 

135 CR: float | NDArrayFloat | None = field(default_factory=lambda: None) 

136 hR: float | NDArrayFloat | None = field(default_factory=lambda: None) 

137 sR: float | NDArrayFloat | None = field(default_factory=lambda: None) 

138 HR: float | NDArrayFloat | None = field(default_factory=lambda: None) 

139 aR: float | NDArrayFloat | None = field(default_factory=lambda: None) 

140 bR: float | NDArrayFloat | None = field(default_factory=lambda: None) 

141 

142 

143@dataclass 

144class CAM_Specification_RLAB(MixinDataclassArray): 

145 """ 

146 Define the *RLAB* colour appearance model specification. 

147 

148 This specification provides a standardized interface for the *RLAB* model 

149 with field names consistent across all colour appearance models in 

150 :mod:`colour.appearance`. While the field names differ from the original 

151 *Fairchild (2013)* reference notation, they map directly to the model's 

152 perceptual correlates. 

153 

154 Parameters 

155 ---------- 

156 J 

157 Correlate of *lightness* :math:`L^R`. 

158 C 

159 Correlate of *achromatic chroma* :math:`C^R`. 

160 h 

161 *Hue* angle :math:`h^R` in degrees. 

162 s 

163 Correlate of *saturation* :math:`s^R`. 

164 HC 

165 *Hue* :math:`h` composition :math:`H^C`. 

166 a 

167 Red-green chromatic response :math:`a^R`. 

168 b 

169 Yellow-blue chromatic response :math:`b^R`. 

170 

171 Notes 

172 ----- 

173 - This specification is the one used in the current model 

174 implementation. 

175 

176 References 

177 ---------- 

178 :cite:`Fairchild1996a`, :cite:`Fairchild2013w` 

179 """ 

180 

181 J: NDArrayFloat | None = field(default_factory=lambda: None) 

182 C: NDArrayFloat | None = field(default_factory=lambda: None) 

183 h: NDArrayFloat | None = field(default_factory=lambda: None) 

184 s: NDArrayFloat | None = field(default_factory=lambda: None) 

185 HC: NDArrayFloat | None = field(default_factory=lambda: None) 

186 a: NDArrayFloat | None = field(default_factory=lambda: None) 

187 b: NDArrayFloat | None = field(default_factory=lambda: None) 

188 

189 

190def XYZ_to_RLAB( 

191 XYZ: Domain100, 

192 XYZ_n: Domain100, 

193 Y_n: ArrayLike, 

194 sigma: ArrayLike = VIEWING_CONDITIONS_RLAB["Average"], 

195 D: ArrayLike = D_FACTOR_RLAB["Hard Copy Images"], 

196) -> Annotated[CAM_Specification_RLAB, 360]: 

197 """ 

198 Compute the *RLAB* colour appearance model correlates from the specified 

199 *CIE XYZ* tristimulus values. 

200 

201 Parameters 

202 ---------- 

203 XYZ 

204 *CIE XYZ* tristimulus values of test sample / stimulus. 

205 XYZ_n 

206 *CIE XYZ* tristimulus values of reference white. 

207 Y_n 

208 Absolute adapting luminance in :math:`cd/m^2`. 

209 sigma 

210 Relative luminance of the surround, see 

211 :attr:`colour.VIEWING_CONDITIONS_RLAB` for reference. 

212 D 

213 *Discounting-the-Illuminant* factor normalised to domain [0, 1]. 

214 

215 Returns 

216 ------- 

217 CAM_Specification_RLAB 

218 *RLAB* colour appearance model specification. 

219 

220 Notes 

221 ----- 

222 +---------------------+-----------------------+---------------+ 

223 | **Domain** | **Scale - Reference** | **Scale - 1** | 

224 +=====================+=======================+===============+ 

225 | ``XYZ`` | 100 | 1 | 

226 +---------------------+-----------------------+---------------+ 

227 | ``XYZ_n`` | 100 | 1 | 

228 +---------------------+-----------------------+---------------+ 

229 

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

231 | **Range** | **Scale - Reference** | **Scale - 1** | 

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

233 | ``specification.h`` | 360 | 1 | 

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

235 

236 References 

237 ---------- 

238 :cite:`Fairchild1996a`, :cite:`Fairchild2013w` 

239 

240 Examples 

241 -------- 

242 >>> XYZ = np.array([19.01, 20.00, 21.78]) 

243 >>> XYZ_n = np.array([109.85, 100, 35.58]) 

244 >>> Y_n = 31.83 

245 >>> sigma = VIEWING_CONDITIONS_RLAB["Average"] 

246 >>> D = D_FACTOR_RLAB["Hard Copy Images"] 

247 >>> XYZ_to_RLAB(XYZ, XYZ_n, Y_n, sigma, D) # doctest: +ELLIPSIS 

248 CAM_Specification_RLAB(J=49.8347069..., C=54.8700585..., \ 

249h=286.4860208..., s=1.1010410..., HC=None, a=15.5711021..., \ 

250b=-52.6142956...) 

251 """ 

252 

253 XYZ = to_domain_100(XYZ) 

254 XYZ_n = to_domain_100(XYZ_n) 

255 Y_n = as_float_array(Y_n) 

256 D = as_float_array(D) 

257 sigma = as_float_array(sigma) 

258 

259 # Converting to cone responses. 

260 LMS_n = XYZ_to_rgb(XYZ_n) 

261 

262 # Computing the :math:`A` matrix. 

263 LMS_l_E = 3 * LMS_n / np.sum(LMS_n, axis=-1)[..., None] 

264 LMS_p_L = (1 + spow(Y_n[..., None], 1 / 3) + LMS_l_E) / ( 

265 1 + spow(Y_n[..., None], 1 / 3) + 1 / LMS_l_E 

266 ) 

267 

268 LMS_a_L = (LMS_p_L + D[..., None] * (1 - LMS_p_L)) / LMS_n 

269 

270 M = np.matmul(np.matmul(MATRIX_R, row_as_diagonal(LMS_a_L)), MATRIX_XYZ_TO_HPE) 

271 XYZ_ref = vecmul(M, XYZ) 

272 

273 Y_ref: NDArrayFloat 

274 X_ref, Y_ref, Z_ref = tsplit(XYZ_ref) 

275 

276 # Computing the correlate of *Lightness* :math:`L^R`. 

277 LR = 100 * spow(Y_ref, sigma) 

278 

279 # Computing opponent colour dimensions :math:`a^R` and :math:`b^R`. 

280 aR = 430 * (spow(X_ref, sigma) - spow(Y_ref, sigma)) 

281 bR = 170 * (spow(Y_ref, sigma) - spow(Z_ref, sigma)) 

282 

283 # Computing the *hue* angle :math:`h^R`. 

284 hR = np.degrees(np.arctan2(bR, aR)) % 360 

285 # TODO: Implement hue composition computation. 

286 

287 # Computing the correlate of *chroma* :math:`C^R`. 

288 CR = np.hypot(aR, bR) 

289 

290 # Computing the correlate of *saturation* :math:`s^R`. 

291 with sdiv_mode(): 

292 sR = sdiv(CR, LR) 

293 

294 return CAM_Specification_RLAB( 

295 J=LR, 

296 C=CR, 

297 h=as_float(from_range_degrees(hR)), 

298 s=sR, 

299 HC=None, 

300 a=as_float(aR), 

301 b=as_float(bR), 

302 )