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

41 statements  

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

1""" 

2Hunter Rd,a,b Colour Scale 

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

4 

5Define the *Hunter Rd,a,b* colour scale transformations. 

6 

7- :func:`colour.XYZ_to_Hunter_Rdab` 

8- :func:`colour.Hunter_Rdab_to_XYZ` 

9 

10References 

11---------- 

12- :cite:`HunterLab2012a` : HunterLab. (2012). Hunter Rd,a,b Color Scale - 

13 History and Application. 

14 https://hunterlabdotcom.files.wordpress.com/2012/07/\ 

15an-1016-hunter-rd-a-b-color-scale-update-12-07-03.pdf 

16""" 

17 

18from __future__ import annotations 

19 

20from colour.colorimetry import TVS_ILLUMINANTS_HUNTERLAB 

21from colour.hints import ( # noqa: TC001 

22 ArrayLike, 

23 Domain100, 

24 Range100, 

25) 

26from colour.models import XYZ_to_K_ab_HunterLab1966 

27from colour.utilities import ( 

28 from_range_100, 

29 get_domain_range_scale, 

30 optional, 

31 to_domain_100, 

32 tsplit, 

33 tstack, 

34) 

35 

36__author__ = "Colour Developers" 

37__copyright__ = "Copyright 2013 Colour Developers" 

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

39__maintainer__ = "Colour Developers" 

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

41__status__ = "Production" 

42 

43__all__ = [ 

44 "XYZ_to_Hunter_Rdab", 

45 "Hunter_Rdab_to_XYZ", 

46] 

47 

48 

49def XYZ_to_Hunter_Rdab( 

50 XYZ: Domain100, 

51 XYZ_n: ArrayLike | None = None, 

52 K_ab: ArrayLike | None = None, 

53) -> Range100: 

54 """ 

55 Convert from *CIE XYZ* tristimulus values to *Hunter Rd,a,b* colour 

56 scale. 

57 

58 Parameters 

59 ---------- 

60 XYZ 

61 *CIE XYZ* tristimulus values. 

62 XYZ_n 

63 Reference *illuminant* tristimulus values. 

64 K_ab 

65 Reference *illuminant* chromaticity coefficients. If ``K_ab`` is 

66 set to *None*, it will be computed using 

67 :func:`colour.XYZ_to_K_ab_HunterLab1966`. 

68 

69 Returns 

70 ------- 

71 :class:`numpy.ndarray` 

72 *Hunter Rd,a,b* colour scale array. 

73 

74 Notes 

75 ----- 

76 +------------+------------------------+--------------------+ 

77 | **Domain** | **Scale - Reference** | **Scale - 1** | 

78 +============+========================+====================+ 

79 | ``XYZ`` | 100 | 1 | 

80 +------------+------------------------+--------------------+ 

81 | ``XYZ_n`` | 100 | 1 | 

82 +------------+------------------------+--------------------+ 

83 

84 +------------+------------------------+--------------------+ 

85 | **Range** | **Scale - Reference** | **Scale - 1** | 

86 +============+========================+====================+ 

87 | ``R_d_ab`` | 100 | 1 | 

88 +------------+------------------------+--------------------+ 

89 

90 References 

91 ---------- 

92 :cite:`HunterLab2012a` 

93 

94 Examples 

95 -------- 

96 >>> import numpy as np 

97 >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) * 100 

98 >>> D65 = TVS_ILLUMINANTS_HUNTERLAB["CIE 1931 2 Degree Standard Observer"]["D65"] 

99 >>> XYZ_to_Hunter_Rdab(XYZ, D65.XYZ_n, D65.K_ab) 

100 ... # doctest: +ELLIPSIS 

101 array([ 12.197225 ..., 57.1253787..., 17.4624134...]) 

102 """ 

103 

104 X, Y, Z = tsplit(to_domain_100(XYZ)) 

105 d65 = TVS_ILLUMINANTS_HUNTERLAB["CIE 1931 2 Degree Standard Observer"]["D65"] 

106 XYZ_n_default = XYZ_n is None 

107 XYZ_n = to_domain_100( 

108 optional( 

109 XYZ_n, 

110 d65.XYZ_n if get_domain_range_scale() == "reference" else d65.XYZ_n / 100, 

111 ) 

112 ) 

