Coverage for difference/delta_e.py: 76%

134 statements  

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

1""" 

2:math:`\\Delta E^*_{ab}` - Delta E Colour Difference 

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

4 

5Define the :math:`\\Delta E^*_{ab}` colour difference computation objects. 

6 

7- :attr:`colour.difference.JND_CIE1976` 

8- :func:`colour.difference.delta_E_CIE1976` 

9- :func:`colour.difference.delta_E_CIE1994` 

10- :func:`colour.difference.delta_E_CIE2000` 

11- :func:`colour.difference.delta_E_CMC` 

12- :func:`colour.difference.delta_E_ITP` 

13- :func:`colour.difference.delta_E_HyAB` 

14- :func:`colour.difference.delta_E_HyCH` 

15 

16References 

17---------- 

18- :cite:`Abasi2020a` : Abasi, S., Amani Tehran, M., & Fairchild, M. D. (2020). 

19 Distance metrics for very large color differences. Color Research & 

20 Application, 45(2), 208-223. doi:10.1002/col.22451 

21- :cite:`InternationalTelecommunicationUnion2019` : International 

22 Telecommunication Union. (2019). Recommendation ITU-R BT.2124-0 - 

23 Objective metric for the assessment of the potential visibility of colour 

24 differences in television (pp. 1-36). 

25 https://www.itu.int/dms_pubrec/itu-r/rec/bt/R-REC-BT.2124-0-201901-I!!PDF-E.pdf 

26- :cite:`Lindbloom2003c` : Lindbloom, B. (2003). Delta E (CIE 1976). 

27 Retrieved February 24, 2014, from 

28 http://brucelindbloom.com/Eqn_DeltaE_CIE76.html 

29- :cite:`Lindbloom2009f` : Lindbloom, B. (2009). Delta E (CMC). Retrieved 

30 February 24, 2014, from http://brucelindbloom.com/Eqn_DeltaE_CMC.html 

31- :cite:`Lindbloom2011a` : Lindbloom, B. (2011). Delta E (CIE 1994). 

32 Retrieved February 24, 2014, from 

33 http://brucelindbloom.com/Eqn_DeltaE_CIE94.html 

34- :cite:`Melgosa2013b` : Melgosa, M. (2013). CIE / ISO new standard: 

35 CIEDE2000. http://www.color.org/events/colorimetry/\ 

36Melgosa_CIEDE2000_Workshop-July4.pdf 

37- :cite:`Sharma2005b` : Sharma, G., Wu, W., & Dalal, E. N. (2005). The 

38 CIEDE2000 color-difference formula: Implementation notes, supplementary 

39 test data, and mathematical observations. Color Research & Application, 

40 30(1), 21-30. doi:10.1002/col.20070 

41- :cite:`Mokrzycki2011` : Mokrzycki, W., & Tatol, M. (2011). Color difference 

42 Delta E - A survey. Machine Graphics and Vision, 20, 383-411. 

43""" 

44 

45from __future__ import annotations 

46 

47from dataclasses import astuple, dataclass, field 

48 

49import numpy as np 

50 

51from colour.algebra import euclidean_distance 

52from colour.hints import ( # noqa: TC001 

53 Domain1, 

54 Domain100, 

55 NDArrayFloat, 

56) 

57from colour.utilities import ( 

58 MixinDataclassArithmetic, 

59 as_float, 

60 to_domain_100, 

61 tsplit, 

62 zeros, 

63) 

64from colour.utilities.documentation import DocstringFloat, is_documentation_building 

65 

66__author__ = "Colour Developers" 

67__copyright__ = "Copyright 2013 Colour Developers" 

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

69__maintainer__ = "Colour Developers" 

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

71__status__ = "Production" 

72 

73__all__ = [ 

74 "JND_CIE1976", 

75 "delta_E_CIE1976", 

76 "delta_E_CIE1994", 

77 "intermediate_attributes_CIE2000", 

78 "delta_E_CIE2000", 

79 "delta_E_CMC", 

80 "delta_E_ITP", 

81 "delta_E_HyAB", 

82 "delta_E_HyCH", 

83] 

84 

85JND_CIE1976 = 2.3 

