예제 #1
0
    def test_sd_to_XYZ_ASTME308_mi_10nm(self):
        """
        Test :func:`colour.colorimetry.tristimulus_values.sd_to_XYZ_ASTME308`
        definition for 10 nm measurement intervals.
        """

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(360, 830, 10)),
                self._cmfs,
                self._A,
            ),
            np.array([14.47779980, 10.86358645, 2.04751388]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(360, 830, 10)),
                self._cmfs,
                self._A,
                use_practice_range=False,
            ),
            np.array([14.47773312, 10.86353641, 2.04750445]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(400, 700, 10)),
                self._cmfs,
                self._A,
            ),
            np.array([14.54137532, 10.88641727, 2.04931318]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(400, 700, 10)),
                self._cmfs,
                self._A,
                use_practice_range=False,
            ),
            np.array([14.54167211, 10.88649849, 2.04930374]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(400, 700, 10)),
                self._cmfs,
                self._A,
                k=1,
            ),
            np.array([1568.94283333, 1174.59084705, 221.11080639]),
            decimal=7,
        )
예제 #2
0
    def setUp(self):
        """Initialise the common tests attributes."""

        # pylint: disable=E1102
        self._cmfs = reshape_msds(
            MSDS_CMFS["CIE 1931 2 Degree Standard Observer"],
            SpectralShape(360, 780, 10),
        )
        self._sd_D65 = reshape_sd(SDS_ILLUMINANTS["D65"], self._cmfs.shape)
        self._sd_E = reshape_sd(SDS_ILLUMINANTS["E"], self._cmfs.shape)
예제 #3
0
    def test_sd_to_XYZ_ASTME308_mi_1nm(self):
        """
        Test :func:`colour.colorimetry.tristimulus_values.sd_to_XYZ_ASTME308`
        definition for 1 nm measurement intervals.
        """

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(reshape_sd(self._sd, self._cmfs.shape),
                               self._cmfs, self._A),
            np.array([14.46372680, 10.85832950, 2.04663200]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, self._cmfs.shape),
                self._cmfs,
                self._A,
                use_practice_range=False,
            ),
            np.array([14.46366018, 10.85827949, 2.04662258]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(400, 700, 1)),
                self._cmfs,
                self._A,
            ),
            np.array([14.54173397, 10.88628632, 2.04965822]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(400, 700, 1)),
                self._cmfs,
                self._A,
                use_practice_range=False,
            ),
            np.array([14.54203076, 10.88636754, 2.04964877]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(400, 700, 1)),
                self._cmfs,
                self._A,
                k=1,
            ),
            np.array([1568.98152997, 1174.57671769, 221.14803420]),
            decimal=7,
        )
예제 #4
0
    def test_raise_exception_colour_fidelity_index_CFI2017(self):
        """
        Test :func:`colour.quality.CIE2017.colour_fidelity_index_CFI2017`
        definition raised exception.
        """

        sd = reshape_sd(SDS_ILLUMINANTS["FL2"], SpectralShape(400, 700, 5))
        self.assertWarns(ColourUsageWarning, colour_fidelity_index_CIE2017, sd)

        sd = reshape_sd(SDS_ILLUMINANTS["FL2"], SpectralShape(380, 780, 10))
        self.assertRaises(ValueError, colour_fidelity_index_CIE2017, sd)
예제 #5
0
    def test_handle_spectral_arguments(self):
        """
        Test :func:`colour.colorimetry.tristimulus_values.\
handle_spectral_arguments` definition.
        """

        cmfs, illuminant = handle_spectral_arguments()
        # pylint: disable=E1102
        self.assertEqual(
            cmfs,
            reshape_msds(MSDS_CMFS["CIE 1931 2 Degree Standard Observer"]),
        )
        self.assertEqual(illuminant, reshape_sd(SDS_ILLUMINANTS["D65"]))

        shape = SpectralShape(400, 700, 20)
        cmfs, illuminant = handle_spectral_arguments(shape_default=shape)
        self.assertEqual(cmfs.shape, shape)
        self.assertEqual(illuminant.shape, shape)

        cmfs, illuminant = handle_spectral_arguments(
            cmfs_default="CIE 2012 2 Degree Standard Observer",
            illuminant_default="E",
            shape_default=shape,
        )
        self.assertEqual(
            cmfs,
            reshape_msds(MSDS_CMFS["CIE 2012 2 Degree Standard Observer"],
                         shape=shape),
        )
        self.assertEqual(illuminant,
                         sd_ones(shape, interpolator=LinearInterpolator) * 100)
예제 #6
0
    def test_optimise(self):
        """Test :class:`colour.recovery.otsu2018.Tree_Otsu2018.optimise` method."""

        node_tree = Tree_Otsu2018(self._reflectances, self._cmfs, self._sd_D65)
        node_tree.optimise(iterations=5)

        dataset = node_tree.to_dataset()
        dataset.write(self._path)

        dataset = Dataset_Otsu2018()
        dataset.read(self._path)

        for sd in SDS_COLOURCHECKERS["ColorChecker N Ohta"].values():
            XYZ = sd_to_XYZ(sd, self._cmfs, self._sd_D65) / 100
            Lab = XYZ_to_Lab(XYZ, self._xy_D65)

            recovered_sd = XYZ_to_sd_Otsu2018(
                XYZ, self._cmfs, self._sd_D65, dataset, False
            )
            recovered_XYZ = (
                sd_to_XYZ(recovered_sd, self._cmfs, self._sd_D65) / 100
            )
            recovered_Lab = XYZ_to_Lab(recovered_XYZ, self._xy_D65)

            error = metric_mse(
                reshape_sd(sd, SPECTRAL_SHAPE_OTSU2018).values,
                recovered_sd.values,
            )
            self.assertLess(error, 0.075)

            delta_E = delta_E_CIE1976(Lab, recovered_Lab)
            self.assertLess(delta_E, 1e-12)
