Example #1
0
    def test_sd_to_XYZ_integration(self):
        """
        Tests :func:`colour.colorimetry.tristimulus.\
sd_to_XYZ_integration`
        definition.
        """

        cmfs = CMFS['CIE 1931 2 Degree Standard Observer']
        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(SAMPLE_SD, cmfs, ILLUMINANTS_SDS['A']),
            np.array([14.46365624, 10.85827910, 2.04662343]),
            decimal=7)

        cmfs = CMFS['CIE 1964 10 Degree Standard Observer']
        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(SAMPLE_SD, cmfs, ILLUMINANTS_SDS['C']),
            np.array([10.77031004, 9.44863775, 6.62745989]),
            decimal=7)

        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(SAMPLE_SD, cmfs, ILLUMINANTS_SDS['FL2']),
            np.array([11.57834054, 9.98738373, 3.95462625]),
            decimal=7)

        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(
                SAMPLE_SD, cmfs, ILLUMINANTS_SDS['FL2'], k=683),
            np.array([122441.23450378, 105616.82732832, 41820.26940409]),
            decimal=7)
    def test_sd_to_XYZ_integration(self):
        """
        Tests :func:`colour.colorimetry.tristimulus.\
sd_to_XYZ_integration`
        definition.
        """

        cmfs = CMFS['CIE 1931 2 Degree Standard Observer']
        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(SAMPLE_SD, cmfs, ILLUMINANTS_SDS['A']),
            np.array([14.46365624, 10.85827910, 2.04662343]),
            decimal=7)

        cmfs = CMFS['CIE 1964 10 Degree Standard Observer']
        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(SAMPLE_SD, cmfs, ILLUMINANTS_SDS['C']),
            np.array([10.77031004, 9.44863775, 6.62745989]),
            decimal=7)

        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(SAMPLE_SD, cmfs, ILLUMINANTS_SDS['FL2']),
            np.array([11.57834054, 9.98738373, 3.95462625]),
            decimal=7)

        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(
                SAMPLE_SD, cmfs, ILLUMINANTS_SDS['FL2'], k=683),
            np.array([122441.23450378, 105616.82732832, 41820.26940409]),
            decimal=7)
Example #3
0
    def test_sd_to_XYZ_integration(self):
        """
        Tests :func:`colour.colorimetry.tristimulus.sd_to_XYZ_integration`
        definition.
        """

        cmfs = MSDS_CMFS['CIE 1931 2 Degree Standard Observer']
        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(SD_SAMPLE, cmfs, SDS_ILLUMINANTS['A']),
            np.array([14.46341147, 10.85819624, 2.04695585]),
            decimal=7)

        cmfs = MSDS_CMFS['CIE 1964 10 Degree Standard Observer']
        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(SD_SAMPLE, cmfs, SDS_ILLUMINANTS['C']),
            np.array([10.77002699, 9.44876636, 6.62415290]),
            decimal=7)

        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(SD_SAMPLE, cmfs, SDS_ILLUMINANTS['FL2']),
            np.array([11.57540576, 9.98608874, 3.95242590]),
            decimal=7)

        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(SD_SAMPLE,
                                  cmfs,
                                  SDS_ILLUMINANTS['FL2'],
                                  k=683),
            np.array([122375.09261493, 105572.84645912, 41785.01342332]),
            decimal=7)
Example #4
0
    def test_domain_range_scale_XYZ_to_sd_Meng2015(self):
        """
        Test :func:`colour.recovery.meng2015.XYZ_to_sd_Meng2015` definition
        domain and range scale support.
        """

        XYZ_i = np.array([0.20654008, 0.12197225, 0.05136952])
        XYZ_o = sd_to_XYZ_integration(
            XYZ_to_sd_Meng2015(XYZ_i, self._cmfs, self._sd_D65),
            self._cmfs,
            self._sd_D65,
        )

        d_r = (("reference", 1, 1), ("1", 1, 0.01), ("100", 100, 1))
        for scale, factor_a, factor_b in d_r:
            with domain_range_scale(scale):
                np.testing.assert_almost_equal(
                    sd_to_XYZ_integration(
                        XYZ_to_sd_Meng2015(
                            XYZ_i * factor_a, self._cmfs, self._sd_D65
                        ),
                        self._cmfs,
                        self._sd_D65,
                    ),
                    XYZ_o * factor_b,
                    decimal=7,
                )