86if is_documentation_building(): # pragma: no cover 

87 JND_CIE1976 = DocstringFloat(JND_CIE1976) 

88 JND_CIE1976.__doc__ = """ 

89Just Noticeable Difference (JND) according to *CIE 1976* colour difference 

90formula, i.e., Euclidean distance in *CIE L\\*a\\*b\\** colourspace. 

91 

92Notes 

93----- 

94A standard observer sees the difference in colour as follows: 

95 

96- 0 < :math:`\\Delta E^*_{ab}` < 1 : Observer does not notice the difference. 

97- 1 < :math:`\\Delta E^*_{ab}` < 2 : Only experienced observer can notice the 

98 difference. 

99- 2 < :math:`\\Delta E^*_{ab}` < 3:5 : Unexperienced observer also notices 

100 the difference. 

101- 3:5 < :math:`\\Delta E^*_{ab}` < 5 : Clear difference in colour is noticed. 

102- 5 < :math:`\\Delta E^*_{ab}` : Observer notices two different colours. 

103 

104References 

105---------- 

106:cite:`Mokrzycki2011` 

107""" 

108 

109 

110def delta_E_CIE1976(Lab_1: Domain100, Lab_2: Domain100) -> NDArrayFloat: 

111 """ 

112 Compute the colour difference :math:`\\Delta E_{76}` between two 

113 specified *CIE L\\*a\\*b\\** colourspace arrays using the *CIE 1976* 

114 recommendation. 

115 

116 Parameters 

117 ---------- 

118 Lab_1 

119 *CIE L\\*a\\*b\\** colourspace array 1. 

120 Lab_2 

121 *CIE L\\*a\\*b\\** colourspace array 2. 

122 

123 Returns 

124 ------- 

125 :class:`numpy.ndarray` 

126 Colour difference :math:`\\Delta E_{76}`. 

127 

128 Notes 

129 ----- 

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

131 | **Domain** | **Scale - Reference** | **Scale - 1** | 

132 +============+=======================+===================+ 

133 | ``Lab_1`` | 100 | 1 | 

134 +------------+-----------------------+-------------------+ 

135 | ``Lab_2`` | 100 | 1 | 

136 +------------+-----------------------+-------------------+ 

137 

138 References 

139 ---------- 

140 :cite:`Lindbloom2003c` 

141 

142 Examples 

143 -------- 

144 >>> Lab_1 = np.array([48.99183622, -0.10561667, 400.65619925]) 

145 >>> Lab_2 = np.array([50.65907324, -0.11671910, 402.82235718]) 

146 >>> delta_E_CIE1976(Lab_1, Lab_2) # doctest: +ELLIPSIS 

147 2.7335037... 

148 """ 

149 

150 return euclidean_distance(to_domain_100(Lab_1), to_domain_100(Lab_2)) 

151 

152 

153def delta_E_CIE1994( 

154 Lab_1: Domain100, 

155 Lab_2: Domain100, 

156 textiles: bool = False, 

157) -> NDArrayFloat: 

158 """ 

159 Compute the colour difference :math:`\\Delta E_{94}` between two specified 

160 *CIE L\\*a\\*b\\** colourspace arrays using the *CIE 1994* recommendation. 

161 

162 Parameters 

163 ---------- 

164 Lab_1 

165 *CIE L\\*a\\*b\\** colourspace array 1. 

166 Lab_2 

167 *CIE L\\*a\\*b\\** colourspace array 2. 

168 textiles 

169 Textiles application specific parametric factors, 

170 :math:`k_L=2,\\ k_C=k_H=1,\\ k_1=0.048,\\ k_2=0.014` weights are used 

171 instead of :math:`k_L=k_C=k_H=1,\\ k_1=0.045,\\ k_2=0.015`. 

172 

173 Returns 

174 ------- 

175 :class:`numpy.ndarray` 

176 Colour difference :math:`\\Delta E_{94}`. 

177 

178 Notes 

179 ----- 

180 +------------+-----------------------+-------------------+ 

181 | **Domain** | **Scale - Reference** | **Scale - 1** | 

182 +============+=======================+===================+ 

183 | ``Lab_1`` | 100 | 1 | 

184 +------------+-----------------------+-------------------+ 

185 | ``Lab_2`` | 100 | 1 | 

186 +------------+-----------------------+-------------------+ 

187 

188 - *CIE 1994* colour differences are not symmetrical: difference between 

189 ``Lab_1`` and ``Lab_2`` may not be the same as difference between 

190 ``Lab_2`` and ``Lab_1`` thus one colour must be understood to be the 

191 reference against which a sample colour is compared. 

192 

193 References 

194 ---------- 

195 :cite:`Lindbloom2011a` 

196 

197 Examples 

198 -------- 

199 >>> Lab_1 = np.array([48.99183622, -0.10561667, 400.65619925]) 

200 >>> Lab_2 = np.array([50.65907324, -0.11671910, 402.82235718]) 

201 >>> delta_E_CIE1994(Lab_1, Lab_2) # doctest: +ELLIPSIS 

202 1.6711191... 

203 >>> delta_E_CIE1994(Lab_1, Lab_2, textiles=True) # doctest: +ELLIPSIS 

204 0.8404677... 

205 """ 