예제 #7
0
def luminous_flux(
    sd: SpectralDistribution,
    lef: Optional[SpectralDistribution] = None,
    K_m: Floating = CONSTANT_K_M,
) -> Floating:
    """
    Return the *luminous flux* for given spectral distribution using given
    luminous efficiency function.

    Parameters
    ----------
    sd
        test spectral distribution
    lef
        :math:`V(\\lambda)` luminous efficiency function, default to the
        *CIE 1924 Photopic Standard Observer*.
    K_m
        :math:`lm\\cdot W^{-1}` maximum photopic luminous efficiency.

    Returns
    -------
    :class:`numpy.floating`
        Luminous flux.

    References
    ----------
    :cite:`Wikipedia2003b`

    Examples
    --------
    >>> from colour import SDS_LIGHT_SOURCES
    >>> sd = SDS_LIGHT_SOURCES['Neodimium Incandescent']
    >>> luminous_flux(sd)  # doctest: +ELLIPSIS
    23807.6555273...
    """

    lef = cast(
        SpectralDistribution,
        optional(lef,
                 SDS_LEFS_PHOTOPIC["CIE 1924 Photopic Standard Observer"]),
    )

    lef = reshape_sd(
        lef,
        sd.shape,
        extrapolator_kwargs={
            "method": "Constant",
            "left": 0,
            "right": 0
        },
    )

    flux = K_m * np.trapz(lef.values * sd.values, sd.wavelengths)

    return as_float_scalar(flux)
예제 #8
0
    def test_raise_exception_sd_to_XYZ_ASTME308(self):
        """
        Test :func:`colour.colorimetry.tristimulus_values.sd_to_XYZ_ASTME308`
        definition raised exception.
        """

        self.assertRaises(
            ValueError,
            sd_to_XYZ_ASTME308,
            reshape_sd(self._sd, SpectralShape(360, 820, 2)),
        )
예제 #9
0
    def __init__(self):
        """Initialise common tests attributes for the mixin."""

        # pylint: disable=E1102
        self._cmfs = reshape_msds(
            MSDS_CMFS["CIE 1931 2 Degree Standard Observer"],
            SpectralShape(360, 780, 10),
        )
        self._sd_D65 = reshape_sd(SDS_ILLUMINANTS["D65"], self._cmfs.shape)
        self._xy_D65 = CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][
            "D65"
        ]
예제 #10
0
    def test_plot_colour_quality_bars(self):
        """
        Test :func:`colour.plotting.quality.plot_colour_quality_bars`
        definition.
        """

        illuminant = SDS_ILLUMINANTS["FL2"]
        light_source = SDS_LIGHT_SOURCES["Kinoton 75P"]
        light_source = reshape_sd(light_source, SpectralShape(360, 830, 1))
        cqs_i = colour_quality_scale(illuminant, additional_data=True)
        cqs_l = colour_quality_scale(light_source, additional_data=True)

        figure, axes = plot_colour_quality_bars([cqs_i, cqs_l])

        self.assertIsInstance(figure, Figure)
        self.assertIsInstance(axes, Axes)
예제 #11
0
def normalise_illuminant(
        illuminant: SpectralDistribution,
        sensitivities: RGB_CameraSensitivities) -> SpectralDistribution:
    """
    Normalise given illuminant with given camera *RGB* spectral sensitivities.

    The multiplicative inverse scaling factor :math:`k` is computed by
    multiplying the illuminant by the sensitivies channel with the maximum
    value.

    Parameters
    ----------
    illuminant
        Illuminant spectral distribution.
    sensitivities
         Camera *RGB* spectral sensitivities.

    Returns
    -------
    :class:`colour.SpectralDistribution`
        Normalised illuminant.

    Examples
    --------
    >>> path = os.path.join(
    ...     RESOURCES_DIRECTORY_RAWTOACES,
    ...     'CANON_EOS_5DMark_II_RGB_Sensitivities.csv')
    >>> sensitivities = sds_and_msds_to_msds(
    ...     read_sds_from_csv_file(path).values())
    >>> illuminant = SDS_ILLUMINANTS['D55']
    >>> np.sum(illuminant.values)  # doctest: +ELLIPSIS
    7276.1490000...
    >>> np.sum(normalise_illuminant(illuminant, sensitivities).values)
    ... # doctest: +ELLIPSIS
    3.4390373...
    """

    shape = sensitivities.shape
    if illuminant.shape != shape:
        runtime_warning(
            f'Aligning "{illuminant.name}" illuminant shape to "{shape}".')
        illuminant = reshape_sd(illuminant, shape)

    c_i = np.argmax(np.max(sensitivities.values, axis=0))
    k = 1 / np.sum(illuminant.values * sensitivities.values[..., c_i])

    return illuminant * k
