Coverage for models/hunter_lab.py: 68%

1"""

2 Hunter L,a,b Colour Scale

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

4

5 Define 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

11 References

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/\

18 an02_02.pdf

19"""

20

21 from __future__ import annotations

22

23 import numpy as np

24

25 from colour.colorimetry import TVS_ILLUMINANTS_HUNTERLAB

26 from colour.hints import ( # noqa: TC001

27 ArrayLike,

28 Domain100,

29 NDArrayFloat,

30 Range100,

31)

32 from 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

55 def 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

91 def 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

171 def 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)