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

52 statements  

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

1""" 

2hdr-IPT Colourspace 

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

4 

5Define the *hdr-IPT* colourspace transformations. 

6 

7- :attr:`colour.HDR_IPT_METHODS` 

8- :func:`colour.XYZ_to_hdr_IPT` 

9- :func:`colour.hdr_IPT_to_XYZ` 

10 

11References 

12---------- 

13- :cite:`Fairchild2010` : Fairchild, M. D., & Wyble, D. R. (2010). hdr-CIELAB 

14 and hdr-IPT: Simple Models for Describing the Color of High-Dynamic-Range 

15 and Wide-Color-Gamut Images. Proc. of Color and Imaging Conference, 

16 322-326. ISBN:978-1-62993-215-6 

17- :cite:`Fairchild2011` : Fairchild, M. D., & Chen, P. (2011). Brightness, 

18 lightness, and specifying color in high-dynamic-range scenes and images. In 

19 S. P. Farnand & F. Gaykema (Eds.), Proc. SPIE 7867, Image Quality and 

20 System Performance VIII (p. 78670O). doi:10.1117/12.872075 

21""" 

22 

23from __future__ import annotations 

24 

25import typing 

26 

27import numpy as np 

28 

29from colour.algebra import vecmul 

30from colour.colorimetry import ( 

31 lightness_Fairchild2010, 

32 lightness_Fairchild2011, 

33 luminance_Fairchild2010, 

34 luminance_Fairchild2011, 

35) 

36 

37if typing.TYPE_CHECKING: 

38 from colour.hints import Literal 

39 

40from colour.hints import ( # noqa: TC001 

41 ArrayLike, 

42 Domain1, 

43 Domain100, 

44 NDArrayFloat, 

45 Range1, 

46 Range100, 

47) 

48from colour.models.ipt import ( 

49 MATRIX_IPT_IPT_TO_LMS_P, 

50 MATRIX_IPT_LMS_P_TO_IPT, 

51 MATRIX_IPT_LMS_TO_XYZ, 

52 MATRIX_IPT_XYZ_TO_LMS, 

53) 

54from colour.utilities import ( 

55 as_float_array, 

56 domain_range_scale, 

57 from_range_1, 

58 from_range_100, 

59 to_domain_1, 

60 to_domain_100, 

61 validate_method, 

62) 

63from colour.utilities.documentation import DocstringTuple, is_documentation_building 

64 

65__author__ = "Colour Developers" 

66__copyright__ = "Copyright 2013 Colour Developers" 

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

68__maintainer__ = "Colour Developers" 

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

70__status__ = "Production" 

71 

72__all__ = [ 

73 "HDR_IPT_METHODS", 

74 "exponent_hdr_IPT", 

75 "XYZ_to_hdr_IPT", 

76 "hdr_IPT_to_XYZ", 

77] 

78 

79HDR_IPT_METHODS: tuple = ("Fairchild 2010", "Fairchild 2011") 

80if is_documentation_building(): # pragma: no cover 

81 HDR_IPT_METHODS = DocstringTuple(HDR_IPT_METHODS) 

82 HDR_IPT_METHODS.__doc__ = """ 

83Supported *hdr-IPT* colourspace computation methods. 

84 

85References 

86---------- 

87:cite:`Fairchild2010`, :cite:`Fairchild2011` 

88""" 

89 

90 

91def exponent_hdr_IPT( 

92 Y_s: Domain1, 

93 Y_abs: ArrayLike, 

94 method: (Literal["Fairchild 2011", "Fairchild 2010"] | str) = "Fairchild 2011", 

95) -> NDArrayFloat: 