예제 #12
0
def white_balance_multipliers(sensitivities: RGB_CameraSensitivities,
                              illuminant: SpectralDistribution) -> NDArray:
    """
    Compute the *RGB* white balance multipliers for given camera *RGB*
    spectral sensitivities and illuminant.

    Parameters
    ----------
    sensitivities
         Camera *RGB* spectral sensitivities.
    illuminant
        Illuminant spectral distribution.

    Returns
    -------
    :class:`numpy.ndarray`
        *RGB* white balance multipliers.

    References
    ----------
    :cite:`Dyer2017`

    Examples
    --------
    >>> path = os.path.join(
    ...     RESOURCES_DIRECTORY_RAWTOACES,
    ...     'CANON_EOS_5DMark_II_RGB_Sensitivities.csv')
    >>> sensitivities = sds_and_msds_to_msds(
    ...     read_sds_from_csv_file(path).values())
    >>> illuminant = SDS_ILLUMINANTS['D55']
    >>> white_balance_multipliers(sensitivities, illuminant)
    ... # doctest: +ELLIPSIS
    array([ 2.3414154...,  1.        ,  1.5163375...])
    """

    shape = sensitivities.shape
    if illuminant.shape != shape:
        runtime_warning(
            f'Aligning "{illuminant.name}" illuminant shape to "{shape}".')
        illuminant = reshape_sd(illuminant, shape)

    RGB_w = 1 / np.sum(
        sensitivities.values * illuminant.values[..., np.newaxis], axis=0)
    RGB_w *= 1 / np.min(RGB_w)

    return RGB_w
예제 #13
0
    def test_XYZ_to_sd_Otsu2018(self):
        """Test :func:`colour.recovery.otsu2018.XYZ_to_sd_Otsu2018` definition."""

        # Tests the round-trip with values of a colour checker.
        for _name, sd in SDS_COLOURCHECKERS["ColorChecker N Ohta"].items():
            XYZ = sd_to_XYZ(sd, self._cmfs, self._sd_D65) / 100
            Lab = XYZ_to_Lab(XYZ, self._xy_D65)

            recovered_sd = XYZ_to_sd_Otsu2018(
                XYZ, self._cmfs, self._sd_D65, clip=False
            )
            recovered_XYZ = (
                sd_to_XYZ(recovered_sd, self._cmfs, self._sd_D65) / 100
            )
            recovered_Lab = XYZ_to_Lab(recovered_XYZ, self._xy_D65)

            error = metric_mse(
                reshape_sd(sd, SPECTRAL_SHAPE_OTSU2018).values,
                recovered_sd.values,
            )
            self.assertLess(error, 0.02)

            delta_E = delta_E_CIE1976(Lab, recovered_Lab)
            self.assertLess(delta_E, 1e-12)
예제 #14
0
def spectral_similarity_index(
    sd_test: SpectralDistribution, sd_reference: SpectralDistribution
) -> NDArray:
    """
    Return the *Academy Spectral Similarity Index* (SSI) of given test
    spectral distribution with given reference spectral distribution.

    Parameters
    ----------
    sd_test
        Test spectral distribution.
    sd_reference
        Reference spectral distribution.

    Returns
    -------
    :class:`numpy.ndarray`
        *Academy Spectral Similarity Index* (SSI).

    References
    ----------
    :cite:`TheAcademyofMotionPictureArtsandSciences2019`

    Examples
    --------
    >>> from colour import SDS_ILLUMINANTS
    >>> sd_test = SDS_ILLUMINANTS['C']
    >>> sd_reference = SDS_ILLUMINANTS['D65']
    >>> spectral_similarity_index(sd_test, sd_reference)
    94.0
    """

    global _MATRIX_INTEGRATION

    if _MATRIX_INTEGRATION is None:
        _MATRIX_INTEGRATION = zeros(
            (
                len(_SPECTRAL_SHAPE_SSI_LARGE.range()),
                len(SPECTRAL_SHAPE_SSI.range()),
            )
        )

        weights = np.array([0.5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0.5])

        for i in range(_MATRIX_INTEGRATION.shape[0]):
            _MATRIX_INTEGRATION[i, (10 * i) : (10 * i + 11)] = weights

    settings = {
        "interpolator": LinearInterpolator,
        "extrapolator_kwargs": {"left": 0, "right": 0},
    }

    sd_test = reshape_sd(sd_test, SPECTRAL_SHAPE_SSI, "Align", **settings)
    sd_reference = reshape_sd(
        sd_reference, SPECTRAL_SHAPE_SSI, "Align", **settings
    )

    test_i = np.dot(_MATRIX_INTEGRATION, sd_test.values)
    reference_i = np.dot(_MATRIX_INTEGRATION, sd_reference.values)

    test_i /= np.sum(test_i)
    reference_i /= np.sum(reference_i)

    d_i = test_i - reference_i
    dr_i = d_i / (reference_i + np.mean(reference_i))
    wdr_i = dr_i * [
        12 / 45,
        22 / 45,
        32 / 45,
        40 / 45,
        44 / 45,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        1,
        11 / 15,
        3 / 15,
    ]
    c_wdr_i = convolve1d(np.hstack([0, wdr_i, 0]), [0.22, 0.56, 0.22])
    m_v = np.sum(c_wdr_i**2)

    SSI = np.around(100 - 32 * np.sqrt(m_v))

    return SSI