206 

207 L_1, a_1, b_1 = tsplit(to_domain_100(Lab_1)) 

208 L_2, a_2, b_2 = tsplit(to_domain_100(Lab_2)) 

209 

210 k_1 = 0.048 if textiles else 0.045 

211 k_2 = 0.014 if textiles else 0.015 

212 k_L = 2 if textiles else 1 

213 k_C = 1 

214 k_H = 1 

215 

216 C_1 = np.hypot(a_1, b_1) 

217 C_2 = np.hypot(a_2, b_2) 

218 

219 s_L = 1 

220 s_C = 1 + k_1 * C_1 

221 s_H = 1 + k_2 * C_1 

222 

223 delta_L = L_1 - L_2 

224 delta_C = C_1 - C_2 

225 delta_A = a_1 - a_2 

226 delta_B = b_1 - b_2 

227 

228 radical = delta_A**2 + delta_B**2 - delta_C**2 

229 delta_H = zeros(radical.shape) 

230 delta_H[radical > 0] = np.sqrt(radical[radical > 0]) 

231 

232 L = (delta_L / (k_L * s_L)) ** 2 

233 C = (delta_C / (k_C * s_C)) ** 2 

234 H = (delta_H / (k_H * s_H)) ** 2 

235 

236 d_E = np.sqrt(L + C + H) 

237 

238 return as_float(d_E) 

239 

240 

241@dataclass 

242class Attributes_Specification_CIE2000(MixinDataclassArithmetic): 

243 """ 

244 Define the *CIE 2000* colour-difference formula attribute specification. 

245 

246 Parameters 

247 ---------- 

248 J 

249 Correlate of *lightness* :math:`J`. 

250 C 

251 Correlate of *chroma* :math:`C`. 

252 h 

253 *Hue* angle :math:`h` in degrees. 

254 s 

255 Correlate of *saturation* :math:`s`. 

256 Q 

257 Correlate of *brightness* :math:`Q`. 

258 M 

259 Correlate of *colourfulness* :math:`M`. 

260 H 

261 *Hue* :math:`h` quadrature :math:`H`. 

262 HC 

263 *Hue* :math:`h` composition :math:`H^C`. 

264 """ 

265 

266 S_L: float | NDArrayFloat | None = field(default_factory=lambda: None) 

267 S_C: float | NDArrayFloat | None = field(default_factory=lambda: None) 

268 S_H: float | NDArrayFloat | None = field(default_factory=lambda: None) 

269 delta_L_p: float | NDArrayFloat | None = field(default_factory=lambda: None) 

270 delta_C_p: float | NDArrayFloat | None = field(default_factory=lambda: None) 

271 delta_H_p: float | NDArrayFloat | None = field(default_factory=lambda: None) 

272 R_T: float | NDArrayFloat | None = field(default_factory=lambda: None) 

273 

274 

275def intermediate_attributes_CIE2000( 

276 Lab_1: Domain100, Lab_2: Domain100 

277) -> Attributes_Specification_CIE2000: 