96 """ 

97 Compute the *hdr-IPT* colourspace *Lightness* :math:`\\epsilon` exponent 

98 using the *Fairchild and Wyble (2010)* or *Fairchild and Chen (2011)* 

99 methods. 

100 

101 Parameters 

102 ---------- 

103 Y_s 

104 Relative luminance :math:`Y_s` of the surround. 

105 Y_abs 

106 Absolute luminance :math:`Y_{abs}` of the scene diffuse white in 

107 :math:`cd/m^2`. 

108 method 

109 Computation method. 

110 

111 Returns 

112 ------- 

113 :class:`numpy.ndarray` 

114 *hdr-IPT* colourspace *Lightness* :math:`\\epsilon` exponent. 

115 

116 Notes 

117 ----- 

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

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

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

121 | ``Y_s`` | 1 | 1 | 

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

123 

124 Examples 

125 -------- 

126 >>> exponent_hdr_IPT(0.2, 100) # doctest: +ELLIPSIS 

127 0.4820209... 

128 >>> exponent_hdr_IPT(0.2, 100, method="Fairchild 2010") 

129 ... # doctest: +ELLIPSIS 

130 1.6891383... 

131 """ 

132 

133 Y_s = to_domain_1(Y_s) 

134 Y_abs = as_float_array(Y_abs) 

135 method = validate_method(method, HDR_IPT_METHODS) 

136 

137 epsilon = 1.38 if method == "fairchild 2010" else 0.59 

138 

139 lf = np.log(318) / np.log(Y_abs) 

140 sf = 1.25 - 0.25 * (Y_s / 0.184) 

141 if method == "fairchild 2010": 

142 epsilon *= sf * lf 

143 else: 

144 epsilon /= sf * lf 

145 

146 return epsilon 

147 

148 

149def XYZ_to_hdr_IPT( 

150 XYZ: Domain1, 

151 Y_s: Domain1 = 0.2, 

152 Y_abs: ArrayLike = 100, 

153 method: (Literal["Fairchild 2011", "Fairchild 2010"] | str) = "Fairchild 2011", 

154) -> Range100: 

155 """ 

156 Convert from *CIE XYZ* tristimulus values to *hdr-IPT* colourspace. 

157 

158 Parameters 

159 ---------- 

160 XYZ 

161 *CIE XYZ* tristimulus values. 

162 Y_s 

163 Relative luminance :math:`Y_s` of the surround. 

164 Y_abs 

165 Absolute luminance :math:`Y_{abs}` of the scene diffuse white in 

166 :math:`cd/m^2`. 

167 method 

168 Computation method. 

169 

170 Returns 

171 ------- 

172 :class:`numpy.ndarray` 

173 *hdr-IPT* colourspace array. 

174 

175 Notes 

176 ----- 

177 +-------------+-------------------------+---------------------+ 

178 | **Domain** | **Scale - Reference** | **Scale - 1** | 

179 +=============+=========================+=====================+ 

180 | ``XYZ`` | 1 | 1 | 

181 +-------------+-------------------------+---------------------+ 

182 | ``Y_s`` | 1 | 1 | 

183 +-------------+-------------------------+---------------------+ 

184 

185 +-------------+-------------------------+---------------------+ 

186 | **Range** | **Scale - Reference** | **Scale - 1** | 

187 +=============+=========================+=====================+ 

188 | ``IPT_hdr`` | 100 | 1 | 

189 +-------------+-------------------------+---------------------+ 

190 

191 - Input *CIE XYZ* tristimulus values must be adapted to 

192 *CIE Standard Illuminant D Series* *D65*. 

193 

194 References 

195 ---------- 

196 :cite:`Fairchild2010`, :cite:`Fairchild2011` 

197 

198 Examples 

199 -------- 

200 >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) 

201 >>> XYZ_to_hdr_IPT(XYZ) # doctest: +ELLIPSIS 

202 array([ 48.3937634..., 42.4499020..., 22.0195403...]) 

203 >>> XYZ_to_hdr_IPT(XYZ, method="Fairchild 2010") # doctest: +ELLIPSIS 

204 array([ 30.0287314..., 83.9384506..., 34.9028738...]) 

205 """ 

206 

207 XYZ = to_domain_1(XYZ) 

208 method = validate_method(method, HDR_IPT_METHODS) 

209 

210 if method == "fairchild 2010": 

211 lightness_callable = lightness_Fairchild2010 

212 else: 