예제 #15
0
def colour_rendering_index(
    sd_test: SpectralDistribution, additional_data: Boolean = False
) -> Union[Floating, ColourRendering_Specification_CRI]:
    """
    Return the *Colour Rendering Index* (CRI) :math:`Q_a` of given spectral
    distribution.

    Parameters
    ----------
    sd_test
        Test spectral distribution.
    additional_data
        Whether to output additional data.

    Returns
    -------
    :class:`numpy.floating` or \
:class:`colour.quality.ColourRendering_Specification_CRI`
        *Colour Rendering Index* (CRI).

    References
    ----------
    :cite:`Ohno2008a`

    Examples
    --------
    >>> from colour import SDS_ILLUMINANTS
    >>> sd = SDS_ILLUMINANTS['FL2']
    >>> colour_rendering_index(sd)  # doctest: +ELLIPSIS
    64.2337241...
    """

    # pylint: disable=E1102
    cmfs = reshape_msds(
        MSDS_CMFS["CIE 1931 2 Degree Standard Observer"],
        SPECTRAL_SHAPE_DEFAULT,
    )

    shape = cmfs.shape
    sd_test = reshape_sd(sd_test, shape)
    tcs_sds = {sd.name: reshape_sd(sd, shape) for sd in SDS_TCS.values()}

    with domain_range_scale("1"):
        XYZ = sd_to_XYZ(sd_test, cmfs)

    uv = UCS_to_uv(XYZ_to_UCS(XYZ))
    CCT, _D_uv = uv_to_CCT_Robertson1968(uv)

    if CCT < 5000:
        sd_reference = sd_blackbody(CCT, shape)
    else:
        xy = CCT_to_xy_CIE_D(CCT)
        sd_reference = sd_CIE_illuminant_D_series(xy)
        sd_reference.align(shape)

    test_tcs_colorimetry_data = tcs_colorimetry_data(
        sd_test, sd_reference, tcs_sds, cmfs, chromatic_adaptation=True
    )

    reference_tcs_colorimetry_data = tcs_colorimetry_data(
        sd_reference, sd_reference, tcs_sds, cmfs
    )

    Q_as = colour_rendering_indexes(
        test_tcs_colorimetry_data, reference_tcs_colorimetry_data
    )

    Q_a = as_float_scalar(
        np.average(
            [v.Q_a for k, v in Q_as.items() if k in (1, 2, 3, 4, 5, 6, 7, 8)]
        )
    )

    if additional_data:
        return ColourRendering_Specification_CRI(
            sd_test.name,
            Q_a,
            Q_as,
            (test_tcs_colorimetry_data, reference_tcs_colorimetry_data),
        )
    else:
        return Q_a
예제 #16
0
    def test_sd_to_XYZ_ASTME308_mi_5nm(self):
        """
        Test :func:`colour.colorimetry.tristimulus_values.sd_to_XYZ_ASTME308`
        definition for 5 nm measurement intervals.
        """

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(360, 830, 5)),
                self._cmfs,
                self._A,
            ),
            np.array([14.46372173, 10.85832502, 2.04664734]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(360, 830, 5)),
                self._cmfs,
                self._A,
                use_practice_range=False,
            ),
            np.array([14.46366388, 10.85828159, 2.04663915]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(360, 830, 5)),
                self._cmfs,
                self._A,
                mi_5nm_omission_method=False,
            ),
            np.array([14.46373399, 10.85833553, 2.0466465]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(400, 700, 5)),
                self._cmfs,
                self._A,
            ),
            np.array([14.54025742, 10.88576251, 2.04950226]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(400, 700, 5)),
                self._cmfs,
                self._A,
                use_practice_range=False,
            ),
            np.array([14.54051517, 10.88583304, 2.04949406]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(400, 700, 5)),
                self._cmfs,
                self._A,
                mi_5nm_omission_method=False,
            ),
            np.array([14.54022093, 10.88575468, 2.04951057]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(360, 830, 5)),
                self._cmfs,
                self._A,
                use_practice_range=False,
                mi_5nm_omission_method=False,
            ),
            np.array([14.46366737, 10.85828552, 2.04663707]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(400, 700, 5)),
                self._cmfs,
                self._A,
                use_practice_range=False,
                mi_5nm_omission_method=False,
            ),
            np.array([14.54051772, 10.88583590, 2.04950113]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(400, 700, 5)),
                self._cmfs,
                self._A,
                k=1,
            ),
            np.array([1568.82479013, 1174.52212708, 221.13156963]),
            decimal=7,
        )
예제 #17
0
def RGB_2_degree_cmfs_to_XYZ_2_degree_cmfs(
    wavelength: FloatingOrArrayLike, ) -> NDArray:
    """
    Convert *Wright & Guild 1931 2 Degree RGB CMFs* colour matching functions
    into the *CIE 1931 2 Degree Standard Observer* colour matching functions.

    Parameters
    ----------
    wavelength
        Wavelength :math:`\\lambda` in nm.

    Returns
    -------
    :class:`numpy.ndarray`
        *CIE 1931 2 Degree Standard Observer* spectral tristimulus values.

    Notes
    -----
    -   Data for the *CIE 1931 2 Degree Standard Observer* already exists,
        this definition is intended for educational purpose.

    References
    ----------
    :cite:`Wyszecki2000bg`

    Examples
    --------
    >>> from colour.utilities import numpy_print_options
    >>> with numpy_print_options(suppress=True):
    ...     RGB_2_degree_cmfs_to_XYZ_2_degree_cmfs(700)  # doctest: +ELLIPSIS
    array([ 0.0113577...,  0.004102  ,  0.        ])
    """

    cmfs = MSDS_CMFS_RGB["Wright & Guild 1931 2 Degree RGB CMFs"]

    rgb_bar = cmfs[wavelength]

    rgb = rgb_bar / np.sum(rgb_bar)

    M1 = np.array([
        [0.49000, 0.31000, 0.20000],
        [0.17697, 0.81240, 0.01063],
        [0.00000, 0.01000, 0.99000],
    ])

    M2 = np.array([
        [0.66697, 1.13240, 1.20063],
        [0.66697, 1.13240, 1.20063],
        [0.66697, 1.13240, 1.20063],
    ])

    xyz = vector_dot(M1, rgb)
    xyz /= vector_dot(M2, rgb)

    x, y, z = xyz[..., 0], xyz[..., 1], xyz[..., 2]

    V = reshape_sd(SDS_LEFS_PHOTOPIC["CIE 1924 Photopic Standard Observer"],
                   cmfs.shape)
    L = V[wavelength]

    x_bar = x / y * L
    y_bar = L
    z_bar = z / y * L

    xyz_bar = tstack([x_bar, y_bar, z_bar])

    return xyz_bar
