diff --git a/colour/difference/__init__.py b/colour/difference/__init__.py index f98a330995..d0225b16ec 100644 --- a/colour/difference/__init__.py +++ b/colour/difference/__init__.py @@ -37,14 +37,15 @@ from .cam02_ucs import delta_E_CAM02LCD, delta_E_CAM02SCD, delta_E_CAM02UCS from .cam16_ucs import delta_E_CAM16LCD, delta_E_CAM16SCD, delta_E_CAM16UCS -from .delta_e import (delta_E_CIE1976, delta_E_CIE1994, delta_E_CIE2000, - delta_E_CMC) +from .delta_e import (JND_CIE1976, delta_E_CIE1976, delta_E_CIE1994, + delta_E_CIE2000, delta_E_CMC) from .din99 import delta_E_DIN99 __all__ = ['delta_E_CAM02LCD', 'delta_E_CAM02SCD', 'delta_E_CAM02UCS'] __all__ += ['delta_E_CAM16LCD', 'delta_E_CAM16SCD', 'delta_E_CAM16UCS'] __all__ += [ - 'delta_E_CIE1976', 'delta_E_CIE1994', 'delta_E_CIE2000', 'delta_E_CMC' + 'JND_CIE1976', 'delta_E_CIE1976', 'delta_E_CIE1994', 'delta_E_CIE2000', + 'delta_E_CMC' ] __all__ += ['delta_E_DIN99'] diff --git a/colour/difference/delta_e.py b/colour/difference/delta_e.py index d1ff28a992..cb8d6fa673 100644 --- a/colour/difference/delta_e.py +++ b/colour/difference/delta_e.py @@ -1,12 +1,13 @@ # -*- coding: utf-8 -*- """ -:math:`\\Delta E_{ab}` - Delta E Colour Difference -================================================= +:math:`\\Delta E^*_{ab}` - Delta E Colour Difference +==================================================== -Defines :math:`\\Delta E_{ab}` colour difference computation objects: +Defines :math:`\\Delta E^*_{ab}` colour difference computation objects: -The following methods are available: +The following attributes and methods are available: +- :attr:`colour.difference.JND_CIE1976` - :func:`colour.difference.delta_E_CIE1976` - :func:`colour.difference.delta_E_CIE1994` - :func:`colour.difference.delta_E_CIE2000` @@ -28,6 +29,8 @@ - :cite:`Melgosa2013b` : Melgosa, M. (2013). CIE / ISO new standard: CIEDE2000. http://www.color.org/events/colorimetry/\ Melgosa_CIEDE2000_Workshop-July4.pdf +- :cite:`Mokrzycki2011` : Mokrzycki, W., & Tatol, M. (2011). Color difference + Delta E - A survey. Machine Graphics and Vision, 20, 383–411. """ from __future__ import division, unicode_literals @@ -36,7 +39,8 @@ from colour.algebra import euclidean_distance from colour.utilities import to_domain_100, tsplit - +from colour.utilities.documentation import (DocstringFloat, + is_documentation_building) __author__ = 'Colour Developers' __copyright__ = 'Copyright (C) 2013-2020 - Colour Developers' __license__ = 'New BSD License - https://opensource.org/licenses/BSD-3-Clause' @@ -45,9 +49,36 @@ __status__ = 'Production' __all__ = [ - 'delta_E_CIE1976', 'delta_E_CIE1994', 'delta_E_CIE2000', 'delta_E_CMC' + 'JND_CIE1976', 'delta_E_CIE1976', 'delta_E_CIE1994', 'delta_E_CIE2000', + 'delta_E_CMC' ] +JND_CIE1976 = 2.3 / 100 +if is_documentation_building(): # pragma: no cover + JND_CIE1976 = DocstringFloat(JND_CIE1976) + JND_CIE1976.__doc__ = """ +Just Noticeable Difference (JND) according to *CIE 1976* colour difference +formula, i.e. Euclidean distance in *CIE L\\*a\\*b\\** colourspace. + +Notes +----- +A standard observer sees the difference in colour as follows: + +- 0 < :math:`\\Delta E^*_{ab}` < 1 : Observer does not notice the difference. +- 1 < :math:`\\Delta E^*_{ab}` < 2 : Only experienced observer can notice the + difference. +- 2 < :math:`\\Delta E^*_{ab}` < 3:5 : Unexperienced observer also notices + the difference. +- 3:5 < :math:`\\Delta E^*_{ab}` < 5 : Clear difference in colour is noticed. +- 5 < :math:`\\Delta E^*_{ab}` : Observer notices two different colours. + +References +---------- +:cite:`Mokrzycki2011` + +JND_CIE1976 : numeric +""" + def delta_E_CIE1976(Lab_1, Lab_2): """ diff --git a/colour/recovery/jakob2019.py b/colour/recovery/jakob2019.py index a39c8dc758..9213b91710 100644 --- a/colour/recovery/jakob2019.py +++ b/colour/recovery/jakob2019.py @@ -30,6 +30,7 @@ from colour.colorimetry import ( STANDARD_OBSERVER_CMFS, SpectralDistribution, SpectralShape, intermediate_lightness_function_CIE1976, sd_ones, sd_to_XYZ) +from colour.difference import JND_CIE1976 from colour.models import XYZ_to_xy, XYZ_to_Lab, RGB_to_XYZ from colour.utilities import (as_float_array, domain_range_scale, full, index_along_last_axis, to_domain_1, @@ -43,37 +44,17 @@ __status__ = 'Production' __all__ = [ - 'DEFAULT_SPECTRAL_SHAPE_JAKOB_2019', 'ACCEPTABLE_DELTA_E', - 'StopMinimizationEarly', 'sd_Jakob2019', 'error_function', - 'dimensionalise_coefficients', 'lightness_scale', + 'JAKOB2019_SPECTRAL_SHAPE', 'StopMinimizationEarly', 'sd_Jakob2019', + 'error_function', 'dimensionalise_coefficients', 'lightness_scale', 'find_coefficients_Jakob2019', 'XYZ_to_sd_Jakob2019', 'Jakob2019Interpolator' ] -DEFAULT_SPECTRAL_SHAPE_JAKOB_2019 = SpectralShape(360, 780, 5) +JAKOB2019_SPECTRAL_SHAPE = SpectralShape(360, 780, 5) """ -Default spectral shape for *Jakob and Hanika (2019)* method. +Spectral shape for *Jakob and Hanika (2019)* method. -DEFAULT_SPECTRAL_SHAPE_JAKOB_2019 : SpectralShape -""" - -ACCEPTABLE_DELTA_E = 2.4 / 100 # 1% of JND -""" -Acceptable *perceptual* distance in the *CIE L\\*a\\*b\\** colourspace. - -Notes ------ -*Jakob and Hanika (2019)* uses :math:`\\Delta E_{76}` in the -*CIE L\\*a\\*b\\** colourspace as an error metric during the optimization -process. While the *CIE L\\*a\\*b\\** colourspace features decent perceptual -uniformity, it was deemed unsatisfactory when comparing some pair of colors, -compelling the CIE into improving the metric with the CIE 1994 -(:math:`\\Delta E_{94}`) quasimetric whose perceptual uniformity was -subsequently corrected with the CIE 2000 (:math:`\\Delta E_{00}`) quasimetric. -Thus, the error metric could be improved by adopting CIE 2000 or even a more -perceptually uniform colourspace such as :math:`IC_TC_P` or :math:`J_zA_zB_z`. - -ACCEPTABLE_DELTA_E = 2.4 / 100 : float +JAKOB2019_SPECTRAL_SHAPE : SpectralShape """ @@ -96,7 +77,7 @@ def __init__(self, coefficients, error): self.error = error -def sd_Jakob2019(coefficients, shape=DEFAULT_SPECTRAL_SHAPE_JAKOB_2019): +def sd_Jakob2019(coefficients, shape=JAKOB2019_SPECTRAL_SHAPE): """ Returns a spectral distribution following the spectral model given by *Jakob and Hanika (2019)*. @@ -327,11 +308,11 @@ def lightness_scale(steps): def find_coefficients_Jakob2019( XYZ, cmfs=STANDARD_OBSERVER_CMFS['CIE 1931 2 Degree Standard Observer'] - .copy().align(DEFAULT_SPECTRAL_SHAPE_JAKOB_2019), + .copy().align(JAKOB2019_SPECTRAL_SHAPE), illuminant=ILLUMINANT_SDS['D65'].copy().align( - DEFAULT_SPECTRAL_SHAPE_JAKOB_2019), + JAKOB2019_SPECTRAL_SHAPE), coefficients_0=zeros(3), - max_error=ACCEPTABLE_DELTA_E, + max_error=JND_CIE1976, dimensionalise=True): """ Computes the coefficients for *Jakob and Hanika (2019)* reflectance @@ -430,8 +411,8 @@ def optimize(target_o, coefficients_0_o): def XYZ_to_sd_Jakob2019( XYZ, cmfs=STANDARD_OBSERVER_CMFS['CIE 1931 2 Degree Standard Observer'] - .copy().align(DEFAULT_SPECTRAL_SHAPE_JAKOB_2019), - illuminant=sd_ones(DEFAULT_SPECTRAL_SHAPE_JAKOB_2019), + .copy().align(JAKOB2019_SPECTRAL_SHAPE), + illuminant=sd_ones(JAKOB2019_SPECTRAL_SHAPE), optimisation_kwargs=None, additional_data=False): """ @@ -617,7 +598,7 @@ def RGB_to_coefficients(self, RGB): return self.cube(coords).squeeze() - def RGB_to_sd(self, RGB, shape=DEFAULT_SPECTRAL_SHAPE_JAKOB_2019): + def RGB_to_sd(self, RGB, shape=JAKOB2019_SPECTRAL_SHAPE): """ Looks up a given *RGB* colourspace array and return the corresponding spectral distribution. diff --git a/colour/recovery/tests/test_jakob2019.py b/colour/recovery/tests/test_jakob2019.py index 336b175c18..9b50ee0227 100644 --- a/colour/recovery/tests/test_jakob2019.py +++ b/colour/recovery/tests/test_jakob2019.py @@ -15,12 +15,12 @@ from colour.colorimetry import (ILLUMINANTS, ILLUMINANT_SDS, STANDARD_OBSERVER_CMFS, SpectralDistribution, sd_to_XYZ) -from colour.difference import delta_E_CIE1976 +from colour.difference import JND_CIE1976, delta_E_CIE1976 from colour.models import RGB_COLOURSPACES, RGB_to_XYZ, XYZ_to_Lab from colour.recovery.jakob2019 import ( XYZ_to_sd_Jakob2019, sd_Jakob2019, error_function, - dimensionalise_coefficients, DEFAULT_SPECTRAL_SHAPE_JAKOB_2019, - ACCEPTABLE_DELTA_E, Jakob2019Interpolator) + dimensionalise_coefficients, JAKOB2019_SPECTRAL_SHAPE, + Jakob2019Interpolator) from colour.utilities import domain_range_scale, full, ones, zeros __author__ = 'Colour Developers' @@ -78,7 +78,7 @@ def test_compare_intermediates(self): ] # error_function will not align these for us. - shape = DEFAULT_SPECTRAL_SHAPE_JAKOB_2019 + shape = JAKOB2019_SPECTRAL_SHAPE aligned_cmfs = CMFS.copy().align(shape) illuminant = D65.copy().align(shape) XYZ_n = sd_to_XYZ(D65) @@ -101,8 +101,8 @@ def test_compare_intermediates(self): np.testing.assert_allclose(sd.values, R, atol=1e-14) np.testing.assert_allclose(XYZ, sd_XYZ, atol=1e-14) - self.assertLess(abs(error_reference - error), ACCEPTABLE_DELTA_E) - self.assertLess(delta_E_CIE1976(Lab, sd_Lab), ACCEPTABLE_DELTA_E) + self.assertLess(abs(error_reference - error), JND_CIE1976) + self.assertLess(delta_E_CIE1976(Lab, sd_Lab), JND_CIE1976) def test_derivatives(self): """ @@ -110,7 +110,7 @@ def test_derivatives(self): derivatives with finite difference approximations. """ - shape = DEFAULT_SPECTRAL_SHAPE_JAKOB_2019 + shape = JAKOB2019_SPECTRAL_SHAPE aligned_cmfs = CMFS.copy().align(shape) illuminant = D65.copy().align(shape) XYZ_n = sd_to_XYZ(D65) @@ -161,7 +161,7 @@ def test_XYZ_to_sd_Jakob2019(self): _recovered_sd, error = XYZ_to_sd_Jakob2019( XYZ, illuminant=D65, additional_data=True) - if error > ACCEPTABLE_DELTA_E: + if error > JND_CIE1976: self.fail('Delta E for \'{0}\' is {1}!'.format(name, error)) def test_domain_range_scale_XYZ_to_sd_Jakob2019(self): @@ -232,7 +232,7 @@ def test_Jakob2019Interpolator(self): recovered_Lab = XYZ_to_Lab(recovered_XYZ, D65_XY) error = delta_E_CIE1976(Lab, recovered_Lab) - if error > 2 * ACCEPTABLE_DELTA_E: + if error > 2 * JND_CIE1976: self.fail('Delta E for RGB={0} in colourspace {1} is {2}!' .format(RGB, sRGB.name, error)) diff --git a/docs/colour.difference.rst b/docs/colour.difference.rst index c5673377a2..48129e19a1 100644 --- a/docs/colour.difference.rst +++ b/docs/colour.difference.rst @@ -26,6 +26,7 @@ CIE 1976 .. autosummary:: :toctree: generated/ + JND_CIE1976 delta_E_CIE1976 CIE 1994