Coverage for models/rgb/derivation.py: 49%

39 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-16 22:49 +1300

1""" 

2RGB Colourspace Derivation 

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

4 

5Define objects for *RGB* colourspace derivation, primarily focused on 

6calculating the normalised primary matrix from the specified *RGB* colourspace 

7primaries and whitepoint. 

8 

9- :func:`colour.normalised_primary_matrix` 

10- :func:`colour.chromatically_adapted_primaries` 

11- :func:`colour.primaries_whitepoint` 

12- :func:`colour.RGB_luminance_equation` 

13- :func:`colour.RGB_luminance` 

14 

15References 

16---------- 

17- :cite:`SocietyofMotionPictureandTelevisionEngineers1993a` : Society of 

18 Motion Picture and Television Engineers. (1993). RP 177:1993 - Derivation 

19 of Basic Television Color Equations. In RP 177:1993: Vol. RP 177:199. The 

20 Society of Motion Picture and Television Engineers. 

21 doi:10.5594/S9781614821915 

22- :cite:`Trieu2015a` : Borer, T. (2017). Private Discussion with Mansencal, 

23 T. and Shaw, N. 

24""" 

25 

26from __future__ import annotations 

27 

28import typing 

29 

30import numpy as np 

31 

32from colour.adaptation import chromatic_adaptation_VonKries 

33 

34if typing.TYPE_CHECKING: 

35 from colour.hints import LiteralChromaticAdaptationTransform, Tuple 

36 

37from colour.hints import ArrayLike, NDArrayFloat, Range1 # noqa: TC001 

38from colour.models import XYZ_to_xy, XYZ_to_xyY, xy_to_XYZ 

39from colour.utilities import as_float, as_float_array, ones, tsplit 

40 

41__author__ = "Colour Developers" 

42__copyright__ = "Copyright 2013 Colour Developers" 

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

44__maintainer__ = "Colour Developers" 

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

46__status__ = "Production" 

47 

48__all__ = [ 

49 "xy_to_z", 

50 "normalised_primary_matrix", 

51 "chromatically_adapted_primaries", 

52 "primaries_whitepoint", 

53 "RGB_luminance_equation", 

54 "RGB_luminance", 

55] 

56 

57 

58def xy_to_z(xy: ArrayLike) -> float: 

59 """ 

60 Return the *z* coordinate using the specified :math:`xy` chromaticity 

61 coordinates. 

62 

63 Parameters 

64 ---------- 

65 xy 

66 :math:`xy` chromaticity coordinates. 

67 

68 Returns 

69 ------- 

70 :class:`float` 

71 *z* coordinate. 

72 

73 Examples 

74 -------- 

75 >>> xy_to_z(np.array([0.25, 0.25])) 

76 0.5 

77 """ 

78 

79 x, y = tsplit(xy) 

80 

81 return 1 - x - y 

82 

83 

84def normalised_primary_matrix( 

85 primaries: ArrayLike, whitepoint: ArrayLike 

86) -> NDArrayFloat: 

87 """ 

88 Compute the *Normalised Primary Matrix* (NPM) converting a *RGB* 

89 colourspace array to *CIE XYZ* tristimulus values using the specified 

90 *primaries* and *whitepoint* :math:`xy` chromaticity coordinates. 

91 

92 Parameters 

93 ---------- 

94 primaries 

95 Primaries :math:`xy` chromaticity coordinates. 

96 whitepoint 

97 Illuminant / whitepoint :math:`xy` chromaticity coordinates. 

98 

99 Returns 

100 ------- 

101 :class:`numpy.ndarray` 

102 *Normalised Primary Matrix* (NPM). 

103 

104 References 

105 ---------- 

106 :cite:`SocietyofMotionPictureandTelevisionEngineers1993a` 

107 

108 Examples 

109 -------- 

110 >>> p = np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700]) 

111 >>> w = np.array([0.32168, 0.33767]) 

112 >>> normalised_primary_matrix(p, w) # doctest: +ELLIPSIS 

113 array([[ 9.5255239...e-01, 0.0000000...e+00, 9.3678631...e-05], 

114 [ 3.4396645...e-01, 7.2816609...e-01, -7.2132546...e-02], 

115 [ 0.0000000...e+00, 0.0000000...e+00, 1.0088251...e+00]]) 

116 """ 

