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

1"""

2 RGB Colourspace Derivation

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

4

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

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

7 primaries 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

15 References

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

26 from __future__ import annotations

27

28 import typing

29

30 import numpy as np

31

32 from colour.adaptation import chromatic_adaptation_VonKries

33

34 if typing.TYPE_CHECKING:

35 from colour.hints import LiteralChromaticAdaptationTransform, Tuple

36

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

38 from colour.models import XYZ_to_xy, XYZ_to_xyY, xy_to_XYZ

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

58 def 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

84 def 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

131 def 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

186 def 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

233 def 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

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