278 """ 

279 Compute intermediate attributes for CIE 2000 colour difference calculation 

280 between two specified *CIE L\\*a\\*b\\** colourspace arrays. 

281 

282 The intermediate attributes include the lightness, chroma, and hue 

283 weighting functions (S_L, S_C, S_H), as well as the adjusted colour 

284 differences (delta_L_p, delta_C_p, delta_H_p) and the rotation term (R_T) 

285 required for computing :math:`\\Delta E_{00}`. 

286 

287 Parameters 

288 ---------- 

289 Lab_1 

290 *CIE L\\*a\\*b\\** colourspace array 1. 

291 Lab_2 

292 *CIE L\\*a\\*b\\** colourspace array 2. 

293 

294 Returns 

295 ------- 

296 :class:`numpy.ndarray` 

297 Intermediate attributes to compute the difference :math:`\\Delta E_{00}`. 

298 

299 Notes 

300 ----- 

301 +------------+-----------------------+-------------------+ 

302 | **Domain** | **Scale - Reference** | **Scale - 1** | 

303 +============+=======================+===================+ 

304 | ``Lab_1`` | 100 | 1 | 

305 +------------+-----------------------+-------------------+ 

306 | ``Lab_2`` | 100 | 1 | 

307 +------------+-----------------------+-------------------+ 

308 

309 References 

310 ---------- 

311 :cite:`Melgosa2013b`, :cite:`Sharma2005b` 

312 

313 Examples 

314 -------- 

315 >>> Lab_1 = np.array([48.99183622, -0.10561667, 400.65619925]) 

316 >>> Lab_2 = np.array([50.65907324, -0.11671910, 402.82235718]) 

317 >>> intermediate_attributes_CIE2000(Lab_1, Lab_2) # doctest: +ELLIPSIS 

318 Attributes_Specification_CIE2000(S_L=1.0001021..., S_C=19.0782682..., \ 

319S_H=4.7226695..., delta_L_p=1.6672370..., delta_C_p=2.1661609..., \ 

320delta_H_p=0.0105030..., R_T=-3...) 

321 """ 

322 

323 L_1, a_1, b_1 = tsplit(to_domain_100(Lab_1)) 

324 L_2, a_2, b_2 = tsplit(to_domain_100(Lab_2)) 

325 

326 C_1_ab = np.hypot(a_1, b_1) 

327 C_2_ab = np.hypot(a_2, b_2) 

328 

329 C_bar_ab = (C_1_ab + C_2_ab) / 2 

330 C_bar_ab_7 = C_bar_ab**7 

331 

332 G = 0.5 * (1 - np.sqrt(C_bar_ab_7 / (C_bar_ab_7 + 25**7))) 

333 

334 a_p_1 = (1 + G) * a_1 

335 a_p_2 = (1 + G) * a_2 

336 

337 C_p_1 = np.hypot(a_p_1, b_1) 

338 C_p_2 = np.hypot(a_p_2, b_2) 

339 

340 h_p_1 = np.where( 

341 np.logical_and(b_1 == 0, a_p_1 == 0), 

342 0, 

343 np.degrees(np.arctan2(b_1, a_p_1)) % 360, 

344 ) 

345 h_p_2 = np.where( 

346 np.logical_and(b_2 == 0, a_p_2 == 0), 

347 0, 

348 np.degrees(np.arctan2(b_2, a_p_2)) % 360, 

349 ) 

350 

351 delta_L_p = L_2 - L_1 

352 

353 delta_C_p = C_p_2 - C_p_1 

354 

355 h_p_2_s_1 = h_p_2 - h_p_1 

356 C_p_1_m_2 = C_p_1 * C_p_2 

357 delta_h_p = np.select( 

358 [ 

359 C_p_1_m_2 == 0, 

360 np.fabs(h_p_2_s_1) <= 180, 

361 h_p_2_s_1 > 180, 

362 h_p_2_s_1 < -180, 

363 ], 

364 [ 

365 0, 

366 h_p_2_s_1, 

367 h_p_2_s_1 - 360, 

368 h_p_2_s_1 + 360, 

369 ], 

370 ) 

371 

372 delta_H_p = 2 * np.sqrt(C_p_1_m_2) * np.sin(np.deg2rad(delta_h_p / 2)) 