Example #5
0
    def test_domain_range_scale_XYZ_to_sd(self):
        """
        Tests :func:`colour.recovery.XYZ_to_sd` definition domain
        and range scale support.
        """

        XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
        m = ('Jakob 2019', 'Mallett 2019', 'Meng 2015', 'Otsu 2018',
             'Smits 1999')
        v = [
            sd_to_XYZ_integration(
                XYZ_to_sd(XYZ,
                          method,
                          cmfs=self._cmfs,
                          illuminant=self._sd_D65), self._cmfs, self._sd_D65)
            for method in m
        ]

        d_r = (('reference', 1, 1), (1, 1, 0.01), (100, 100, 1))
        for method, value in zip(m, v):
            for scale, factor_a, factor_b in d_r:
                with domain_range_scale(scale):
                    np.testing.assert_almost_equal(sd_to_XYZ_integration(
                        XYZ_to_sd(XYZ * factor_a,
                                  method,
                                  cmfs=self._cmfs,
                                  illuminant=self._sd_D65), self._cmfs,
                        self._sd_D65),
                                                   value * factor_b,
                                                   decimal=7)
Example #6
0
    def test_XYZ_to_sd_Meng2015(self):
        """Test :func:`colour.recovery.meng2015.XYZ_to_sd_Meng2015` definition."""

        XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(
                XYZ_to_sd_Meng2015(XYZ, self._cmfs, self._sd_D65),
                self._cmfs,
                self._sd_D65,
            )
            / 100,
            XYZ,
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(
                XYZ_to_sd_Meng2015(XYZ, self._cmfs, self._sd_E),
                self._cmfs,
                self._sd_E,
            )
            / 100,
            XYZ,
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(
                XYZ_to_sd_Meng2015(
                    XYZ,
                    self._cmfs,
                    self._sd_D65,
                    optimisation_kwargs={
                        "options": {
                            "ftol": 1e-10,
                        }
                    },
                ),
                self._cmfs,
                self._sd_D65,
            )
            / 100,
            XYZ,
            decimal=7,
        )

        shape = SpectralShape(400, 700, 5)
        # pylint: disable=E1102
        cmfs = reshape_msds(self._cmfs, shape)
        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(
                XYZ_to_sd_Meng2015(XYZ, cmfs, self._sd_D65), cmfs, self._sd_D65
            )
            / 100,
            XYZ,
            decimal=7,
        )
Example #7
0
    def test_XYZ_to_sd_Meng2015(self):
        """
        Tests :func:`colour.recovery.meng2015.XYZ_to_sd_Meng2015`
        definition.
        """

        cmfs = (STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'].
                copy().trim(DEFAULT_SPECTRAL_SHAPE))
        shape = SpectralShape(cmfs.shape.start, cmfs.shape.end, 5)
        cmfs_c = cmfs.copy().align(shape)

        XYZ = np.array([0.21781186, 0.12541048, 0.04697113])
        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(XYZ_to_sd_Meng2015(XYZ, cmfs_c), cmfs_c) /
            100,
            XYZ,
            decimal=7)

        shape = SpectralShape(cmfs.shape.start, cmfs.shape.end, 10)
        cmfs_c = cmfs.copy().align(shape)

        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(XYZ_to_sd_Meng2015(XYZ, cmfs_c), cmfs_c) /
            100,
            XYZ,
            decimal=7)

        np.testing.assert_almost_equal(sd_to_XYZ_integration(
            XYZ_to_sd_Meng2015(XYZ, cmfs_c, ILLUMINANTS_SDS['D65']), cmfs_c,
            ILLUMINANTS_SDS['D65']) / 100,
                                       XYZ,
                                       decimal=7)

        np.testing.assert_almost_equal(sd_to_XYZ_integration(
            XYZ_to_sd_Meng2015(
                XYZ,
                cmfs_c,
                optimisation_parameters={'options': {
                    'ftol': 1e-10,
                }}), cmfs_c) / 100,
                                       XYZ,
                                       decimal=7)

        shape = SpectralShape(400, 700, 5)
        cmfs_c = cmfs.copy().align(shape)
        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(XYZ_to_sd_Meng2015(XYZ, cmfs_c), cmfs_c) /
            100,
            XYZ,
            decimal=7)
