예제 #1
0
 def __init__(
     self,
     name: Optional[str] = None,
     comments: Optional[Sequence[str]] = None,
 ):
     self._name = f"LUT Sequence Operator {id(self)}"
     self.name = optional(name, self._name)
     # TODO: Remove pragma when https://github.com/python/mypy/issues/3004
     # is resolved.
     self._comments: List[str] = []
     self.comments = optional(comments,
                              self._comments)  # type: ignore[arg-type]
예제 #2
0
def get_attribute(attribute: str) -> Any:
    """
    Return given attribute value.

    Parameters
    ----------
    attribute
        Attribute to retrieve, ``attribute`` must have a namespace module, e.g.
        *colour.models.eotf_BT2020*.

    Returns
    -------
    :class:`object`
        Retrieved attribute value.

    Examples
    --------
    >>> get_attribute('colour.models.eotf_BT2020')  # doctest: +ELLIPSIS
    <function eotf_BT2020 at 0x...>
    """

    attest("." in attribute, '"{0}" attribute has no namespace!')

    module_name, attribute = attribute.rsplit(".", 1)

    module = optional(sys.modules.get(module_name), import_module(module_name))

    attest(
        module is not None,
        f'"{module_name}" module does not exists or cannot be imported!',
    )

    return attrgetter(attribute)(module)
예제 #3
0
def _UCS_Luo2006_callable_to_UCS_Li2017_docstring(callable_: Callable) -> str:
    """
    Convert given *Luo et al. (2006)* callable docstring to
    *Li et al. (2017)* docstring.

    Parameters
    ----------
    callable_
        Callable to use the docstring from.

    Returns
    -------
    :class:`str`
        Docstring.
    """

    docstring = callable_.__doc__
    # NOTE: Required for optimised python launch.
    docstring = optional(docstring, "")

    docstring = docstring.replace("Luo et al. (2006)", "Li et al. (2017)")
    docstring = docstring.replace("CIECAM02", "CAM16")
    docstring = docstring.replace("CAM02", "CAM16")
    docstring = docstring.replace("Luo2006b", "Li2017")

    match = re.match("(.*)Examples", docstring, re.DOTALL)
    if match is not None:
        docstring = match.group(1)

    return docstring
예제 #4
0
    def __init__(
        self,
        matrix: Optional[ArrayLike] = None,
        offset: Optional[ArrayLike] = None,
        *args: Any,
        **kwargs: Any,
    ):
        super().__init__(*args, **kwargs)

        # TODO: Remove pragma when https://github.com/python/mypy/issues/3004
        # is resolved.
        self._matrix: NDArray = np.diag(ones(4))
        self.matrix = cast(ArrayLike,
                           optional(matrix,
                                    self._matrix))  # type: ignore[assignment]
        self._offset: NDArray = zeros(4)
        self.offset = cast(ArrayLike,
                           optional(offset,
                                    self._offset))  # type: ignore[assignment]
예제 #5
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)
예제 #6
0
    def __init__(
        self,
        interpolator: Optional[TypeInterpolator] = None,
        method: Union[Literal["Linear", "Constant"], str] = "Linear",
        left: Optional[Number] = None,
        right: Optional[Number] = None,
        dtype: Optional[Type[DTypeNumber]] = None,
    ):
        dtype = cast(Type[DTypeNumber], optional(dtype, DEFAULT_FLOAT_DTYPE))

        self._interpolator: TypeInterpolator = NullInterpolator(
            np.array([-np.inf, np.inf]), np.array([-np.inf, np.inf]))
        self.interpolator = optional(interpolator, self._interpolator)
        self._method: Union[Literal["Linear", "Constant"], str] = "Linear"
        self.method = cast(
            Union[Literal["Linear", "Constant"], str],
            optional(method, self._method),
        )
        self._right: Optional[Number] = None
        self.right = right
        self._left: Optional[Number] = None
        self.left = left

        self._dtype: Type[DTypeNumber] = dtype
예제 #7
0
def sd_to_XYZ(
    sd: Union[ArrayLike, SpectralDistribution, MultiSpectralDistributions],
    cmfs: Optional[MultiSpectralDistributions] = None,
    illuminant: Optional[SpectralDistribution] = None,
    k: Optional[Number] = None,
    method: Union[Literal["ASTM E308", "Integration"], str] = "ASTM E308",
    **kwargs: Any,
) -> NDArray:
    """
    Convert given spectral distribution to *CIE XYZ* tristimulus values using
    given colour matching functions, illuminant and method.

    This placeholder docstring is replaced with the modified
    :func:`colour.sd_to_XYZ` definition docstring.
    """

    illuminant = cast(
        SpectralDistribution,
        optional(illuminant, SDS_ILLUMINANTS[_ILLUMINANT_DEFAULT]),
    )

    return colour.sd_to_XYZ(sd, cmfs, illuminant, k, method, **kwargs)
예제 #8
0
    def test_read(self, sd: Optional[SpectralDistribution] = None):
        """
        Test :meth:`colour.io.tm2714.SpectralDistribution_IESTM2714.read`
        method.

        Parameters
        ----------
        sd
            Optional *IES TM-27-14* spectral distribution for read tests.
        """

        sd = cast(
            SpectralDistribution_IESTM2714,
            optional(
                sd,
                SpectralDistribution_IESTM2714(
                    os.path.join(RESOURCES_DIRECTORY,
                                 "Fluorescent.spdx")).read(),
            ),
        )

        sd_r = SpectralDistribution(FLUORESCENT_FILE_SPECTRAL_DATA)

        np.testing.assert_array_equal(sd_r.domain, sd.domain)
        np.testing.assert_almost_equal(sd_r.values, sd.values, decimal=7)

        test_read: List[Tuple[Dict,
                              Union[Header_IESTM2714,
                                    SpectralDistribution_IESTM2714]]] = [
                                        (FLUORESCENT_FILE_HEADER, sd.header),
                                        (FLUORESCENT_FILE_SPECTRAL_DESCRIPTION,
                                         sd),
                                    ]
        for test, read in test_read:
            for key, value in test.items():
                for specification in read.mapping.elements:
                    if key == specification.element:
                        self.assertEqual(
                            getattr(read, specification.attribute), value)
예제 #9
0
def XYZ_to_sd_Jakob2019(
    XYZ: ArrayLike,
    cmfs: Optional[MultiSpectralDistributions] = None,
    illuminant: Optional[SpectralDistribution] = None,
    optimisation_kwargs: Optional[Dict] = None,
    additional_data: Boolean = False,
) -> Union[Tuple[SpectralDistribution, Floating], SpectralDistribution]:
    """
    Recover the spectral distribution of given RGB colourspace array
    using *Jakob and Hanika (2019)* method.

    Parameters
    ----------
    XYZ
        *CIE XYZ* tristimulus values to recover the spectral distribution from.
    cmfs
        Standard observer colour matching functions, default to the
        *CIE 1931 2 Degree Standard Observer*.
    illuminant
        Illuminant spectral distribution, default to
        *CIE Standard Illuminant D65*.
    optimisation_kwargs
        Parameters for :func:`colour.recovery.find_coefficients_Jakob2019`
        definition.
    additional_data
        If *True*, ``error`` will be returned alongside the recovered spectral
        distribution.

    Returns
    -------
    :class:`tuple` or :class:`colour.SpectralDistribution`
        Tuple of recovered spectral distribution and :math:`\\Delta E_{76}`
        between the target colour and the colour corresponding to the computed
        coefficients or recovered spectral distribution.

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

    Examples
    --------
    >>> from colour import (
    ...     CCS_ILLUMINANTS, MSDS_CMFS, SDS_ILLUMINANTS, XYZ_to_sRGB)
    >>> from colour.colorimetry import sd_to_XYZ_integration
    >>> from colour.utilities import numpy_print_options
    >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
    >>> cmfs = (
    ...     MSDS_CMFS['CIE 1931 2 Degree Standard Observer'].
    ...     copy().align(SpectralShape(360, 780, 10))
    ... )
    >>> illuminant = SDS_ILLUMINANTS['D65'].copy().align(cmfs.shape)
    >>> sd = XYZ_to_sd_Jakob2019(XYZ, cmfs, illuminant)
    >>> with numpy_print_options(suppress=True):
    ...     sd  # doctest: +ELLIPSIS
    SpectralDistribution([[ 360.        ,    0.4893773...],
                          [ 370.        ,    0.3258214...],
                          [ 380.        ,    0.2147792...],
                          [ 390.        ,    0.1482413...],
                          [ 400.        ,    0.1086169...],
                          [ 410.        ,    0.0841255...],
                          [ 420.        ,    0.0683114...],
                          [ 430.        ,    0.0577144...],
                          [ 440.        ,    0.0504267...],
                          [ 450.        ,    0.0453552...],
                          [ 460.        ,    0.0418520...],
                          [ 470.        ,    0.0395259...],
                          [ 480.        ,    0.0381430...],
                          [ 490.        ,    0.0375741...],
                          [ 500.        ,    0.0377685...],
                          [ 510.        ,    0.0387432...],
                          [ 520.        ,    0.0405871...],
                          [ 530.        ,    0.0434783...],
                          [ 540.        ,    0.0477225...],
                          [ 550.        ,    0.0538256...],
                          [ 560.        ,    0.0626314...],
                          [ 570.        ,    0.0755869...],
                          [ 580.        ,    0.0952675...],
                          [ 590.        ,    0.1264265...],
                          [ 600.        ,    0.1779272...],
                          [ 610.        ,    0.2649393...],
                          [ 620.        ,    0.4039779...],
                          [ 630.        ,    0.5832105...],
                          [ 640.        ,    0.7445440...],
                          [ 650.        ,    0.8499970...],
                          [ 660.        ,    0.9094792...],
                          [ 670.        ,    0.9425378...],
                          [ 680.        ,    0.9616376...],
                          [ 690.        ,    0.9732481...],
                          [ 700.        ,    0.9806562...],
                          [ 710.        ,    0.9855873...],
                          [ 720.        ,    0.9889903...],
                          [ 730.        ,    0.9914117...],
                          [ 740.        ,    0.9931801...],
                          [ 750.        ,    0.9945009...],
                          [ 760.        ,    0.9955066...],
                          [ 770.        ,    0.9962855...],
                          [ 780.        ,    0.9968976...]],
                         interpolator=SpragueInterpolator,
                         interpolator_kwargs={},
                         extrapolator=Extrapolator,
                         extrapolator_kwargs={...})
    >>> sd_to_XYZ_integration(sd, cmfs, illuminant) / 100  # doctest: +ELLIPSIS
    array([ 0.2066217...,  0.1220128...,  0.0513958...])
    """

    XYZ = to_domain_1(XYZ)

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

    optimisation_kwargs = optional(optimisation_kwargs, {})

    with domain_range_scale("ignore"):
        coefficients, error = find_coefficients_Jakob2019(
            XYZ, cmfs, illuminant, **optimisation_kwargs
        )

    sd = sd_Jakob2019(coefficients, cmfs.shape)
    sd.name = f"{XYZ} (XYZ) - Jakob (2019)"

    if additional_data:
        return sd, error
    else:
        return sd
예제 #10
0
 def __init__(self, name: Optional[str] = None):
     self._name: str = f"{self.__class__.__name__} ({id(self)})"
     self.name = optional(name, self._name)
예제 #11
0
def mesopic_weighting_function(
    wavelength: FloatingOrArrayLike,
    L_p: Floating,
    source: Union[Literal["Blue Heavy", "Red Heavy"], str] = "Blue Heavy",
    method: Union[Literal["MOVE", "LRC"], str] = "MOVE",
    photopic_lef: Optional[SpectralDistribution] = None,
    scotopic_lef: Optional[SpectralDistribution] = None,
) -> FloatingOrNDArray:
    """
    Calculate the mesopic weighting function factor :math:`V_m` at given
    wavelength :math:`\\lambda` using the photopic luminance :math:`L_p`.

    Parameters
    ----------
    wavelength
        Wavelength :math:`\\lambda` to calculate the mesopic weighting function
        factor.
    L_p
        Photopic luminance :math:`L_p`.
    source
        Light source colour temperature.
    method
        Method to calculate the weighting factor.
    photopic_lef
        :math:`V(\\lambda)` photopic luminous efficiency function, default to
        the *CIE 1924 Photopic Standard Observer*.
    scotopic_lef
        :math:`V^\\prime(\\lambda)` scotopic luminous efficiency function,
        default to the *CIE 1951 Scotopic Standard Observer*.

    Returns
    -------
    :class:`numpy.floating` or :class:`numpy.ndarray`
        Mesopic weighting function factor :math:`V_m`.

    References
    ----------
    :cite:`Wikipedia2005d`

    Examples
    --------
    >>> mesopic_weighting_function(500, 0.2)  # doctest: +ELLIPSIS
    0.7052200...
    """

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

    scotopic_lef = cast(
        SpectralDistribution,
        optional(
            scotopic_lef,
            SDS_LEFS_SCOTOPIC["CIE 1951 Scotopic Standard Observer"],
        ),
    )

    source = validate_method(
        source,
        ["Blue Heavy", "Red Heavy"],
        '"{0}" light source colour temperature is invalid, '
        "it must be one of {1}!",
    )
    method = validate_method(method, ["MOVE", "LRC"])

    mesopic_x_luminance_values = sorted(DATA_MESOPIC_X.keys())
    index = mesopic_x_luminance_values.index(
        closest(mesopic_x_luminance_values, L_p))
    x = DATA_MESOPIC_X[mesopic_x_luminance_values[index]][source][method]

    V_m = (1 - x) * scotopic_lef[wavelength] + x * photopic_lef[wavelength]

    return V_m
