Exemplo n.º 1
0
    def test_nan_uv_to_CCT_Ohno2013(self):
        """
        Test :func:`colour.temperature.ohno2013.uv_to_CCT_Ohno2013` definition
        nan support.
        """

        cases = [-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]
        cases = set(permutations(cases * 3, r=2))
        for case in cases:
            uv = np.array(case)
            uv_to_CCT_Ohno2013(uv)
Exemplo n.º 2
0
    def test_nan_uv_to_CCT_Ohno2013(self):
        """
        Tests :func:`colour.temperature.cct.uv_to_CCT_Ohno2013` definition nan
        support.
        """

        cases = [-1.0, 0.0, 1.0, -np.inf, np.inf, np.nan]
        cases = set(permutations(cases * 3, r=2))
        for case in cases:
            uv = np.array(case)
            uv_to_CCT_Ohno2013(uv)
Exemplo n.º 3
0
def CCT_reference_illuminant(sd: SpectralDistribution) -> NDArray:
    """
    Compute the reference illuminant correlated colour temperature
    :math:`T_{cp}` and :math:`\\Delta_{uv}` for given test spectral
    distribution using *Ohno (2013)* method.

    Parameters
    ----------
    sd
        Test spectral distribution.

    Returns
    -------
    :class:`numpy.ndarray`
        Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`.

    Examples
    --------
    >>> from colour import SDS_ILLUMINANTS
    >>> sd = SDS_ILLUMINANTS['FL2']
    >>> CCT_reference_illuminant(sd)  # doctest: +ELLIPSIS
    array([  4.2244697...e+03,   1.7871111...e-03])
    """

    XYZ = sd_to_XYZ(sd)

    return uv_to_CCT_Ohno2013(UCS_to_uv(XYZ_to_UCS(XYZ)))
Exemplo n.º 4
0
def CCT_reference_illuminant(sd):
    """
    Computes the reference illuminant correlated colour temperature
    :math:`T_{cp}` and :math:`\\Delta_{uv}` for given test spectral
    distribution using *Ohno (2013)* method.

    Parameters
    ----------
    sd : SpectralDistribution
        Test spectral distribution.

    Returns
    -------
    ndarray
        Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`.

    Examples
    --------
    >>> from colour import SDS_ILLUMINANTS
    >>> sd = SDS_ILLUMINANTS['FL2']
    >>> CCT_reference_illuminant(sd)  # doctest: +ELLIPSIS
    (4224.4697052..., 0.0017871...)
    """

    XYZ = sd_to_XYZ(sd)

    CCT, D_uv = uv_to_CCT_Ohno2013(UCS_to_uv(XYZ_to_UCS(XYZ)))

    return CCT, D_uv
Exemplo n.º 5
0
    def test_n_dimensional_uv_to_CCT_Ohno2013(self):
        """
        Tests :func:`colour.temperature.cct.uv_to_CCT_Ohno2013` definition
        n-dimensional arrays support.
        """

        uv = np.array([0.1978, 0.3122])
        CCT_D_uv = uv_to_CCT_Ohno2013(uv)

        uv = np.tile(uv, (6, 1))
        CCT_D_uv = np.tile(CCT_D_uv, (6, 1))
        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(uv), CCT_D_uv, decimal=7)

        uv = np.reshape(uv, (2, 3, 2))
        CCT_D_uv = np.reshape(CCT_D_uv, (2, 3, 2))
        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(uv), CCT_D_uv, decimal=7)
Exemplo n.º 6
0
    def test_uv_to_CCT_Ohno2013(self):
        """
        Tests :func:`colour.temperature.cct.uv_to_CCT_Ohno2013` definition.
        """

        cmfs = STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer']
        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.1978, 0.3122]), cmfs),
            np.array([6507.47380460, 0.00322335]),
            decimal=7)

        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.4328, 0.2883]), cmfs),
            np.array([1041.68315360, -0.06737802]),
            decimal=7)

        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.2927, 0.2722]), cmfs, iterations=4),
            np.array([2452.15316417, -0.08437064]),
            decimal=7)
Exemplo n.º 7
0
    def test_n_dimensional_uv_to_CCT_Ohno2013(self):
        """
        Test :func:`colour.temperature.ohno2013.uv_to_CCT_Ohno2013` definition
        n-dimensional arrays support.
        """

        uv = np.array([0.1978, 0.3122])
        CCT_D_uv = uv_to_CCT_Ohno2013(uv)

        uv = np.tile(uv, (6, 1))
        CCT_D_uv = np.tile(CCT_D_uv, (6, 1))
        np.testing.assert_almost_equal(uv_to_CCT_Ohno2013(uv),
                                       CCT_D_uv,
                                       decimal=7)

        uv = np.reshape(uv, (2, 3, 2))
        CCT_D_uv = np.reshape(CCT_D_uv, (2, 3, 2))
        np.testing.assert_almost_equal(uv_to_CCT_Ohno2013(uv),
                                       CCT_D_uv,
                                       decimal=7)
