Coverage for models/hunter_lab.py: 68%

47 statements  

« prev     ^ index     » next       coverage.py v7.11.0, created at 2025-11-16 22:49 +1300

1""" 

2Hunter L,a,b Colour Scale 

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

4 

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

6 

7- :func:`colour.XYZ_to_K_ab_HunterLab1966` 

8- :func:`colour.XYZ_to_Hunter_Lab` 

9- :func:`colour.Hunter_Lab_to_XYZ` 

10 

11References 

12---------- 

13- :cite:`HunterLab2008b` : HunterLab. (2008). Hunter L,a,b Color Scale. 

14 http://www.hunterlab.se/wp-content/uploads/2012/11/Hunter-L-a-b.pdf 

15- :cite:`HunterLab2008c` : HunterLab. (2008). Illuminant Factors in 

16 Universal Software and EasyMatch Coatings. 

17 https://support.hunterlab.com/hc/en-us/article_attachments/201437785/\ 

18an02_02.pdf 

19""" 

20 

21from __future__ import annotations 

22 

23import numpy as np 

24 

25from colour.colorimetry import TVS_ILLUMINANTS_HUNTERLAB 

26from colour.hints import ( # noqa: TC001 

27 ArrayLike, 

28 Domain100, 

29 NDArrayFloat, 

30 Range100, 

31) 

32from colour.utilities import ( 

33 from_range_100, 

34 get_domain_range_scale, 

35 optional, 

36 to_domain_100, 

37 tsplit, 

38 tstack, 

39) 

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 "XYZ_to_K_ab_HunterLab1966", 

50 "XYZ_to_Hunter_Lab", 

51 "Hunter_Lab_to_XYZ", 

52] 

53 

54 

55def XYZ_to_K_ab_HunterLab1966(XYZ: ArrayLike) -> NDArrayFloat: 

56 """ 

57 Convert from *whitepoint* *CIE XYZ* tristimulus values to 

58 *Hunter L,a,b* :math:`K_{a}` and :math:`K_{b}` chromaticity 

59 coefficients. 

60 

61 Parameters 

62 ---------- 

63 XYZ 

64 *Whitepoint* *CIE XYZ* tristimulus values. 

65 

66 Returns 

67 ------- 

68 :class:`numpy.ndarray` 

69 *Hunter L,a,b* :math:`K_{a}` and :math:`K_{b}` chromaticity 

70 coefficients. 

71 

72 References 

73 ---------- 

74 :cite:`HunterLab2008c` 

75 

76 Examples 

77 -------- 

78 >>> XYZ = np.array([109.850, 100.000, 35.585]) 

79 >>> XYZ_to_K_ab_HunterLab1966(XYZ) # doctest: +ELLIPSIS 

80 array([ 185.2378721..., 38.4219142...]) 

81 """ 

82 

83 X, _Y, Z = tsplit(XYZ) 

84 

85 K_a = 175 * np.sqrt(X / 98.043) 

86 K_b = 70 * np.sqrt(Z / 118.115) 

87 

88 return tstack([K_a, K_b]) 

89 

90 

91def XYZ_to_Hunter_Lab( 

92 XYZ: Domain100, 

93 XYZ_n: ArrayLike | None = None, 

94 K_ab: ArrayLike | None = None, 

95) -> Range100: 

96 """ 

97 Convert from *CIE XYZ* tristimulus values to *Hunter L,a,b* colour 

98 scale. 

99 

100 Parameters 

101 ---------- 

102 XYZ 

103 *CIE XYZ* tristimulus values. 

104 XYZ_n 

105 Reference *illuminant* tristimulus values. 

106 K_ab 

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

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

109 :func:`colour.XYZ_to_K_ab_HunterLab1966`. 

110 

111 Returns 

112 ------- 

113 :class:`numpy.ndarray` 

114 *Hunter L,a,b* colour scale array. 

115 

116 Notes 

117 ----- 

118 +------------+-----------------------+-----------------+ 

119 | **Domain** | **Scale - Reference** | **Scale - 1** | 

120 +============+=======================+=================+ 

121 | ``XYZ`` | 100 | 1 | 

122 +------------+-----------------------+-----------------+ 

123 | ``XYZ_n`` | 100 | 1 | 

124 +------------+-----------------------+-----------------+ 

125 

126 +------------+-----------------------+-----------------+ 

127 | **Range** | **Scale - Reference** | **Scale - 1** | 

128 +============+=======================+=================+ 

129 | ``Lab`` | 100 | 1 | 

130 +------------+-----------------------+-----------------+ 

131 

132 References 

133 ---------- 

134 :cite:`HunterLab2008b` 

135 

136 Examples 

137 -------- 

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

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

140 >>> XYZ_to_Hunter_Lab(XYZ, D65.XYZ_n, D65.K_ab) # doctest: +ELLIPSIS 

141 array([ 34.9245257..., 47.0618985..., 14.3861510...]) 

142 """ 

