Coverage for graph/tests/test_conversion.py: 100%

1"""Define the unit tests for the :mod:`colour.graph.conversion` module."""

2

3 from __future__ import annotations

4

5 import numpy as np

6 import pytest

7

8 from colour.characterisation import SDS_COLOURCHECKERS

9 from colour.colorimetry import CCS_ILLUMINANTS, SDS_ILLUMINANTS

10 from colour.constants import TOLERANCE_ABSOLUTE_TESTS

11 from colour.graph import convert, describe_conversion_path

12 from colour.models import COLOURSPACE_MODELS, RGB_COLOURSPACE_ACES2065_1, XYZ_to_Lab

13 from colour.utilities import get_domain_range_scale_metadata

14

15 __author__ = "Colour Developers"

16 __copyright__ = "Copyright 2013 Colour Developers"

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

18 __maintainer__ = "Colour Developers"

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

20 __status__ = "Production"

21

22 __all__ = [

23 "TestDescribeConversionPath",

24 "TestConvert",

25]

26

27

28 class TestDescribeConversionPath:

29 """

30 Define :func:`colour.graph.conversion.describe_conversion_path` definition

31 unit tests methods.

32 """

33

34 def test_describe_conversion_path(self) -> None:

35 """

36 Test :func:`colour.graph.conversion.describe_conversion_path`

37 definition.

38 """

39

40 describe_conversion_path("Spectral Distribution", "sRGB")

41

42 describe_conversion_path("Spectral Distribution", "sRGB", mode="Long")

43

44 describe_conversion_path(

45 "Spectral Distribution",

46 "sRGB",

47 mode="Extended",

48 sd_to_XYZ={

49 "illuminant": SDS_ILLUMINANTS["FL2"],

50 "return": np.array([0.47924575, 0.31676968, 0.17362725]),

51 },

52 )

53

54

55 class TestConvert:

56 """

57 Define :func:`colour.graph.conversion.convert` definition unit tests

58 methods.

59 """

60

61 def test_convert(self) -> None:

62 """Test :func:`colour.graph.conversion.convert` definition."""

63

64 RGB_a = convert(

65 SDS_COLOURCHECKERS["ColorChecker N Ohta"]["dark skin"],

66 "Spectral Distribution",

67 "sRGB",

68 )

69 np.testing.assert_allclose(

70 RGB_a,

71 np.array([0.49034776, 0.30185875, 0.23587685]),

72 atol=TOLERANCE_ABSOLUTE_TESTS,

73 )

74

75 Jpapbp = convert(RGB_a, "Output-Referred RGB", "CAM16UCS")

76 np.testing.assert_allclose(

77 Jpapbp,

78 np.array([0.40738741, 0.12046560, 0.09284385]),

79 atol=TOLERANCE_ABSOLUTE_TESTS,

80 )

81

82 RGB_b = convert(Jpapbp, "CAM16UCS", "sRGB", verbose={"mode": "Extended"})

83 # NOTE: The "CIE XYZ" tristimulus values to "sRGB" matrix is given

84 # rounded at 4 decimals as per "IEC 61966-2-1:1999" and thus preventing

85 # exact roundtrip.

86 np.testing.assert_allclose(RGB_a, RGB_b, atol=1e-4)

87

88 np.testing.assert_allclose(

89 convert("#808080", "Hexadecimal", "Scene-Referred RGB"),

90 np.array([0.21586050, 0.21586050, 0.21586050]),

91 atol=TOLERANCE_ABSOLUTE_TESTS,

92 )

93

94 np.testing.assert_allclose(

95 convert("#808080", "Hexadecimal", "RGB Luminance"),

96 0.21586050,

97 atol=TOLERANCE_ABSOLUTE_TESTS,

98 )

99

100 np.testing.assert_allclose(

101 convert(

102 convert(

103 np.array([0.5, 0.5, 0.5]),

104 "Output-Referred RGB",

105 "Scene-Referred RGB",

106 ),

107 "RGB",

108 "YCbCr",

109 ),

110 np.array([0.49215686, 0.50196078, 0.50196078]),

111 atol=TOLERANCE_ABSOLUTE_TESTS,

112 )

113

114 np.testing.assert_allclose(

115 convert(

116 RGB_a,

117 "RGB",

118 "Scene-Referred RGB",

119 RGB_to_RGB={"output_colourspace": RGB_COLOURSPACE_ACES2065_1},

120 ),

121 np.array([0.37308227, 0.31241444, 0.24746366]),

122 atol=TOLERANCE_ABSOLUTE_TESTS,

123 )

124

125 # Consistency check to verify that all the colour models are properly

126 # named in the graph:

127 for model in COLOURSPACE_MODELS:

128 convert(

129 np.array([0.20654008, 0.12197225, 0.05136952]),

130 "CIE XYZ",

131 model,

132 )