213 lightness_callable = lightness_Fairchild2011 

214 

215 e = exponent_hdr_IPT(Y_s, Y_abs, method)[..., None] 

216 

217 LMS = vecmul(MATRIX_IPT_XYZ_TO_LMS, XYZ) 

218 

219 # Domain and range scaling has already been handled. 

220 with domain_range_scale("ignore"): 

221 LMS_prime = np.sign(LMS) * np.abs(lightness_callable(LMS, e)) 

222 

223 IPT_hdr = vecmul(MATRIX_IPT_LMS_P_TO_IPT, LMS_prime) 

224 

225 return from_range_100(IPT_hdr) 

226 

227 

228def hdr_IPT_to_XYZ( 

229 IPT_hdr: Domain100, 

230 Y_s: Domain1 = 0.2, 

231 Y_abs: ArrayLike = 100, 

232 method: (Literal["Fairchild 2011", "Fairchild 2010"] | str) = "Fairchild 2011", 

233) -> Range1: 

234 """ 

235 Convert from *hdr-IPT* colourspace to *CIE XYZ* tristimulus values. 

236 

237 Parameters 

238 ---------- 

239 IPT_hdr 

240 *hdr-IPT* colourspace array. 

241 Y_s 

242 Relative luminance :math:`Y_s` of the surround. 

243 Y_abs 

244 Absolute luminance :math:`Y_{abs}` of the scene diffuse white in 

245 :math:`cd/m^2`. 

246 method 

247 Computation method. 

248 

249 Returns 

250 ------- 

251 :class:`numpy.ndarray` 

252 *CIE XYZ* tristimulus values. 

253 

254 Notes 

255 ----- 

256 +-------------+-------------------------+---------------------+ 

257 | **Domain** | **Scale - Reference** | **Scale - 1** | 

258 +=============+=========================+=====================+ 

259 | ``IPT_hdr`` | 100 | 1 | 

260 +-------------+-------------------------+---------------------+ 

261 | ``Y_s`` | 1 | 1 | 

262 +-------------+-------------------------+---------------------+ 

263 

264 +-------------+-------------------------+---------------------+ 

265 | **Range** | **Scale - Reference** | **Scale - 1** | 

266 +=============+=========================+=====================+ 

267 | ``XYZ`` | 1 | 1 | 

268 +-------------+-------------------------+---------------------+ 

269 

270 References 

271 ---------- 

272 :cite:`Fairchild2010`, :cite:`Fairchild2011` 

273 

274 Examples 

275 -------- 

276 >>> IPT_hdr = np.array([48.39376346, 42.44990202, 22.01954033]) 

277 >>> hdr_IPT_to_XYZ(IPT_hdr) # doctest: +ELLIPSIS 

278 array([ 0.2065400..., 0.1219722..., 0.0513695...]) 

279 >>> IPT_hdr = np.array([30.02873147, 83.93845061, 34.90287382]) 

280 >>> hdr_IPT_to_XYZ(IPT_hdr, method="Fairchild 2010") 

281 ... # doctest: +ELLIPSIS 

282 array([ 0.2065400..., 0.1219722..., 0.0513695...]) 

283 """ 

284 

285 IPT_hdr = to_domain_100(IPT_hdr) 

286 method = validate_method(method, HDR_IPT_METHODS) 

287 

288 if method == "fairchild 2010": 

289 luminance_callable = luminance_Fairchild2010 

290 else: 

291 luminance_callable = luminance_Fairchild2011 

292 

293 e = exponent_hdr_IPT(Y_s, Y_abs, method)[..., None] 

294 

295 LMS = vecmul(MATRIX_IPT_IPT_TO_LMS_P, IPT_hdr) 

296 

297 # Domain and range scaling has already be handled. 

298 with domain_range_scale("ignore"): 

299 LMS_prime = np.sign(LMS) * np.abs(luminance_callable(LMS, e)) 

300 

301 XYZ = vecmul(MATRIX_IPT_LMS_TO_XYZ, LMS_prime) 

302 

303 return from_range_1(XYZ)