예제 #18
0
    def test_sd_to_XYZ_tristimulus_weighting_factors_ASTME308(self):
        """
        Test :func:`colour.colorimetry.tristimulus_values.\
sd_to_XYZ_tristimulus_weighting_factors_ASTME308`
        definition.
        """

        cmfs = MSDS_CMFS["CIE 1931 2 Degree Standard Observer"]
        np.testing.assert_almost_equal(
            sd_to_XYZ_tristimulus_weighting_factors_ASTME308(
                SD_SAMPLE, cmfs, SDS_ILLUMINANTS["A"]),
            np.array([14.46341867, 10.85820227, 2.04697034]),
            decimal=7,
        )

        cmfs = MSDS_CMFS["CIE 1964 10 Degree Standard Observer"]
        np.testing.assert_almost_equal(
            sd_to_XYZ_tristimulus_weighting_factors_ASTME308(
                SD_SAMPLE, cmfs, SDS_ILLUMINANTS["C"]),
            np.array([10.77005571, 9.44877491, 6.62428210]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_tristimulus_weighting_factors_ASTME308(
                SD_SAMPLE, cmfs, SDS_ILLUMINANTS["FL2"]),
            np.array([11.57542759, 9.98605604, 3.95273304]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_tristimulus_weighting_factors_ASTME308(
                reshape_sd(SD_SAMPLE, SpectralShape(400, 700, 5), "Trim"),
                cmfs,
                SDS_ILLUMINANTS["A"],
            ),
            np.array([14.38153638, 10.74503131, 2.01613844]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_tristimulus_weighting_factors_ASTME308(
                reshape_sd(SD_SAMPLE, SpectralShape(400, 700, 10),
                           "Interpolate"),
                cmfs,
                SDS_ILLUMINANTS["A"],
            ),
            np.array([14.38257202, 10.74568178, 2.01588427]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_tristimulus_weighting_factors_ASTME308(
                reshape_sd(SD_SAMPLE, SpectralShape(400, 700, 20),
                           "Interpolate"),
                cmfs,
                SDS_ILLUMINANTS["A"],
            ),
            np.array([14.38329645, 10.74603515, 2.01561113]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_tristimulus_weighting_factors_ASTME308(
                reshape_sd(SD_SAMPLE, SpectralShape(400, 700, 20),
                           "Interpolate"),
                cmfs,
                SDS_ILLUMINANTS["A"],
                k=1,
            ),
            np.array([1636.74881983, 1222.84626486, 229.36669308]),
            decimal=7,
        )
예제 #19
0
    def test_tristimulus_weighting_factors_ASTME2022(self):
        """
        Test :func:`colour.colorimetry.tristimulus_values.\
tristimulus_weighting_factors_ASTME2022` definition.

        Notes
        -----
        -   :attr:`TWF_A_CIE_1964_10_10`, :attr:`TWF_A_CIE_1964_10_20` and
            :attr:`TWF_D65_CIE_1931_2_20` attributes data is matching
            :cite:`ASTMInternational2015b`.

        References
        ----------
        :cite:`ASTMInternational2015b`
        """

        cmfs = MSDS_CMFS["CIE 1964 10 Degree Standard Observer"]
        A = sd_CIE_standard_illuminant_A(cmfs.shape)

        twf = tristimulus_weighting_factors_ASTME2022(
            cmfs, A, SpectralShape(360, 830, 10))
        np.testing.assert_almost_equal(np.round(twf, 3),
                                       TWF_A_CIE_1964_10_10,
                                       decimal=3)

        twf = tristimulus_weighting_factors_ASTME2022(
            cmfs, A, SpectralShape(360, 830, 20))
        np.testing.assert_almost_equal(np.round(twf, 3),
                                       TWF_A_CIE_1964_10_20,
                                       decimal=3)

        cmfs = MSDS_CMFS["CIE 1931 2 Degree Standard Observer"]
        D65 = reshape_sd(SDS_ILLUMINANTS["D65"],
                         cmfs.shape,
                         interpolator=LinearInterpolator)
        twf = tristimulus_weighting_factors_ASTME2022(
            cmfs, D65, SpectralShape(360, 830, 20))
        np.testing.assert_almost_equal(np.round(twf, 3),
                                       TWF_D65_CIE_1931_2_20,
                                       decimal=3)

        twf = tristimulus_weighting_factors_ASTME2022(cmfs,
                                                      D65,
                                                      SpectralShape(
                                                          360, 830, 20),
                                                      k=1)
        np.testing.assert_almost_equal(twf,
                                       TWF_D65_CIE_1931_2_20_K1,
                                       decimal=7)

        # Testing that the cache returns a copy of the data.
        cmfs = MSDS_CMFS["CIE 1964 10 Degree Standard Observer"]
        twf = tristimulus_weighting_factors_ASTME2022(
            cmfs, A, SpectralShape(360, 830, 10))
        np.testing.assert_almost_equal(np.round(twf, 3),
                                       TWF_A_CIE_1964_10_10,
                                       decimal=3)

        np.testing.assert_almost_equal(
            np.round(
                tristimulus_weighting_factors_ASTME2022(
                    cmfs, A, SpectralShape(360, 830, 10)),
                3,
            ),
            TWF_A_CIE_1964_10_10,
            decimal=3,
        )
예제 #20
0
def sd_to_aces_relative_exposure_values(
    sd: SpectralDistribution,
    illuminant: Optional[SpectralDistribution] = None,
    apply_chromatic_adaptation: Boolean = False,
    chromatic_adaptation_transform: Union[Literal["Bianco 2010",
                                                  "Bianco PC 2010", "Bradford",
                                                  "CAT02 Brill 2008", "CAT02",
                                                  "CAT16", "CMCCAT2000",
                                                  "CMCCAT97", "Fairchild",
                                                  "Sharp", "Von Kries",
                                                  "XYZ Scaling", ],
                                          str, ] = "CAT02",
) -> NDArray:
    """
    Convert given spectral distribution to *ACES2065-1* colourspace relative
    exposure values.

    Parameters
    ----------
    sd
        Spectral distribution.
    illuminant
        *Illuminant* spectral distribution, default to
        *CIE Standard Illuminant D65*.
    apply_chromatic_adaptation
        Whether to apply chromatic adaptation using given transform.
    chromatic_adaptation_transform
        *Chromatic adaptation* transform.

    Returns
    -------
    :class:`numpy.ndarray`
        *ACES2065-1* colourspace relative exposure values array.

    Notes
    -----
    +------------+-----------------------+---------------+
    | **Range**  | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``XYZ``    | [0, 100]              | [0, 1]        |
    +------------+-----------------------+---------------+

    -   The chromatic adaptation method implemented here is a bit unusual
        as it involves building a new colourspace based on *ACES2065-1*
        colourspace primaries but using the whitepoint of the illuminant that
        the spectral distribution was measured under.

    References
    ----------
    :cite:`Forsythe2018`,
    :cite:`TheAcademyofMotionPictureArtsandSciences2014q`,
    :cite:`TheAcademyofMotionPictureArtsandSciences2014r`,
    :cite:`TheAcademyofMotionPictureArtsandSciencese`

    Examples
    --------
    >>> from colour import SDS_COLOURCHECKERS
    >>> sd = SDS_COLOURCHECKERS['ColorChecker N Ohta']['dark skin']
    >>> sd_to_aces_relative_exposure_values(sd)  # doctest: +ELLIPSIS
    array([ 0.1171814...,  0.0866360...,  0.0589726...])
    >>> sd_to_aces_relative_exposure_values(sd,
    ...     apply_chromatic_adaptation=True)  # doctest: +ELLIPSIS
    array([ 0.1180779...,  0.0869031...,  0.0589125...])
    """

    illuminant = cast(SpectralDistribution,
                      optional(illuminant, SDS_ILLUMINANTS["D65"]))

    shape = MSDS_ACES_RICD.shape
    if sd.shape != MSDS_ACES_RICD.shape:
        sd = reshape_sd(sd, shape)

    if illuminant.shape != MSDS_ACES_RICD.shape:
        illuminant = reshape_sd(illuminant, shape)

    s_v = sd.values
    i_v = illuminant.values

    r_bar, g_bar, b_bar = tsplit(MSDS_ACES_RICD.values)

    def k(x: NDArray, y: NDArray) -> NDArray:
        """Compute the :math:`K_r`, :math:`K_g` or :math:`K_b` scale factors."""

        return 1 / np.sum(x * y)

    k_r = k(i_v, r_bar)
    k_g = k(i_v, g_bar)
    k_b = k(i_v, b_bar)

    E_r = k_r * np.sum(i_v * s_v * r_bar)
    E_g = k_g * np.sum(i_v * s_v * g_bar)
    E_b = k_b * np.sum(i_v * s_v * b_bar)

    E_rgb = np.array([E_r, E_g, E_b])

    # Accounting for flare.
    E_rgb += FLARE_PERCENTAGE
    E_rgb *= S_FLARE_FACTOR

    if apply_chromatic_adaptation:
        xy = XYZ_to_xy(sd_to_XYZ(illuminant) / 100)
        NPM = normalised_primary_matrix(RGB_COLOURSPACE_ACES2065_1.primaries,
                                        xy)
        XYZ = RGB_to_XYZ(
            E_rgb,
            xy,
            RGB_COLOURSPACE_ACES2065_1.whitepoint,
            NPM,
            chromatic_adaptation_transform,
        )
        E_rgb = XYZ_to_RGB(
            XYZ,
            RGB_COLOURSPACE_ACES2065_1.whitepoint,
            RGB_COLOURSPACE_ACES2065_1.whitepoint,
            RGB_COLOURSPACE_ACES2065_1.matrix_XYZ_to_RGB,
        )

    return from_range_1(E_rgb)
예제 #21
0
    def setUp(self):
        """Initialise the common tests attributes."""

        self._cmfs = MSDS_CMFS["CIE 1931 2 Degree Standard Observer"]
        self._A = sd_CIE_standard_illuminant_A(self._cmfs.shape)
        self._sd = reshape_sd(SD_SAMPLE, self._cmfs.shape)
예제 #22
0
    def test_sd_to_XYZ_ASTME308_mi_20nm(self):
        """
        Test :func:`colour.colorimetry.tristimulus_values.sd_to_XYZ_ASTME308`
        definition for 20 nm measurement intervals.
        """

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(360, 820, 20)),
                self._cmfs,
                self._A,
            ),
            np.array([14.50187464, 10.87217124, 2.04918305]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(360, 820, 20)),
                self._cmfs,
                self._A,
                use_practice_range=False,
            ),
            np.array([14.50180785, 10.87212116, 2.04917361]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(360, 820, 20)),
                self._cmfs,
                self._A,
                mi_20nm_interpolation_method=False,
            ),
            np.array([14.50216194, 10.87236873, 2.04977256]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(400, 700, 20)),
                self._cmfs,
                self._A,
            ),
            np.array([14.54114025, 10.88634755, 2.04916445]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(400, 700, 20)),
                self._cmfs,
                self._A,
                use_practice_range=False,
            ),
            np.array([14.54143704, 10.88642877, 2.04915501]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(400, 700, 20)),
                self._cmfs,
                self._A,
                mi_20nm_interpolation_method=False,
            ),
            np.array([14.54242562, 10.88694088, 2.04919645]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(360, 820, 20)),
                self._cmfs,
                self._A,
                use_practice_range=False,
                mi_20nm_interpolation_method=False,
            ),
            np.array([14.50209515, 10.87231865, 2.04976312]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(400, 700, 20)),
                self._cmfs,
                self._A,
                use_practice_range=False,
                mi_20nm_interpolation_method=False,
            ),
            np.array([14.54272240, 10.88702210, 2.04918701]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            sd_to_XYZ_ASTME308(
                reshape_sd(self._sd, SpectralShape(400, 700, 20)),
                self._cmfs,
                self._A,
                k=1,
            ),
            np.array([1568.91747040, 1174.58332427, 221.09475945]),
            decimal=7,
        )