113 X_n, Y_n, Z_n = tsplit(XYZ_n) 

114 K_ab = d65.K_ab if K_ab is None and XYZ_n_default else K_ab 

115 K_a, K_b = tsplit(XYZ_to_K_ab_HunterLab1966(XYZ_n) if K_ab is None else K_ab) 

116 

117 f = 0.51 * ((21 + 0.2 * Y) / (1 + 0.2 * Y)) 

118 Y_Yn = Y / Y_n 

119 

120 R_d = Y 

121 a_Rd = K_a * f * (X / X_n - Y_Yn) 

122 b_Rd = K_b * f * (Y_Yn - Z / Z_n) 

123 

124 R_d_ab = tstack([R_d, a_Rd, b_Rd]) 

125 

126 return from_range_100(R_d_ab) 

127 

128 

129def Hunter_Rdab_to_XYZ( 

130 R_d_ab: Domain100, 

131 XYZ_n: ArrayLike | None = None, 

132 K_ab: ArrayLike | None = None, 

133) -> Range100: 

134 """ 

135 Convert from *Hunter Rd,a,b* colour scale to *CIE XYZ* tristimulus 

136 values. 

137 

138 Parameters 

139 ---------- 

140 R_d_ab 

141 *Hunter Rd,a,b* colour scale array. 

142 XYZ_n 

143 Reference *illuminant* tristimulus values. 

144 K_ab 

145 Reference *illuminant* chromaticity coefficients. If ``K_ab`` is 

146 set to *None*, it will be computed using 

147 :func:`colour.XYZ_to_K_ab_HunterLab1966`. 

148 

149 Returns 

150 ------- 

151 :class:`numpy.ndarray` 

152 *CIE XYZ* tristimulus values. 

153 

154 Notes 

155 ----- 

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

157 | **Domain** | **Scale - Reference** | **Scale - 1** | 

158 +============+========================+====================+ 

159 | ``R_d_ab`` | 100 | 1 | 

160 +------------+------------------------+--------------------+ 

161 | ``XYZ_n`` | 100 | 1 | 

162 +------------+------------------------+--------------------+ 

163 

164 +------------+------------------------+--------------------+ 

165 | **Range** | **Scale - Reference** | **Scale - 1** | 

166 +============+========================+====================+ 

167 | ``XYZ`` | 100 | 1 | 

168 +------------+------------------------+--------------------+ 

169 

170 References 

171 ---------- 

172 :cite:`HunterLab2012a` 

173 

174 Examples 

175 -------- 

176 >>> import numpy as np 

177 >>> R_d_ab = np.array([12.19722500, 57.12537874, 17.46241341]) 

178 >>> D65 = TVS_ILLUMINANTS_HUNTERLAB["CIE 1931 2 Degree Standard Observer"]["D65"] 

179 >>> Hunter_Rdab_to_XYZ(R_d_ab, D65.XYZ_n, D65.K_ab) 

180 array([ 20.654008, 12.197225, 5.136952]) 

181 """ 

182 

183 R_d, a_Rd, b_Rd = tsplit(to_domain_100(R_d_ab)) 

184 TVS_D65 = TVS_ILLUMINANTS_HUNTERLAB["CIE 1931 2 Degree Standard Observer"]["D65"] 

185 XYZ_n_default = XYZ_n is None 

186 XYZ_n = to_domain_100( 

187 optional( 

188 XYZ_n, 

189 TVS_D65.XYZ_n 

190 if get_domain_range_scale() == "reference" 

191 else TVS_D65.XYZ_n / 100, 

192 ) 

193 ) 

194 X_n, Y_n, Z_n = tsplit(XYZ_n) 

195 K_ab = TVS_D65.K_ab if K_ab is None and XYZ_n_default else K_ab 

196 K_a, K_b = tsplit(XYZ_to_K_ab_HunterLab1966(XYZ_n) if K_ab is None else K_ab) 

197 

198 f = 0.51 * ((21 + 0.2 * R_d) / (1 + 0.2 * R_d)) 

199 Rd_Yn = R_d / Y_n 

200 X = (a_Rd / (K_a * f) + Rd_Yn) * X_n 

201 Z = -(b_Rd / (K_b * f) - Rd_Yn) * Z_n 

202 

203 XYZ = tstack([X, R_d, Z]) 

204 

205 return from_range_100(XYZ)