117 

118 primaries = np.reshape(primaries, (3, 2)) 

119 

120 z = as_float_array(xy_to_z(primaries))[..., None] 

121 primaries = np.transpose(np.hstack([primaries, z])) 

122 

123 whitepoint = xy_to_XYZ(whitepoint) 

124 

125 coefficients = np.dot(np.linalg.inv(primaries), whitepoint) 

126 coefficients = np.diagflat(coefficients) 

127 

128 return np.dot(primaries, coefficients) 

129 

130 

131def chromatically_adapted_primaries( 

132 primaries: ArrayLike, 

133 whitepoint_t: ArrayLike, 

134 whitepoint_r: ArrayLike, 

135 chromatic_adaptation_transform: ( 

136 LiteralChromaticAdaptationTransform | str 

137 ) = "CAT02", 

138) -> NDArrayFloat: 

139 """ 

140 Chromatically adapt specified *primaries* :math:`xy` chromaticity 

141 coordinates from test ``whitepoint_t`` to reference ``whitepoint_r``. 

142 

143 Parameters 

144 ---------- 

145 primaries 

146 Primaries :math:`xy` chromaticity coordinates. 

147 whitepoint_t 

148 Test illuminant / whitepoint :math:`xy` chromaticity coordinates. 

149 whitepoint_r 

150 Reference illuminant / whitepoint :math:`xy` chromaticity 

151 coordinates. 

152 chromatic_adaptation_transform 

153 *Chromatic adaptation* transform. 

154 

155 Returns 

156 ------- 

157 :class:`numpy.ndarray` 

158 Chromatically adapted primaries :math:`xy` chromaticity 

159 coordinates. 

160 

161 Examples 

162 -------- 

163 >>> p = np.array([0.64, 0.33, 0.30, 0.60, 0.15, 0.06]) 

164 >>> w_t = np.array([0.31270, 0.32900]) 

165 >>> w_r = np.array([0.34570, 0.35850]) 

166 >>> chromatic_adaptation_transform = "Bradford" 

167 >>> chromatically_adapted_primaries(p, w_t, w_r, chromatic_adaptation_transform) 

168 ... # doctest: +ELLIPSIS 

169 array([[ 0.6484414..., 0.3308533...], 

170 [ 0.3211951..., 0.5978443...], 

171 [ 0.1558932..., 0.0660492...]]) 

172 """ 

173 

174 primaries = np.reshape(primaries, (3, 2)) 

175 

176 XYZ_a = chromatic_adaptation_VonKries( 

177 xy_to_XYZ(primaries), 

178 xy_to_XYZ(whitepoint_t), 

179 xy_to_XYZ(whitepoint_r), 

180 chromatic_adaptation_transform, 

181 ) 

182 

183 return XYZ_to_xyY(XYZ_a)[..., 0:2] 

184 

185 

186def primaries_whitepoint(npm: ArrayLike) -> Tuple[NDArrayFloat, NDArrayFloat]: 

