Coverage for colour/models/hunter_lab.py: 100%
47 statements
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
1"""
2Hunter L,a,b Colour Scale
3=========================
5Define the *Hunter L,a,b* colour scale transformations.
7- :func:`colour.XYZ_to_K_ab_HunterLab1966`
8- :func:`colour.XYZ_to_Hunter_Lab`
9- :func:`colour.Hunter_Lab_to_XYZ`
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"""
21from __future__ import annotations
23import numpy as np
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)
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"
48__all__ = [
49 "XYZ_to_K_ab_HunterLab1966",
50 "XYZ_to_Hunter_Lab",
51 "Hunter_Lab_to_XYZ",
52]
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.
61 Parameters
62 ----------
63 XYZ
64 *Whitepoint* *CIE XYZ* tristimulus values.
66 Returns
67 -------
68 :class:`numpy.ndarray`
69 *Hunter L,a,b* :math:`K_{a}` and :math:`K_{b}` chromaticity
70 coefficients.
72 References
73 ----------
74 :cite:`HunterLab2008c`
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 """
83 X, _Y, Z = tsplit(XYZ)
85 K_a = 175 * np.sqrt(X / 98.043)
86 K_b = 70 * np.sqrt(Z / 118.115)
88 return tstack([K_a, K_b])
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.
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`.
111 Returns
112 -------
113 :class:`numpy.ndarray`
114 *Hunter L,a,b* colour scale array.
116 Notes
117 -----
118 +------------+-----------------------+-----------------+
119 | **Domain** | **Scale - Reference** | **Scale - 1** |
120 +============+=======================+=================+
121 | ``XYZ`` | 100 | 1 |
122 +------------+-----------------------+-----------------+
123 | ``XYZ_n`` | 100 | 1 |
124 +------------+-----------------------+-----------------+
126 +------------+-----------------------+-----------------+
127 | **Range** | **Scale - Reference** | **Scale - 1** |
128 +============+=======================+=================+
129 | ``Lab`` | 100 | 1 |
130 +------------+-----------------------+-----------------+
132 References
133 ----------
134 :cite:`HunterLab2008b`
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 """
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)
159 Y_Y_n = Y / Y_n
160 sqrt_Y_Y_n = np.sqrt(Y_Y_n)
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)
166 Lab = tstack([L, a, b])
168 return from_range_100(Lab)
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.
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`.
191 Returns
192 -------
193 :class:`numpy.ndarray`
194 *CIE XYZ* tristimulus values.
196 Notes
197 -----
198 +------------+-----------------------+-----------------+
199 | **Domain** | **Scale - Reference** | **Scale - 1** |
200 +============+=======================+=================+
201 | ``Lab`` | 100 | 1 |
202 +------------+-----------------------+-----------------+
203 | ``XYZ_n`` | 100 | 1 |
204 +------------+-----------------------+-----------------+
206 +------------+-----------------------+-----------------+
207 | **Range** | **Scale - Reference** | **Scale - 1** |
208 +============+=======================+=================+
209 | ``XYZ`` | 100 | 1 |
210 +------------+-----------------------+-----------------+
212 References
213 ----------
214 :cite:`HunterLab2008b`
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 """
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)
237 L_100 = L / 100
238 L_100_2 = L_100**2
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
244 XYZ = tstack([X, Y, Z])
246 return from_range_100(XYZ)