Exemplo n.º 8
0
    def test_uv_to_CCT_Ohno2013(self):
        """
        Tests :func:`colour.temperature.cct.uv_to_CCT_Ohno2013` definition.
        """

        cmfs = STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer']
        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.1978, 0.3122]), cmfs),
            np.array([6507.51282029, 0.00322336]),
            decimal=7)

        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.4328, 0.2883]), cmfs),
            np.array([1041.68315360, -0.06737802]),
            decimal=7)

        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.2927, 0.2722]), cmfs, iterations=4),
            np.array([2452.15316417, -0.08437064]),
            decimal=7)
Exemplo n.º 9
0
    def test_uv_to_CCT_Ohno2013(self):
        """
        Tests :func:`colour.temperature.cct.uv_to_CCT_Ohno2013` definition.
        """

        cmfs = STANDARD_OBSERVERS_CMFS.get(
            'CIE 1931 2 Degree Standard Observer')
        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.1978, 0.3122]), cmfs),
            np.array([6507.5470349001507, 0.0032236908012382953]),
            decimal=7)

        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.4328, 0.2883]), cmfs),
            np.array([1041.8672179878763, -0.067377582642145384]),
            decimal=7)

        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.2927, 0.2722]), cmfs, iterations=4),
            np.array([2452.1932942782669, -0.084369982045528508]),
            decimal=7)
Exemplo n.º 10
0
    def test_uv_to_CCT_Ohno2013(self):
        """
        Tests :func:`colour.temperature.cct.uv_to_CCT_Ohno2013` definition.
        """

        cmfs = STANDARD_OBSERVERS_CMFS.get(
            'CIE 1931 2 Degree Standard Observer')
        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.1978, 0.3122]), cmfs),
            np.array([6507.5470349001507, 0.0032236908012382953]),
            decimal=7)

        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.4328, 0.2883]), cmfs),
            np.array([1041.8672179878763, -0.067377582642145384]),
            decimal=7)

        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.2927, 0.2722]), cmfs, iterations=4),
            np.array([2452.1932942782669, -0.084369982045528508]),
            decimal=7)
Exemplo n.º 11
0
    def test_uv_to_CCT_Ohno2013(self):
        """
        Test :func:`colour.temperature.ohno2013.uv_to_CCT_Ohno2013`
        definition.
        """

        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.1978, 0.3122])),
            np.array([6507.47380460, 0.00322335]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.4328, 0.2883])),
            np.array([1041.68315360, -0.06737802]),
            decimal=7,
        )

        np.testing.assert_almost_equal(
            uv_to_CCT_Ohno2013(np.array([0.2927, 0.2722]), iterations=4),
            np.array([2452.15316417, -0.08437064]),
            decimal=7,
        )