Example #8
0
    def test_domain_range_scale_sd_to_XYZ_integration(self):
        """
        Tests :func:`colour.colorimetry.tristimulus.sd_to_XYZ_integration`
        definition domain and range scale support.
        """

        cmfs = MSDS_CMFS['CIE 1931 2 Degree Standard Observer']
        XYZ = sd_to_XYZ_integration(SD_SAMPLE, cmfs, SDS_ILLUMINANTS['A'])

        d_r = (('reference', 1), (1, 0.01), (100, 1))
        for scale, factor in d_r:
            with domain_range_scale(scale):
                np.testing.assert_almost_equal(sd_to_XYZ_integration(
                    SD_SAMPLE, cmfs, SDS_ILLUMINANTS['A']),
                                               XYZ * factor,
                                               decimal=7)
Example #9
0
    def constraint_function(a: ArrayLike) -> NDArray:
        """Define the constraint function."""

        sd[:] = a
        return (
            sd_to_XYZ_integration(sd, cmfs=cmfs, illuminant=illuminant) - XYZ
        )
Example #10
0
    def test_domain_range_scale_XYZ_to_sd_Meng2015(self):
        """
        Tests :func:`colour.recovery.meng2015.XYZ_to_sd_Meng2015`
        definition domain and range scale support.
        """

        XYZ_i = np.array([0.21781186, 0.12541048, 0.04697113])
        XYZ_o = sd_to_XYZ_integration(XYZ_to_sd_Meng2015(XYZ_i))

        d_r = (('reference', 1, 1), (1, 1, 0.01), (100, 100, 1))
        for scale, factor_a, factor_b in d_r:
            with domain_range_scale(scale):
                np.testing.assert_almost_equal(sd_to_XYZ_integration(
                    XYZ_to_sd_Meng2015(XYZ_i * factor_a)),
                                               XYZ_o * factor_b,
                                               decimal=7)
Example #11
0
    def test_XYZ_to_sd_Meng2015(self):
        """
        Tests :func:`colour.recovery.meng2015.XYZ_to_sd_Meng2015`
        definition.
        """

        cmfs = STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer']
        shape = SpectralShape(cmfs.shape.start, cmfs.shape.end, 5)
        cmfs_c = cmfs.copy().align(shape)

        XYZ = np.array([0.21781186, 0.12541048, 0.04697113])
        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(XYZ_to_sd_Meng2015(XYZ), cmfs=cmfs_c) / 100,
            XYZ,
            decimal=7)

        shape = SpectralShape(cmfs.shape.start, cmfs.shape.end, 10)
        cmfs_c = cmfs.copy().align(shape)

        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(
                XYZ_to_sd_Meng2015(XYZ, interval=10), cmfs=cmfs_c) / 100,
            XYZ,
            decimal=7)

        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(
                XYZ_to_sd_Meng2015(
                    XYZ,
                    interval=10,
                    optimisation_parameters={
                        'options': {
                            'ftol': 1e-10,
                            'maxiter': 2000
                        }
                    }),
                cmfs=cmfs_c) / 100,
            XYZ,
            decimal=7)

        shape = SpectralShape(400, 700, 5)
        cmfs_c = cmfs.copy().align(shape)
        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(
                XYZ_to_sd_Meng2015(XYZ, cmfs=cmfs_c), cmfs=cmfs_c) / 100,
            XYZ,
            decimal=7)
