Coverage for colour/models/icacb.py: 100%

31 statements  

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

1""" 

2:math:`IC_AC_B` Colourspace 

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

4 

5Define the :math:`IC_AC_B` colourspace transformations. 

6 

7- :func:`colour.XYZ_to_ICaCb` 

8- :func:`colour.ICaCb_to_XYZ` 

9 

10References 

11---------- 

12- :cite:`Frohlich2017` : Frohlich, J. (2017). Encoding high dynamic range 

13 and wide color gamut imagery. doi:10.18419/OPUS-9664 

14""" 

15 

16from __future__ import annotations 

17 

18import numpy as np 

19 

20from colour.hints import ( # noqa: TC001 

21 ArrayLike, 

22 Domain1, 

23 NDArrayFloat, 

24 Range1, 

25) 

26from colour.models import Iab_to_XYZ, XYZ_to_Iab 

27from colour.models.rgb.transfer_functions import eotf_inverse_ST2084, eotf_ST2084 

28from colour.utilities import domain_range_scale 

29 

30__author__ = "Colour Developers" 

31__copyright__ = "Copyright 2013 Colour Developers" 

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

33__maintainer__ = "Colour Developers" 

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

35__status__ = "Production" 

36 

37__all__ = [ 

38 "MATRIX_ICACB_XYZ_TO_LMS", 

39 "MATRIX_ICACB_LMS_TO_XYZ", 

40 "MATRIX_ICACB_XYZ_TO_LMS_2", 

41 "MATRIX_ICACB_LMS_TO_XYZ_2", 

42 "XYZ_to_ICaCb", 

43 "ICaCb_to_XYZ", 

44] 

45 

46MATRIX_ICACB_XYZ_TO_LMS: NDArrayFloat = np.array( 

47 [ 

48 [0.37613, 0.70431, -0.05675], 

49 [-0.21649, 1.14744, 0.05356], 

50 [0.02567, 0.16713, 0.74235], 

51 ] 

52) 

53"""*CIE XYZ* tristimulus values to normalised cone responses matrix.""" 

54 

55MATRIX_ICACB_LMS_TO_XYZ: NDArrayFloat = np.linalg.inv(MATRIX_ICACB_XYZ_TO_LMS) 

56"""Normalised cone responses to *CIE XYZ* tristimulus values matrix.""" 

57 

58MATRIX_ICACB_XYZ_TO_LMS_2: NDArrayFloat = np.array( 

59 [ 

60 [0.4949, 0.5037, 0.0015], 

61 [4.2854, -4.5462, 0.2609], 

62 [0.3605, 1.1499, -1.5105], 

63 ] 

64) 

65"""Normalised non-linear cone responses to :math:`IC_AC_B` colourspace matrix.""" 

66 

67MATRIX_ICACB_LMS_TO_XYZ_2: NDArrayFloat = np.linalg.inv(MATRIX_ICACB_XYZ_TO_LMS_2) 

68""":math:`IC_AC_B` to normalised non-linear cone responses colourspace matrix.""" 

69 

70 

71def XYZ_to_ICaCb(XYZ: Domain1) -> Range1: 

72 """ 

73 Convert from *CIE XYZ* tristimulus values to :math:`IC_AC_B` colourspace. 

74 

75 Parameters 

76 ---------- 

77 XYZ 

78 *CIE XYZ* tristimulus values. 

79 

80 Returns 

81 ------- 

82 :class:`numpy.ndarray` 

83 :math:`IC_AC_B` colourspace array. 

84 

85 Notes 

86 ----- 

87 +------------+-----------------------+-----------------+ 

88 | **Domain** | **Scale - Reference** | **Scale - 1** | 

89 +============+=======================+=================+ 

90 | ``XYZ`` | 1 | 1 | 

91 +------------+-----------------------+-----------------+ 

92 

93 +------------+-----------------------+-----------------+ 

94 | **Range** | **Scale - Reference** | **Scale - 1** | 

95 +============+=======================+=================+ 

96 | ``ICaCb`` | 1 | 1 | 

97 +------------+-----------------------+-----------------+ 

98 

99 - Input *CIE XYZ* tristimulus values must be adapted to 

100 *CIE Standard Illuminant D Series* *D65*. 

101 

102 References 

103 ---------- 

104 :cite:`Frohlich2017` 

105 

106 Examples 

107 -------- 

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

109 >>> XYZ_to_ICaCb(XYZ) # doctest: +ELLIPSIS 

110 array([ 0.0687529..., 0.0575335..., 0.0208154...]) 

111 """ 

112 

113 def LMS_to_LMS_p_callable(LMS: ArrayLike) -> NDArrayFloat: 

114 """ 

115 Callable applying the forward non-linearity to the :math:`LMS` 

116 colourspace array. 

117 """ 

118 

119 with domain_range_scale("ignore"): 

120 return eotf_inverse_ST2084(LMS) 

121 

122 return XYZ_to_Iab( 

123 XYZ, 

124 LMS_to_LMS_p_callable, 

125 MATRIX_ICACB_XYZ_TO_LMS, 

126 MATRIX_ICACB_XYZ_TO_LMS_2, 

127 ) 

128 

129 

130def ICaCb_to_XYZ(ICaCb: Domain1) -> Range1: 

131 """ 

132 Convert from :math:`IC_AC_B` colourspace to *CIE XYZ* tristimulus values. 

133 

134 Parameters 

135 ---------- 

136 ICaCb 

137 :math:`IC_AC_B` colourspace array. 

138 

139 Returns 

140 ------- 

141 :class:`numpy.ndarray` 

142 *CIE XYZ* tristimulus values. 

143 

144 Notes 

145 ----- 

146 +------------+-----------------------+-----------------+ 

147 | **Domain** | **Scale - Reference** | **Scale - 1** | 

148 +============+=======================+=================+ 

149 | ``ICaCb`` | 1 | 1 | 

150 +------------+-----------------------+-----------------+ 

151 

152 +------------+-----------------------+-----------------+ 

153 | **Range** | **Scale - Reference** | **Scale - 1** | 

154 +============+=======================+=================+ 

155 | ``XYZ`` | 1 | 1 | 

156 +------------+-----------------------+-----------------+ 

157 

158 - Output *CIE XYZ* tristimulus values are adapted to 

159 *CIE Standard Illuminant D Series* *D65*. 

160 

161 References 

162 ---------- 

163 :cite:`Frohlich2017` 

164 

165 Examples 

166 -------- 

167 >>> ICaCb = np.array([0.06875297, 0.05753352, 0.02081548]) 

168 >>> ICaCb_to_XYZ(ICaCb) # doctest: +ELLIPSIS 

169 array([ 0.2065400..., 0.1219722..., 0.0513695...]) 

170 """ 

171 

172 def LMS_p_to_LMS_callable(LMS_p: ArrayLike) -> NDArrayFloat: 

173 """ 

174 Callable applying the reverse non-linearity to the :math:`LMS_p` 

175 colourspace array. 

176 """ 

177 

178 with domain_range_scale("ignore"): 

179 return eotf_ST2084(LMS_p) 

180 

181 return Iab_to_XYZ( 

182 ICaCb, 

183 LMS_p_to_LMS_callable, 

184 MATRIX_ICACB_LMS_TO_XYZ_2, 

185 MATRIX_ICACB_LMS_TO_XYZ, 

186 )