187 """ 

188 Compute the *primaries* and *whitepoint* :math:`xy` chromaticity 

189 coordinates using the specified *Normalised Primary Matrix* (NPM). 

190 

191 Parameters 

192 ---------- 

193 npm 

194 *Normalised Primary Matrix*. 

195 

196 Returns 

197 ------- 

198 :class:`tuple` 

199 *Primaries* and *whitepoint* :math:`xy` chromaticity coordinates. 

200 

201 References 

202 ---------- 

203 :cite:`Trieu2015a` 

204 

205 Examples 

206 -------- 

207 >>> npm = np.array( 

208 ... [ 

209 ... [9.52552396e-01, 0.00000000e00, 9.36786317e-05], 

210 ... [3.43966450e-01, 7.28166097e-01, -7.21325464e-02], 

211 ... [0.00000000e00, 0.00000000e00, 1.00882518e00], 

212 ... ] 

213 ... ) 

214 >>> p, w = primaries_whitepoint(npm) 

215 >>> p # doctest: +ELLIPSIS 

216 array([[ 7.3470000...e-01, 2.6530000...e-01], 

217 [ 0.0000000...e+00, 1.0000000...e+00], 

218 [ 1.0000000...e-04, -7.7000000...e-02]]) 

219 >>> w # doctest: +ELLIPSIS 

220 array([ 0.32168, 0.33767]) 

221 """ 

222 

223 npm = np.reshape(npm, (3, 3)) 

224 

225 primaries = XYZ_to_xy(np.transpose(np.dot(npm, np.identity(3)))) 

226 whitepoint = np.squeeze(XYZ_to_xy(np.transpose(np.dot(npm, ones((3, 1)))))) 

227 

228 # TODO: Investigate if we return an ndarray here with primaries and 

229 # whitepoint stacked together. 

230 return primaries, whitepoint 

231 

232 

233def RGB_luminance_equation(primaries: ArrayLike, whitepoint: ArrayLike) -> str: 

234 """ 

235 Return the *luminance equation* from the specified *primaries* and 

236 *whitepoint*. 

237 

238 Parameters 

239 ---------- 

240 primaries 

241 Primaries chromaticity coordinates. 

242 whitepoint 

243 Illuminant / whitepoint chromaticity coordinates. 

244 

245 Returns 

246 ------- 

247 :class:`str` 

248 *Luminance* equation. 

249 

250 Examples 

251 -------- 

252 >>> p = np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700]) 

253 >>> whitepoint = np.array([0.32168, 0.33767]) 

254 >>> RGB_luminance_equation(p, whitepoint) # doctest: +ELLIPSIS 

255 'Y = 0.3439664...(R) + 0.7281660...(G) + -0.0721325...(B)' 

256 """ 

257 

258 return "Y = {}(R) + {}(G) + {}(B)".format( 

259 *np.ravel(normalised_primary_matrix(primaries, whitepoint))[3:6] 

260 ) 

261 

262 

263def RGB_luminance( 

264 RGB: ArrayLike, primaries: ArrayLike, whitepoint: ArrayLike 

265) -> Range1: 

266 """ 

267 Calculate the *luminance* :math:`Y` of the specified *RGB* components using 

268 the specified *primaries* and *whitepoint* chromaticity coordinates. 

269 

270 Parameters 

271 ---------- 

272 RGB 

273 *RGB* colour components array. 

274 primaries 

275 Primaries :math:`xy` chromaticity coordinates array. 

276 whitepoint 

277 Illuminant / whitepoint :math:`xy` chromaticity coordinates. 

278 

279 Returns 

280 ------- 

281 :class:`numpy.ndarray` 

282 *Luminance* :math:`Y`. 

283 

284 Notes 

285 ----- 

286 +-----------+-----------------------+---------------+ 

287 | **Range** | **Scale - Reference** | **Scale - 1** | 

288 +===========+=======================+===============+ 

289 | ``Y`` | 1 | 1 | 

290 +-----------+-----------------------+---------------+ 

291 

292 Examples 

293 -------- 

294 >>> RGB = np.array([0.21959402, 0.06986677, 0.04703877]) 

295 >>> p = np.array([0.73470, 0.26530, 0.00000, 1.00000, 0.00010, -0.07700]) 

296 >>> whitepoint = np.array([0.32168, 0.33767]) 

297 >>> RGB_luminance(RGB, p, whitepoint) # doctest: +ELLIPSIS 

298 0.1230145... 

299 """ 

300 

301 Y = np.sum(normalised_primary_matrix(primaries, whitepoint)[1] * RGB, axis=-1) 

302 

303 return as_float(Y)