133

134 def test_convert_direct_keyword_argument_passing(self) -> None:

135 """

136 Test :func:`colour.graph.conversion.convert` definition behaviour when

137 direct keyword arguments are passed.

138 """

139

140 a = np.array([0.20654008, 0.12197225, 0.05136952])

141 illuminant = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"]["D50"]

142 np.testing.assert_allclose(

143 convert(a, "CIE XYZ", "CIE UVW", XYZ_to_UVW={"illuminant": illuminant}),

144 convert(a, "CIE XYZ", "CIE UVW", illuminant=illuminant),

145 atol=TOLERANCE_ABSOLUTE_TESTS,

146 )

147

148 # Illuminant "ndarray" is converted to tuple here so that it can

149 # be hashed by the "sd_to_XYZ" definition, this should never occur

150 # in practical application.

151 pytest.raises(

152 AttributeError,

153 lambda: convert(

154 SDS_COLOURCHECKERS["ColorChecker N Ohta"]["dark skin"],

155 "Spectral Distribution",

156 "sRGB",

157 illuminant=tuple(illuminant),

158 ),

159 )

160

161 def test_convert_reference_scale(self) -> None:

162 """

163 Test :func:`colour.graph.conversion.convert` definition behaviour with

164 `from_reference_scale` and `to_reference_scale` parameters.

165 """

166

167 XYZ = np.array([0.20654008, 0.12197225, 0.05136952])

168

169 Lab_auto = convert(XYZ, "CIE XYZ", "CIE Lab", to_reference_scale=True)

170

171 Lab_manual = convert(XYZ, "CIE XYZ", "CIE Lab")

172 metadata = get_domain_range_scale_metadata(XYZ_to_Lab)

173 range_scale = metadata["range"]

174 Lab_manual_scaled = Lab_manual * range_scale

175

176 np.testing.assert_allclose(

177 Lab_auto,

178 Lab_manual_scaled,

179 atol=TOLERANCE_ABSOLUTE_TESTS,

180 )

181

182 Lab_native = Lab_auto

183

184 XYZ_auto = convert(

185 Lab_native,

186 "CIE Lab",

187 "CIE XYZ",

188 from_reference_scale=True,

189 )

190

191 Lab_manual_normalized = Lab_native / range_scale

192 XYZ_manual = convert(Lab_manual_normalized, "CIE Lab", "CIE XYZ")

193

194 np.testing.assert_allclose(

195 XYZ_auto,

196 XYZ_manual,

197 atol=TOLERANCE_ABSOLUTE_TESTS,

198 )

199

200 XYZ_roundtrip = convert(

201 convert(

202 XYZ,

203 "CIE XYZ",

204 "CIE Lab",

205 to_reference_scale=True,

206 ),

207 "CIE Lab",

208 "CIE XYZ",

209 from_reference_scale=True,

210 )

211

212 np.testing.assert_allclose(

213 XYZ_roundtrip,

214 XYZ,

215 atol=TOLERANCE_ABSOLUTE_TESTS,

216 )

217

218 XYZ = np.array([0.20654008, 0.12197225, 0.05136952])

219

220 # Test CIE Lab and CIE LCHab consistency

221 Lab = convert(XYZ, "CIE XYZ", "CIE Lab", to_reference_scale=True)

222 LCHab = convert(XYZ, "CIE XYZ", "CIE LCHab", to_reference_scale=True)

223

224 # L component should be identical

225 np.testing.assert_allclose(

226 Lab[0],

227 LCHab[0],

228 atol=TOLERANCE_ABSOLUTE_TESTS,

229 )

230

231 # C should equal sqrt(a^2 + b^2)

232 expected_C = np.sqrt(Lab[1] ** 2 + Lab[2] ** 2)

233 np.testing.assert_allclose(

234 LCHab[1],

235 expected_C,

236 atol=TOLERANCE_ABSOLUTE_TESTS,

237 )

238

239 # Test CIE Luv and CIE LCHuv consistency

240 Luv = convert(XYZ, "CIE XYZ", "CIE Luv", to_reference_scale=True)

241 LCHuv = convert(XYZ, "CIE XYZ", "CIE LCHuv", to_reference_scale=True)

242

243 # L component should be identical

244 np.testing.assert_allclose(

245 Luv[0],

246 LCHuv[0],

247 atol=TOLERANCE_ABSOLUTE_TESTS,

248 )

249

250 # C should equal sqrt(u^2 + v^2)

251 expected_C = np.sqrt(Luv[1] ** 2 + Luv[2] ** 2)

252 np.testing.assert_allclose(

253 LCHuv[1],

254 expected_C,

255 atol=TOLERANCE_ABSOLUTE_TESTS,

256 )