373 

374 L_bar_p = (L_1 + L_2) / 2 

375 

376 C_bar_p = (C_p_1 + C_p_2) / 2 

377 

378 a_h_p_1_s_2 = np.fabs(h_p_1 - h_p_2) 

379 h_p_1_a_2 = h_p_1 + h_p_2 

380 h_bar_p = np.select( 

381 [ 

382 C_p_1_m_2 == 0, 

383 a_h_p_1_s_2 <= 180, 

384 np.logical_and(a_h_p_1_s_2 > 180, h_p_1_a_2 < 360), 

385 np.logical_and(a_h_p_1_s_2 > 180, h_p_1_a_2 >= 360), 

386 ], 

387 [ 

388 h_p_1_a_2, 

389 h_p_1_a_2 / 2, 

390 (h_p_1_a_2 + 360) / 2, 

391 (h_p_1_a_2 - 360) / 2, 

392 ], 

393 ) 

394 

395 T = ( 

396 1 

397 - 0.17 * np.cos(np.deg2rad(h_bar_p - 30)) 

398 + 0.24 * np.cos(np.deg2rad(2 * h_bar_p)) 

399 + 0.32 * np.cos(np.deg2rad(3 * h_bar_p + 6)) 

400 - 0.20 * np.cos(np.deg2rad(4 * h_bar_p - 63)) 

401 ) 

402 

403 delta_theta = 30 * np.exp(-(((h_bar_p - 275) / 25) ** 2)) 

404 

405 C_bar_p_7 = C_bar_p**7 

406 R_C = 2 * np.sqrt(C_bar_p_7 / (C_bar_p_7 + 25**7)) 

407 

408 L_bar_p_2 = (L_bar_p - 50) ** 2 

409 S_L = 1 + ((0.015 * L_bar_p_2) / np.sqrt(20 + L_bar_p_2)) 

410 

411 S_C = 1 + 0.045 * C_bar_p 

412 

413 S_H = 1 + 0.015 * C_bar_p * T 

414 

415 R_T = -np.sin(np.deg2rad(2 * delta_theta)) * R_C 

416 

417 return Attributes_Specification_CIE2000( 

418 S_L, 

419 S_C, 

420 S_H, 

421 delta_L_p, 

422 delta_C_p, 

423 delta_H_p, 

424 R_T, 

425 ) 

426 

427 

428def delta_E_CIE2000( 

429 Lab_1: Domain100, 

430 Lab_2: Domain100, 

431 textiles: bool = False, 

432) -> NDArrayFloat: 

433 """ 

434 Compute the colour difference :math:`\\Delta E_{00}` between two specified 

435 *CIE L\\*a\\*b\\** colourspace arrays using the *CIE 2000* recommendation. 

436 

437 Parameters 

438 ---------- 

439 Lab_1 

440 *CIE L\\*a\\*b\\** colourspace array 1. 

441 Lab_2 

442 *CIE L\\*a\\*b\\** colourspace array 2. 

443 textiles 

444 Textiles application specific parametric factors. 

445 :math:`k_L=2,\\ k_C=k_H=1` weights are used instead of 

446 :math:`k_L=k_C=k_H=1`. 

447 

448 Returns 

449 ------- 

450 :class:`numpy.ndarray` 

451 Colour difference :math:`\\Delta E_{00}`. 

452 

453 Notes 

454 ----- 

455 +------------+-----------------------+-------------------+ 

456 | **Domain** | **Scale - Reference** | **Scale - 1** | 

457 +============+=======================+===================+ 

458 | ``Lab_1`` | 100 | 1 | 

459 +------------+-----------------------+-------------------+ 

460 | ``Lab_2`` | 100 | 1 | 

461 +------------+-----------------------+-------------------+ 

462 

463 - Parametric factors :math:`k_L=k_C=k_H=1` weights under 

464 *reference conditions*: 

465 

466 - Illumination: D65 source 

467 - Illuminance: 1000 lx 

468 - Observer: Normal colour vision 

469 - Background field: Uniform, neutral gray with :math:`L^*=50` 

470 - Viewing mode: Object 

471 - Sample size: Greater than 4 degrees 

472 - Sample separation: Direct edge contact 

473 - Sample colour-difference magnitude: Lower than 5.0 

474 :math:`\\Delta E_{00}` 

475 - Sample structure: Homogeneous (without texture) 

476 

477 References 

478 ---------- 

479 :cite:`Melgosa2013b`, :cite:`Sharma2005b` 

480 

481 Examples 

482 -------- 

483 >>> Lab_1 = np.array([48.99183622, -0.10561667, 400.65619925]) 

484 >>> Lab_2 = np.array([50.65907324, -0.11671910, 402.82235718]) 

485 >>> delta_E_CIE2000(Lab_1, Lab_2) # doctest: +ELLIPSIS 

486 1.6709303... 

487 >>> delta_E_CIE2000(Lab_1, Lab_2, textiles=True) # doctest: +ELLIPSIS 

488 0.8412338... 

489 """ 