예제 #12
0
def sd_mesopic_luminous_efficiency_function(
    L_p: Floating,
    source: Union[Literal["Blue Heavy", "Red Heavy"], str] = "Blue Heavy",
    method: Union[Literal["MOVE", "LRC"], str] = "MOVE",
    photopic_lef: Optional[SpectralDistribution] = None,
    scotopic_lef: Optional[SpectralDistribution] = None,
) -> SpectralDistribution:
    """
    Return the mesopic luminous efficiency function :math:`V_m(\\lambda)` for
    given photopic luminance :math:`L_p`.

    Parameters
    ----------
    L_p
        Photopic luminance :math:`L_p`.
    source
        Light source colour temperature.
    method
        Method to calculate the weighting factor.
    photopic_lef
        :math:`V(\\lambda)` photopic luminous efficiency function, default to
        the *CIE 1924 Photopic Standard Observer*.
    scotopic_lef
        :math:`V^\\prime(\\lambda)` scotopic luminous efficiency function,
        default to the *CIE 1951 Scotopic Standard Observer*.

    Returns
    -------
    :class:`colour.SpectralDistribution`
        Mesopic luminous efficiency function :math:`V_m(\\lambda)`.

    References
    ----------
    :cite:`Wikipedia2005d`

    Examples
    --------
    >>> from colour.utilities import numpy_print_options
    >>> with numpy_print_options(suppress=True):
    ...     sd_mesopic_luminous_efficiency_function(0.2)  # doctest: +ELLIPSIS
    SpectralDistribution([[ 380.        ,    0.000424 ...],
                          [ 381.        ,    0.0004781...],
                          [ 382.        ,    0.0005399...],
                          [ 383.        ,    0.0006122...],
                          [ 384.        ,    0.0006961...],
                          [ 385.        ,    0.0007929...],
                          [ 386.        ,    0.000907 ...],
                          [ 387.        ,    0.0010389...],
                          [ 388.        ,    0.0011923...],
                          [ 389.        ,    0.0013703...],
                          [ 390.        ,    0.0015771...],
                          [ 391.        ,    0.0018167...],
                          [ 392.        ,    0.0020942...],
                          [ 393.        ,    0.0024160...],
                          [ 394.        ,    0.0027888...],
                          [ 395.        ,    0.0032196...],
                          [ 396.        ,    0.0037222...],
                          [ 397.        ,    0.0042957...],
                          [ 398.        ,    0.0049531...],
                          [ 399.        ,    0.0057143...],
                          [ 400.        ,    0.0065784...],
                          [ 401.        ,    0.0075658...],
                          [ 402.        ,    0.0086912...],
                          [ 403.        ,    0.0099638...],
                          [ 404.        ,    0.0114058...],
                          [ 405.        ,    0.0130401...],
                          [ 406.        ,    0.0148750...],
                          [ 407.        ,    0.0169310...],
                          [ 408.        ,    0.0192211...],
                          [ 409.        ,    0.0217511...],
                          [ 410.        ,    0.0245342...],
                          [ 411.        ,    0.0275773...],
                          [ 412.        ,    0.0309172...],
                          [ 413.        ,    0.0345149...],
                          [ 414.        ,    0.0383998...],
                          [ 415.        ,    0.0425744...],
                          [ 416.        ,    0.0471074...],
                          [ 417.        ,    0.0519322...],
                          [ 418.        ,    0.0570541...],
                          [ 419.        ,    0.0625466...],
                          [ 420.        ,    0.0683463...],
                          [ 421.        ,    0.0745255...],
                          [ 422.        ,    0.0809440...],
                          [ 423.        ,    0.0877344...],
                          [ 424.        ,    0.0948915...],
                          [ 425.        ,    0.1022731...],
                          [ 426.        ,    0.109877 ...],
                          [ 427.        ,    0.1178421...],
                          [ 428.        ,    0.1260316...],
                          [ 429.        ,    0.1343772...],
                          [ 430.        ,    0.143017 ...],
                          [ 431.        ,    0.1518128...],
                          [ 432.        ,    0.1608328...],
                          [ 433.        ,    0.1700088...],
                          [ 434.        ,    0.1792726...],
                          [ 435.        ,    0.1886934...],
                          [ 436.        ,    0.1982041...],
                          [ 437.        ,    0.2078032...],
                          [ 438.        ,    0.2174184...],
                          [ 439.        ,    0.2271147...],
                          [ 440.        ,    0.2368196...],
                          [ 441.        ,    0.2464623...],
                          [ 442.        ,    0.2561153...],
                          [ 443.        ,    0.2657160...],
                          [ 444.        ,    0.2753387...],
                          [ 445.        ,    0.2848520...],
                          [ 446.        ,    0.2944648...],
                          [ 447.        ,    0.3034902...],
                          [ 448.        ,    0.3132347...],
                          [ 449.        ,    0.3223257...],
                          [ 450.        ,    0.3314513...],
                          [ 451.        ,    0.3406129...],
                          [ 452.        ,    0.3498117...],
                          [ 453.        ,    0.3583617...],
                          [ 454.        ,    0.3676377...],
                          [ 455.        ,    0.3762670...],
                          [ 456.        ,    0.3849392...],
                          [ 457.        ,    0.3936540...],
                          [ 458.        ,    0.4024077...],
                          [ 459.        ,    0.4111965...],
                          [ 460.        ,    0.4193298...],
                          [ 461.        ,    0.4281803...],
                          [ 462.        ,    0.4363804...],
                          [ 463.        ,    0.4453117...],
                          [ 464.        ,    0.4542949...],
                          [ 465.        ,    0.4626509...],
                          [ 466.        ,    0.4717570...],
                          [ 467.        ,    0.4809300...],
                          [ 468.        ,    0.4901776...],
                          [ 469.        ,    0.4995075...],
                          [ 470.        ,    0.5096145...],
                          [ 471.        ,    0.5191293...],
                          [ 472.        ,    0.5294259...],
                          [ 473.        ,    0.5391316...],
                          [ 474.        ,    0.5496217...],
                          [ 475.        ,    0.5602103...],
                          [ 476.        ,    0.5702197...],
                          [ 477.        ,    0.5810207...],
                          [ 478.        ,    0.5919093...],
                          [ 479.        ,    0.6028683...],
                          [ 480.        ,    0.6138806...],
                          [ 481.        ,    0.6249373...],
                          [ 482.        ,    0.6360619...],
                          [ 483.        ,    0.6465989...],
                          [ 484.        ,    0.6579538...],
                          [ 485.        ,    0.6687841...],
                          [ 486.        ,    0.6797939...],
                          [ 487.        ,    0.6909887...],
                          [ 488.        ,    0.7023827...],
                          [ 489.        ,    0.7133032...],
                          [ 490.        ,    0.7244513...],
                          [ 491.        ,    0.7358470...],
                          [ 492.        ,    0.7468118...],
                          [ 493.        ,    0.7580294...],
                          [ 494.        ,    0.7694964...],
                          [ 495.        ,    0.7805225...],
                          [ 496.        ,    0.7917805...],
                          [ 497.        ,    0.8026123...],
                          [ 498.        ,    0.8130793...],
                          [ 499.        ,    0.8239297...],
                          [ 500.        ,    0.8352251...],
                          [ 501.        ,    0.8456342...],
                          [ 502.        ,    0.8564818...],
                          [ 503.        ,    0.8676921...],
                          [ 504.        ,    0.8785021...],
                          [ 505.        ,    0.8881489...],
                          [ 506.        ,    0.8986405...],
                          [ 507.        ,    0.9079322...],
                          [ 508.        ,    0.9174255...],
                          [ 509.        ,    0.9257739...],
                          [ 510.        ,    0.9350656...],
                          [ 511.        ,    0.9432365...],
                          [ 512.        ,    0.9509063...],
                          [ 513.        ,    0.9586931...],
                          [ 514.        ,    0.9658413...],
                          [ 515.        ,    0.9722825...],
                          [ 516.        ,    0.9779924...],
                          [ 517.        ,    0.9836106...],
                          [ 518.        ,    0.9883465...],
                          [ 519.        ,    0.9920964...],
                          [ 520.        ,    0.9954436...],
                          [ 521.        ,    0.9976202...],
                          [ 522.        ,    0.9993457...],
                          [ 523.        ,    1.       ...],
                          [ 524.        ,    0.9996498...],
                          [ 525.        ,    0.9990487...],
                          [ 526.        ,    0.9975356...],
                          [ 527.        ,    0.9957615...],
                          [ 528.        ,    0.9930143...],
                          [ 529.        ,    0.9899559...],
                          [ 530.        ,    0.9858741...],
                          [ 531.        ,    0.9814453...],
                          [ 532.        ,    0.9766885...],
                          [ 533.        ,    0.9709363...],
                          [ 534.        ,    0.9648947...],
                          [ 535.        ,    0.9585832...],
                          [ 536.        ,    0.952012 ...],
                          [ 537.        ,    0.9444916...],
                          [ 538.        ,    0.9367089...],
                          [ 539.        ,    0.9293506...],
                          [ 540.        ,    0.9210429...],
                          [ 541.        ,    0.9124772...],
                          [ 542.        ,    0.9036604...],
                          [ 543.        ,    0.8945958...],
                          [ 544.        ,    0.8845999...],
                          [ 545.        ,    0.8750500...],
                          [ 546.        ,    0.8659457...],
                          [ 547.        ,    0.8559224...],
                          [ 548.        ,    0.8456846...],
                          [ 549.        ,    0.8352499...],
                          [ 550.        ,    0.8253229...],
                          [ 551.        ,    0.8152079...],
                          [ 552.        ,    0.8042205...],
                          [ 553.        ,    0.7944209...],
                          [ 554.        ,    0.7837466...],
                          [ 555.        ,    0.7735680...],
                          [ 556.        ,    0.7627808...],
                          [ 557.        ,    0.7522710...],
                          [ 558.        ,    0.7417549...],
                          [ 559.        ,    0.7312909...],
                          [ 560.        ,    0.7207983...],
                          [ 561.        ,    0.7101939...],
                          [ 562.        ,    0.6996362...],
                          [ 563.        ,    0.6890656...],
                          [ 564.        ,    0.6785599...],
                          [ 565.        ,    0.6680593...],
                          [ 566.        ,    0.6575697...],
                          [ 567.        ,    0.6471578...],
                          [ 568.        ,    0.6368208...],
                          [ 569.        ,    0.6264871...],
                          [ 570.        ,    0.6161541...],
                          [ 571.        ,    0.6058896...],
                          [ 572.        ,    0.5957000...],
                          [ 573.        ,    0.5855937...],
                          [ 574.        ,    0.5754412...],
                          [ 575.        ,    0.5653883...],
                          [ 576.        ,    0.5553742...],
                          [ 577.        ,    0.5454680...],
                          [ 578.        ,    0.5355972...],
                          [ 579.        ,    0.5258267...],
                          [ 580.        ,    0.5160152...],
                          [ 581.        ,    0.5062322...],
                          [ 582.        ,    0.4965595...],
                          [ 583.        ,    0.4868746...],
                          [ 584.        ,    0.4773299...],
                          [ 585.        ,    0.4678028...],
                          [ 586.        ,    0.4583704...],
                          [ 587.        ,    0.4489722...],
                          [ 588.        ,    0.4397606...],
                          [ 589.        ,    0.4306131...],
                          [ 590.        ,    0.4215446...],
                          [ 591.        ,    0.4125681...],
                          [ 592.        ,    0.4037550...],
                          [ 593.        ,    0.3950359...],
                          [ 594.        ,    0.3864104...],
                          [ 595.        ,    0.3778777...],
                          [ 596.        ,    0.3694405...],
                          [ 597.        ,    0.3611074...],
                          [ 598.        ,    0.3528596...],
                          [ 599.        ,    0.3447056...],
                          [ 600.        ,    0.3366470...],
                          [ 601.        ,    0.3286917...],
                          [ 602.        ,    0.3208410...],
                          [ 603.        ,    0.3130808...],
                          [ 604.        ,    0.3054105...],
                          [ 605.        ,    0.2978225...],
                          [ 606.        ,    0.2903027...],
                          [ 607.        ,    0.2828727...],
                          [ 608.        ,    0.2755311...],
                          [ 609.        ,    0.2682900...],
                          [ 610.        ,    0.2611478...],
                          [ 611.        ,    0.2541176...],
                          [ 612.        ,    0.2471885...],
                          [ 613.        ,    0.2403570...],
                          [ 614.        ,    0.2336057...],
                          [ 615.        ,    0.2269379...],
                          [ 616.        ,    0.2203527...],
                          [ 617.        ,    0.2138465...],
                          [ 618.        ,    0.2073946...],
                          [ 619.        ,    0.2009789...],
                          [ 620.        ,    0.1945818...],
                          [ 621.        ,    0.1881943...],
                          [ 622.        ,    0.1818226...],
                          [ 623.        ,    0.1754987...],
                          [ 624.        ,    0.1692476...],
                          [ 625.        ,    0.1630876...],
                          [ 626.        ,    0.1570257...],
                          [ 627.        ,    0.151071 ...],
                          [ 628.        ,    0.1452469...],
                          [ 629.        ,    0.1395845...],
                          [ 630.        ,    0.1341087...],
                          [ 631.        ,    0.1288408...],
                          [ 632.        ,    0.1237666...],
                          [ 633.        ,    0.1188631...],
                          [ 634.        ,    0.1141075...],
                          [ 635.        ,    0.1094766...],
                          [ 636.        ,    0.1049613...],
                          [ 637.        ,    0.1005679...],
                          [ 638.        ,    0.0962924...],
                          [ 639.        ,    0.0921296...],
                          [ 640.        ,    0.0880778...],
                          [ 641.        ,    0.0841306...],
                          [ 642.        ,    0.0802887...],
                          [ 643.        ,    0.0765559...],
                          [ 644.        ,    0.0729367...],
                          [ 645.        ,    0.0694345...],
                          [ 646.        ,    0.0660491...],
                          [ 647.        ,    0.0627792...],
                          [ 648.        ,    0.0596278...],
                          [ 649.        ,    0.0565970...],
                          [ 650.        ,    0.0536896...],
                          [ 651.        ,    0.0509068...],
                          [ 652.        ,    0.0482444...],
                          [ 653.        ,    0.0456951...],
                          [ 654.        ,    0.0432510...],
                          [ 655.        ,    0.0409052...],
                          [ 656.        ,    0.0386537...],
                          [ 657.        ,    0.0364955...],
                          [ 658.        ,    0.0344285...],
                          [ 659.        ,    0.0324501...],
                          [ 660.        ,    0.0305579...],
                          [ 661.        ,    0.0287496...],
                          [ 662.        ,    0.0270233...],
                          [ 663.        ,    0.0253776...],
                          [ 664.        ,    0.0238113...],
                          [ 665.        ,    0.0223226...],
                          [ 666.        ,    0.0209086...],
                          [ 667.        ,    0.0195688...],
                          [ 668.        ,    0.0183056...],
                          [ 669.        ,    0.0171216...],
                          [ 670.        ,    0.0160192...],
                          [ 671.        ,    0.0149986...],
                          [ 672.        ,    0.0140537...],
                          [ 673.        ,    0.0131784...],
                          [ 674.        ,    0.0123662...],
                          [ 675.        ,    0.0116107...],
                          [ 676.        ,    0.0109098...],
                          [ 677.        ,    0.0102587...],
                          [ 678.        ,    0.0096476...],
                          [ 679.        ,    0.0090665...],
                          [ 680.        ,    0.0085053...],
                          [ 681.        ,    0.0079567...],
                          [ 682.        ,    0.0074229...],
                          [ 683.        ,    0.0069094...],
                          [ 684.        ,    0.0064213...],
                          [ 685.        ,    0.0059637...],
                          [ 686.        ,    0.0055377...],
                          [ 687.        ,    0.0051402...],
                          [ 688.        ,    0.00477  ...],
                          [ 689.        ,    0.0044263...],
                          [ 690.        ,    0.0041081...],
                          [ 691.        ,    0.0038149...],
                          [ 692.        ,    0.0035456...],
                          [ 693.        ,    0.0032984...],
                          [ 694.        ,    0.0030718...],
                          [ 695.        ,    0.0028639...],
                          [ 696.        ,    0.0026738...],
                          [ 697.        ,    0.0025000...],
                          [ 698.        ,    0.0023401...],
                          [ 699.        ,    0.0021918...],
                          [ 700.        ,    0.0020526...],
                          [ 701.        ,    0.0019207...],
                          [ 702.        ,    0.001796 ...],
                          [ 703.        ,    0.0016784...],
                          [ 704.        ,    0.0015683...],
                          [ 705.        ,    0.0014657...],
                          [ 706.        ,    0.0013702...],
                          [ 707.        ,    0.001281 ...],
                          [ 708.        ,    0.0011976...],
                          [ 709.        ,    0.0011195...],
                          [ 710.        ,    0.0010464...],
                          [ 711.        ,    0.0009776...],
                          [ 712.        ,    0.0009131...],
                          [ 713.        ,    0.0008525...],
                          [ 714.        ,    0.0007958...],
                          [ 715.        ,    0.0007427...],
                          [ 716.        ,    0.0006929...],
                          [ 717.        ,    0.0006462...],
                          [ 718.        ,    0.0006026...],
                          [ 719.        ,    0.0005619...],
                          [ 720.        ,    0.0005240...],
                          [ 721.        ,    0.0004888...],
                          [ 722.        ,    0.0004561...],
                          [ 723.        ,    0.0004255...],
                          [ 724.        ,    0.0003971...],
                          [ 725.        ,    0.0003704...],
                          [ 726.        ,    0.0003455...],
                          [ 727.        ,    0.0003221...],
                          [ 728.        ,    0.0003001...],
                          [ 729.        ,    0.0002796...],
                          [ 730.        ,    0.0002604...],
                          [ 731.        ,    0.0002423...],
                          [ 732.        ,    0.0002254...],
                          [ 733.        ,    0.0002095...],
                          [ 734.        ,    0.0001947...],
                          [ 735.        ,    0.0001809...],
                          [ 736.        ,    0.0001680...],
                          [ 737.        ,    0.0001560...],
                          [ 738.        ,    0.0001449...],
                          [ 739.        ,    0.0001345...],
                          [ 740.        ,    0.0001249...],
                          [ 741.        ,    0.0001159...],
                          [ 742.        ,    0.0001076...],
                          [ 743.        ,    0.0000999...],
                          [ 744.        ,    0.0000927...],
                          [ 745.        ,    0.0000862...],
                          [ 746.        ,    0.0000801...],
                          [ 747.        ,    0.0000745...],
                          [ 748.        ,    0.0000693...],
                          [ 749.        ,    0.0000646...],
                          [ 750.        ,    0.0000602...],
                          [ 751.        ,    0.0000561...],
                          [ 752.        ,    0.0000523...],
                          [ 753.        ,    0.0000488...],
                          [ 754.        ,    0.0000456...],
                          [ 755.        ,    0.0000425...],
                          [ 756.        ,    0.0000397...],
                          [ 757.        ,    0.0000370...],
                          [ 758.        ,    0.0000346...],
                          [ 759.        ,    0.0000322...],
                          [ 760.        ,    0.0000301...],
                          [ 761.        ,    0.0000281...],
                          [ 762.        ,    0.0000262...],
                          [ 763.        ,    0.0000244...],
                          [ 764.        ,    0.0000228...],
                          [ 765.        ,    0.0000213...],
                          [ 766.        ,    0.0000198...],
                          [ 767.        ,    0.0000185...],
                          [ 768.        ,    0.0000173...],
                          [ 769.        ,    0.0000161...],
                          [ 770.        ,    0.0000150...],
                          [ 771.        ,    0.0000140...],
                          [ 772.        ,    0.0000131...],
                          [ 773.        ,    0.0000122...],
                          [ 774.        ,    0.0000114...],
                          [ 775.        ,    0.0000106...],
                          [ 776.        ,    0.0000099...],
                          [ 777.        ,    0.0000092...],
                          [ 778.        ,    0.0000086...],
                          [ 779.        ,    0.0000080...],
                          [ 780.        ,    0.0000075...]],
                         interpolator=SpragueInterpolator,
                         interpolator_kwargs={},
                         extrapolator=Extrapolator,
                         extrapolator_kwargs={...})
    """

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

    scotopic_lef = cast(
        SpectralDistribution,
        optional(
            scotopic_lef,
            SDS_LEFS_SCOTOPIC["CIE 1951 Scotopic Standard Observer"],
        ),
    )

    shape = SpectralShape(
        max([photopic_lef.shape.start, scotopic_lef.shape.start]),
        min([photopic_lef.shape.end, scotopic_lef.shape.end]),
        max([photopic_lef.shape.interval, scotopic_lef.shape.interval]),
    )

    wavelengths = shape.range()

    sd = SpectralDistribution(
        mesopic_weighting_function(wavelengths, L_p, source, method,
                                   photopic_lef, scotopic_lef),
        wavelengths,
        name=f"{L_p} Lp Mesopic Luminous Efficiency Function",
    )

    return sd.normalise()