143 

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

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

146 XYZ_n_default = XYZ_n is None 

147 XYZ_n = to_domain_100( 

148 optional( 

149 XYZ_n, 

150 TVS_D65.XYZ_n 

151 if get_domain_range_scale() == "reference" 

152 else TVS_D65.XYZ_n / 100, 

153 ) 

154 ) 

155 X_n, Y_n, Z_n = tsplit(XYZ_n) 

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

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

158 

159 Y_Y_n = Y / Y_n 

160 sqrt_Y_Y_n = np.sqrt(Y_Y_n) 

161 

162 L = 100 * sqrt_Y_Y_n 

163 a = K_a * ((X / X_n - Y_Y_n) / sqrt_Y_Y_n) 

164 b = K_b * ((Y_Y_n - Z / Z_n) / sqrt_Y_Y_n) 

165 

166 Lab = tstack([L, a, b]) 

167 

168 return from_range_100(Lab) 

169 

170 

171def Hunter_Lab_to_XYZ( 

172 Lab: Domain100, 

173 XYZ_n: ArrayLike | None = None, 

174 K_ab: ArrayLike | None = None, 

175) -> Range100: 

176 """ 

177 Convert from *Hunter L,a,b* colour scale to *CIE XYZ* tristimulus 

178 values. 

179 

180 Parameters 

181 ---------- 

182 Lab 

183 *Hunter L,a,b* colour scale array. 

184 XYZ_n 

185 Reference *illuminant* tristimulus values. 

186 K_ab 

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

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

189 :func:`colour.XYZ_to_K_ab_HunterLab1966`. 

190 

191 Returns 

192 ------- 

193 :class:`numpy.ndarray` 

194 *CIE XYZ* tristimulus values. 

195 

196 Notes 

197 ----- 

198 +------------+-----------------------+-----------------+ 

199 | **Domain** | **Scale - Reference** | **Scale - 1** | 

200 +============+=======================+=================+ 

201 | ``Lab`` | 100 | 1 | 

202 +------------+-----------------------+-----------------+ 

203 | ``XYZ_n`` | 100 | 1 | 

204 +------------+-----------------------+-----------------+ 

205 

206 +------------+-----------------------+-----------------+ 

207 | **Range** | **Scale - Reference** | **Scale - 1** | 

208 +============+=======================+=================+ 

209 | ``XYZ`` | 100 | 1 | 

210 +------------+-----------------------+-----------------+ 

211 

212 References 

213 ---------- 

214 :cite:`HunterLab2008b` 

215 

216 Examples 

217 -------- 

218 >>> Lab = np.array([34.92452577, 47.06189858, 14.38615107]) 

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

220 >>> Hunter_Lab_to_XYZ(Lab, D65.XYZ_n, D65.K_ab) 

221 array([ 20.654008, 12.197225, 5.136952]) 

222 """ 

223 

224 L, a, b = tsplit(to_domain_100(Lab)) 

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

226 XYZ_n_default = XYZ_n is None 

227 XYZ_n = to_domain_100( 

228 optional( 

229 XYZ_n, 

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

231 ) 

232 ) 

233 X_n, Y_n, Z_n = tsplit(XYZ_n) 

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

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

236 

237 L_100 = L / 100 

238 L_100_2 = L_100**2 

239 

240 Y = L_100_2 * Y_n 

241 X = ((a / K_a) * L_100 + L_100_2) * X_n 

242 Z = -((b / K_b) * L_100 - L_100_2) * Z_n 

243 

244 XYZ = tstack([X, Y, Z]) 

245 

246 return from_range_100(XYZ)