490 

491 S_L, S_C, S_H, delta_L_p, delta_C_p, delta_H_p, R_T = astuple( 

492 intermediate_attributes_CIE2000(Lab_1, Lab_2) 

493 ) 

494 

495 k_L = 2 if textiles else 1 

496 k_C = 1 

497 k_H = 1 

498 

499 d_E = np.sqrt( 

500 (delta_L_p / (k_L * S_L)) ** 2 

501 + (delta_C_p / (k_C * S_C)) ** 2 

502 + (delta_H_p / (k_H * S_H)) ** 2 

503 + R_T * (delta_C_p / (k_C * S_C)) * (delta_H_p / (k_H * S_H)) 

504 ) 

505 

506 return as_float(d_E) 

507 

508 

509def delta_E_CMC( 

510 Lab_1: Domain100, 

511 Lab_2: Domain100, 

512 l: float = 2, # noqa: E741 

513 c: float = 1, 

514) -> NDArrayFloat: 

515 """ 

516 Compute the colour difference :math:`\\Delta E_{CMC}` between two 

517 specified *CIE L\\*a\\*b\\** colourspace arrays using the *Colour 

518 Measurement Committee* recommendation. 

519 

520 The quasimetric has two parameters: *lightness* (l) and *chroma* (c), 

521 allowing users to weight the difference based on the ratio of l:c. 

522 Commonly used values are 2:1 for acceptability and 1:1 for the 

523 threshold of imperceptibility. 

524 

525 Parameters 

526 ---------- 

527 Lab_1 

528 *CIE L\\*a\\*b\\** colourspace array 1. 

529 Lab_2 

530 *CIE L\\*a\\*b\\** colourspace array 2. 

531 l 

532 *Lightness* weighting factor. 

533 c 

534 *Chroma* weighting factor. 

535 

536 Returns 

537 ------- 

538 :class:`numpy.ndarray` 

539 Colour difference :math:`\\Delta E_{CMC}`. 

540 

541 Notes 

542 ----- 

543 +------------+-----------------------+-------------------+ 

544 | **Domain** | **Scale - Reference** | **Scale - 1** | 

545 +============+=======================+===================+ 

546 | ``Lab_1`` | 100 | 1 | 

547 +------------+-----------------------+-------------------+ 

548 | ``Lab_2`` | 100 | 1 | 

549 +------------+-----------------------+-------------------+ 

550 

551 References 

552 ---------- 

553 :cite:`Lindbloom2009f` 

554 

555 Examples 

556 -------- 

557 >>> Lab_1 = np.array([48.99183622, -0.10561667, 400.65619925]) 

558 >>> Lab_2 = np.array([50.65907324, -0.11671910, 402.82235718]) 

559 >>> delta_E_CMC(Lab_1, Lab_2) # doctest: +ELLIPSIS 

560 0.8996999... 

561 """ 

562 

563 L_1, a_1, b_1 = tsplit(to_domain_100(Lab_1)) 

564 L_2, a_2, b_2 = tsplit(to_domain_100(Lab_2)) 

565 

566 C_1 = np.hypot(a_1, b_1) 

567 C_2 = np.hypot(a_2, b_2) 