예제 #23
0
def training_data_sds_to_RGB(
    training_data: MultiSpectralDistributions,
    sensitivities: RGB_CameraSensitivities,
    illuminant: SpectralDistribution,
) -> Tuple[NDArray, NDArray]:
    """
    Convert given training data to *RGB* tristimulus values using given
    illuminant and given camera *RGB* spectral sensitivities.

    Parameters
    ----------
    training_data
        Training data multi-spectral distributions.
    sensitivities
         Camera *RGB* spectral sensitivities.
    illuminant
        Illuminant spectral distribution.

    Returns
    -------
    :class:`tuple`
        Tuple of training data *RGB* tristimulus values and white balance
        multipliers.

    Examples
    --------
    >>> path = os.path.join(
    ...     RESOURCES_DIRECTORY_RAWTOACES,
    ...     'CANON_EOS_5DMark_II_RGB_Sensitivities.csv')
    >>> sensitivities = sds_and_msds_to_msds(
    ...     read_sds_from_csv_file(path).values())
    >>> illuminant = normalise_illuminant(
    ...     SDS_ILLUMINANTS['D55'], sensitivities)
    >>> training_data = read_training_data_rawtoaces_v1()
    >>> RGB, RGB_w = training_data_sds_to_RGB(
    ...     training_data, sensitivities, illuminant)
    >>> RGB[:5]  # doctest: +ELLIPSIS
    array([[ 0.0207582...,  0.0196857...,  0.0213935...],
           [ 0.0895775...,  0.0891922...,  0.0891091...],
           [ 0.7810230...,  0.7801938...,  0.7764302...],
           [ 0.1995   ...,  0.1995   ...,  0.1995   ...],
           [ 0.5898478...,  0.5904015...,  0.5851076...]])
    >>> RGB_w  # doctest: +ELLIPSIS
    array([ 2.3414154...,  1.        ,  1.5163375...])
    """

    shape = sensitivities.shape
    if illuminant.shape != shape:
        runtime_warning(
            f'Aligning "{illuminant.name}" illuminant shape to "{shape}".')
        illuminant = reshape_sd(illuminant, shape)

    if training_data.shape != shape:
        runtime_warning(
            f'Aligning "{training_data.name}" training data shape to "{shape}".'
        )
        # pylint: disable=E1102
        training_data = reshape_msds(training_data, shape)

    RGB_w = white_balance_multipliers(sensitivities, illuminant)

    RGB = np.dot(
        np.transpose(illuminant.values[..., np.newaxis] *
                     training_data.values),
        sensitivities.values,
    )

    RGB *= RGB_w

    return RGB, RGB_w