Exemplo n.º 12
0
def colour_quality_scale(spd_test, additional_data=False):
    """
    Returns the *colour quality scale* of given spectral power distribution.

    Parameters
    ----------
    spd_test : SpectralPowerDistribution
        Test spectral power distribution.
    additional_data : bool, optional
        Output additional data.

    Returns
    -------
    numeric or CQS_Specification
        Color quality scale.

    Examples
    --------
    >>> from colour import ILLUMINANTS_RELATIVE_SPDS
    >>> spd = ILLUMINANTS_RELATIVE_SPDS.get('F2')
    >>> colour_quality_scale(spd)  # doctest: +ELLIPSIS
    64.6781117...
    """

    cmfs = STANDARD_OBSERVERS_CMFS.get(
        'CIE 1931 2 Degree Standard Observer')

    shape = cmfs.shape

    XYZ = spectral_to_XYZ(spd_test, cmfs)
    uv = UCS_to_uv(XYZ_to_UCS(XYZ))
    CCT, _D_uv = uv_to_CCT_Ohno2013(uv)

    if CCT < 5000:
        spd_reference = blackbody_spd(CCT, shape)
    else:
        xy = CCT_to_xy_CIE_D(CCT)
        spd_reference = D_illuminant_relative_spd(xy)
        spd_reference.align(shape)

    test_vs_colorimetry_data = vs_colorimetry_data(
        spd_test,
        spd_reference,
        VS_SPDS,
        cmfs,
        chromatic_adaptation=True)

    reference_vs_colorimetry_data = vs_colorimetry_data(
        spd_reference,
        spd_reference,
        VS_SPDS,
        cmfs)

    XYZ_r = spectral_to_XYZ(spd_reference, cmfs)
    XYZ_r /= XYZ_r[1]
    CCT_f = CCT_factor(reference_vs_colorimetry_data, XYZ_r)

    Q_as = colour_quality_scales(
        test_vs_colorimetry_data, reference_vs_colorimetry_data, 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)
    Q_f = scale_conversion(D_E_RMS, CCT_f, 2.928)

    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 = 100 - 3.6 * (D_Ep_RMS - p_delta_C)

    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 / D65_GAMUT_AREA * 100
    Q_d = G_t / G_r * CCT_f * 100

    if additional_data:
        return CQS_Specification(spd_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
Exemplo n.º 13
0
def colour_quality_scale(sd_test,
                         additional_data=False,
                         method='NIST CQS 9.0'):
    """
    Returns the *Colour Quality Scale* (CQS) of given spectral distribution
    using given method.

    Parameters
    ----------
    sd_test : SpectralDistribution
        Test spectral distribution.
    additional_data : bool, optional
        Whether to output additional data.
    method : unicode, optional
        **{'NIST CQS 9.0', 'NIST CQS 7.4'}**,
        Computation method.

    Returns
    -------
    numeric or CQS_Specification
        Color quality scale.

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

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

    method = method.lower()
    assert method.lower() in [
        m.lower() for m in COLOUR_QUALITY_SCALE_METHODS
    ], ('"{0}" method is invalid, must be one of {1}!'.format(
        method, COLOUR_QUALITY_SCALE_METHODS))

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

    shape = cmfs.shape
    sd_test = sd_test.copy().align(shape)
    vs_sds = {
        sd.name: sd.copy().align(shape)
        for sd in VS_SDS[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)

    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 / D65_GAMUT_AREA * 100

    if method == 'nist cqs 9.0':
        Q_d = Q_p = 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 = 100 - 3.6 * (D_Ep_RMS - p_delta_C)
        Q_d = G_t / G_r * CCT_f * 100

    if additional_data:
        return CQS_Specification(
            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
Exemplo n.º 14
0
def colour_quality_scale(spd_test, additional_data=False):
    """
    Returns the *Colour Quality Scale* (CQS) of given spectral power
    distribution.

    Parameters
    ----------
    spd_test : SpectralPowerDistribution
        Test spectral power distribution.
    additional_data : bool, optional
        Output additional data.

    Returns
    -------
    numeric or CQS_Specification
        Color quality scale.

    Examples
    --------
    >>> from colour import ILLUMINANTS_RELATIVE_SPDS
    >>> spd = ILLUMINANTS_RELATIVE_SPDS['F2']
    >>> colour_quality_scale(spd)  # doctest: +ELLIPSIS
    64.6864169...
    """

    cmfs = STANDARD_OBSERVERS_CMFS[
        'CIE 1931 2 Degree Standard Observer'].clone().trim_wavelengths(
            ASTME30815_PRACTISE_SHAPE)

    shape = cmfs.shape
    spd_test = spd_test.clone().align(shape)
    vs_spds = {spd.name: spd.clone().align(shape) for spd in VS_SPDS.values()}

    XYZ = spectral_to_XYZ(spd_test, cmfs)
    uv = UCS_to_uv(XYZ_to_UCS(XYZ))
    CCT, _D_uv = uv_to_CCT_Ohno2013(uv)

    if CCT < 5000:
        spd_reference = blackbody_spd(CCT, shape)
    else:
        xy = CCT_to_xy_CIE_D(CCT)
        spd_reference = D_illuminant_relative_spd(xy)
        spd_reference.align(shape)

    test_vs_colorimetry_data = vs_colorimetry_data(spd_test,
                                                   spd_reference,
                                                   vs_spds,
                                                   cmfs,
                                                   chromatic_adaptation=True)

    reference_vs_colorimetry_data = vs_colorimetry_data(
        spd_reference, spd_reference, vs_spds, cmfs)

    XYZ_r = spectral_to_XYZ(spd_reference, cmfs)
    XYZ_r /= XYZ_r[1]
    CCT_f = CCT_factor(reference_vs_colorimetry_data, XYZ_r)

    Q_as = colour_quality_scales(test_vs_colorimetry_data,
                                 reference_vs_colorimetry_data, 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)
    Q_f = scale_conversion(D_E_RMS, CCT_f, 2.928)

    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()])  # yapf: disable
    Q_p = 100 - 3.6 * (D_Ep_RMS - p_delta_C)

    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 / D65_GAMUT_AREA * 100
    Q_d = G_t / G_r * CCT_f * 100

    if additional_data:
        return CQS_Specification(
            spd_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
Exemplo n.º 15
0
def colour_quality_scale(sd_test, additional_data=False,
                         method='NIST CQS 9.0'):
    """
    Returns the *Colour Quality Scale* (CQS) of given spectral distribution
    using given method.

    Parameters
    ----------
    sd_test : SpectralDistribution
        Test spectral distribution.
    additional_data : bool, optional
        Whether to output additional data.
    method : unicode, optional
        **{'NIST CQS 9.0', 'NIST CQS 7.4'}**,
        Computation method.

    Returns
    -------
    numeric or CQS_Specification
        Color quality scale.

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

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

    method = method.lower()
    assert method.lower() in [
        m.lower() for m in COLOUR_QUALITY_SCALE_METHODS
    ], ('"{0}" method is invalid, must be one of {1}!'.format(
        method, COLOUR_QUALITY_SCALE_METHODS))

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

    shape = cmfs.shape
    sd_test = sd_test.copy().align(shape)
    vs_sds = {
        sd.name: sd.copy().align(shape)
        for sd in VS_SDS[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)

    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 / D65_GAMUT_AREA * 100

    if method == 'nist cqs 9.0':
        Q_d = Q_p = 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 = 100 - 3.6 * (D_Ep_RMS - p_delta_C)
        Q_d = G_t / G_r * CCT_f * 100

    if additional_data:
        return CQS_Specification(
            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
Exemplo n.º 16
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