568 s_L = np.where(L_1 < 16, 0.511, (0.040975 * L_1) / (1 + 0.01765 * L_1)) 

569 s_C = 0.0638 * C_1 / (1 + 0.0131 * C_1) + 0.638 

570 h_1 = np.degrees(np.arctan2(b_1, a_1)) % 360 

571 

572 t = np.where( 

573 np.logical_and(h_1 >= 164, h_1 <= 345), 

574 0.56 + np.fabs(0.2 * np.cos(np.deg2rad(h_1 + 168))), 

575 0.36 + np.fabs(0.4 * np.cos(np.deg2rad(h_1 + 35))), 

576 ) 

577 

578 C_4 = C_1 * C_1 * C_1 * C_1 

579 f = np.sqrt(C_4 / (C_4 + 1900)) 

580 s_h = s_C * (f * t + 1 - f) 

581 

582 delta_L = L_1 - L_2 

583 delta_C = C_1 - C_2 

584 delta_A = a_1 - a_2 

585 delta_B = b_1 - b_2 

586 delta_H2 = delta_A**2 + delta_B**2 - delta_C**2 

587 

588 v_1 = delta_L / (l * s_L) 

589 v_2 = delta_C / (c * s_C) 

590 v_3 = s_h 

591 

592 d_E = np.sqrt(v_1**2 + v_2**2 + (delta_H2 / (v_3 * v_3))) 

593 

594 return as_float(d_E) 

595 

596 

597def delta_E_ITP(ICtCp_1: Domain1, ICtCp_2: Domain1) -> NDArrayFloat: 

598 """ 

599 Compute the colour difference :math:`\\Delta E_{ITP}` between two specified 

600 :math:`IC_TC_P` colour encoding arrays using the 

601 *Recommendation ITU-R BT.2124*. 

602 

603 Parameters 

604 ---------- 

605 ICtCp_1 

606 :math:`IC_TC_P` colour encoding array 1. 

607 ICtCp_2 

608 :math:`IC_TC_P` colour encoding array 2. 

609 

610 Returns 

611 ------- 

612 :class:`numpy.ndarray` 

613 Colour difference :math:`\\Delta E_{ITP}`. 

614 

615 Notes 

616 ----- 

617 - A value of 1 is equivalent to a just noticeable difference when viewed 

618 in the most critical adaptation state. 

619 

620 +--------------+-----------------------+--------------------+ 

621 | **Domain** | **Scale - Reference** | **Scale - 1** | 

622 +==============+=======================+====================+ 

623 | ``ICtCp_1`` | 1 | 1 | 

624 +--------------+-----------------------+--------------------+ 

625 | ``ICtCp_2`` | 1 | 1 | 

626 +--------------+-----------------------+--------------------+ 

627 

628 References 

629 ---------- 

630 :cite:`InternationalTelecommunicationUnion2019` 

631 

632 Examples 

633 -------- 

634 >>> ICtCp_1 = np.array([0.4885468072, -0.04739350675, 0.07475401302]) 

635 >>> ICtCp_2 = np.array([0.4899203231, -0.04567508203, 0.07361341775]) 

636 >>> delta_E_ITP(ICtCp_1, ICtCp_2) # doctest: +ELLIPSIS 

637 1.42657228... 

638 """ 

639 

640 I_1, T_1, P_1 = tsplit(ICtCp_1) 

641 T_1 *= 0.5 

642 

643 I_2, T_2, P_2 = tsplit(ICtCp_2) 

644 T_2 *= 0.5 

645 

646 d_E_ITP = 720 * np.sqrt( 

647 ((I_2 - I_1) ** 2) + ((T_2 - T_1) ** 2) + ((P_2 - P_1) ** 2) 

648 ) 

649 

650 return as_float(d_E_ITP) 

651 

652 

653def delta_E_HyAB(Lab_1: Domain100, Lab_2: Domain100) -> NDArrayFloat: 