Example #12
0
    def test_XYZ_to_sd_Meng2015(self):
        """
        Tests :func:`colour.recovery.meng2015.XYZ_to_sd_Meng2015`
        definition.
        """

        cmfs = STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer']
        shape = SpectralShape(cmfs.shape.start, cmfs.shape.end, 5)
        cmfs_c = cmfs.copy().align(shape)

        XYZ = np.array([0.21781186, 0.12541048, 0.04697113])
        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(XYZ_to_sd_Meng2015(XYZ), cmfs=cmfs_c) / 100,
            XYZ,
            decimal=7)

        shape = SpectralShape(cmfs.shape.start, cmfs.shape.end, 10)
        cmfs_c = cmfs.copy().align(shape)

        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(
                XYZ_to_sd_Meng2015(XYZ, interval=10), cmfs=cmfs_c) / 100,
            XYZ,
            decimal=7)

        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(
                XYZ_to_sd_Meng2015(
                    XYZ,
                    interval=10,
                    optimisation_parameters={
                        'options': {
                            'ftol': 1e-10,
                            'maxiter': 2000
                        }
                    }),
                cmfs=cmfs_c) / 100,
            XYZ,
            decimal=7)

        shape = SpectralShape(400, 700, 5)
        cmfs_c = cmfs.copy().align(shape)
        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(
                XYZ_to_sd_Meng2015(XYZ, cmfs=cmfs_c), cmfs=cmfs_c) / 100,
            XYZ,
            decimal=7)
Example #13
0
    def test_domain_range_scale_sd_to_XYZ_integration(self):
        """
        Tests :func:`colour.colorimetry.tristimulus.\
sd_to_XYZ_integration` definition domain and range scale support.
        """

        cmfs = CMFS['CIE 1931 2 Degree Standard Observer']
        XYZ = sd_to_XYZ_integration(SAMPLE_SD, cmfs, ILLUMINANTS_SDS['A'])

        d_r = (('reference', 1), (1, 0.01), (100, 1))
        for scale, factor in d_r:
            with domain_range_scale(scale):
                np.testing.assert_almost_equal(
                    sd_to_XYZ_integration(SAMPLE_SD, cmfs,
                                          ILLUMINANTS_SDS['A']),
                    XYZ * factor,
                    decimal=7)
Example #14
0
    def constraint_function(a):
        """
        Function defining the constraint.
        """

        sd[:] = a
        return sd_to_XYZ_integration(
            sd, cmfs=cmfs, illuminant=illuminant) - XYZ
Example #15
0
    def test_domain_range_scale_XYZ_to_sd_Meng2015(self):
        """
        Tests :func:`colour.recovery.meng2015.XYZ_to_sd_Meng2015`
        definition domain and range scale support.
        """

        XYZ_i = np.array([0.21781186, 0.12541048, 0.04697113])
        XYZ_o = sd_to_XYZ_integration(XYZ_to_sd_Meng2015(XYZ_i))

        d_r = (('reference', 1, 1), (1, 1, 0.01), (100, 100, 1))
        for scale, factor_a, factor_b in d_r:
            with domain_range_scale(scale):
                np.testing.assert_almost_equal(
                    sd_to_XYZ_integration(
                        XYZ_to_sd_Meng2015(XYZ_i * factor_a)),
                    XYZ_o * factor_b,
                    decimal=7)