예제 #13
0
def label_rectangles(
    labels: Sequence[str],
    rectangles: Sequence[Patch],
    rotation: Union[Literal["horizontal", "vertical"], str] = "vertical",
    text_size: Floating = 10,
    offset: Optional[ArrayLike] = None,
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Add labels above given rectangles.

    Parameters
    ----------
    labels
        Labels to display.
    rectangles
        Rectangles to used to set the labels value and position.
    rotation
        Labels orientation.
    text_size
        Labels text size.
    offset
        Labels offset as percentages of the largest rectangle dimensions.

    Other Parameters
    ----------------
    figure
        Figure to apply the render elements onto.
    axes
        Axes to apply the render elements onto.

    Returns
    -------
    :class:`tuple`
        Current figure and axes.
    """

    rotation = validate_method(
        rotation,
        ["horizontal", "vertical"],
        '"{0}" rotation is invalid, it must be one of {1}!',
    )

    figure = kwargs.get("figure", plt.gcf())
    axes = kwargs.get("axes", plt.gca())

    offset = as_float_array(cast(ArrayLike, optional(offset, (0.0, 0.025))))

    x_m, y_m = 0, 0
    for rectangle in rectangles:
        x_m = max(x_m, rectangle.get_width())
        y_m = max(y_m, rectangle.get_height())

    for i, rectangle in enumerate(rectangles):
        x = rectangle.get_x()
        height = rectangle.get_height()
        width = rectangle.get_width()
        ha = "center"
        va = "bottom"
        axes.text(
            x + width / 2 + offset[0] * width,
            height + offset[1] * y_m,
            labels[i],
            ha=ha,
            va=va,
            rotation=rotation,
            fontsize=text_size,
            clip_on=True,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_label,
        )

    return figure, axes
예제 #14
0
def plot_planckian_locus(
    planckian_locus_colours: Optional[Union[ArrayLike, str]] = None,
    planckian_locus_opacity: Floating = 1,
    planckian_locus_labels: Optional[Sequence] = None,
    method: Union[Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"],
                  str] = "CIE 1931",
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot the *Planckian Locus* according to given method.

    Parameters
    ----------
    planckian_locus_colours
        Colours of the *Planckian Locus*, if ``planckian_locus_colours`` is set
        to *RGB*, the colours will be computed according to the corresponding
        chromaticity coordinates.
    planckian_locus_opacity
       Opacity of the *Planckian Locus*.
    planckian_locus_labels
        Array of labels used to customise which iso-temperature lines will be
        drawn along the *Planckian Locus*. Passing an empty array will result
        in no iso-temperature lines being drawn.
    method
        *Chromaticity Diagram* method.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
        See the documentation of the previously listed definitions.

    Returns
    -------
    :class:`tuple`
        Current figure and axes.

    Examples
    --------
    >>> plot_planckian_locus(planckian_locus_colours='RGB')
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_Plot_Planckian_Locus.png
        :align: center
        :alt: plot_planckian_locus
    """

    method = validate_method(method,
                             ["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"])

    planckian_locus_colours = optional(planckian_locus_colours,
                                       CONSTANTS_COLOUR_STYLE.colour.dark)

    labels = cast(
        Tuple,
        optional(
            planckian_locus_labels,
            (10**6 / 600, 2000, 2500, 3000, 4000, 6000, 10**6 / 100),
        ),
    )
    D_uv = 0.05

    settings: Dict[str, Any] = {"uniform": True}
    settings.update(kwargs)

    _figure, axes = artist(**settings)

    if method == "cie 1931":

        def uv_to_ij(uv: NDArray) -> NDArray:
            """
            Convert given *uv* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return UCS_uv_to_xy(uv)

    elif method == "cie 1960 ucs":

        def uv_to_ij(uv: NDArray) -> NDArray:
            """
            Convert given *uv* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return uv

    elif method == "cie 1976 ucs":

        def uv_to_ij(uv: NDArray) -> NDArray:
            """
            Convert given *uv* chromaticity coordinates to *ij* chromaticity
            coordinates.
            """

            return xy_to_Luv_uv(UCS_uv_to_xy(uv))

    def CCT_D_uv_to_plotting_colourspace(CCT_D_uv):
        """
        Convert given *uv* chromaticity coordinates to the default plotting
        colourspace.
        """

        return normalise_maximum(
            XYZ_to_plotting_colourspace(
                xy_to_XYZ(UCS_uv_to_xy(CCT_to_uv(CCT_D_uv,
                                                 "Robertson 1968")))),
            axis=-1,
        )

    start, end = 10**6 / 600, 10**6 / 10
    CCT = np.arange(start, end + 100, 100)
    CCT_D_uv = np.reshape(tstack([CCT, zeros(CCT.shape)]), (-1, 1, 2))
    ij = uv_to_ij(CCT_to_uv(CCT_D_uv, "Robertson 1968"))

    use_RGB_planckian_locus_colours = (
        str(planckian_locus_colours).upper() == "RGB")
    if use_RGB_planckian_locus_colours:
        pl_colours = CCT_D_uv_to_plotting_colourspace(CCT_D_uv)
    else:
        pl_colours = planckian_locus_colours

    line_collection = LineCollection(
        np.concatenate([ij[:-1], ij[1:]], axis=1),
        colors=pl_colours,
        alpha=planckian_locus_opacity,
        zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_line,
    )
    axes.add_collection(line_collection)

    for label in labels:
        CCT_D_uv = np.reshape(
            tstack([full(10, label),
                    np.linspace(-D_uv, D_uv, 10)]), (-1, 1, 2))

        if use_RGB_planckian_locus_colours:
            itl_colours = CCT_D_uv_to_plotting_colourspace(CCT_D_uv)
        else:
            itl_colours = planckian_locus_colours

        ij = uv_to_ij(CCT_to_uv(CCT_D_uv, "Robertson 1968"))

        line_collection = LineCollection(
            np.concatenate([ij[:-1], ij[1:]], axis=1),
            colors=itl_colours,
            alpha=planckian_locus_opacity,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_line,
        )
        axes.add_collection(line_collection)
        axes.annotate(
            f"{as_int_scalar(label)}K",
            xy=(ij[-1, :, 0], ij[-1, :, 1]),
            xytext=(0, CONSTANTS_COLOUR_STYLE.geometry.long / 2),
            textcoords="offset points",
            size="x-small",
            zorder=CONSTANTS_COLOUR_STYLE.zorder.foreground_label,
        )

    settings = {"axes": axes}
    settings.update(kwargs)

    return render(**settings)
예제 #15
0
def plot_RGB_colourspaces_gamuts(
    colourspaces: Union[RGB_Colourspace, str, Sequence[Union[RGB_Colourspace,
                                                             str]]],
    reference_colourspace: Union[Literal["CAM02LCD", "CAM02SCD", "CAM02UCS",
                                         "CAM16LCD", "CAM16SCD", "CAM16UCS",
                                         "CIE XYZ", "CIE xyY", "CIE Lab",
                                         "CIE Luv", "CIE UCS", "CIE UVW",
                                         "DIN99", "Hunter Lab", "Hunter Rdab",
                                         "ICaCb", "ICtCp", "IPT", "IgPgTg",
                                         "Jzazbz", "OSA UCS", "Oklab",
                                         "hdr-CIELAB", "hdr-IPT", ],
                                 str, ] = "CIE xyY",
    segments: Integer = 8,
    show_grid: Boolean = True,
    grid_segments: Integer = 10,
    show_spectral_locus: Boolean = False,
    spectral_locus_colour: Optional[Union[ArrayLike, str]] = None,
    cmfs: Union[MultiSpectralDistributions, str, Sequence[Union[
        MultiSpectralDistributions,
        str]], ] = "CIE 1931 2 Degree Standard Observer",
    chromatically_adapt: Boolean = False,
    convert_kwargs: Optional[Dict] = None,
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot given *RGB* colourspaces gamuts in given reference colourspace.

    Parameters
    ----------
    colourspaces
        *RGB* colourspaces to plot the gamuts. ``colourspaces`` elements
        can be of any type or form supported by the
        :func:`colour.plotting.filter_RGB_colourspaces` definition.
    reference_colourspace
        Reference colourspace model to plot the gamuts into, see
        :attr:`colour.COLOURSPACE_MODELS` attribute for the list of supported
        colourspace models.
    segments
        Edge segments count for each *RGB* colourspace cubes.
    show_grid
        Whether to show a grid at the bottom of the *RGB* colourspace cubes.
    grid_segments
        Edge segments count for the grid.
    show_spectral_locus
        Whether to show the spectral locus.
    spectral_locus_colour
        Spectral locus colour.
    cmfs
        Standard observer colour matching functions used for computing the
        spectral locus boundaries. ``cmfs`` can be of any type or form
        supported by the :func:`colour.plotting.filter_cmfs` definition.
    chromatically_adapt
        Whether to chromatically adapt the *RGB* colourspaces given in
        ``colourspaces`` to the whitepoint of the default plotting colourspace.
    convert_kwargs
        Keyword arguments for the :func:`colour.convert` definition.

    Other Parameters
    ----------------
    edge_colours
        Edge colours array such as `edge_colours = (None, (0.5, 0.5, 1.0))`.
    edge_alpha
        Edge opacity value such as `edge_alpha = (0.0, 1.0)`.
    face_alpha
        Face opacity value such as `face_alpha = (0.5, 1.0)`.
    face_colours
        Face colours array such as `face_colours = (None, (0.5, 0.5, 1.0))`.
    kwargs
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.volume.nadir_grid`},
        See the documentation of the previously listed definitions.

    Returns
    -------
    :class:`tuple`
        Current figure and axes.

    Examples
    --------
    >>> plot_RGB_colourspaces_gamuts(['ITU-R BT.709', 'ACEScg', 'S-Gamut'])
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...Axes3DSubplot...>)

    .. image:: ../_static/Plotting_Plot_RGB_Colourspaces_Gamuts.png
        :align: center
        :alt: plot_RGB_colourspaces_gamuts
    """

    colourspaces = cast(
        List[RGB_Colourspace],
        list(filter_RGB_colourspaces(colourspaces).values()),
    )

    convert_kwargs = optional(convert_kwargs, {})

    count_c = len(colourspaces)

    title = (
        f"{', '.join([colourspace.name for colourspace in colourspaces])} "
        f"- {reference_colourspace} Reference Colourspace")

    illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint

    convert_settings = {"illuminant": illuminant}
    convert_settings.update(convert_kwargs)

    settings = Structure(
        **{
            "face_colours": [None] * count_c,
            "edge_colours": [None] * count_c,
            "face_alpha": [1] * count_c,
            "edge_alpha": [1] * count_c,
            "title": title,
        })
    settings.update(kwargs)

    figure = plt.figure()
    axes = figure.add_subplot(111, projection="3d")

    points = zeros((4, 3))
    if show_spectral_locus:
        cmfs = cast(MultiSpectralDistributions,
                    first_item(filter_cmfs(cmfs).values()))
        XYZ = cmfs.values

        points = colourspace_model_axis_reorder(
            convert(XYZ, "CIE XYZ", reference_colourspace, **convert_settings),
            reference_colourspace,
        )

        points[np.isnan(points)] = 0

        c = ((0.0, 0.0, 0.0,
              0.5) if spectral_locus_colour is None else spectral_locus_colour)

        axes.plot(
            points[..., 0],
            points[..., 1],
            points[..., 2],
            color=c,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
        )
        axes.plot(
            (points[-1][0], points[0][0]),
            (points[-1][1], points[0][1]),
            (points[-1][2], points[0][2]),
            color=c,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line,
        )

    plotting_colourspace = CONSTANTS_COLOUR_STYLE.colour.colourspace

    quads_c: List = []
    RGB_cf: List = []
    RGB_ce: List = []
    for i, colourspace in enumerate(colourspaces):

        if chromatically_adapt and not np.array_equal(
                colourspace.whitepoint, plotting_colourspace.whitepoint):
            colourspace = colourspace.chromatically_adapt(
                plotting_colourspace.whitepoint,
                plotting_colourspace.whitepoint_name,
            )

        quads_cb, RGB = RGB_identity_cube(
            width_segments=segments,
            height_segments=segments,
            depth_segments=segments,
        )

        XYZ = RGB_to_XYZ(
            quads_cb,
            colourspace.whitepoint,
            colourspace.whitepoint,
            colourspace.matrix_RGB_to_XYZ,
        )

        convert_settings = {"illuminant": colourspace.whitepoint}
        convert_settings.update(convert_kwargs)

        quads_c.extend(
            colourspace_model_axis_reorder(
                convert(XYZ, "CIE XYZ", reference_colourspace,
                        **convert_settings),
                reference_colourspace,
            ))

        if settings.face_colours[i] is not None:
            RGB = ones(RGB.shape) * settings.face_colours[i]

        RGB_cf.extend(
            np.hstack([RGB,
                       full((RGB.shape[0], 1), settings.face_alpha[i])]))

        if settings.edge_colours[i] is not None:
            RGB = ones(RGB.shape) * settings.edge_colours[i]

        RGB_ce.extend(
            np.hstack([RGB,
                       full((RGB.shape[0], 1), settings.edge_alpha[i])]))

    quads = as_float_array(quads_c)
    RGB_f = as_float_array(RGB_cf)
    RGB_e = as_float_array(RGB_ce)

    quads[np.isnan(quads)] = 0

    if quads.size != 0:
        for i, axis in enumerate("xyz"):
            min_a = np.minimum(np.min(quads[..., i]), np.min(points[..., i]))
            max_a = np.maximum(np.max(quads[..., i]), np.max(points[..., i]))
            getattr(axes, f"set_{axis}lim")((min_a, max_a))

    labels = np.array(
        COLOURSPACE_MODELS_AXIS_LABELS[reference_colourspace])[as_int_array(
            colourspace_model_axis_reorder([0, 1, 2], reference_colourspace))]
    for i, axis in enumerate("xyz"):
        getattr(axes, f"set_{axis}label")(labels[i])

    if show_grid:
        limits = np.array([[-1.5, 1.5], [-1.5, 1.5]])

        quads_g, RGB_gf, RGB_ge = nadir_grid(limits, grid_segments, labels,
                                             axes, **settings)
        quads = np.vstack([quads_g, quads])
        RGB_f = np.vstack([RGB_gf, RGB_f])
        RGB_e = np.vstack([RGB_ge, RGB_e])

    collection = Poly3DCollection(quads)
    collection.set_facecolors(RGB_f)
    collection.set_edgecolors(RGB_e)

    axes.add_collection3d(collection)

    settings.update({
        "axes": axes,
        "axes_visible": False,
        "camera_aspect": "equal"
    })
    settings.update(kwargs)

    return render(**settings)
예제 #16
0
def Izazbz_to_XYZ(
    Izazbz: ArrayLike,
    constants: Optional[Structure] = None,
    method: Union[
        Literal["Safdar 2017", "Safdar 2021", "ZCAM"], str
    ] = "Safdar 2017",
) -> NDArray:
    """
    Convert from :math:`I_za_zb_z` colourspace to *CIE XYZ* tristimulus
    values.

    Parameters
    ----------
    Izazbz
        :math:`I_za_zb_z` colourspace array where :math:`I_z` is the
        achromatic response, :math:`a_z` is redness-greenness and
        :math:`b_z` is yellowness-blueness.
    constants
        :math:`J_za_zb_z` colourspace constants.
    method
        Computation methods, *Safdar 2021* and *ZCAM* methods are equivalent.

    Returns
    -------
    :class:`numpy.ndarray`
        *CIE XYZ* tristimulus values under
        *CIE Standard Illuminant D Series D65*.

    Warnings
    --------
    The underlying *SMPTE ST 2084:2014* transfer function is an absolute
    transfer function.

    Notes
    -----
    -   The underlying *SMPTE ST 2084:2014* transfer function is an absolute
        transfer function, thus the domain and range values for the *Reference*
        and *1* scales are only indicative that the data is not affected by
        scale transformations.

    +------------+-----------------------+------------------+
    | **Domain** | **Scale - Reference** | **Scale - 1**    |
    +============+=======================+==================+
    | ``Izazbz`` | ``Iz`` : [0, 1]       | ``Iz`` : [0, 1]  |
    |            |                       |                  |
    |            | ``az`` : [-1, 1]      | ``az`` : [-1, 1] |
    |            |                       |                  |
    |            | ``bz`` : [-1, 1]      | ``bz`` : [-1, 1] |
    +------------+-----------------------+------------------+

    +------------+-----------------------+------------------+
    | **Range**  | **Scale - Reference** | **Scale - 1**    |
    +============+=======================+==================+
    | ``XYZ``    | ``UN``                | ``UN``           |
    +------------+-----------------------+------------------+

    References
    ----------
    :cite:`Safdar2017`, :cite:`Safdar2021`

    Examples
    --------
    >>> Izazbz = np.array([0.01207793, 0.00924302, 0.00526007])
    >>> Izazbz_to_XYZ(Izazbz)  # doctest: +ELLIPSIS
    array([ 0.2065401...,  0.1219723...,  0.0513696...])
    """

    Izazbz = as_float_array(Izazbz)

    method = validate_method(method, IZAZBZ_METHODS)

    constants = optional(
        constants,
        CONSTANTS_JZAZBZ_SAFDAR2017
        if method == "safdar 2017"
        else CONSTANTS_JZAZBZ_SAFDAR2021,
    )

    if method == "safdar 2017":
        LMS_p = vector_dot(MATRIX_JZAZBZ_IZAZBZ_TO_LMS_P_SAFDAR2017, Izazbz)
    else:
        Izazbz[..., 0] += constants.d_0
        LMS_p = vector_dot(MATRIX_JZAZBZ_IZAZBZ_TO_LMS_P_SAFDAR2021, Izazbz)

    with domain_range_scale("ignore"):
        LMS = eotf_ST2084(LMS_p, 10000, constants)

    X_p_D65, Y_p_D65, Z_p_D65 = tsplit(
        vector_dot(MATRIX_JZAZBZ_LMS_TO_XYZ, LMS)
    )

    X_D65 = (X_p_D65 + (constants.b - 1) * Z_p_D65) / constants.b
    Y_D65 = (Y_p_D65 + (constants.g - 1) * X_D65) / constants.g

    XYZ_D65 = tstack([X_D65, Y_D65, Z_p_D65])

    return XYZ_D65
예제 #17
0
def plot_multi_colour_swatches(
    colour_swatches: Sequence[Union[ArrayLike, ColourSwatch]],
    width: Floating = 1,
    height: Floating = 1,
    spacing: Floating = 0,
    columns: Optional[Integer] = None,
    direction: Union[Literal["+y", "-y"], str] = "+y",
    text_kwargs: Optional[Dict] = None,
    background_colour: ArrayLike = (1.0, 1.0, 1.0),
    compare_swatches: Optional[Union[Literal["Diagonal", "Stacked"],
                                     str]] = None,
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot given colours swatches.

    Parameters
    ----------
    colour_swatches
        Colour swatch sequence, either a regular `ArrayLike` or a sequence of
        :class:`colour.plotting.ColourSwatch` class instances.
    width
        Colour swatch width.
    height
        Colour swatch height.
    spacing
        Colour swatches spacing.
    columns
        Colour swatches columns count, defaults to the colour swatch count or
        half of it if comparing.
    direction
        Row stacking direction.
    text_kwargs
        Keyword arguments for the :func:`matplotlib.pyplot.text` definition.
        The following special keywords can also be used:

        -   ``offset``: Sets the text offset.
        -   ``visible``: Sets the text visibility.
    background_colour
        Background colour.
    compare_swatches
        Whether to compare the swatches, in which case the colour swatch
        count must be an even number with alternating reference colour swatches
        and test colour swatches. *Stacked* will draw the test colour swatch in
        the center of the reference colour swatch, *Diagonal* will draw
        the reference colour swatch in the upper left diagonal area and the
        test colour swatch in the bottom right diagonal area.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
        See the documentation of the previously listed definitions.

    Returns
    -------
    :class:`tuple`
        Current figure and axes.

    Examples
    --------
    >>> RGB_1 = ColourSwatch((0.45293517, 0.31732158, 0.26414773))
    >>> RGB_2 = ColourSwatch((0.77875824, 0.57726450, 0.50453169))
    >>> plot_multi_colour_swatches([RGB_1, RGB_2])  # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_Plot_Multi_Colour_Swatches.png
        :align: center
        :alt: plot_multi_colour_swatches
    """

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

    if compare_swatches is not None:
        compare_swatches = validate_method(
            compare_swatches,
            ["Diagonal", "Stacked"],
            '"{0}" compare swatches method is invalid, it must be one of {1}!',
        )

    _figure, axes = artist(**kwargs)

    # Handling case where `colour_swatches` is a regular *ArrayLike*.
    colour_swatches = list(colour_swatches)
    colour_swatches_converted = []
    if not isinstance(first_item(colour_swatches), ColourSwatch):
        for i, colour_swatch in enumerate(
                as_float_array(cast(ArrayLike,
                                    colour_swatches)).reshape([-1, 3])):
            colour_swatches_converted.append(ColourSwatch(colour_swatch))
    else:
        colour_swatches_converted = cast(List[ColourSwatch], colour_swatches)

    colour_swatches = colour_swatches_converted

    if compare_swatches is not None:
        attest(
            len(colour_swatches) % 2 == 0,
            "Cannot compare an odd number of colour swatches!",
        )

        colour_swatches_reference = colour_swatches[0::2]
        colour_swatches_test = colour_swatches[1::2]
    else:
        colour_swatches_reference = colour_swatches_test = colour_swatches

    columns = optional(columns, len(colour_swatches_reference))

    text_settings = {
        "offset": 0.05,
        "visible": True,
        "zorder": CONSTANTS_COLOUR_STYLE.zorder.midground_label,
    }
    if text_kwargs is not None:
        text_settings.update(text_kwargs)
    text_offset = text_settings.pop("offset")

    offset_X: Floating = 0
    offset_Y: Floating = 0
    x_min, x_max, y_min, y_max = 0, width, 0, height
    y = 1 if direction == "+y" else -1
    for i, colour_swatch in enumerate(colour_swatches_reference):
        if i % columns == 0 and i != 0:
            offset_X = 0
            offset_Y += (height + spacing) * y

        x_0, x_1 = offset_X, offset_X + width
        y_0, y_1 = offset_Y, offset_Y + height * y

        axes.fill(
            (x_0, x_1, x_1, x_0),
            (y_0, y_0, y_1, y_1),
            color=np.clip(colour_swatches_reference[i].RGB, 0, 1),
            zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_polygon,
        )

        if compare_swatches == "stacked":
            margin_X = width * 0.25
            margin_Y = height * 0.25
            axes.fill(
                (
                    x_0 + margin_X,
                    x_1 - margin_X,
                    x_1 - margin_X,
                    x_0 + margin_X,
                ),
                (
                    y_0 + margin_Y * y,
                    y_0 + margin_Y * y,
                    y_1 - margin_Y * y,
                    y_1 - margin_Y * y,
                ),
                color=np.clip(colour_swatches_test[i].RGB, 0, 1),
                zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_polygon,
            )
        else:
            axes.fill(
                (x_0, x_1, x_1),
                (y_0, y_0, y_1),
                color=np.clip(colour_swatches_test[i].RGB, 0, 1),
                zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_polygon,
            )

        if colour_swatch.name is not None and text_settings["visible"]:
            axes.text(
                x_0 + text_offset,
                y_0 + text_offset * y,
                colour_swatch.name,
                verticalalignment="bottom" if y == 1 else "top",
                clip_on=True,
                **text_settings,
            )

        offset_X += width + spacing

    x_max = min(len(colour_swatches), int(columns))
    x_max = x_max * width + x_max * spacing - spacing
    y_max = offset_Y

    axes.patch.set_facecolor(background_colour)

    if y == 1:
        bounding_box = [
            x_min - spacing,
            x_max + spacing,
            y_min - spacing,
            y_max + spacing + height,
        ]
    else:
        bounding_box = [
            x_min - spacing,
            x_max + spacing,
            y_max - spacing - height,
            y_min + spacing,
        ]

    settings: Dict[str, Any] = {
        "axes": axes,
        "bounding_box": bounding_box,
        "aspect": "equal",
    }
    settings.update(kwargs)

    return render(**settings)
예제 #18
0
def plot_chromaticity_diagram_colours(
    samples: Integer = 256,
    diagram_colours: Optional[Union[ArrayLike, str]] = None,
    diagram_opacity: Floating = 1,
    diagram_clipping_path: Optional[ArrayLike] = None,
    cmfs: Union[MultiSpectralDistributions, str, Sequence[Union[
        MultiSpectralDistributions,
        str]], ] = "CIE 1931 2 Degree Standard Observer",
    method: Union[Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"],
                  str] = "CIE 1931",
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot the *Chromaticity Diagram* colours according to given method.

    Parameters
    ----------
    samples
        Samples count on one axis when computing the *Chromaticity Diagram*
        colours.
    diagram_colours
        Colours of the *Chromaticity Diagram*, if ``diagram_colours`` is set
        to *RGB*, the colours will be computed according to the corresponding
        coordinates.
    diagram_opacity
        Opacity of the *Chromaticity Diagram*.
    diagram_clipping_path
        Path of points used to clip the *Chromaticity Diagram* colours.
    cmfs
        Standard observer colour matching functions used for computing the
        spectral locus boundaries. ``cmfs`` can be of any type or form
        supported by the :func:`colour.plotting.filter_cmfs` definition.
    method
        *Chromaticity Diagram* method.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
        See the documentation of the previously listed definitions.

    Returns
    -------
    :class:`tuple`
        Current figure and axes.

    Examples
    --------
    >>> plot_chromaticity_diagram_colours(diagram_colours='RGB')
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_Plot_Chromaticity_Diagram_Colours.png
        :align: center
        :alt: plot_chromaticity_diagram_colours
    """

    method = validate_method(method,
                             ["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"])

    settings: Dict[str, Any] = {"uniform": True}
    settings.update(kwargs)

    _figure, axes = artist(**settings)

    diagram_colours = cast(
        ArrayLike,
        optional(diagram_colours,
                 HEX_to_RGB(CONSTANTS_COLOUR_STYLE.colour.average)),
    )

    cmfs = cast(MultiSpectralDistributions,
                first_item(filter_cmfs(cmfs).values()))

    illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint

    if method == "cie 1931":
        spectral_locus = XYZ_to_xy(cmfs.values, illuminant)
    elif method == "cie 1960 ucs":
        spectral_locus = UCS_to_uv(XYZ_to_UCS(cmfs.values))
    elif method == "cie 1976 ucs":
        spectral_locus = Luv_to_uv(XYZ_to_Luv(cmfs.values, illuminant),
                                   illuminant)

    use_RGB_diagram_colours = str(diagram_colours).upper() == "RGB"
    if use_RGB_diagram_colours:
        ii, jj = np.meshgrid(np.linspace(0, 1, samples),
                             np.linspace(1, 0, samples))
        ij = tstack([ii, jj])

        # NOTE: Various values in the grid have potential to generate
        # zero-divisions, they could be avoided by perturbing the grid, e.g.
        # adding a small epsilon. It was decided instead to disable warnings.
        with suppress_warnings(python_warnings=True):
            if method == "cie 1931":
                XYZ = xy_to_XYZ(ij)
            elif method == "cie 1960 ucs":
                XYZ = xy_to_XYZ(UCS_uv_to_xy(ij))
            elif method == "cie 1976 ucs":
                XYZ = xy_to_XYZ(Luv_uv_to_xy(ij))

        diagram_colours = normalise_maximum(XYZ_to_plotting_colourspace(
            XYZ, illuminant),
                                            axis=-1)

    polygon = Polygon(
        spectral_locus
        if diagram_clipping_path is None else diagram_clipping_path,
        facecolor="none" if use_RGB_diagram_colours else np.hstack(
            [diagram_colours, diagram_opacity]),
        edgecolor="none" if use_RGB_diagram_colours else np.hstack(
            [diagram_colours, diagram_opacity]),
        zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,
    )
    axes.add_patch(polygon)

    if use_RGB_diagram_colours:
        # Preventing bounding box related issues as per
        # https://github.com/matplotlib/matplotlib/issues/10529
        image = axes.imshow(
            diagram_colours,
            interpolation="bilinear",
            extent=(0, 1, 0, 1),
            clip_path=None,
            alpha=diagram_opacity,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,
        )
        image.set_clip_path(polygon)

    settings = {"axes": axes}
    settings.update(kwargs)

    return render(**kwargs)
예제 #19
0
def plot_spectral_locus(
    cmfs: Union[MultiSpectralDistributions, str, Sequence[Union[
        MultiSpectralDistributions,
        str]], ] = "CIE 1931 2 Degree Standard Observer",
    spectral_locus_colours: Optional[Union[ArrayLike, str]] = None,
    spectral_locus_opacity: Floating = 1,
    spectral_locus_labels: Optional[Sequence] = None,
    method: Union[Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"],
                  str] = "CIE 1931",
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot the *Spectral Locus* according to given method.

    Parameters
    ----------
    cmfs
        Standard observer colour matching functions used for computing the
        spectral locus boundaries. ``cmfs`` can be of any type or form
        supported by the :func:`colour.plotting.filter_cmfs` definition.
    spectral_locus_colours
        Colours of the *Spectral Locus*, if ``spectral_locus_colours`` is set
        to *RGB*, the colours will be computed according to the corresponding
        chromaticity coordinates.
    spectral_locus_opacity
        Opacity of the *Spectral Locus*.
    spectral_locus_labels
        Array of wavelength labels used to customise which labels will be drawn
        around the spectral locus. Passing an empty array will result in no
        wavelength labels being drawn.
    method
        *Chromaticity Diagram* method.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
        See the documentation of the previously listed definitions.

    Returns
    -------
    :class:`tuple`
        Current figure and axes.

    Examples
    --------
    >>> plot_spectral_locus(spectral_locus_colours='RGB')  # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_Plot_Spectral_Locus.png
        :align: center
        :alt: plot_spectral_locus
    """

    method = validate_method(method,
                             ["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"])

    spectral_locus_colours = optional(spectral_locus_colours,
                                      CONSTANTS_COLOUR_STYLE.colour.dark)

    settings: Dict[str, Any] = {"uniform": True}
    settings.update(kwargs)

    _figure, axes = artist(**settings)

    cmfs = cast(MultiSpectralDistributions,
                first_item(filter_cmfs(cmfs).values()))

    illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint

    wavelengths = list(cmfs.wavelengths)
    equal_energy = np.array([1 / 3] * 2)

    if method == "cie 1931":
        ij = XYZ_to_xy(cmfs.values, illuminant)
        labels = cast(
            Tuple,
            optional(
                spectral_locus_labels,
                (
                    390,
                    460,
                    470,
                    480,
                    490,
                    500,
                    510,
                    520,
                    540,
                    560,
                    580,
                    600,
                    620,
                    700,
                ),
            ),
        )
    elif method == "cie 1960 ucs":
        ij = UCS_to_uv(XYZ_to_UCS(cmfs.values))
        labels = cast(
            Tuple,
            optional(
                spectral_locus_labels,
                (
                    420,
                    440,
                    450,
                    460,
                    470,
                    480,
                    490,
                    500,
                    510,
                    520,
                    530,
                    540,
                    550,
                    560,
                    570,
                    580,
                    590,
                    600,
                    610,
                    620,
                    630,
                    645,
                    680,
                ),
            ),
        )
    elif method == "cie 1976 ucs":
        ij = Luv_to_uv(XYZ_to_Luv(cmfs.values, illuminant), illuminant)
        labels = cast(
            Tuple,
            optional(
                spectral_locus_labels,
                (
                    420,
                    440,
                    450,
                    460,
                    470,
                    480,
                    490,
                    500,
                    510,
                    520,
                    530,
                    540,
                    550,
                    560,
                    570,
                    580,
                    590,
                    600,
                    610,
                    620,
                    630,
                    645,
                    680,
                ),
            ),
        )

    pl_ij = np.reshape(
        tstack([
            np.linspace(ij[0][0], ij[-1][0], 20),
            np.linspace(ij[0][1], ij[-1][1], 20),
        ]),
        (-1, 1, 2),
    )
    sl_ij = np.copy(ij).reshape(-1, 1, 2)

    purple_line_colours: Optional[Union[ArrayLike, str]]
    if str(spectral_locus_colours).upper() == "RGB":
        spectral_locus_colours = normalise_maximum(XYZ_to_plotting_colourspace(
            cmfs.values),
                                                   axis=-1)

        if method == "cie 1931":
            XYZ = xy_to_XYZ(pl_ij)
        elif method == "cie 1960 ucs":
            XYZ = xy_to_XYZ(UCS_uv_to_xy(pl_ij))
        elif method == "cie 1976 ucs":
            XYZ = xy_to_XYZ(Luv_uv_to_xy(pl_ij))

        purple_line_colours = normalise_maximum(XYZ_to_plotting_colourspace(
            np.reshape(XYZ, (-1, 3))),
                                                axis=-1)
    else:
        purple_line_colours = spectral_locus_colours

    for slp_ij, slp_colours in (
        (pl_ij, purple_line_colours),
        (sl_ij, spectral_locus_colours),
    ):
        line_collection = LineCollection(
            np.concatenate([slp_ij[:-1], slp_ij[1:]], axis=1),
            colors=slp_colours,
            alpha=spectral_locus_opacity,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_scatter,
        )
        axes.add_collection(line_collection)

    wl_ij = dict(zip(wavelengths, ij))
    for label in labels:
        ij_l = wl_ij.get(label)

        if ij_l is None:
            continue

        ij_l = as_float_array([ij_l])
        i, j = tsplit(ij_l)

        index = bisect.bisect(wavelengths, label)
        left = wavelengths[index - 1] if index >= 0 else wavelengths[index]
        right = (wavelengths[index]
                 if index < len(wavelengths) else wavelengths[-1])

        dx = wl_ij[right][0] - wl_ij[left][0]
        dy = wl_ij[right][1] - wl_ij[left][1]

        direction = np.array([-dy, dx])

        normal = (np.array([-dy, dx]) if np.dot(
            normalise_vector(ij_l - equal_energy),
            normalise_vector(direction),
        ) > 0 else np.array([dy, -dx]))
        normal = normalise_vector(normal) / 30

        label_colour = (
            spectral_locus_colours if is_string(spectral_locus_colours) else
            spectral_locus_colours[index]  # type: ignore[index]
        )
        axes.plot(
            (i, i + normal[0] * 0.75),
            (j, j + normal[1] * 0.75),
            color=label_colour,
            alpha=spectral_locus_opacity,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.background_line,
        )

        axes.plot(
            i,
            j,
            "o",
            color=label_colour,
            alpha=spectral_locus_opacity,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.background_line,
        )

        axes.text(
            i + normal[0],
            j + normal[1],
            label,
            clip_on=True,
            ha="left" if normal[0] >= 0 else "right",
            va="center",
            fontdict={"size": "small"},
            zorder=CONSTANTS_COLOUR_STYLE.zorder.background_label,
        )

    settings = {"axes": axes}
    settings.update(kwargs)

    return render(**kwargs)
예제 #20
0
 def __init__(self, module: ModuleType, changes: Optional[Dict] = None):
     self._module = module
     self._changes = optional(changes, {})
예제 #21
0
def XYZ_to_Izazbz(
    XYZ_D65: ArrayLike,
    constants: Optional[Structure] = None,
    method: Union[
        Literal["Safdar 2017", "Safdar 2021", "ZCAM"], str
    ] = "Safdar 2017",
) -> NDArray:
    """
    Convert from *CIE XYZ* tristimulus values to :math:`I_za_zb_z`
    colourspace.

    Parameters
    ----------
    XYZ_D65
        *CIE XYZ* tristimulus values under
        *CIE Standard Illuminant D Series D65*.
    constants
        :math:`J_za_zb_z` colourspace constants.
    method
        Computation methods, *Safdar 2021* and *ZCAM* methods are equivalent.

    Returns
    -------
    :class:`numpy.ndarray`
        :math:`I_za_zb_z` colourspace array where :math:`I_z` is the achromatic
        response, :math:`a_z` is redness-greenness and :math:`b_z` is
        yellowness-blueness.

    Warnings
    --------
    The underlying *SMPTE ST 2084:2014* transfer function is an absolute
    transfer function.

    Notes
    -----
    -   The underlying *SMPTE ST 2084:2014* transfer function is an absolute
        transfer function, thus the domain and range values for the *Reference*
        and *1* scales are only indicative that the data is not affected by
        scale transformations. The effective domain of *SMPTE ST 2084:2014*
        inverse electro-optical transfer function (EOTF) is
        [0.0001, 10000].

    +------------+-----------------------+------------------+
    | **Domain** | **Scale - Reference** | **Scale - 1**    |
    +============+=======================+==================+
    | ``XYZ``    | ``UN``                | ``UN``           |
    +------------+-----------------------+------------------+

    +------------+-----------------------+------------------+
    | **Range**  | **Scale - Reference** | **Scale - 1**    |
    +============+=======================+==================+
    | ``Izazbz`` | ``Iz`` : [0, 1]       | ``Iz`` : [0, 1]  |
    |            |                       |                  |
    |            | ``az`` : [-1, 1]      | ``az`` : [-1, 1] |
    |            |                       |                  |
    |            | ``bz`` : [-1, 1]      | ``bz`` : [-1, 1] |
    +------------+-----------------------+------------------+

    References
    ----------
    :cite:`Safdar2017`, :cite:`Safdar2021`

    Examples
    --------
    >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952])
    >>> XYZ_to_Izazbz(XYZ)  # doctest: +ELLIPSIS
    array([ 0.0120779...,  0.0092430...,  0.0052600...])
    """

    X_D65, Y_D65, Z_D65 = tsplit(as_float_array(XYZ_D65))

    method = validate_method(method, IZAZBZ_METHODS)

    constants = optional(
        constants,
        CONSTANTS_JZAZBZ_SAFDAR2017
        if method == "safdar 2017"
        else CONSTANTS_JZAZBZ_SAFDAR2021,
    )

    X_p_D65 = constants.b * X_D65 - (constants.b - 1) * Z_D65
    Y_p_D65 = constants.g * Y_D65 - (constants.g - 1) * X_D65

    XYZ_p_D65 = tstack([X_p_D65, Y_p_D65, Z_D65])

    LMS = vector_dot(MATRIX_JZAZBZ_XYZ_TO_LMS, XYZ_p_D65)

    with domain_range_scale("ignore"):
        LMS_p = eotf_inverse_ST2084(LMS, 10000, constants)

    if method == "safdar 2017":
        Izazbz = vector_dot(MATRIX_JZAZBZ_LMS_P_TO_IZAZBZ_SAFDAR2017, LMS_p)
    else:
        Izazbz = vector_dot(MATRIX_JZAZBZ_LMS_P_TO_IZAZBZ_SAFDAR2021, LMS_p)
        Izazbz[..., 0] -= constants.d_0

    return Izazbz
예제 #22
0
def nadir_grid(
    limits: Optional[ArrayLike] = None,
    segments: Integer = 10,
    labels: Optional[Sequence[str]] = None,
    axes: Optional[plt.Axes] = None,
    **kwargs: Any,
) -> Tuple[NDArray, NDArray, NDArray]:
    """
    Return a grid on *CIE xy* plane made of quad geometric elements and its
    associated faces and edges colours. Ticks and labels are added to the
    given axes according to the extended grid settings.

    Parameters
    ----------
    limits
        Extended grid limits.
    segments
        Edge segments count for the extended grid.
    labels
        Axis labels.
    axes
        Axes to add the grid.

    Other Parameters
    ----------------
    grid_edge_alpha
        Grid edge opacity value such as `grid_edge_alpha = 0.5`.
    grid_edge_colours
        Grid edge colours array such as
        `grid_edge_colours = (0.25, 0.25, 0.25)`.
    grid_face_alpha
        Grid face opacity value such as `grid_face_alpha = 0.1`.
    grid_face_colours
        Grid face colours array such as
        `grid_face_colours = (0.25, 0.25, 0.25)`.
    ticks_and_label_location
        Location of the *X* and *Y* axis ticks and labels such as
        `ticks_and_label_location = ('-x', '-y')`.
    x_axis_colour
        *X* axis colour array such as `x_axis_colour = (0.0, 0.0, 0.0, 1.0)`.
    x_label_colour
        *X* axis label colour array such as
        `x_label_colour = (0.0, 0.0, 0.0, 0.85)`.
    x_ticks_colour
        *X* axis ticks colour array such as
        `x_ticks_colour = (0.0, 0.0, 0.0, 0.85)`.
    y_axis_colour
        *Y* axis colour array such as `y_axis_colour = (0.0, 0.0, 0.0, 1.0)`.
    y_label_colour
        *Y* axis label colour array such as
        `y_label_colour = (0.0, 0.0, 0.0, 0.85)`.
    y_ticks_colour
        *Y* axis ticks colour array such as
        `y_ticks_colour = (0.0, 0.0, 0.0, 0.85)`.

    Returns
    -------
    :class:`tuple`
        Grid quads, faces colours, edges colours.

    Examples
    --------
    >>> nadir_grid(segments=1)
    (array([[[-1.   , -1.   ,  0.   ],
            [ 1.   , -1.   ,  0.   ],
            [ 1.   ,  1.   ,  0.   ],
            [-1.   ,  1.   ,  0.   ]],
    <BLANKLINE>
           [[-1.   , -1.   ,  0.   ],
            [ 0.   , -1.   ,  0.   ],
            [ 0.   ,  0.   ,  0.   ],
            [-1.   ,  0.   ,  0.   ]],
    <BLANKLINE>
           [[-1.   ,  0.   ,  0.   ],
            [ 0.   ,  0.   ,  0.   ],
            [ 0.   ,  1.   ,  0.   ],
            [-1.   ,  1.   ,  0.   ]],
    <BLANKLINE>
           [[ 0.   , -1.   ,  0.   ],
            [ 1.   , -1.   ,  0.   ],
            [ 1.   ,  0.   ,  0.   ],
            [ 0.   ,  0.   ,  0.   ]],
    <BLANKLINE>
           [[ 0.   ,  0.   ,  0.   ],
            [ 1.   ,  0.   ,  0.   ],
            [ 1.   ,  1.   ,  0.   ],
            [ 0.   ,  1.   ,  0.   ]],
    <BLANKLINE>
           [[-1.   , -0.001,  0.   ],
            [ 1.   , -0.001,  0.   ],
            [ 1.   ,  0.001,  0.   ],
            [-1.   ,  0.001,  0.   ]],
    <BLANKLINE>
           [[-0.001, -1.   ,  0.   ],
            [ 0.001, -1.   ,  0.   ],
            [ 0.001,  1.   ,  0.   ],
            [-0.001,  1.   ,  0.   ]]]), array([[ 0.25,  0.25,  0.25,  0.1 ],
           [ 0.  ,  0.  ,  0.  ,  0.  ],
           [ 0.  ,  0.  ,  0.  ,  0.  ],
           [ 0.  ,  0.  ,  0.  ,  0.  ],
           [ 0.  ,  0.  ,  0.  ,  0.  ],
           [ 0.  ,  0.  ,  0.  ,  1.  ],
           [ 0.  ,  0.  ,  0.  ,  1.  ]]), array([[ 0.5 ,  0.5 ,  0.5 ,  0.5 ],
           [ 0.75,  0.75,  0.75,  0.25],
           [ 0.75,  0.75,  0.75,  0.25],
           [ 0.75,  0.75,  0.75,  0.25],
           [ 0.75,  0.75,  0.75,  0.25],
           [ 0.  ,  0.  ,  0.  ,  1.  ],
           [ 0.  ,  0.  ,  0.  ,  1.  ]]))
    """

    limits = as_float_array(
        cast(ArrayLike, optional(limits, np.array([[-1, 1], [-1, 1]]))))
    labels = cast(Sequence, optional(labels, ("x", "y")))

    extent = np.max(np.abs(limits[..., 1] - limits[..., 0]))

    settings = Structure(
        **{
            "grid_face_colours": (0.25, 0.25, 0.25),
            "grid_edge_colours": (0.50, 0.50, 0.50),
            "grid_face_alpha": 0.1,
            "grid_edge_alpha": 0.5,
            "x_axis_colour": (0.0, 0.0, 0.0, 1.0),
            "y_axis_colour": (0.0, 0.0, 0.0, 1.0),
            "x_ticks_colour": (0.0, 0.0, 0.0, 0.85),
            "y_ticks_colour": (0.0, 0.0, 0.0, 0.85),
            "x_label_colour": (0.0, 0.0, 0.0, 0.85),
            "y_label_colour": (0.0, 0.0, 0.0, 0.85),
            "ticks_and_label_location": ("-x", "-y"),
        })
    settings.update(**kwargs)

    # Outer grid.
    quads_g = primitive_vertices_grid_mpl(
        origin=(-extent / 2, -extent / 2),
        width=extent,
        height=extent,
        height_segments=segments,
        width_segments=segments,
    )

    RGB_g = ones((quads_g.shape[0], quads_g.shape[-1]))
    RGB_gf = RGB_g * settings.grid_face_colours
    RGB_gf = np.hstack(
        [RGB_gf, full((RGB_gf.shape[0], 1), settings.grid_face_alpha)])
    RGB_ge = RGB_g * settings.grid_edge_colours
    RGB_ge = np.hstack(
        [RGB_ge, full((RGB_ge.shape[0], 1), settings.grid_edge_alpha)])

    # Inner grid.
    quads_gs = primitive_vertices_grid_mpl(
        origin=(-extent / 2, -extent / 2),
        width=extent,
        height=extent,
        height_segments=segments * 2,
        width_segments=segments * 2,
    )

    RGB_gs = ones((quads_gs.shape[0], quads_gs.shape[-1]))
    RGB_gsf = RGB_gs * 0
    RGB_gsf = np.hstack([RGB_gsf, full((RGB_gsf.shape[0], 1), 0)])
    RGB_gse = np.clip(RGB_gs * settings.grid_edge_colours * 1.5, 0, 1)
    RGB_gse = np.hstack(
        (RGB_gse, full((RGB_gse.shape[0], 1), settings.grid_edge_alpha / 2)))

    # Axis.
    thickness = extent / 1000
    quad_x = primitive_vertices_grid_mpl(origin=(limits[0, 0], -thickness / 2),
                                         width=extent,
                                         height=thickness)
    RGB_x = ones((quad_x.shape[0], quad_x.shape[-1] + 1))
    RGB_x = RGB_x * settings.x_axis_colour

    quad_y = primitive_vertices_grid_mpl(origin=(-thickness / 2, limits[1, 0]),
                                         width=thickness,
                                         height=extent)
    RGB_y = ones((quad_y.shape[0], quad_y.shape[-1] + 1))
    RGB_y = RGB_y * settings.y_axis_colour

    if axes is not None:
        # Ticks.
        x_s = 1 if "+x" in settings.ticks_and_label_location else -1
        y_s = 1 if "+y" in settings.ticks_and_label_location else -1
        for i, axis in enumerate("xy"):
            h_a = "center" if axis == "x" else "left" if x_s == 1 else "right"
            v_a = "center"

            ticks = list(sorted(set(quads_g[..., 0, i])))
            ticks += [ticks[-1] + ticks[-1] - ticks[-2]]
            for tick in ticks:
                x = (limits[1, 1 if x_s == 1 else 0] +
                     (x_s * extent / 25) if i else tick)
                y = (tick if i else limits[0, 1 if y_s == 1 else 0] +
                     (y_s * extent / 25))

                tick = as_int_scalar(tick) if is_integer(tick) else tick
                c = settings[f"{axis}_ticks_colour"]

                axes.text(
                    x,
                    y,
                    0,
                    tick,
                    "x",
                    horizontalalignment=h_a,
                    verticalalignment=v_a,
                    color=c,
                    clip_on=True,
                )

        # Labels.
        for i, axis in enumerate("xy"):
            h_a = "center" if axis == "x" else "left" if x_s == 1 else "right"
            v_a = "center"

            x = (limits[1, 1 if x_s == 1 else 0] +
                 (x_s * extent / 10) if i else 0)
            y = (0 if i else limits[0, 1 if y_s == 1 else 0] +
                 (y_s * extent / 10))

            c = settings[f"{axis}_label_colour"]

            axes.text(
                x,
                y,
                0,
                labels[i],
                "x",
                horizontalalignment=h_a,
                verticalalignment=v_a,
                color=c,
                size=20,
                clip_on=True,
            )

    quads = np.vstack([quads_g, quads_gs, quad_x, quad_y])
    RGB_f = np.vstack([RGB_gf, RGB_gsf, RGB_x, RGB_y])
    RGB_e = np.vstack([RGB_ge, RGB_gse, RGB_x, RGB_y])

    return quads, RGB_f, RGB_e
예제 #23
0
def plot_hull_section_contour(
    hull: trimesh.Trimesh,  # type: ignore[name-defined]  # noqa
    model: Union[Literal["CAM02LCD", "CAM02SCD", "CAM02UCS", "CAM16LCD",
                         "CAM16SCD", "CAM16UCS", "CIE XYZ", "CIE xyY",
                         "CIE Lab", "CIE Luv", "CIE UCS", "CIE UVW", "DIN99",
                         "Hunter Lab", "Hunter Rdab", "ICaCb", "ICtCp", "IPT",
                         "IgPgTg", "Jzazbz", "OSA UCS", "Oklab", "hdr-CIELAB",
                         "hdr-IPT", ], str, ] = "CIE xyY",
    axis: Union[Literal["+z", "+x", "+y"], str] = "+z",
    origin: Floating = 0.5,
    normalise: Boolean = True,
    contour_colours: Optional[Union[ArrayLike, str]] = None,
    contour_opacity: Floating = 1,
    convert_kwargs: Optional[Dict] = None,
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot the section contour of given *trimesh* hull along given axis and
    origin.

    Parameters
    ----------
    hull
        *Trimesh* hull.
    model
        Colourspace model, see :attr:`colour.COLOURSPACE_MODELS` attribute for
        the list of supported colourspace models.
    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.
    contour_colours
        Colours of the hull section contour, if ``contour_colours`` is set to
        *RGB*, the colours will be computed according to the corresponding
        coordinates.
    contour_opacity
        Opacity of the hull section contour.
    convert_kwargs
        Keyword arguments for the :func:`colour.convert` definition.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.render`},
        See the documentation of the previously listed definitions.

    Returns
    -------
    :class:`tuple`
        Current figure and axes.

    Examples
    --------
    >>> from colour.models import RGB_COLOURSPACE_sRGB
    >>> from colour.utilities import is_trimesh_installed
    >>> vertices, faces, _outline = primitive_cube(1, 1, 1, 64, 64, 64)
    >>> XYZ_vertices = RGB_to_XYZ(
    ...     vertices['position'] + 0.5,
    ...     RGB_COLOURSPACE_sRGB.whitepoint,
    ...     RGB_COLOURSPACE_sRGB.whitepoint,
    ...     RGB_COLOURSPACE_sRGB.matrix_RGB_to_XYZ,
    ... )
    >>> if is_trimesh_installed:
    ...     import trimesh
    ...     hull = trimesh.Trimesh(XYZ_vertices, faces, process=False)
    ...     plot_hull_section_contour(hull, contour_colours='RGB')
    ...     # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_Plot_Hull_Section_Contour.png
        :align: center
        :alt: plot_hull_section_contour
    """

    hull = hull.copy()

    contour_colours = cast(
        Union[ArrayLike, str],
        optional(contour_colours, CONSTANTS_COLOUR_STYLE.colour.dark),
    )

    settings: Dict[str, Any] = {"uniform": True}
    settings.update(kwargs)

    _figure, axes = artist(**settings)

    convert_kwargs = optional(convert_kwargs, {})

    # Luminance / Lightness is re-ordered along "z-up" axis.
    with suppress_warnings(python_warnings=True):
        ijk_vertices = colourspace_model_axis_reorder(
            convert(hull.vertices, "CIE XYZ", model, **convert_kwargs), model)
        ijk_vertices = np.nan_to_num(ijk_vertices)
        ijk_vertices *= COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[
            model]

    hull.vertices = ijk_vertices

    plane = MAPPING_AXIS_TO_PLANE[axis]

    padding = 0.1 * np.mean(
        COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[model])
    min_x = np.min(ijk_vertices[..., plane[0]]) - padding
    max_x = np.max(ijk_vertices[..., plane[0]]) + padding
    min_y = np.min(ijk_vertices[..., plane[1]]) - padding
    max_y = np.max(ijk_vertices[..., plane[1]]) + padding
    extent = (min_x, max_x, min_y, max_y)

    use_RGB_contour_colours = str(contour_colours).upper() == "RGB"
    section = hull_section(hull, axis, origin, normalise)
    if use_RGB_contour_colours:
        ijk_section = section / (
            COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[model])
        XYZ_section = convert(
            colourspace_model_axis_reorder(ijk_section, model, "Inverse"),
            model,
            "CIE XYZ",
            **convert_kwargs,
        )
        contour_colours = np.clip(XYZ_to_plotting_colourspace(XYZ_section), 0,
                                  1)

    section = np.reshape(section[..., plane], (-1, 1, 2))
    line_collection = LineCollection(
        np.concatenate([section[:-1], section[1:]], axis=1),
        colors=contour_colours,
        alpha=contour_opacity,
        zorder=CONSTANTS_COLOUR_STYLE.zorder.background_line,
    )
    axes.add_collection(line_collection)

    settings = {
        "axes": axes,
        "bounding_box": extent,
    }
    settings.update(kwargs)

    return render(**settings)
예제 #24
0
def plot_multi_functions(
    functions: Dict[str, Callable],
    samples: Optional[ArrayLike] = None,
    log_x: Optional[Integer] = None,
    log_y: Optional[Integer] = None,
    plot_kwargs: Optional[Union[Dict, List[Dict]]] = None,
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot given functions.

    Parameters
    ----------
    functions
        Functions to plot.
    samples
        Samples to evaluate the functions with.
    log_x
        Log base to use for the *x* axis scale, if *None*, the *x* axis scale
        will be linear.
    log_y
        Log base to use for the *y* axis scale, if *None*, the *y* axis scale
        will be linear.
    plot_kwargs
        Keyword arguments for the :func:`matplotlib.pyplot.plot` definition,
        used to control the style of the plotted functions. ``plot_kwargs``
        can be either a single dictionary applied to all the plotted functions
        with the same settings or a sequence of dictionaries with different
        settings for each plotted function.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
        See the documentation of the previously listed definitions.

    Returns
    -------
    :class:`tuple`
        Current figure and axes.

    Examples
    --------
    >>> functions = {
    ...     'Gamma 2.2' : lambda x: x ** (1 / 2.2),
    ...     'Gamma 2.4' : lambda x: x ** (1 / 2.4),
    ...     'Gamma 2.6' : lambda x: x ** (1 / 2.6),
    ... }
    >>> plot_multi_functions(functions)
    ... # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_Plot_Multi_Functions.png
        :align: center
        :alt: plot_multi_functions
    """

    settings: Dict[str, Any] = dict(kwargs)

    _figure, axes = artist(**settings)

    plot_settings_collection = [{
        "label":
        f"{name}",
        "zorder":
        CONSTANTS_COLOUR_STYLE.zorder.midground_label,
    } for name in functions.keys()]

    if plot_kwargs is not None:
        update_settings_collection(plot_settings_collection, plot_kwargs,
                                   len(functions))

    # TODO: Remove when "Matplotlib" minimum version can be set to 3.5.0.
    matplotlib_3_5 = tuple(
        int(token)
        for token in matplotlib.__version__.split(".")[:2]) >= (3, 5)

    if log_x is not None and log_y is not None:
        attest(
            log_x >= 2 and log_y >= 2,
            "Log base must be equal or greater than 2.",
        )

        plotting_function = axes.loglog

        axes.set_xscale("log", base=log_x)
        axes.set_yscale("log", base=log_y)
    elif log_x is not None:
        attest(log_x >= 2, "Log base must be equal or greater than 2.")

        if matplotlib_3_5:  # pragma: no cover
            plotting_function = partial(axes.semilogx, base=log_x)
        else:  # pragma: no cover
            plotting_function = partial(axes.semilogx, basex=log_x)
    elif log_y is not None:
        attest(log_y >= 2, "Log base must be equal or greater than 2.")

        if matplotlib_3_5:  # pragma: no cover
            plotting_function = partial(axes.semilogy, base=log_y)
        else:  # pragma: no cover
            plotting_function = partial(axes.semilogy, basey=log_y)
    else:
        plotting_function = axes.plot

    samples = cast(ArrayLike, optional(samples, np.linspace(0, 1, 1000)))

    for i, (_name, function) in enumerate(functions.items()):
        plotting_function(samples, function(samples),
                          **plot_settings_collection[i])

    x_label = (f"x - Log Base {log_x} Scale"
               if log_x is not None else "x - Linear Scale")
    y_label = (f"y - Log Base {log_y} Scale"
               if log_y is not None else "y - Linear Scale")
    settings = {
        "axes": axes,
        "legend": True,
        "title": f"{', '.join(functions)} - Functions",
        "x_label": x_label,
        "y_label": y_label,
    }
    settings.update(kwargs)

    return render(**settings)
예제 #25
0
def plot_RGB_scatter(
    RGB: ArrayLike,
    colourspace: Union[RGB_Colourspace, str, Sequence[Union[RGB_Colourspace,
                                                            str]]],
    reference_colourspace: Union[Literal["CAM02LCD", "CAM02SCD", "CAM02UCS",
                                         "CAM16LCD", "CAM16SCD", "CAM16UCS",
                                         "CIE XYZ", "CIE xyY", "CIE Lab",
                                         "CIE Luv", "CIE UCS", "CIE UVW",
                                         "DIN99", "Hunter Lab", "Hunter Rdab",
                                         "ICaCb", "ICtCp", "IPT", "IgPgTg",
                                         "Jzazbz", "OSA UCS", "Oklab",
                                         "hdr-CIELAB", "hdr-IPT", ],
                                 str, ] = "CIE xyY",
    colourspaces: Optional[Union[RGB_Colourspace, str,
                                 Sequence[Union[RGB_Colourspace,
                                                str]]]] = None,
    segments: Integer = 8,
    show_grid: Boolean = True,
    grid_segments: Integer = 10,
    show_spectral_locus: Boolean = False,
    spectral_locus_colour: Optional[Union[ArrayLike, str]] = None,
    points_size: Floating = 12,
    cmfs: Union[MultiSpectralDistributions, str, Sequence[Union[
        MultiSpectralDistributions,
        str]], ] = "CIE 1931 2 Degree Standard Observer",
    chromatically_adapt: Boolean = False,
    convert_kwargs: Optional[Dict] = None,
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot given *RGB* colourspace array in a scatter plot.

    Parameters
    ----------
    RGB
        *RGB* colourspace array.
    colourspace
        *RGB* colourspace of the *RGB* array. ``colourspace`` can be of any
        type or form supported by the
        :func:`colour.plotting.filter_RGB_colourspaces` definition.
    reference_colourspace
        Reference colourspace model to plot the gamuts into, see
        :attr:`colour.COLOURSPACE_MODELS` attribute for the list of supported
        colourspace models.
    colourspaces
        *RGB* colourspaces to plot the gamuts. ``colourspaces`` elements
        can be of any type or form supported by the
        :func:`colour.plotting.filter_RGB_colourspaces` definition.
    segments
        Edge segments count for each *RGB* colourspace cubes.
    show_grid
        Whether to show a grid at the bottom of the *RGB* colourspace cubes.
    grid_segments
        Edge segments count for the grid.
    show_spectral_locus
        Whether to show the spectral locus.
    spectral_locus_colour
        Spectral locus colour.
    points_size
        Scatter points size.
    cmfs
        Standard observer colour matching functions used for computing the
        spectral locus boundaries. ``cmfs`` can be of any type or form
        supported by the :func:`colour.plotting.filter_cmfs` definition.
    chromatically_adapt
        Whether to chromatically adapt the *RGB* colourspaces given in
        ``colourspaces`` to the whitepoint of the default plotting colourspace.
    convert_kwargs
        Keyword arguments for the :func:`colour.convert` definition.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.plot_RGB_colourspaces_gamuts`},
        See the documentation of the previously listed definitions.

    Returns
    -------
    :class:`tuple`
        Current figure and axes.

    Examples
    --------
    >>> RGB = np.random.random((128, 128, 3))
    >>> plot_RGB_scatter(RGB, 'ITU-R BT.709')  # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...Axes3DSubplot...>)

    .. image:: ../_static/Plotting_Plot_RGB_Scatter.png
        :align: center
        :alt: plot_RGB_scatter
    """

    colourspace = cast(
        RGB_Colourspace,
        first_item(filter_RGB_colourspaces(colourspace).values()),
    )
    colourspaces = cast(List[str], optional(colourspaces, [colourspace.name]))

    convert_kwargs = optional(convert_kwargs, {})

    count_c = len(colourspaces)
    settings = Structure(
        **{
            "face_colours": [None] * count_c,
            "edge_colours": [(0.25, 0.25, 0.25)] * count_c,
            "face_alpha": [0.0] * count_c,
            "edge_alpha": [0.1] * count_c,
        })
    settings.update(kwargs)
    settings["standalone"] = False

    plot_RGB_colourspaces_gamuts(
        colourspaces=colourspaces,
        reference_colourspace=reference_colourspace,
        segments=segments,
        show_grid=show_grid,
        grid_segments=grid_segments,
        show_spectral_locus=show_spectral_locus,
        spectral_locus_colour=spectral_locus_colour,
        cmfs=cmfs,
        chromatically_adapt=chromatically_adapt,
        **settings,
    )

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

    convert_settings = {"illuminant": colourspace.whitepoint}
    convert_settings.update(convert_kwargs)

    points = colourspace_model_axis_reorder(
        convert(XYZ, "CIE XYZ", reference_colourspace, **convert_settings),
        reference_colourspace,
    )

    axes = plt.gca()
    axes.scatter(
        points[..., 0],
        points[..., 1],
        points[..., 2],
        color=np.reshape(RGB, (-1, 3)),
        s=points_size,
        zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_scatter,
    )

    settings.update({"axes": axes, "standalone": True})
    settings.update(kwargs)

    return render(**settings)
예제 #26
0
def logarithmic_function_camera(
    x: FloatingOrArrayLike,
    style: Union[Literal["cameraLinToLog", "cameraLogToLin"],
                 str] = "cameraLinToLog",
    base: Integer = 2,
    log_side_slope: Floating = 1,
    lin_side_slope: Floating = 1,
    log_side_offset: Floating = 0,
    lin_side_offset: Floating = 0,
    lin_side_break: Floating = 0.005,
    linear_slope: Optional[Floating] = None,
) -> FloatingOrNDArray:
    """
    Define the camera logarithmic function.

    Parameters
    ----------
    x
        Linear/non-linear data to undergo encoding/decoding.
    style
        Defines the behaviour for the logarithmic function to operate:

        -   *cameraLinToLog*: Applies a piece-wise function with logarithmic
            and linear segments on linear values, converting them to non-linear
            values.
        -   *cameraLogToLin*: Applies a piece-wise function with logarithmic
            and linear segments on non-linear values, converting them to linear
            values.
    base
        Logarithmic base used for the conversion.
    log_side_slope
        Slope (or gain) applied to the log side of the logarithmic segment. The
        default value is 1.
    lin_side_slope
        Slope of the linear side of the logarithmic segment. The default value
        is 1.
    log_side_offset
        Offset applied to the log side of the logarithmic segment. The default
        value is 0.
    lin_side_offset
        Offset applied to the linear side of the logarithmic segment. The
        default value is 0.
    lin_side_break
        Break-point, defined in linear space, at which the piece-wise function
        transitions between the logarithmic and linear segments.
    linear_slope
        Slope of the linear portion of the curve. The default value is *None*.

    Returns
    -------
    :class:`numpy.floating` or :class:`numpy.ndarray`
        Encoded/Decoded data.

    Examples
    --------
    >>> logarithmic_function_camera(  # doctest: +ELLIPSIS
    ...    0.18, 'cameraLinToLog')
    -2.4739311...
    >>> logarithmic_function_camera(  # doctest: +ELLIPSIS
    ...    -2.4739311883324122, 'cameraLogToLin')
    0.1800000...
    """

    x = as_float_array(x)
    style = validate_method(
        style,
        ["cameraLinToLog", "cameraLogToLin"],
        '"{0}" style is invalid, it must be one of {1}!',
    )

    log_side_break = (
        log_side_slope *
        (np.log(lin_side_slope * lin_side_break + lin_side_offset) /
         np.log(base)) + log_side_offset)

    linear_slope = cast(
        Floating,
        optional(
            linear_slope,
            (log_side_slope *
             (lin_side_slope /
              ((lin_side_slope * lin_side_break + lin_side_offset) *
               np.log(base)))),
        ),
    )

    linear_offset = log_side_break - linear_slope * lin_side_break

    if style == "cameralintolog":
        return as_float(
            np.where(
                x <= lin_side_break,
                linear_slope * x + linear_offset,
                logarithmic_function_quasilog(
                    x,
                    "linToLog",
                    base,
                    log_side_slope,
                    lin_side_slope,
                    log_side_offset,
                    lin_side_offset,
                ),
            ))
    else:  # style == 'cameralogtolin'
        return as_float(
            np.where(
                x <= log_side_break,
                (x - linear_offset) / linear_slope,
                logarithmic_function_quasilog(
                    x,
                    "logToLin",
                    base,
                    log_side_slope,
                    lin_side_slope,
                    log_side_offset,
                    lin_side_offset,
                ),
            ))
예제 #27
0
    def signal_unpack_data(
        data=Optional[Union[ArrayLike, dict, Series, "Signal"]],
        domain: Optional[ArrayLike] = None,
        dtype: Optional[Type[DTypeFloating]] = None,
    ) -> Tuple:
        """
        Unpack given data for continuous signal instantiation.

        Parameters
        ----------
        data
            Data to unpack for continuous signal instantiation.
        domain
            Values to initialise the :attr:`colour.continuous.Signal.domain`
            attribute with. If both ``data`` and ``domain`` arguments are
            defined, the latter will be used to initialise the
            :attr:`colour.continuous.Signal.domain` property.
        dtype
            Floating point data type.

        Returns
        -------
        :class:`tuple`
            Independent domain variable :math:`x` and corresponding range
            variable :math:`y` unpacked for continuous signal instantiation.

        Examples
        --------
        Unpacking using implicit *domain*:

        >>> range_ = np.linspace(10, 100, 10)
        >>> domain, range_ = Signal.signal_unpack_data(range_)
        >>> print(domain)
        [ 0.  1.  2.  3.  4.  5.  6.  7.  8.  9.]
        >>> print(range_)
        [  10.   20.   30.   40.   50.   60.   70.   80.   90.  100.]

        Unpacking using explicit *domain*:

        >>> domain = np.arange(100, 1100, 100)
        >>> domain, range = Signal.signal_unpack_data(range_, domain)
        >>> print(domain)
        [  100.   200.   300.   400.   500.   600.   700.   800.   900.  1000.]
        >>> print(range_)
        [  10.   20.   30.   40.   50.   60.   70.   80.   90.  100.]

        Unpacking using a *dict*:

        >>> domain, range_ = Signal.signal_unpack_data(
        ...     dict(zip(domain, range_)))
        >>> print(domain)
        [  100.   200.   300.   400.   500.   600.   700.   800.   900.  1000.]
        >>> print(range_)
        [  10.   20.   30.   40.   50.   60.   70.   80.   90.  100.]

        Unpacking using a *Pandas* :class:`pandas.Series`:

        >>> if is_pandas_installed():
        ...     from pandas import Series
        ...     domain, range = Signal.signal_unpack_data(
        ...         Series(dict(zip(domain, range_))))
        ... # doctest: +ELLIPSIS
        >>> print(domain)  # doctest: +SKIP
        [  100.   200.   300.   400.   500.   600.   700.   800.   900.  1000.]
        >>> print(range_)  # doctest: +SKIP
        [  10.   20.   30.   40.   50.   60.   70.   80.   90.  100.]

        Unpacking using a :class:`colour.continuous.Signal` class:

        >>> domain, range_ = Signal.signal_unpack_data(
        ...     Signal(range_, domain))
        >>> print(domain)
        [  100.   200.   300.   400.   500.   600.   700.   800.   900.  1000.]
        >>> print(range_)
        [  10.   20.   30.   40.   50.   60.   70.   80.   90.  100.]
        """

        dtype = cast(Type[DTypeFloating], optional(dtype, DEFAULT_FLOAT_DTYPE))

        domain_unpacked: NDArray = np.array([])
        range_unpacked: NDArray = np.array([])

        if isinstance(data, Signal):
            domain_unpacked = data.domain
            range_unpacked = data.range
        elif issubclass(type(data), Sequence) or isinstance(
            data, (tuple, list, np.ndarray, Iterator, ValuesView)
        ):
            data_array = tsplit(list(data))

            attest(data_array.ndim == 1, 'User "data" must be 1-dimensional!')

            domain_unpacked, range_unpacked = (
                np.arange(0, data_array.size, dtype=dtype),
                data_array,
            )
        elif issubclass(type(data), Mapping) or isinstance(data, dict):
            domain_unpacked, range_unpacked = tsplit(sorted(data.items()))
        elif is_pandas_installed():
            if isinstance(data, Series):
                domain_unpacked = data.index.values
                range_unpacked = data.values

        if domain is not None:
            domain_array = as_float_array(list(domain), dtype)  # type: ignore[arg-type]

            attest(
                len(domain_array) == len(range_unpacked),
                'User "domain" length is not compatible with unpacked "range"!',
            )

            domain_unpacked = domain_array

        if range_unpacked is not None:
            range_unpacked = as_float_array(range_unpacked, dtype)

        return domain_unpacked, range_unpacked
예제 #28
0
def plot_single_sd_colour_rendition_report_full(
    sd: SpectralDistribution,
    source: Optional[str] = None,
    date: Optional[str] = None,
    manufacturer: Optional[str] = None,
    model: Optional[str] = None,
    notes: Optional[str] = None,
    report_size: Tuple = CONSTANT_REPORT_SIZE_FULL,
    report_row_height_ratios: Tuple = CONSTANT_REPORT_ROW_HEIGHT_RATIOS_FULL,
    report_box_padding: Optional[Dict] = None,
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:  # noqa: D405,D407,D410,D411
    """
    Generate the full *ANSI/IES TM-30-18 Colour Rendition Report* for given
    spectral distribution.

    Parameters
    ----------
    sd
        Spectral distribution of the emission source to generate the report
        for.
    source
        Emission source name, defaults to
        `colour.SpectralDistribution_IESTM2714.header.description` or
        `colour.SpectralDistribution_IESTM2714.name` properties value.
    date
        Emission source measurement date, defaults to
        `colour.SpectralDistribution_IESTM2714.header.report_date` property
        value.
    manufacturer
        Emission source manufacturer, defaults to
        `colour.SpectralDistribution_IESTM2714.header.manufacturer` property
        value.
    model
        Emission source model, defaults to
        `colour.SpectralDistribution_IESTM2714.header.catalog_number` property
        value.
    notes
        Notes pertaining to the emission source, defaults to
        `colour.SpectralDistribution_IESTM2714.header.comments` property
        value.
    report_size
        Report size, default to A4 paper size in inches.
    report_row_height_ratios
        Report size row height ratios.
    report_box_padding
        Report box padding, tries to define the padding around the figure and
        in-between the axes.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
        See the documentation of the previously listed definitions.

    Returns
    -------
    :class:`tuple`
        Current figure and axes.

    Examples
    --------
    >>> from colour import SDS_ILLUMINANTS
    >>> sd = SDS_ILLUMINANTS['FL2']
    >>> plot_single_sd_colour_rendition_report_full(sd)
    ... # doctest: +ELLIPSIS
    (<Figure size ... with ... Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_\
Plot_Single_SD_Colour_Rendition_Report_Full.png
        :align: center
        :alt: plot_single_sd_colour_rendition_report_full
    """

    report_box_padding = optional(report_box_padding,
                                  CONSTANT_REPORT_PADDING_FULL)

    specification: ColourQuality_Specification_ANSIIESTM3018 = cast(
        ColourQuality_Specification_ANSIIESTM3018,
        colour_fidelity_index_ANSIIESTM3018(sd, True),
    )

    sd = (SpectralDistribution_IESTM2714(data=sd, name=sd.name)
          if not isinstance(sd, SpectralDistribution_IESTM2714) else sd)

    NA = _VALUE_NOT_APPLICABLE

    source = optional(optional(source, sd.header.description), sd.name)
    date = optional(optional(date, sd.header.report_date), NA)
    manufacturer = optional(optional(manufacturer, sd.header.manufacturer), NA)
    model = optional(optional(model, sd.header.catalog_number), NA)
    notes = optional(optional(notes, sd.header.comments), NA)

    figure = plt.figure(figsize=report_size, constrained_layout=True)

    settings: Dict[str, Any] = dict(kwargs)
    settings["standalone"] = False
    settings["tight_layout"] = False

    gridspec_report = figure.add_gridspec(
        5, 1, height_ratios=report_row_height_ratios)

    # Title Row
    gridspec_title = gridspec_report[0].subgridspec(1, 1)
    axes_title = figure.add_subplot(gridspec_title[0])
    _plot_report_header(axes_title)

    # Description Rows & Columns
    gridspec_description = gridspec_report[1].subgridspec(1, 2)
    # Source & Date Column
    axes_source_date = figure.add_subplot(gridspec_description[0])
    axes_source_date.set_axis_off()
    axes_source_date.text(
        0.25,
        2 / 3,
        "Source: ",
        ha="right",
        va="center",
        size="medium",
        weight="bold",
    )
    axes_source_date.text(0.25, 2 / 3, source, va="center", size="medium")

    axes_source_date.text(
        0.25,
        1 / 3,
        "Date: ",
        ha="right",
        va="center",
        size="medium",
        weight="bold",
    )
    axes_source_date.text(0.25, 1 / 3, date, va="center", size="medium")

    # Manufacturer & Model Column
    axes_manufacturer_model = figure.add_subplot(gridspec_description[1])
    axes_manufacturer_model.set_axis_off()
    axes_manufacturer_model.text(
        0.25,
        2 / 3,
        "Manufacturer: ",
        ha="right",
        va="center",
        size="medium",
        weight="bold",
    )
    axes_manufacturer_model.text(0.25,
                                 2 / 3,
                                 manufacturer,
                                 va="center",
                                 size="medium")

    axes_manufacturer_model.text(
        0.25,
        1 / 3,
        "Model: ",
        ha="right",
        va="center",
        size="medium",
        weight="bold",
    )
    axes_manufacturer_model.text(0.25,
                                 1 / 3,
                                 model,
                                 va="center",
                                 size="medium")

    # Main Figures Rows & Columns
    gridspec_figures = gridspec_report[2].subgridspec(
        4, 2, height_ratios=[1, 1, 1, 1.5])
    axes_spectra = figure.add_subplot(gridspec_figures[0, 0])
    plot_spectra_ANSIIESTM3018(specification, axes=axes_spectra, **settings)

    axes_vector_graphics = figure.add_subplot(gridspec_figures[1:3, 0])
    plot_colour_vector_graphic(specification,
                               axes=axes_vector_graphics,
                               **settings)

    axes_chroma_shifts = figure.add_subplot(gridspec_figures[0, 1])
    plot_local_chroma_shifts(specification,
                             axes=axes_chroma_shifts,
                             **settings)

    axes_hue_shifts = figure.add_subplot(gridspec_figures[1, 1])
    plot_local_hue_shifts(specification, axes=axes_hue_shifts, **settings)

    axes_colour_fidelities = figure.add_subplot(gridspec_figures[2, 1])
    plot_local_colour_fidelities(specification,
                                 axes=axes_colour_fidelities,
                                 x_ticker=True,
                                 **settings)

    # Colour Fidelity Indexes Row
    axes_colour_fidelity_indexes = figure.add_subplot(gridspec_figures[3, :])
    plot_colour_fidelity_indexes(specification,
                                 axes=axes_colour_fidelity_indexes,
                                 **settings)

    # Notes & Chromaticities / CRI Row and Columns
    gridspec_notes_chromaticities_CRI = gridspec_report[3].subgridspec(1, 2)
    axes_notes = figure.add_subplot(gridspec_notes_chromaticities_CRI[0])
    axes_notes.set_axis_off()
    axes_notes.text(
        0.25,
        1,
        "Notes: ",
        ha="right",
        va="center",
        size="medium",
        weight="bold",
    )
    axes_notes.text(0.25, 1, notes, va="center", size="medium")
    gridspec_chromaticities_CRI = gridspec_notes_chromaticities_CRI[
        1].subgridspec(1, 2)

    XYZ = sd_to_XYZ(specification.sd_test)
    xy = XYZ_to_xy(XYZ)
    Luv = XYZ_to_Luv(XYZ, xy)
    uv_p = Luv_to_uv(Luv, xy)

    gridspec_chromaticities = gridspec_chromaticities_CRI[0].subgridspec(1, 1)
    axes_chromaticities = figure.add_subplot(gridspec_chromaticities[0])
    axes_chromaticities.set_axis_off()
    axes_chromaticities.text(
        0.5,
        4 / 5,
        f"$x$ {xy[0]:.4f}",
        ha="center",
        va="center",
        size="medium",
        weight="bold",
    )

    axes_chromaticities.text(
        0.5,
        3 / 5,
        f"$y$ {xy[1]:.4f}",
        ha="center",
        va="center",
        size="medium",
        weight="bold",
    )

    axes_chromaticities.text(
        0.5,
        2 / 5,
        f"$u'$ {uv_p[0]:.4f}",
        ha="center",
        va="center",
        size="medium",
        weight="bold",
    )

    axes_chromaticities.text(
        0.5,
        1 / 5,
        f"$v'$ {uv_p[1]:.4f}",
        ha="center",
        va="center",
        size="medium",
        weight="bold",
    )

    gridspec_CRI = gridspec_chromaticities_CRI[1].subgridspec(1, 1)

    CRI_spec: ColourRendering_Specification_CRI = cast(
        ColourRendering_Specification_CRI,
        colour_rendering_index(specification.sd_test, additional_data=True),
    )

    axes_CRI = figure.add_subplot(gridspec_CRI[0])
    axes_CRI.set_xticks([])
    axes_CRI.set_yticks([])
    axes_CRI.text(
        0.5,
        4 / 5,
        "CIE 13.31-1995",
        ha="center",
        va="center",
        size="medium",
        weight="bold",
    )

    axes_CRI.text(
        0.5,
        3 / 5,
        "(CRI)",
        ha="center",
        va="center",
        size="medium",
        weight="bold",
    )

    axes_CRI.text(
        0.5,
        2 / 5,
        f"$R_a$ {float(CRI_spec.Q_a):.0f}",
        ha="center",
        va="center",
        size="medium",
        weight="bold",
    )

    axes_CRI.text(
        0.5,
        1 / 5,
        f"$R_9$ {float(CRI_spec.Q_as[8].Q_a):.0f}",
        ha="center",
        va="center",
        size="medium",
        weight="bold",
    )

    gridspec_footer = gridspec_report[4].subgridspec(1, 1)
    axes_footer = figure.add_subplot(gridspec_footer[0])
    _plot_report_footer(axes_footer)

    figure.set_constrained_layout_pads(**report_box_padding)

    settings = dict(kwargs)
    settings["tight_layout"] = False

    return render(**settings)
예제 #29
0
def plot_hull_section_colours(
    hull: trimesh.Trimesh,  # type: ignore[name-defined]  # noqa
    model: Union[Literal["CAM02LCD", "CAM02SCD", "CAM02UCS", "CAM16LCD",
                         "CAM16SCD", "CAM16UCS", "CIE XYZ", "CIE xyY",
                         "CIE Lab", "CIE Luv", "CIE UCS", "CIE UVW", "DIN99",
                         "Hunter Lab", "Hunter Rdab", "ICaCb", "ICtCp", "IPT",
                         "IgPgTg", "Jzazbz", "OSA UCS", "Oklab", "hdr-CIELAB",
                         "hdr-IPT", ], str, ] = "CIE xyY",
    axis: Union[Literal["+z", "+x", "+y"], str] = "+z",
    origin: Floating = 0.5,
    normalise: Boolean = True,
    section_colours: Optional[Union[ArrayLike, str]] = None,
    section_opacity: Floating = 1,
    convert_kwargs: Optional[Dict] = None,
    samples: Integer = 256,
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Plot the section colours of given *trimesh* hull along given axis and
    origin.

    Parameters
    ----------
    hull
        *Trimesh* hull.
    model
        Colourspace model, see :attr:`colour.COLOURSPACE_MODELS` attribute for
        the list of supported colourspace models.
    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.
    section_colours
        Colours of the hull section, if ``section_colours`` is set to *RGB*,
        the colours will be computed according to the corresponding
        coordinates.
    section_opacity
        Opacity of the hull section colours.
    convert_kwargs
        Keyword arguments for the :func:`colour.convert` definition.
    samples
        Samples count on one axis when computing the hull section colours.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`,
        :func:`colour.plotting.render`},
        See the documentation of the previously listed definitions.

    Returns
    -------
    :class:`tuple`
        Current figure and axes.

    Examples
    --------
    >>> from colour.models import RGB_COLOURSPACE_sRGB
    >>> from colour.utilities import is_trimesh_installed
    >>> vertices, faces, _outline = primitive_cube(1, 1, 1, 64, 64, 64)
    >>> XYZ_vertices = RGB_to_XYZ(
    ...     vertices['position'] + 0.5,
    ...     RGB_COLOURSPACE_sRGB.whitepoint,
    ...     RGB_COLOURSPACE_sRGB.whitepoint,
    ...     RGB_COLOURSPACE_sRGB.matrix_RGB_to_XYZ,
    ... )
    >>> if is_trimesh_installed:
    ...     import trimesh
    ...     hull = trimesh.Trimesh(XYZ_vertices, faces, process=False)
    ...     plot_hull_section_colours(hull, section_colours='RGB')
    ...     # doctest: +ELLIPSIS
    (<Figure size ... with 1 Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_Plot_Hull_Section_Colours.png
        :align: center
        :alt: plot_hull_section_colours
    """

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

    hull = hull.copy()

    settings: Dict[str, Any] = {"uniform": True}
    settings.update(kwargs)

    _figure, axes = artist(**settings)

    section_colours = cast(
        ArrayLike,
        optional(section_colours,
                 HEX_to_RGB(CONSTANTS_COLOUR_STYLE.colour.average)),
    )

    convert_kwargs = optional(convert_kwargs, {})

    # Luminance / Lightness reordered along "z" axis.
    with suppress_warnings(python_warnings=True):
        ijk_vertices = colourspace_model_axis_reorder(
            convert(hull.vertices, "CIE XYZ", model, **convert_kwargs), model)
        ijk_vertices = np.nan_to_num(ijk_vertices)
        ijk_vertices *= COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[
            model]

    hull.vertices = ijk_vertices

    if axis == "+x":
        index_origin = 0
    elif axis == "+y":
        index_origin = 1
    elif axis == "+z":
        index_origin = 2
    plane = MAPPING_AXIS_TO_PLANE[axis]

    section = hull_section(hull, axis, origin, normalise)

    padding = 0.1 * np.mean(
        COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[model])
    min_x = np.min(ijk_vertices[..., plane[0]]) - padding
    max_x = np.max(ijk_vertices[..., plane[0]]) + padding
    min_y = np.min(ijk_vertices[..., plane[1]]) - padding
    max_y = np.max(ijk_vertices[..., plane[1]]) + padding
    extent = (min_x, max_x, min_y, max_y)

    use_RGB_section_colours = str(section_colours).upper() == "RGB"
    if use_RGB_section_colours:
        ii, jj = np.meshgrid(
            np.linspace(min_x, max_x, samples),
            np.linspace(max_y, min_y, samples),
        )
        ij = tstack([ii, jj])
        ijk_section = full((samples, samples, 3),
                           np.median(section[..., index_origin]))
        ijk_section[..., plane] = ij
        ijk_section /= COLOURSPACE_MODELS_DOMAIN_RANGE_SCALE_1_TO_REFERENCE[
            model]
        XYZ_section = convert(
            colourspace_model_axis_reorder(ijk_section, model, "Inverse"),
            model,
            "CIE XYZ",
            **convert_kwargs,
        )
        RGB_section = XYZ_to_plotting_colourspace(XYZ_section)
    else:
        section_colours = np.hstack([section_colours, section_opacity])

    facecolor = "none" if use_RGB_section_colours else section_colours
    polygon = Polygon(
        section[..., plane],
        facecolor=facecolor,
        edgecolor="none",
        zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,
    )
    axes.add_patch(polygon)
    if use_RGB_section_colours:
        image = axes.imshow(
            np.clip(RGB_section, 0, 1),
            interpolation="bilinear",
            extent=extent,
            clip_path=None,
            alpha=section_opacity,
            zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon,
        )
        image.set_clip_path(polygon)

    settings = {
        "axes": axes,
        "bounding_box": extent,
    }
    settings.update(kwargs)

    return render(**settings)
예제 #30
0
def plot_single_sd_colour_rendition_report_simple(
    sd: SpectralDistribution,
    report_size: Tuple = CONSTANT_REPORT_SIZE_SIMPLE,
    report_row_height_ratios: Tuple = CONSTANT_REPORT_ROW_HEIGHT_RATIOS_SIMPLE,
    report_box_padding: Optional[Dict] = None,
    **kwargs: Any,
) -> Tuple[plt.Figure, plt.Axes]:
    """
    Generate the simple *ANSI/IES TM-30-18 Colour Rendition Report* for given
    spectral distribution.

    Parameters
    ----------
    sd
        Spectral distribution of the emission source to generate the report
        for.
    report_size
        Report size, default to A4 paper size in inches.
    report_row_height_ratios
        Report size row height ratios.
    report_box_padding
        Report box padding, tries to define the padding around the figure and
        in-between the axes.

    Other Parameters
    ----------------
    kwargs
        {:func:`colour.plotting.artist`, :func:`colour.plotting.render`},
        See the documentation of the previously listed definitions.

    Returns
    -------
    :class:`tuple`
        Current figure and axes.

    Examples
    --------
    >>> from colour import SDS_ILLUMINANTS
    >>> sd = SDS_ILLUMINANTS['FL2']
    >>> plot_single_sd_colour_rendition_report_simple(sd)
    ... # doctest: +ELLIPSIS
    (<Figure size ... with ... Axes>, <...AxesSubplot...>)

    .. image:: ../_static/Plotting_\
Plot_Single_SD_Colour_Rendition_Report_Simple.png
        :align: center
        :alt: plot_single_sd_colour_rendition_report_simple
    """

    report_box_padding = optional(report_box_padding,
                                  CONSTANT_REPORT_PADDING_SIMPLE)

    specification: ColourQuality_Specification_ANSIIESTM3018 = cast(
        ColourQuality_Specification_ANSIIESTM3018,
        colour_fidelity_index_ANSIIESTM3018(sd, True),
    )

    figure = plt.figure(figsize=report_size, constrained_layout=True)

    settings: Dict[str, Any] = dict(kwargs)
    settings["standalone"] = False
    settings["tight_layout"] = False

    gridspec_report = figure.add_gridspec(
        3, 1, height_ratios=report_row_height_ratios)

    # Title Row
    gridspec_title = gridspec_report[0].subgridspec(1, 1)
    axes_title = figure.add_subplot(gridspec_title[0])
    _plot_report_header(axes_title)

    # Main Figures Rows & Columns
    gridspec_figures = gridspec_report[1].subgridspec(1, 1)

    axes_vector_graphics = figure.add_subplot(gridspec_figures[0, 0])
    plot_colour_vector_graphic(specification,
                               axes=axes_vector_graphics,
                               **settings)

    gridspec_footer = gridspec_report[2].subgridspec(1, 1)
    axes_footer = figure.add_subplot(gridspec_footer[0])
    _plot_report_footer(axes_footer)

    figure.set_constrained_layout_pads(**report_box_padding)

    settings = dict(kwargs)
    settings["tight_layout"] = False

    return render(**settings)