654 """ 

655 Compute the colour difference between two *CIE L\\*a\\*b\\** colourspace arrays 

656 using a combination of a Euclidean metric in hue and chroma with a 

657 city-block metric to incorporate lightness differences. 

658 

659 This metric is intended for large colour differences, on the order of 10 

660 *CIE L\\*a\\*b\\** units or greater. 

661 

662 Parameters 

663 ---------- 

664 Lab_1 

665 *CIE L\\*a\\*b\\** colourspace array 1. 

666 Lab_2 

667 *CIE L\\*a\\*b\\** colourspace array 2. 

668 

669 Returns 

670 ------- 

671 :class:`numpy.ndarray` 

672 Colour difference :math:`\\Delta E_{HyAB}`. 

673 

674 Notes 

675 ----- 

676 +------------+-----------------------+-------------------+ 

677 | **Domain** | **Scale - Reference** | **Scale - 1** | 

678 +============+=======================+===================+ 

679 | ``Lab_1`` | 100 | 1 | 

680 +------------+-----------------------+-------------------+ 

681 | ``Lab_2`` | 100 | 1 | 

682 +------------+-----------------------+-------------------+ 

683 

684 References 

685 ---------- 

686 :cite:`Abasi2020a` 

687 

688 Examples 

689 -------- 

690 >>> Lab_1 = np.array([39.91531343, 51.16658481, 146.12933781]) 

691 >>> Lab_2 = np.array([53.12207516, -39.92365056, 249.54831278]) 

692 >>> delta_E_HyAB(Lab_1, Lab_2) # doctest: +ELLIPSIS 

693 151.0215481... 

694 """ 

695 

696 dLab = to_domain_100(Lab_1) - to_domain_100(Lab_2) 

697 dL, da, db = tsplit(dLab) 

698 HyAB = np.abs(dL) + np.hypot(da, db) 

699 

700 return as_float(HyAB) 

701 

702 

703def delta_E_HyCH( 

704 Lab_1: Domain100, 

705 Lab_2: Domain100, 

706 textiles: bool = False, 

707) -> NDArrayFloat: 

708 """ 

709 Compute the colour difference between two *CIE L\\*a\\*b\\** colourspace 

710 arrays using a combination of Euclidean metric in hue and chroma with a 

711 city-block metric to incorporate lightness differences based on 

712 *CIE 2000* recommendation attributes. 

713 

714 This metric is intended for large colour differences, on the order of 10 

715 *CIE L\\*a\\*b\\** units or greater. 

716 

717 Parameters 

718 ---------- 

719 Lab_1 

720 *CIE L\\*a\\*b\\** colourspace array 1. 

721 Lab_2 

722 *CIE L\\*a\\*b\\** colourspace array 2. 

723 textiles 

724 Whether to use the textile-specific parametrization. 

725 

726 Returns 

727 ------- 

728 :class:`numpy.ndarray` 

729 Colour difference :math:`\\Delta E_{HyCH}`. 

730 

731 Notes 

732 ----- 

733 +------------+-----------------------+-------------------+ 

734 | **Domain** | **Scale - Reference** | **Scale - 1** | 

735 +============+=======================+===================+ 

736 | ``Lab_1`` | 100 | 1 | 

737 +------------+-----------------------+-------------------+ 

738 | ``Lab_2`` | 100 | 1 | 

739 +------------+-----------------------+-------------------+ 

740 

741 References 

742 ---------- 

743 :cite:`Abasi2020a` 

744 

745 Examples 

746 -------- 

747 >>> Lab_1 = np.array([39.91531343, 51.16658481, 146.12933781]) 

748 >>> Lab_2 = np.array([53.12207516, -39.92365056, 249.54831278]) 

749 >>> delta_E_HyCH(Lab_1, Lab_2) # doctest: +ELLIPSIS 

750 48.664279419760369... 

751 """ 

752 

753 S_L, S_C, S_H, delta_L_p, delta_C_p, delta_H_p, R_T = astuple( 

754 intermediate_attributes_CIE2000(Lab_1, Lab_2) 

755 ) 

756 

757 k_L = 2 if textiles else 1 

758 k_C = 1 

759 k_H = 1 

760 

761 HyCH = np.abs(delta_L_p / (k_L * S_L)) + np.sqrt( 

762 (delta_C_p / (k_C * S_C)) ** 2 + (delta_H_p / (k_H * S_H)) ** 2 

763 ) 

764 

765 return as_float(HyCH)