Beispiel #1
0
def luminous_efficacy(sd: SpectralDistribution,
                      lef: Optional[SpectralDistribution] = None) -> Floating:
    """
    Return the *luminous efficacy* in :math:`lm\\cdot W^{-1}` of 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*.

    Returns
    -------
    :class:`numpy.floating`
        Luminous efficacy in :math:`lm\\cdot W^{-1}`.

    References
    ----------
    :cite:`Wikipedia2005c`

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

    efficacy = CONSTANT_K_M * luminous_efficiency(sd, lef)

    return as_float_scalar(efficacy)
Beispiel #2
0
def colour_rendering_indexes(
    test_data: Tuple[TCS_ColorimetryData, ...],
    reference_data: Tuple[TCS_ColorimetryData, ...],
) -> Dict[Integer, TCS_ColourQualityScaleData]:
    """
    Return the *test colour samples* rendering indexes :math:`Q_a`.

    Parameters
    ----------
    test_data
        Test data.
    reference_data
        Reference data.

    Returns
    -------
    :class:`dict`
        *Test colour samples* *Colour Rendering Index* (CRI).
    """

    Q_as = {}
    for i in range(len(test_data)):
        Q_as[i + 1] = TCS_ColourQualityScaleData(
            test_data[i].name,
            100
            - 4.6
            * as_float_scalar(
                euclidean_distance(reference_data[i].UVW, test_data[i].UVW)
            ),
        )

    return Q_as
Beispiel #3
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)
Beispiel #4
0
    def bandwidth_FWHM(self, value: Optional[Floating]):
        """Setter for the **self.bandwidth_FWHM** property."""

        if value is not None:
            attest(
                is_numeric(value),
                f'"bandwidth_FWHM" property: "{value}" is not a "number"!',
            )

            value = as_float_scalar(value)

        self._bandwidth_FWHM = value
Beispiel #5
0
def colour_quality_scales(
    test_data: Tuple[VS_ColorimetryData, ...],
    reference_data: Tuple[VS_ColorimetryData, ...],
    scaling_f: Floating,
    CCT_f: Floating,
) -> Dict[Integer, VS_ColourQualityScaleData]:
    """
    Return the *VS test colour samples* rendering scales.

    Parameters
    ----------
    test_data
        Test data.
    reference_data
        Reference data.
    scaling_f
        Scaling factor constant.
    CCT_f
        Factor penalizing lamps with extremely low correlated colour
        temperatures.

    Returns
    -------
    :class:`dict`
        *VS Test colour samples* colour rendering scales.
    """

    Q_as = {}
    for i in range(len(test_data)):
        D_C_ab = test_data[i].C - reference_data[i].C
        D_E_ab = as_float_scalar(
            euclidean_distance(test_data[i].Lab, reference_data[i].Lab))

        if D_C_ab > 0:
            D_Ep_ab = np.sqrt(D_E_ab**2 - D_C_ab**2)
        else:
            D_Ep_ab = D_E_ab

        Q_a = scale_conversion(D_Ep_ab, CCT_f, scaling_f)

        Q_as[i + 1] = VS_ColourQualityScaleData(test_data[i].name, Q_a, D_C_ab,
                                                D_E_ab, D_Ep_ab)
    return Q_as
Beispiel #6
0
def uv_to_UCS(uv: ArrayLike, V: Floating = 1) -> NDArray:
    """
    Return the *CIE 1960 UCS* colourspace array from given *uv* chromaticity
    coordinates.

    Parameters
    ----------
    uv
        *uv* chromaticity coordinates.
    V
        Optional :math:`V` *luminance* value used to construct the
        *CIE 1960 UCS* colourspace array, the default :math:`V` *luminance* is
        set to 1.

    Returns
    -------
    :class:`numpy.ndarray`
        *CIE 1960 UCS* colourspace array.

    References
    ----------
    :cite:`Wikipedia2008c`

    Examples
    --------
    >>> import numpy as np
    >>> uv = np.array([0.37720213, 0.33413508])
    >>> uv_to_UCS(uv)  # doctest: +ELLIPSIS
    array([ 1.1288911...,  1.        ,  0.8639104...])
    """

    u, v = tsplit(uv)
    V = as_float_scalar(to_domain_1(V))

    UVW = tstack([V * u / v, full(u.shape, V), -V * (u + v - 1) / v])

    return from_range_1(UVW)
Beispiel #7
0
def cctf_decoding_RIMMRGB(
    X_p: Union[FloatingOrArrayLike, IntegerOrArrayLike],
    bit_depth: Integer = 8,
    in_int: Boolean = False,
    E_clip: Floating = 2.0,
) -> FloatingOrNDArray:
    """
    Define the *RIMM RGB* decoding colour component transfer function
    (Encoding CCTF).

    Parameters
    ----------
    X_p
        Non-linear data :math:`X'_{RIMM}`.
    bit_depth
        Bit depth used for conversion.
    in_int
        Whether to treat the input value as integer code value or float
        equivalent of a code value at a given bit depth.
    E_clip
        Maximum exposure level.

    Returns
    -------
    :class:`numpy.floating` or :class:`numpy.ndarray`
        Linear data :math:`X_{RIMM}`.

    Notes
    -----
    +----------------+-----------------------+---------------+
    | **Domain \\***  | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``X_p``        | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    +----------------+-----------------------+---------------+
    | **Range \\***   | **Scale - Reference** | **Scale - 1** |
    +================+=======================+===============+
    | ``X``          | [0, 1]                | [0, 1]        |
    +----------------+-----------------------+---------------+

    \\* This definition has an input integer switch, thus the domain-range
    scale information is only given for the floating point mode.

    References
    ----------
    :cite:`Spaulding2000b`

    Examples
    --------
    >>> cctf_decoding_RIMMRGB(0.291673732475746)  # doctest: +ELLIPSIS
    0.1...
    >>> cctf_decoding_RIMMRGB(74, in_int=True)  # doctest: +ELLIPSIS
    0.1...
    """

    X_p = to_domain_1(X_p)

    I_max = as_float_scalar(2**bit_depth - 1)

    if not in_int:
        X_p = X_p * I_max

    V_clip = 1.099 * spow(E_clip, 0.45) - 0.099

    m = V_clip * X_p / I_max

    with domain_range_scale("ignore"):
        X = np.where(
            X_p / I_max
            < cctf_encoding_RIMMRGB(0.018, bit_depth, E_clip=E_clip),
            m / 4.5,
            spow((m + 0.099) / 1.099, 1 / 0.45),
        )

    return as_float(from_range_1(X))
Beispiel #8
0
def hull_section(
    hull: trimesh.Trimesh,  # type: ignore[name-defined]  # noqa
    axis: Union[Literal["+z", "+x", "+y"], str] = "+z",
    origin: Floating = 0.5,
    normalise: Boolean = False,
) -> NDArray:
    """
    Compute the hull section for given axis at given origin.

    Parameters
    ----------
    hull
        *Trimesh* hull.
    axis
        Axis the hull section will be normal to.
    origin
        Coordinate along ``axis`` at which to plot the hull section.
    normalise
        Whether to normalise ``axis`` to the extent of the hull along it.

    Returns
    -------
    :class:`numpy.ndarray`
        Hull section vertices.

    Examples
    --------
    >>> from colour.geometry import primitive_cube
    >>> from colour.utilities import is_trimesh_installed
    >>> vertices, faces, outline = primitive_cube(1, 1, 1, 2, 2, 2)
    >>> if is_trimesh_installed:
    ...     import trimesh
    ...     hull = trimesh.Trimesh(vertices['position'], faces, process=False)
    ...     hull_section(hull, origin=0)
    array([[-0. , -0.5,  0. ],
           [ 0.5, -0.5,  0. ],
           [ 0.5,  0. , -0. ],
           [ 0.5,  0.5, -0. ],
           [-0. ,  0.5,  0. ],
           [-0.5,  0.5,  0. ],
           [-0.5,  0. , -0. ],
           [-0.5, -0.5, -0. ],
           [-0. , -0.5,  0. ]])
    """

    import trimesh

    axis = validate_method(
        axis,
        ["+z", "+x", "+y"],
        '"{0}" axis is invalid, it must be one of {1}!',
    )

    if axis == "+x":
        normal, plane = np.array([1, 0, 0]), np.array([origin, 0, 0])
    elif axis == "+y":
        normal, plane = np.array([0, 1, 0]), np.array([0, origin, 0])
    elif axis == "+z":
        normal, plane = np.array([0, 0, 1]), np.array([0, 0, origin])

    if normalise:
        vertices = hull.vertices * normal
        origin = as_float_scalar(
            linear_conversion(
                origin, [0, 1],
                [np.min(vertices), np.max(vertices)]))
        plane[plane != 0] = origin

    section = trimesh.intersections.mesh_plane(hull, normal, plane)
    if len(section) == 0:
        raise ValueError(
            f'No section exists on "{axis}" axis at {origin} origin!')
    section = close_chord(unique_vertices(edges_to_chord(section)))

    return section
Beispiel #9
0
 def __init__(self, coefficients: ArrayLike, error: Floating):
     self._coefficients = as_float_array(coefficients)
     self._error = as_float_scalar(error)
Beispiel #10
0
def uv_to_Luv(
    uv: ArrayLike,
    illuminant: ArrayLike = CCS_ILLUMINANTS[
        "CIE 1931 2 Degree Standard Observer"]["D65"],
    Y: Floating = 1,
) -> NDArray:
    """
    Return the *CIE L\\*u\\*v\\** colourspace array from given :math:`uv^p`
    chromaticity coordinates by extending the array last dimension with given
    :math:`L` *Lightness*.

    Parameters
    ----------
    uv
        :math:`uv^p` chromaticity coordinates.
    illuminant
        Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY*
        colourspace array.
    Y
        Optional :math:`Y` *luminance* value used to construct the intermediate
        *CIE XYZ* colourspace array, the default :math:`Y` *luminance* value is
        1.

    Returns
    -------
    :class:`numpy.ndarray`
        *CIE L\\*u\\*v\\** colourspace array.

    Notes
    -----
    +----------------+-----------------------+-----------------+
    | **Range**      | **Scale - Reference** | **Scale - 1**   |
    +================+=======================+=================+
    | ``Luv``        | ``L`` : [0, 100]      | ``L`` : [0, 1]  |
    |                |                       |                 |
    |                | ``u`` : [-100, 100]   | ``u`` : [-1, 1] |
    |                |                       |                 |
    |                | ``v`` : [-100, 100]   | ``v`` : [-1, 1] |
    +----------------+-----------------------+-----------------+
    | ``illuminant`` | [0, 1]                | [0, 1]          |
    +----------------+-----------------------+-----------------+

    References
    ----------
    :cite:`CIETC1-482004j`

    Examples
    --------
    >>> import numpy as np
    >>> uv = np.array([0.37720213, 0.50120264])
    >>> uv_to_Luv(uv)  # doctest: +ELLIPSIS
    array([ 100.        ,  233.1837603...,   42.7474385...])
    """

    u, v = tsplit(uv)
    Y = as_float_scalar(to_domain_1(Y))

    X = 9 * u / (4 * v)
    Z = (-5 * Y * v - 3 * u / 4 + 3) / v

    XYZ = tstack([X, full(u.shape, Y), Z])

    return XYZ_to_Luv(from_range_1(XYZ), illuminant)
Beispiel #11
0
def colour_fidelity_index_CIE2017(
    sd_test: SpectralDistribution,
    additional_data: Boolean = False
) -> Union[Floating, ColourRendering_Specification_CIE2017]:
    """
    Return the *CIE 2017 Colour Fidelity Index* (CFI) :math:`R_f` 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_CIE2017`
        *CIE 2017 Colour Fidelity Index* (CFI) :math:`R_f`.

    References
    ----------
    :cite:`CIETC1-902017`

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

    if sd_test.shape.start > 380 or sd_test.shape.end < 780:
        usage_warning("Test spectral distribution shape does not span the"
                      "recommended 380-780nm range, missing values will be"
                      "filled with zeros!")

        # NOTE: "CIE 2017 Colour Fidelity Index" standard recommends filling
        # missing values with zeros.
        sd_test = cast(SpectralDistribution, sd_test.copy())
        sd_test.extrapolator = Extrapolator
        sd_test.extrapolator_kwargs = {
            "method": "constant",
            "left": 0,
            "right": 0,
        }

    if sd_test.shape.interval > 5:
        raise ValueError("Test spectral distribution interval is greater than"
                         "5nm which is the maximum recommended value "
                         'for computing the "CIE 2017 Colour Fidelity Index"!')

    shape = SpectralShape(
        SPECTRAL_SHAPE_CIE2017.start,
        SPECTRAL_SHAPE_CIE2017.end,
        sd_test.shape.interval,
    )

    CCT, D_uv = tsplit(CCT_reference_illuminant(sd_test))
    sd_reference = sd_reference_illuminant(CCT, shape)

    # NOTE: All computations except CCT calculation use the
    # "CIE 1964 10 Degree Standard Observer".
    # pylint: disable=E1102
    cmfs_10 = reshape_msds(MSDS_CMFS["CIE 1964 10 Degree Standard Observer"],
                           shape)

    # pylint: disable=E1102
    sds_tcs = reshape_msds(load_TCS_CIE2017(shape), shape)

    test_tcs_colorimetry_data = tcs_colorimetry_data(sd_test, sds_tcs, cmfs_10)
    reference_tcs_colorimetry_data = tcs_colorimetry_data(
        sd_reference, sds_tcs, cmfs_10)

    delta_E_s = np.empty(len(sds_tcs.labels))
    for i, _delta_E in enumerate(delta_E_s):
        delta_E_s[i] = euclidean_distance(
            test_tcs_colorimetry_data[i].Jpapbp,
            reference_tcs_colorimetry_data[i].Jpapbp,
        )

    R_s = as_float_array(delta_E_to_R_f(delta_E_s))
    R_f = as_float_scalar(delta_E_to_R_f(np.average(delta_E_s)))

    if additional_data:
        return ColourRendering_Specification_CIE2017(
            sd_test.name,
            sd_reference,
            R_f,
            R_s,
            CCT,
            D_uv,
            (test_tcs_colorimetry_data, reference_tcs_colorimetry_data),
            delta_E_s,
        )
    else:
        return R_f
Beispiel #12
0
    def test_as_float_scalar(self):
        """Test :func:`colour.utilities.array.as_float_scalar` definition."""

        self.assertEqual(as_float_scalar(1), 1.0)

        self.assertEqual(as_float_scalar(1).dtype, DEFAULT_FLOAT_DTYPE)
Beispiel #13
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
Beispiel #14
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
Beispiel #15
0
def xy_to_xyY(xy: ArrayLike, Y: Floating = 1) -> NDArray:
    """
    Convert from *CIE xy* chromaticity coordinates to *CIE xyY* colourspace by
    extending the array last dimension with given :math:`Y` *luminance*.

    ``xy`` argument with last dimension being equal to 3 will be assumed to be
    a *CIE xyY* colourspace array argument and will be returned directly by the
    definition.

    Parameters
    ----------
    xy
        *CIE xy* chromaticity coordinates or *CIE xyY* colourspace array.
    Y
        Optional :math:`Y` *luminance* value used to construct the *CIE xyY*
        colourspace array, the default :math:`Y` *luminance* value is 1.

    Returns
    -------
    :class:`numpy.ndarray`
        *CIE xyY* colourspace array.

    Notes
    -----
    +------------+-----------------------+---------------+
    | **Domain** | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``xy``     | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    +------------+-----------------------+---------------+
    | **Range**  | **Scale - Reference** | **Scale - 1** |
    +============+=======================+===============+
    | ``xyY``    | [0, 1]                | [0, 1]        |
    +------------+-----------------------+---------------+

    -   This definition is a convenient object provided to implement support of
        illuminant argument *luminance* value in various :mod:`colour.models`
        package objects such as :func:`colour.Lab_to_XYZ` or
        :func:`colour.Luv_to_XYZ`.

    References
    ----------
    :cite:`Wikipedia2005`

    Examples
    --------
    >>> xy = np.array([0.54369557, 0.32107944])
    >>> xy_to_xyY(xy)  # doctest: +ELLIPSIS
    array([ 0.5436955...,  0.3210794...,  1.        ])
    >>> xy = np.array([0.54369557, 0.32107944, 1.00000000])
    >>> xy_to_xyY(xy)  # doctest: +ELLIPSIS
    array([ 0.5436955...,  0.3210794...,  1.        ])
    >>> xy = np.array([0.54369557, 0.32107944])
    >>> xy_to_xyY(xy, 100)  # doctest: +ELLIPSIS
    array([   0.5436955...,    0.3210794...,  100.        ])
    """

    xy = as_float_array(xy)
    Y = as_float_scalar(to_domain_1(Y))

    # Assuming ``xy`` is actually a *CIE xyY* colourspace array argument and
    # returning it directly.
    if xy.shape[-1] == 3:
        return xy

    x, y = tsplit(xy)

    xyY = tstack([x, y, full(x.shape, Y)])

    return from_range_1(xyY, np.array([1, 1, 100]))
Beispiel #16
0
def convert_experiment_results_Breneman1987(
    experiment: Literal[1, 2, 3, 4, 6, 8, 9, 11, 12]
) -> CorrespondingColourDataset:
    """
    Convert *Breneman (1987)* experiment results to a
    :class:`colour.CorrespondingColourDataset` class instance.

    Parameters
    ----------
    experiment
        *Breneman (1987)* experiment number.

    Returns
    -------
    :class:`colour.CorrespondingColourDataset`
        :class:`colour.CorrespondingColourDataset` class instance.

    Examples
    --------
    >>> from pprint import pprint
    >>> pprint(tuple(convert_experiment_results_Breneman1987(2)))
    ... # doctest: +ELLIPSIS
    (2,
     array([ 0.9582463...,  1.        ,  0.9436325...]),
     array([ 0.9587332...,  1.        ,  0.4385796...]),
     array([[ 388.125     ,  405.        ,  345.625     ],
           [ 266.8957925...,  135.        ,   28.5983365...],
           [ 474.5717821...,  405.        ,  222.75     ...],
           [ 538.3899082...,  405.        ,   24.8944954...],
           [ 178.7430167...,  135.        ,   19.6089385...],
           [ 436.6749547...,  405.        ,   26.5483725...],
           [ 124.7746282...,  135.        ,   36.1965613...],
           [  77.0794172...,  135.        ,   60.5850563...],
           [ 279.9390889...,  405.        ,  455.8395127...],
           [ 149.5808157...,  135.        ,  498.7046827...],
           [ 372.1113689...,  405.        ,  669.9883990...],
           [ 212.3638968...,  135.        ,  414.6704871...]]),
     array([[ 400.1039651...,  405.        ,  191.7287234...],
           [ 271.0384615...,  135.        ,   13.5      ...],
           [ 495.4705323...,  405.        ,  119.7290874...],
           [ 580.7967033...,  405.        ,    6.6758241...],
           [ 190.1933701...,  135.        ,    7.4585635...],
           [ 473.7184115...,  405.        ,   10.2346570...],
           [ 135.4936014...,  135.        ,   20.2376599...],
           [  86.4689781...,  135.        ,   35.2281021...],
           [ 283.5396281...,  405.        ,  258.1775929...],
           [ 119.7044335...,  135.        ,  282.6354679...],
           [ 359.9532224...,  405.        ,  381.0031185...],
           [ 181.8271461...,  135.        ,  204.0661252...]]),
     1500.0,
     1500.0,
     0.3,
     0.3,
     {})
    """

    valid_experiment_results = [1, 2, 3, 4, 6, 8, 9, 11, 12]
    attest(
        experiment in valid_experiment_results,
        f'"Breneman (1987)" experiment result is invalid, it must be one of '
        f'"{valid_experiment_results}"!',
    )

    samples_luminance = [
        0.270,
        0.090,
        0.270,
        0.270,
        0.090,
        0.270,
        0.090,
        0.090,
        0.270,
        0.090,
        0.270,
        0.090,
    ]

    experiment_results = list(BRENEMAN_EXPERIMENTS[experiment])
    illuminant_chromaticities = experiment_results.pop(0)
    Y_r = Y_t = as_float(
        BRENEMAN_EXPERIMENT_PRIMARIES_CHROMATICITIES[experiment].Y)
    B_r = B_t = 0.3

    XYZ_t, XYZ_r = (xy_to_XYZ(
        np.hstack([
            Luv_uv_to_xy(illuminant_chromaticities[1:3]),
            full((2, 1), as_float_scalar(Y_r)),
        ])) / Y_r)

    xyY_cr, xyY_ct = [], []
    for i, experiment_result in enumerate(experiment_results):
        xyY_cr.append(
            np.hstack([
                Luv_uv_to_xy(experiment_result[2]),
                samples_luminance[i] * Y_r,
            ]))
        xyY_ct.append(
            np.hstack([
                Luv_uv_to_xy(experiment_result[1]),
                samples_luminance[i] * Y_t,
            ]))

    XYZ_cr = xyY_to_XYZ(xyY_cr)
    XYZ_ct = xyY_to_XYZ(xyY_ct)

    return CorrespondingColourDataset(experiment, XYZ_r, XYZ_t, XYZ_cr, XYZ_ct,
                                      Y_r, Y_t, B_r, B_t, {})