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
« prev ^ index » next coverage.py v7.11.0, created at 2025-11-15 19:01 +1300
1"""
2Hunter Rd,a,b Colour Scale
3==========================
5Define the *Hunter Rd,a,b* colour scale transformations.
7- :func:`colour.XYZ_to_Hunter_Rdab`
8- :func:`colour.Hunter_Rdab_to_XYZ`
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"""
18from __future__ import annotations
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)
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"
43__all__ = [
44 "XYZ_to_Hunter_Rdab",
45 "Hunter_Rdab_to_XYZ",
46]
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.
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`.
69 Returns
70 -------
71 :class:`numpy.ndarray`
72 *Hunter Rd,a,b* colour scale array.
74 Notes
75 -----
76 +------------+------------------------+--------------------+
77 | **Domain** | **Scale - Reference** | **Scale - 1** |
78 +============+========================+====================+
79 | ``XYZ`` | 100 | 1 |
80 +------------+------------------------+--------------------+
81 | ``XYZ_n`` | 100 | 1 |
82 +------------+------------------------+--------------------+
84 +------------+------------------------+--------------------+
85 | **Range** | **Scale - Reference** | **Scale - 1** |
86 +============+========================+====================+
87 | ``R_d_ab`` | 100 | 1 |
88 +------------+------------------------+--------------------+
90 References
91 ----------
92 :cite:`HunterLab2012a`
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 """
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)
117 f = 0.51 * ((21 + 0.2 * Y) / (1 + 0.2 * Y))
118 Y_Yn = Y / Y_n
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)
124 R_d_ab = tstack([R_d, a_Rd, b_Rd])
126 return from_range_100(R_d_ab)
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.
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`.
149 Returns
150 -------
151 :class:`numpy.ndarray`
152 *CIE XYZ* tristimulus values.
154 Notes
155 -----
156 +------------+------------------------+--------------------+
157 | **Domain** | **Scale - Reference** | **Scale - 1** |
158 +============+========================+====================+
159 | ``R_d_ab`` | 100 | 1 |
160 +------------+------------------------+--------------------+
161 | ``XYZ_n`` | 100 | 1 |
162 +------------+------------------------+--------------------+
164 +------------+------------------------+--------------------+
165 | **Range** | **Scale - Reference** | **Scale - 1** |
166 +============+========================+====================+
167 | ``XYZ`` | 100 | 1 |
168 +------------+------------------------+--------------------+
170 References
171 ----------
172 :cite:`HunterLab2012a`
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 """
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)
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
203 XYZ = tstack([X, R_d, Z])
205 return from_range_100(XYZ)