Example #16
0
    def test_sd_to_XYZ_integration(self):
        """
        Test :func:`colour.colorimetry.tristimulus_values.\
sd_to_XYZ_integration` definition.
        """

        cmfs = MSDS_CMFS["CIE 1931 2 Degree Standard Observer"]
        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(SD_SAMPLE, cmfs, SDS_ILLUMINANTS["A"]),
            np.array([14.46341147, 10.85819624, 2.04695585]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(
                SD_SAMPLE.values,
                cmfs,
                SDS_ILLUMINANTS["A"],
                shape=SD_SAMPLE.shape,
            ),
            np.array([14.46365947, 10.85828084, 2.04663993]),
            decimal=7,
        )

        cmfs = MSDS_CMFS["CIE 1964 10 Degree Standard Observer"]
        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(SD_SAMPLE, cmfs, SDS_ILLUMINANTS["C"]),
            np.array([10.77002699, 9.44876636, 6.62415290]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(SD_SAMPLE, cmfs, SDS_ILLUMINANTS["FL2"]),
            np.array([11.57540576, 9.98608874, 3.95242590]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_integration(SD_SAMPLE,
                                  cmfs,
                                  SDS_ILLUMINANTS["FL2"],
                                  k=683),
            np.array([122375.09261493, 105572.84645912, 41785.01342332]),
            decimal=7,
        )
Example #17
0
    def test_domain_range_scale_RGB_to_sd_Smits1999(self):
        """
        Tests :func:`colour.recovery.smits1999.RGB_to_sd_Smits1999`
        definition domain and range scale support.
        """

        XYZ_i = np.array([0.20654008, 0.12197225, 0.05136952])
        RGB_i = XYZ_to_RGB_Smits1999(XYZ_i)
        XYZ_o = sd_to_XYZ_integration(RGB_to_sd_Smits1999(RGB_i))

        d_r = (('reference', 1, 1), (1, 1, 0.01), (100, 100, 1))
        for scale, factor_a, factor_b in d_r:
            with domain_range_scale(scale):
                np.testing.assert_almost_equal(
                    sd_to_XYZ_integration(
                        RGB_to_sd_Smits1999(RGB_i * factor_a)),
                    XYZ_o * factor_b,
                    decimal=7)
Example #18
0
    def test_domain_range_scale_XYZ_to_sd(self):
        """
        Tests :func:`colour.recovery.XYZ_to_sd` definition domain
        and range scale support.
        """

        XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
        m = ('Smits 1999', 'Meng 2015')
        v = [sd_to_XYZ_integration(XYZ_to_sd(XYZ, method)) for method in m]

        d_r = (('reference', 1, 1), (1, 1, 0.01), (100, 100, 1))
        for method, value in zip(m, v):
            for scale, factor_a, factor_b in d_r:
                with domain_range_scale(scale):
                    np.testing.assert_almost_equal(
                        sd_to_XYZ_integration(
                            XYZ_to_sd(XYZ * factor_a, method=method)),
                        value * factor_b,
                        decimal=7)
Example #19
0
    def test_domain_range_scale_XYZ_to_sd(self):
        """
        Tests :func:`colour.recovery.XYZ_to_sd` definition domain
        and range scale support.
        """

        cmfs = (STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'].
                copy().align(DEFAULT_SPECTRAL_SHAPE_MENG_2015))

        XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
        m = ('Smits 1999', 'Meng 2015')
        v = [
            sd_to_XYZ_integration(XYZ_to_sd(XYZ, method), cmfs) for method in m
        ]

        d_r = (('reference', 1, 1), (1, 1, 0.01), (100, 100, 1))
        for method, value in zip(m, v):
            for scale, factor_a, factor_b in d_r:
                with domain_range_scale(scale):
                    np.testing.assert_almost_equal(sd_to_XYZ_integration(
                        XYZ_to_sd(XYZ * factor_a, method), cmfs),
                                                   value * factor_b,
                                                   decimal=7)
Example #20
0
    def test_XYZ_to_sd_Meng2015(self):
        """
        Tests :func:`colour.recovery.meng2015.XYZ_to_sd_Meng2015` definition.
        """

        XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
        np.testing.assert_almost_equal(sd_to_XYZ_integration(
            XYZ_to_sd_Meng2015(XYZ, self._cmfs, self._sd_D65), self._cmfs,
            self._sd_D65) / 100,
                                       XYZ,
                                       decimal=7)

        np.testing.assert_almost_equal(sd_to_XYZ_integration(
            XYZ_to_sd_Meng2015(XYZ, self._cmfs, self._sd_E), self._cmfs,
            self._sd_E) / 100,
                                       XYZ,
                                       decimal=7)

        np.testing.assert_almost_equal(sd_to_XYZ_integration(
            XYZ_to_sd_Meng2015(
                XYZ,
                self._cmfs,
                self._sd_D65,
                optimisation_kwargs={'options': {
                    'ftol': 1e-10,
                }}), self._cmfs, self._sd_D65) / 100,
                                       XYZ,
                                       decimal=7)

        shape = SpectralShape(400, 700, 5)
        cmfs = self._cmfs.copy().align(shape)
        np.testing.assert_almost_equal(sd_to_XYZ_integration(
            XYZ_to_sd_Meng2015(XYZ, cmfs, self._sd_D65), cmfs, self._sd_D65) /
                                       100,
                                       XYZ,
                                       decimal=7)
Example #21
0
def find_coefficients_Jakob2019(
    XYZ: ArrayLike,
    cmfs: Optional[MultiSpectralDistributions] = None,
    illuminant: Optional[SpectralDistribution] = None,
    coefficients_0: ArrayLike = zeros(3),
    max_error: Floating = JND_CIE1976 / 100,
    dimensionalise: Boolean = True,
) -> Tuple[NDArray, Floating]:
    """
    Compute the coefficients for *Jakob and Hanika (2019)* reflectance
    spectral model.

    Parameters
    ----------
    XYZ
        *CIE XYZ* tristimulus values to find the coefficients for.
    cmfs
        Standard observer colour matching functions, default to the
        *CIE 1931 2 Degree Standard Observer*.
    illuminant
        Illuminant spectral distribution, default to
        *CIE Standard Illuminant D65*.
    coefficients_0
        Starting coefficients for the solver.
    max_error
        Maximal acceptable error. Set higher to save computational time.
        If *None*, the solver will keep going until it is very close to the
        minimum. The default is ``ACCEPTABLE_DELTA_E``.
    dimensionalise
        If *True*, returned coefficients are dimensionful and will not work
        correctly if fed back as ``coefficients_0``. The default is *True*.

    Returns
    -------
    :class:`tuple`
        Tuple of computed coefficients that best fit the given colour and
        :math:`\\Delta E_{76}` between the target colour and the colour
        corresponding to the computed coefficients.

    References
    ----------
    :cite:`Jakob2019`

    Examples
    --------
    >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
    >>> find_coefficients_Jakob2019(XYZ)  # doctest: +ELLIPSIS
    (array([  1.3723791...e-04,  -1.3514399...e-01,   3.0838973...e+01]), \
0.0141941...)
    """

    coefficients_0 = as_float_array(coefficients_0)

    cmfs, illuminant = handle_spectral_arguments(
        cmfs, illuminant, shape_default=SPECTRAL_SHAPE_JAKOB2019
    )

    def optimize(
        target_o: NDArray, coefficients_0_o: NDArray
    ) -> Tuple[NDArray, Floating]:
        """Minimise the error function using *L-BFGS-B* method."""

        try:
            result = minimize(
                error_function,
                coefficients_0_o,
                (target_o, cmfs, illuminant, max_error),
                method="L-BFGS-B",
                jac=True,
            )

            return result.x, result.fun
        except StopMinimizationEarly as error:
            return error.coefficients, error.error

    xy_n = XYZ_to_xy(sd_to_XYZ_integration(illuminant, cmfs))

    XYZ_good = full(3, 0.5)
    coefficients_good = zeros(3)

    divisions = 3
    while divisions < 10:
        XYZ_r = XYZ_good
        coefficient_r = coefficients_good
        keep_divisions = False

        coefficients_0 = coefficient_r
        for i in range(1, divisions):
            XYZ_i = (XYZ - XYZ_r) * i / (divisions - 1) + XYZ_r
            Lab_i = XYZ_to_Lab(XYZ_i)

            coefficients_0, error = optimize(Lab_i, coefficients_0)

            if error > max_error:
                break
            else:
                XYZ_good = XYZ_i
                coefficients_good = coefficients_0
                keep_divisions = True
        else:
            break

        if not keep_divisions:
            divisions += 2

    target = XYZ_to_Lab(XYZ, xy_n)
    coefficients, error = optimize(target, coefficients_0)

    if dimensionalise:
        coefficients = dimensionalise_coefficients(coefficients, cmfs.shape)

    return coefficients, error
Example #22
0
    def generate(
        self,
        colourspace: RGB_Colourspace,
        cmfs: Optional[MultiSpectralDistributions] = None,
        illuminant: Optional[SpectralDistribution] = None,
        size: Integer = 64,
        print_callable: Callable = print,
    ):
        """
        Generate the lookup table data for given *RGB* colourspace, colour
        matching functions, illuminant and given size.

        Parameters
        ----------
        colourspace
            The *RGB* colourspace to create a lookup table for.
        cmfs
            Standard observer colour matching functions, default to the
            *CIE 1931 2 Degree Standard Observer*.
        illuminant
            Illuminant spectral distribution, default to
            *CIE Standard Illuminant D65*.
        size
            The resolution of the lookup table. Higher values will decrease
            errors but at the cost of a much longer run time. The published
            *\\*.coeff* files have a resolution of 64.
        print_callable
            Callable used to print progress and diagnostic information.

        Examples
        --------
        >>> from colour import MSDS_CMFS, SDS_ILLUMINANTS
        >>> from colour.models import RGB_COLOURSPACE_sRGB
        >>> from colour.utilities import numpy_print_options
        >>> cmfs = (
        ...     MSDS_CMFS['CIE 1931 2 Degree Standard Observer'].
        ...     copy().align(SpectralShape(360, 780, 10))
        ... )
        >>> illuminant = SDS_ILLUMINANTS['D65'].copy().align(cmfs.shape)
        >>> LUT = LUT3D_Jakob2019()
        >>> print(LUT.interpolator)
        None
        >>> LUT.generate(RGB_COLOURSPACE_sRGB, cmfs, illuminant, 3)
        ======================================================================\
=========
        *                                                                     \
        *
        *   "Jakob et al. (2018)" LUT Optimisation                            \
        *
        *                                                                     \
        *
        ======================================================================\
=========
        <BLANKLINE>
        Optimising 27 coefficients...
        <BLANKLINE>
        >>> print(LUT.interpolator)
        ... # doctest: +ELLIPSIS
        <scipy.interpolate...RegularGridInterpolator object at 0x...>
        """

        cmfs, illuminant = handle_spectral_arguments(
            cmfs, illuminant, shape_default=SPECTRAL_SHAPE_JAKOB2019
        )
        shape = cmfs.shape

        xy_n = XYZ_to_xy(sd_to_XYZ_integration(illuminant, cmfs))

        # It could be interesting to have different resolutions for lightness
        # and chromaticity, but the current file format doesn't allow it.
        lightness_steps = size
        chroma_steps = size

        self._lightness_scale = lightness_scale(lightness_steps)
        self._coefficients = np.empty(
            [3, chroma_steps, chroma_steps, lightness_steps, 3]
        )

        cube_indexes = np.ndindex(3, chroma_steps, chroma_steps)
        total_coefficients = chroma_steps**2 * 3

        # First, create a list of all the fully bright colours with the order
        # matching cube_indexes.
        samples = np.linspace(0, 1, chroma_steps)
        ij = np.reshape(
            np.transpose(np.meshgrid([1], samples, samples, indexing="ij")),
            (-1, 3),
        )
        chromas = np.concatenate(
            [
                as_float_array(ij),
                np.roll(ij, 1, axis=1),
                np.roll(ij, 2, axis=1),
            ]
        )

        message_box(
            '"Jakob et al. (2018)" LUT Optimisation',
            print_callable=print_callable,
        )

        print_callable(f"\nOptimising {total_coefficients} coefficients...\n")

        def optimize(ijkL: ArrayLike, coefficients_0: ArrayLike) -> NDArray:
            """
            Solve for a specific lightness and stores the result in the
            appropriate cell.
            """

            i, j, k, L = tsplit(ijkL, dtype=DEFAULT_INT_DTYPE)

            RGB = self._lightness_scale[L] * chroma

            XYZ = RGB_to_XYZ(
                RGB,
                colourspace.whitepoint,
                xy_n,
                colourspace.matrix_RGB_to_XYZ,
            )

            coefficients, _error = find_coefficients_Jakob2019(
                XYZ, cmfs, illuminant, coefficients_0, dimensionalise=False
            )

            self._coefficients[i, L, j, k, :] = dimensionalise_coefficients(
                coefficients, shape
            )

            return coefficients

        with tqdm(total=total_coefficients) as progress:
            for ijk, chroma in zip(cube_indexes, chromas):
                progress.update()

                # Starts from somewhere in the middle, similarly to how
                # feedback works in "colour.recovery.\
                # find_coefficients_Jakob2019" definition.
                L_middle = lightness_steps // 3
                coefficients_middle = optimize(
                    np.hstack([ijk, L_middle]), zeros(3)
                )

                # Down the lightness scale.
                coefficients_0 = coefficients_middle
                for L in reversed(range(0, L_middle)):
                    coefficients_0 = optimize(
                        np.hstack([ijk, L]), coefficients_0
                    )

                # Up the lightness scale.
                coefficients_0 = coefficients_middle
                for L in range(L_middle + 1, lightness_steps):
                    coefficients_0 = optimize(
                        np.hstack([ijk, L]), coefficients_0
                    )

        self._size = size
        self._create_interpolator()
Example #23
0
def error_function(
    coefficients: ArrayLike,
    target: ArrayLike,
    cmfs: MultiSpectralDistributions,
    illuminant: SpectralDistribution,
    max_error: Optional[Floating] = None,
    additional_data: Boolean = False,
) -> Union[
    Tuple[Floating, NDArray],
    Tuple[Floating, NDArray, NDArray, NDArray, NDArray],
]:
    """
    Compute :math:`\\Delta E_{76}` between the target colour and the colour
    defined by given spectral model, along with its gradient.

    Parameters
    ----------
    coefficients
        Dimensionless coefficients for *Jakob and Hanika (2019)* reflectance
        spectral model.
    target
        *CIE L\\*a\\*b\\** colourspace array of the target colour.
    cmfs
        Standard observer colour matching functions.
    illuminant
        Illuminant spectral distribution.
    max_error
        Raise ``StopMinimizationEarly`` if the error is smaller than this.
        The default is *None* and the function doesn't raise anything.
    additional_data
        If *True*, some intermediate calculations are returned, for use in
        correctness tests: R, XYZ and Lab.

    Returns
    -------
    :class:`tuple` or :class:`tuple`
        Tuple of computed :math:`\\Delta E_{76}` error and gradient of error,
        i.e. the first derivatives of error with respect to the input
        coefficients or tuple of computed :math:`\\Delta E_{76}` error,
        gradient of error, computed spectral reflectance, *CIE XYZ* tristimulus
        values corresponding to ``R`` and *CIE L\\*a\\*b\\** colourspace array
        corresponding to ``XYZ``.

    Raises
    ------
    StopMinimizationEarly
        Raised when the error is below ``max_error``.
    """

    target = as_float_array(target)

    c_0, c_1, c_2 = as_float_array(coefficients)
    wv = np.linspace(0, 1, len(cmfs.shape))

    U = c_0 * wv**2 + c_1 * wv + c_2
    t1 = np.sqrt(1 + U**2)
    R = 1 / 2 + U / (2 * t1)

    t2 = 1 / (2 * t1) - U**2 / (2 * t1**3)
    dR = np.array([wv**2 * t2, wv * t2, t2])

    XYZ = sd_to_XYZ_integration(R, cmfs, illuminant, shape=cmfs.shape) / 100
    dXYZ = np.transpose(
        sd_to_XYZ_integration(dR, cmfs, illuminant, shape=cmfs.shape) / 100
    )

    XYZ_n = sd_to_XYZ_integration(illuminant, cmfs)
    XYZ_n /= XYZ_n[1]
    XYZ_XYZ_n = XYZ / XYZ_n

    XYZ_f = intermediate_lightness_function_CIE1976(XYZ, XYZ_n)
    dXYZ_f = np.where(
        XYZ_XYZ_n[..., np.newaxis] > (24 / 116) ** 3,
        1
        / (
            3
            * spow(XYZ_n[..., np.newaxis], 1 / 3)
            * spow(XYZ[..., np.newaxis], 2 / 3)
        )
        * dXYZ,
        (841 / 108) * dXYZ / XYZ_n[..., np.newaxis],
    )

    def intermediate_XYZ_to_Lab(
        XYZ_i: NDArray, offset: Optional[Floating] = 16
    ) -> NDArray:
        """
        Return the final intermediate value for the *CIE Lab* to *CIE XYZ*
        conversion.
        """

        return np.array(
            [
                116 * XYZ_i[1] - offset,
                500 * (XYZ_i[0] - XYZ_i[1]),
                200 * (XYZ_i[1] - XYZ_i[2]),
            ]
        )

    Lab_i = intermediate_XYZ_to_Lab(as_float_array(XYZ_f))
    dLab_i = intermediate_XYZ_to_Lab(as_float_array(dXYZ_f), 0)

    error = np.sqrt(np.sum((Lab_i - target) ** 2))
    if max_error is not None and error <= max_error:
        raise StopMinimizationEarly(coefficients, error)

    derror = (
        np.sum(
            dLab_i * (Lab_i[..., np.newaxis] - target[..., np.newaxis]), axis=0
        )
        / error
    )

    if additional_data:
        return error, derror, R, XYZ, Lab_i
    else:
        return error, derror