예제 #24
0
def colour_quality_scale(
    sd_test: SpectralDistribution,
    additional_data: Boolean = False,
    method: Union[Literal["NIST CQS 7.4", "NIST CQS 9.0"],
                  str] = "NIST CQS 9.0",
) -> Union[Floating, ColourRendering_Specification_CQS]:
    """
    Return the *Colour Quality Scale* (CQS) of given spectral distribution
    using given method.

    Parameters
    ----------
    sd_test
        Test spectral distribution.
    additional_data
        Whether to output additional data.
    method
        Computation method.

    Returns
    -------
    :class:`numpy.floating` or \
:class:`colour.quality.ColourRendering_Specification_CQS`
        *Colour Quality Scale* (CQS).

    References
    ----------
    :cite:`Davis2010a`, :cite:`Ohno2008a`, :cite:`Ohno2013`

    Examples
    --------
    >>> from colour import SDS_ILLUMINANTS
    >>> sd = SDS_ILLUMINANTS['FL2']
    >>> colour_quality_scale(sd)  # doctest: +ELLIPSIS
    64.1117031...
    """

    method = validate_method(method, COLOUR_QUALITY_SCALE_METHODS)

    # pylint: disable=E1102
    cmfs = reshape_msds(
        MSDS_CMFS["CIE 1931 2 Degree Standard Observer"],
        SPECTRAL_SHAPE_DEFAULT,
    )

    shape = cmfs.shape
    sd_test = reshape_sd(sd_test, shape)
    vs_sds = {sd.name: reshape_sd(sd, shape) for sd in SDS_VS[method].values()}

    with domain_range_scale("1"):
        XYZ = sd_to_XYZ(sd_test, cmfs)

    uv = UCS_to_uv(XYZ_to_UCS(XYZ))
    CCT, _D_uv = uv_to_CCT_Ohno2013(uv)

    if CCT < 5000:
        sd_reference = sd_blackbody(CCT, shape)
    else:
        xy = CCT_to_xy_CIE_D(CCT)
        sd_reference = sd_CIE_illuminant_D_series(xy)
        sd_reference.align(shape)

    test_vs_colorimetry_data = vs_colorimetry_data(sd_test,
                                                   sd_reference,
                                                   vs_sds,
                                                   cmfs,
                                                   chromatic_adaptation=True)

    reference_vs_colorimetry_data = vs_colorimetry_data(
        sd_reference, sd_reference, vs_sds, cmfs)

    CCT_f: Floating
    if method == "nist cqs 9.0":
        CCT_f = 1
        scaling_f = 3.2
    else:
        XYZ_r = sd_to_XYZ(sd_reference, cmfs)
        XYZ_r /= XYZ_r[1]
        CCT_f = CCT_factor(reference_vs_colorimetry_data, XYZ_r)
        scaling_f = 3.104

    Q_as = colour_quality_scales(
        test_vs_colorimetry_data,
        reference_vs_colorimetry_data,
        scaling_f,
        CCT_f,
    )

    D_E_RMS = delta_E_RMS(Q_as, "D_E_ab")
    D_Ep_RMS = delta_E_RMS(Q_as, "D_Ep_ab")

    Q_a = scale_conversion(D_Ep_RMS, CCT_f, scaling_f)

    if method == "nist cqs 9.0":
        scaling_f = 2.93 * 1.0343
    else:
        scaling_f = 2.928

    Q_f = scale_conversion(D_E_RMS, CCT_f, scaling_f)

    G_t = gamut_area(
        [vs_CQS_data.Lab for vs_CQS_data in test_vs_colorimetry_data])
    G_r = gamut_area(
        [vs_CQS_data.Lab for vs_CQS_data in reference_vs_colorimetry_data])

    Q_g = G_t / GAMUT_AREA_D65 * 100

    if method == "nist cqs 9.0":
        Q_p = Q_d = None
    else:
        p_delta_C = np.average([
            sample_data.D_C_ab if sample_data.D_C_ab > 0 else 0
            for sample_data in Q_as.values()
        ])
        Q_p = as_float_scalar(100 - 3.6 * (D_Ep_RMS - p_delta_C))
        Q_d = as_float_scalar(G_t / G_r * CCT_f * 100)

    if additional_data:
        return ColourRendering_Specification_CQS(
            sd_test.name,
            Q_a,
            Q_f,
            Q_p,
            Q_g,
            Q_d,
            Q_as,
            (test_vs_colorimetry_data, reference_vs_colorimetry_data),
        )
    else:
        return Q_a
