Coverage for quality/ssi.py: 59%

1"""

2 Academy Spectral Similarity Index (SSI)

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

4

5 Define the *Academy Spectral Similarity Index* (SSI) computation objects.

6

7- :func:`colour.spectral_similarity_index`

8

9 References

10----------

11- :cite:`TheAcademyofMotionPictureArtsandSciences2020a` : The Academy of

12 Motion Picture Arts and Sciences. (2020). Academy Spectral Similarity

13 Index (SSI): Overview (pp. 1-7). Retrieved June 5, 2023, from

14 https://www.oscars.org/sites/oscars/files/ssi_overview_2020-09-16.pdf

15"""

16

17 from __future__ import annotations

18

19 import typing

20

21 import numpy as np

22

23 from colour.algebra import LinearInterpolator, sdiv, sdiv_mode

24 from colour.colorimetry import (

25 MultiSpectralDistributions,

26 SpectralDistribution,

27 SpectralShape,

28 reshape_msds,

29 reshape_sd,

30)

31

32 if typing.TYPE_CHECKING:

33 from colour.hints import NDArrayFloat

34

35

36 from colour.utilities import required, zeros

37

38 __author__ = "Colour Developers"

39 __copyright__ = "Copyright 2013 Colour Developers"

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

41 __maintainer__ = "Colour Developers"

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

43 __status__ = "Production"

44

45 __all__ = [

46 "SPECTRAL_SHAPE_SSI",

47 "spectral_similarity_index",

48]

49

50 SPECTRAL_SHAPE_SSI: SpectralShape = SpectralShape(375, 675, 1)

51"""*Academy Spectral Similarity Index* (SSI) spectral shape."""

52

53 _SPECTRAL_SHAPE_SSI_LARGE: SpectralShape = SpectralShape(380, 670, 10)

54

55 _MATRIX_INTEGRATION: NDArrayFloat | None = None

56

57

58@required("SciPy")

59 def spectral_similarity_index(

60 sd_test: SpectralDistribution | MultiSpectralDistributions,

61 sd_reference: SpectralDistribution | MultiSpectralDistributions,

62 round_result: bool = True,

63) -> NDArrayFloat:

64 """

65 Compute the *Academy Spectral Similarity Index* (SSI) of the specified

66 test spectral distribution or multi-spectral distributions with the

67 specified reference spectral distribution or multi-spectral distributions.

68

69 Parameters

70 ----------

71 sd_test

72 Test spectral distribution or multi-spectral distributions.

73 sd_reference

74 Reference spectral distribution or multi-spectral distributions.

75 round_result

76 Whether to round the result/output. This is particularly useful when

77 using SSI in an optimisation routine. Default is *True*.

78

79 Returns

80 -------

81 :class:`numpy.ndarray`

82 *Academy Spectral Similarity Index* (SSI). When both inputs are

83 :class:`colour.SpectralDistribution` objects, returns a scalar.

84 When either input is a :class:`colour.MultiSpectralDistributions`

85 object, returns an array with one SSI value per spectral distribution.

86

87 References

88 ----------

89 :cite:`TheAcademyofMotionPictureArtsandSciences2020a`

90

91 Examples

92 --------

93 >>> from colour import SDS_ILLUMINANTS

94 >>> sd_test = SDS_ILLUMINANTS["C"]

95 >>> sd_reference = SDS_ILLUMINANTS["D65"]

96 >>> spectral_similarity_index(sd_test, sd_reference)

97 94.0

98

99 Computing SSI for multi-spectral distributions:

100

101 >>> from colour.colorimetry import sd_single_led, sds_and_msds_to_msds

102 >>> sd_led_1 = sd_single_led(520, half_spectral_width=45)

103 >>> sd_led_2 = sd_single_led(540, half_spectral_width=55)

104 >>> sd_led_3 = sd_single_led(560, half_spectral_width=50)

105 >>> msds = sds_and_msds_to_msds([sd_led_1, sd_led_2, sd_led_3])

106 >>> sd_reference = sd_single_led(535, half_spectral_width=48)

107 >>> spectral_similarity_index(msds, sd_reference)

108 array([ 52., 82., 18.])

109 """

110

111 from scipy.ndimage import convolve1d # noqa: PLC0415

112

113 global _MATRIX_INTEGRATION # noqa: PLW0603

114

115 if _MATRIX_INTEGRATION is None:

116 _MATRIX_INTEGRATION = zeros(

117 (

118 len(_SPECTRAL_SHAPE_SSI_LARGE.wavelengths),

119 len(SPECTRAL_SHAPE_SSI.wavelengths),

120 )

121 )

122

123 weights = np.array([0.5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.5])

124

125 for i in range(_MATRIX_INTEGRATION.shape[0]):

126 _MATRIX_INTEGRATION[i, (10 * i) : (10 * i + 11)] = weights

127

128 settings = {

129 "interpolator": LinearInterpolator,

130 "extrapolator_kwargs": {"left": 0, "right": 0},

131 }

132

133 sd_test = (

134 reshape_msds(sd_test, SPECTRAL_SHAPE_SSI, "Align", copy=False, **settings)

135 if isinstance(sd_test, MultiSpectralDistributions)

136 else reshape_sd(sd_test, SPECTRAL_SHAPE_SSI, "Align", copy=False, **settings)

137 )

138 sd_reference = (

139 reshape_msds(sd_reference, SPECTRAL_SHAPE_SSI, "Align", copy=False, **settings)

140 if isinstance(sd_reference, MultiSpectralDistributions)

141 else reshape_sd(

142 sd_reference, SPECTRAL_SHAPE_SSI, "Align", copy=False, **settings

143 )

144 )

145

146 test_i = np.dot(_MATRIX_INTEGRATION, sd_test.values)

147 reference_i = np.dot(_MATRIX_INTEGRATION, sd_reference.values)

148

149 if test_i.ndim == 1 and reference_i.ndim == 2:

150 test_i = np.tile(test_i[:, np.newaxis], (1, reference_i.shape[1]))

151 elif test_i.ndim == 2 and reference_i.ndim == 1:

152 reference_i = np.tile(reference_i[:, np.newaxis], (1, test_i.shape[1]))

153

154 with sdiv_mode():

155 test_i = sdiv(test_i, np.sum(test_i, axis=0, keepdims=True))

156 reference_i = sdiv(reference_i, np.sum(reference_i, axis=0, keepdims=True))

157 dr_i = sdiv(test_i - reference_i, reference_i + 1 / 30)

158

159 weights = np.array(

160 [

161 4 / 15,

162 22 / 45,

163 32 / 45,

164 40 / 45,

165 44 / 45,

166 1,

167 1,

168 1,

169 1,

170 1,

171 1,

172 1,

173 1,

174 1,

175 1,

176 1,

177 1,

178 1,

179 1,

180 1,

181 1,

182 1,

183 1,

184 1,

185 1,

186 1,

187 1,

188 1,

189 11 / 15,

190 3 / 15,

191 ]

192 )

193

194 if dr_i.ndim == 2:

195 weights = weights[:, np.newaxis]

196

197 wdr_i = dr_i * weights

198 c_wdr_i = convolve1d(wdr_i, [0.22, 0.56, 0.22], axis=0, mode="constant", cval=0)

199 m_v = np.sum(np.square(c_wdr_i), axis=0)

200

201 SSI = 100 - 32 * np.sqrt(m_v)

202

203 return np.around(SSI) if round_result else SSI