예제 #25
0
def training_data_sds_to_XYZ(
    training_data: MultiSpectralDistributions,
    cmfs: MultiSpectralDistributions,
    illuminant: SpectralDistribution,
    chromatic_adaptation_transform: Union[Literal["Bianco 2010",
                                                  "Bianco PC 2010", "Bradford",
                                                  "CAT02 Brill 2008", "CAT02",
                                                  "CAT16", "CMCCAT2000",
                                                  "CMCCAT97", "Fairchild",
                                                  "Sharp", "Von Kries",
                                                  "XYZ Scaling", ],
                                          str, ] = "CAT02",
) -> NDArray:
    """
    Convert given training data to *CIE XYZ* tristimulus values using given
    illuminant and given standard observer colour matching functions.

    Parameters
    ----------
    training_data
        Training data multi-spectral distributions.
    cmfs
        Standard observer colour matching functions.
    illuminant
        Illuminant spectral distribution.
    chromatic_adaptation_transform
        *Chromatic adaptation* transform, if *None* no chromatic adaptation is
        performed.

    Returns
    -------
    :class:`numpy.ndarray`
        Training data *CIE XYZ* tristimulus values.

    Examples
    --------
    >>> from colour import MSDS_CMFS
    >>> path = os.path.join(
    ...     RESOURCES_DIRECTORY_RAWTOACES,
    ...     'CANON_EOS_5DMark_II_RGB_Sensitivities.csv')
    >>> cmfs = MSDS_CMFS['CIE 1931 2 Degree Standard Observer']
    >>> sensitivities = sds_and_msds_to_msds(
    ...     read_sds_from_csv_file(path).values())
    >>> illuminant = normalise_illuminant(
    ...     SDS_ILLUMINANTS['D55'], sensitivities)
    >>> training_data = read_training_data_rawtoaces_v1()
    >>> training_data_sds_to_XYZ(training_data, cmfs, illuminant)[:5]
    ... # doctest: +ELLIPSIS
    array([[ 0.0174353...,  0.0179504...,  0.0196109...],
           [ 0.0855607...,  0.0895735...,  0.0901703...],
           [ 0.7455880...,  0.7817549...,  0.7834356...],
           [ 0.1900528...,  0.1995   ...,  0.2012606...],
           [ 0.5626319...,  0.5914544...,  0.5894500...]])
    """

    shape = cmfs.shape
    if illuminant.shape != shape:
        runtime_warning(
            f'Aligning "{illuminant.name}" illuminant shape to "{shape}".')
        illuminant = reshape_sd(illuminant, shape)

    if training_data.shape != shape:
        runtime_warning(
            f'Aligning "{training_data.name}" training data shape to "{shape}".'
        )
        # pylint: disable=E1102
        training_data = reshape_msds(training_data, shape)

    XYZ = np.dot(
        np.transpose(illuminant.values[..., np.newaxis] *
                     training_data.values),
        cmfs.values,
    )

    XYZ *= 1 / np.sum(cmfs.values[..., 1] * illuminant.values)

    XYZ_w = np.dot(np.transpose(cmfs.values), illuminant.values)
    XYZ_w *= 1 / XYZ_w[1]

    if chromatic_adaptation_transform is not None:
        M_CAT = matrix_chromatic_adaptation_VonKries(
            XYZ_w,
            xy_to_XYZ(RGB_COLOURSPACE_ACES2065_1.whitepoint),
            chromatic_adaptation_transform,
        )

        XYZ = vector_dot(M_CAT, XYZ)

    return XYZ