def test_artist(self): """Test :func:`colour.plotting.common.artist` definition.""" figure_1, axes_1 = artist() self.assertIsInstance(figure_1, Figure) self.assertIsInstance(axes_1, Axes) _figure_2, axes_2 = artist(axes=axes_1, uniform=True) self.assertIs(axes_1, axes_2) figure_3, _axes_3 = artist(uniform=True) self.assertEqual(figure_3.get_figwidth(), figure_3.get_figheight())
def test_render(self): """ Tests :func:`colour.plotting.common.render` definition. """ figure, axes = artist() render( figure=figure, axes=axes, standalone=False, aspect='equal', axes_visible=True, bounding_box=[0, 1, 0, 1], tight_layout=False, legend=True, legend_columns=2, transparent_background=False, title='Render Unit Test', wrap_title=True, x_label='x Label', y_label='y Label', x_ticker=False, y_ticker=False, ) render(standalone=True) render(filename=os.path.join(self._temporary_directory, 'render.png'), axes_visible=False)
def plot_colour_fidelity_indexes( specification: ColourQuality_Specification_ANSIIESTM3018, **kwargs: Any) -> Tuple[plt.Figure, plt.Axes]: """ Plot the local chroma shifts according to *ANSI/IES TM-30-18 Colour Rendition Report*. Parameters ---------- specification *ANSI/IES TM-30-18 Colour Rendition Report* specification. 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 >>> from colour.quality import colour_fidelity_index_ANSIIESTM3018 >>> sd = SDS_ILLUMINANTS['FL2'] >>> specification = colour_fidelity_index_ANSIIESTM3018(sd, True) >>> plot_colour_fidelity_indexes(specification) ... # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, <...AxesSubplot...>) """ _figure, axes = artist(**kwargs) bar_count = len(_COLOURS_TCS_BAR) axes.bar( np.arange(bar_count) + 1, specification.R_s, color=_COLOURS_TCS_BAR, width=1, edgecolor="black", linewidth=CONSTANTS_COLOUR_STYLE.geometry.short / 3, zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon, ) axes.set_xlim(0.5, bar_count + 0.5) axes.set_ylim(0, 100) axes.set_yticks(np.arange(0, 110, 10)) axes.set_ylabel("Color Sample Fidelity ($R_{f,CESi}$)") ticks = list(range(1, bar_count + 1, 1)) axes.set_xticks(ticks) labels = [ f"CES{i:02d}" if i % 3 == 1 else "" for i in range(1, bar_count + 1) ] axes.set_xticklabels(labels, rotation=90) return render(**kwargs)
def plot_spectra_ANSIIESTM3018(specification, **kwargs): """ Plots a comparison of the spectral distributions of a test emission source and a reference illuminant for *ANSI/IES TM-30-18 Colour Rendition Report*. Parameters ---------- specification : ColourQuality_Specification_ANSIIESTM3018 *ANSI/IES TM-30-18 Colour Rendition Report* specification. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes Examples -------- >>> from colour import SDS_ILLUMINANTS >>> from colour.quality import colour_fidelity_index_ANSIIESTM3018 >>> sd = SDS_ILLUMINANTS['FL2'] >>> specification = colour_fidelity_index_ANSIIESTM3018(sd, True) >>> plot_spectra_ANSIIESTM3018(specification) ... # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, <...AxesSubplot...>) """ settings = kwargs.copy() _figure, axes = artist(**settings) Y_reference = sd_to_XYZ(specification.sd_reference)[1] Y_test = sd_to_XYZ(specification.sd_test)[1] axes.plot(specification.sd_reference.wavelengths, specification.sd_reference.values / Y_reference, 'black', label='Reference') axes.plot(specification.sd_test.wavelengths, specification.sd_test.values / Y_test, '#F05046', label='Test') axes.tick_params(axis='y', which='both', length=0) axes.set_yticklabels([]) settings = { 'axes': axes, 'legend': True, 'legend_columns': 2, 'x_label': 'Wavelength (nm)', 'y_label': 'Radiant Power\n(Equal Luminous Flux)', } settings.update(kwargs) return render(**settings)
def plot_colour_fidelity_indexes(specification, **kwargs): """ Plots the local chroma shifts according to *ANSI/IES TM-30-18 Colour Rendition Report*. Parameters ---------- specification : ColourQuality_Specification_ANSIIESTM3018 *ANSI/IES TM-30-18 Colour Rendition Report* specification. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes Examples -------- >>> from colour import SDS_ILLUMINANTS >>> from colour.quality import colour_fidelity_index_ANSIIESTM3018 >>> sd = SDS_ILLUMINANTS['FL2'] >>> specification = colour_fidelity_index_ANSIIESTM3018(sd, True) >>> plot_colour_fidelity_indexes(specification) ... # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, <...AxesSubplot...>) """ _figure, axes = artist(**kwargs) bar_count = len(_TCS_BAR_COLOURS) axes.bar(np.arange(bar_count) + 1, specification.R_s, color=_TCS_BAR_COLOURS, width=1, edgecolor='black', linewidth=CONSTANTS_COLOUR_STYLE.geometry.short / 3) axes.set_xlim(0.5, bar_count + 0.5) axes.set_ylim(0, 100) axes.set_yticks(np.arange(0, 110, 10)) axes.set_ylabel('Color Sample Fidelity ($R_{f,CESi}$)') ticks = list(range(1, bar_count + 1, 1)) axes.set_xticks(ticks) labels = [ 'CES{0:02d}'.format(i) if i % 3 == 1 else '' for i in range(1, bar_count + 1) ] axes.set_xticklabels(labels, rotation=90) return render(**kwargs)
def test_uniform_axes3d(self): """Test :func:`colour.plotting.common.uniform_axes3d` definition.""" figure, _axes = artist() axes = figure.add_subplot(111, projection="3d") uniform_axes3d(axes=axes) self.assertEqual(axes.get_xlim(), axes.get_ylim()) self.assertEqual(axes.get_xlim(), axes.get_zlim())
def test_camera(self): """Test :func:`colour.plotting.common.camera` definition.""" figure, _axes = artist() axes = figure.add_subplot(111, projection="3d") _figure, axes = camera(axes=axes, elevation=45, azimuth=90) self.assertEqual(axes.elev, 45) self.assertEqual(axes.azim, 90)
def test_label_rectangles(self): """ Tests :func:`colour.plotting.common.label_rectangles` definition. """ figure, axes = artist() samples = np.linspace(0, 1, 10) _figure, axes = label_rectangles( samples, axes.bar(samples, 1), figure=figure, axes=axes) self.assertEqual(len(axes.texts), len(samples))
def plot_visible_spectrum_section( cmfs: Union[MultiSpectralDistributions, str, Sequence[Union[ MultiSpectralDistributions, str]], ] = "CIE 1931 2 Degree Standard Observer", illuminant: Union[SpectralDistribution, str] = "D65", 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, show_section_colours: Boolean = True, show_section_contour: Boolean = True, **kwargs: Any, ) -> Tuple[plt.Figure, plt.Axes]: """ Plot the visible spectrum volume, i.e. *Rösch-MacAdam* colour solid, section colours along given axis and origin. Parameters ---------- cmfs Standard observer colour matching functions, default to the *CIE 1931 2 Degree Standard Observer*. ``cmfs`` can be of any type or form supported by the :func:`colour.plotting.filter_cmfs` definition. illuminant Illuminant spectral distribution, default to *CIE Illuminant D65*. ``illuminant`` can be of any type or form supported by the :func:`colour.plotting.filter_illuminants` definition. 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. show_section_colours Whether to show the hull section colours. show_section_contour Whether to show the hull section contour. Other Parameters ---------------- kwargs {:func:`colour.plotting.artist`, :func:`colour.plotting.render`, :func:`colour.plotting.section.plot_hull_section_colours` :func:`colour.plotting.section.plot_hull_section_contour`}, See the documentation of the previously listed definitions. Returns ------- :class:`tuple` Current figure and axes. Examples -------- >>> from colour.utilities import is_trimesh_installed >>> if is_trimesh_installed: ... plot_visible_spectrum_section( ... section_colours='RGB', section_opacity=0.15) ... # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, <...AxesSubplot...>) .. image:: ../_static/Plotting_Plot_Visible_Spectrum_Section.png :align: center :alt: plot_visible_spectrum_section """ import trimesh settings: Dict[str, Any] = {"uniform": True} settings.update(kwargs) _figure, axes = artist(**settings) # pylint: disable=E1102 cmfs = reshape_msds(first_item(filter_cmfs(cmfs).values()), SpectralShape(360, 780, 1)) illuminant = cast( SpectralDistribution, first_item(filter_illuminants(illuminant).values()), ) vertices = solid_RoschMacAdam( cmfs, illuminant, point_order="Pulse Wave Width", filter_jagged_points=True, ) mesh = trimesh.Trimesh(vertices) hull = trimesh.convex.convex_hull(mesh) if show_section_colours: settings = {"axes": axes} settings.update(kwargs) settings["standalone"] = False plot_hull_section_colours(hull, model, axis, origin, normalise, **settings) if show_section_contour: settings = {"axes": axes} settings.update(kwargs) settings["standalone"] = False plot_hull_section_contour(hull, model, axis, origin, normalise, **settings) title = (f"Visible Spectrum Section - " f"{f'{origin * 100}%' if normalise else origin} - " f"{model} - " f"{cmfs.strict_name}") plane = MAPPING_AXIS_TO_PLANE[axis] labels = np.array(COLOURSPACE_MODELS_AXIS_LABELS[model])[as_int_array( colourspace_model_axis_reorder([0, 1, 2], model))] x_label, y_label = labels[plane[0]], labels[plane[1]] settings.update({ "axes": axes, "standalone": True, "title": title, "x_label": x_label, "y_label": y_label, }) settings.update(kwargs) return render(**settings)
def plot_colour_quality_bars( specifications: Sequence[ Union[ ColourRendering_Specification_CQS, ColourRendering_Specification_CRI, ] ], labels: Boolean = True, hatching: Optional[Boolean] = None, hatching_repeat: Integer = 2, **kwargs: Any, ) -> Tuple[plt.Figure, plt.Axes]: """ Plot the colour quality data of given illuminants or light sources colour quality specifications. Parameters ---------- specifications Array of illuminants or light sources colour quality specifications. labels Add labels above bars. hatching Use hatching for the bars. hatching_repeat Hatching pattern repeat. Other Parameters ---------------- kwargs {:func:`colour.plotting.artist`, :func:`colour.plotting.quality.plot_colour_quality_bars`, :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, ... SDS_LIGHT_SOURCES, SpectralShape) >>> illuminant = SDS_ILLUMINANTS['FL2'] >>> light_source = SDS_LIGHT_SOURCES['Kinoton 75P'] >>> light_source = light_source.copy().align(SpectralShape(360, 830, 1)) >>> cqs_i = colour_quality_scale(illuminant, additional_data=True) >>> cqs_l = colour_quality_scale(light_source, additional_data=True) >>> plot_colour_quality_bars([cqs_i, cqs_l]) # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, <...AxesSubplot...>) .. image:: ../_static/Plotting_Plot_Colour_Quality_Bars.png :align: center :alt: plot_colour_quality_bars """ settings: Dict[str, Any] = {"uniform": True} settings.update(kwargs) _figure, axes = artist(**settings) bar_width = 0.5 y_ticks_interval = 10 count_s, count_Q_as = len(specifications), 0 patterns = cycle(CONSTANTS_COLOUR_STYLE.hatch.patterns) if hatching is None: hatching = False if count_s == 1 else True for i, specification in enumerate(specifications): Q_a, Q_as, colorimetry_data = ( specification.Q_a, specification.Q_as, specification.colorimetry_data, ) count_Q_as = len(Q_as) RGB = [[1] * 3] + [ np.clip(XYZ_to_plotting_colourspace(x.XYZ), 0, 1) for x in colorimetry_data[0] ] x = ( as_float_array( i + np.arange( 0, (count_Q_as + 1) * (count_s + 1), (count_s + 1), dtype=DEFAULT_FLOAT_DTYPE, ) ) * bar_width ) y = as_float_array( [Q_a] + [ s[1].Q_a # type: ignore[attr-defined] for s in sorted(Q_as.items(), key=lambda s: s[0]) ] ) bars = axes.bar( x, np.abs(y), color=RGB, width=bar_width, edgecolor=CONSTANTS_COLOUR_STYLE.colour.dark, label=specification.name, zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon, ) hatches = ( [next(patterns) * hatching_repeat] * (count_Q_as + 1) if hatching else list( np.where(y < 0, next(patterns), None) # type: ignore[call-overload] ) ) for j, bar in enumerate(bars.patches): bar.set_hatch(hatches[j]) if labels: label_rectangles( [f"{y_v:.1f}" for y_v in y], bars, rotation="horizontal" if count_s == 1 else "vertical", offset=( 0 if count_s == 1 else 3 / 100 * count_s + 65 / 1000, 0.025, ), text_size=-5 / 7 * count_s + 12.5, axes=axes, ) axes.axhline( y=100, color=CONSTANTS_COLOUR_STYLE.colour.dark, linestyle="--", zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line, ) axes.set_xticks( ( np.arange( 0, (count_Q_as + 1) * (count_s + 1), (count_s + 1), dtype=DEFAULT_FLOAT_DTYPE, ) - bar_width ) * bar_width + (count_s * bar_width / 2) ) axes.set_xticklabels( ["Qa"] + [f"Q{index + 1}" for index in range(0, count_Q_as, 1)] ) axes.set_yticks(range(0, 100 + y_ticks_interval, y_ticks_interval)) aspect = 1 / (120 / (bar_width + len(Q_as) + bar_width * 2)) bounding_box = ( -bar_width, ((count_Q_as + 1) * (count_s + 1)) / 2 - bar_width, 0, 120, ) settings = { "axes": axes, "aspect": aspect, "bounding_box": bounding_box, "legend": hatching, "title": "Colour Quality", } settings.update(kwargs) return render(**settings)
def plot_multi_colour_checkers( colour_checkers: Union[ColourChecker, str, Sequence[Union[ColourChecker, str]]], **kwargs: Any, ) -> Tuple[plt.Figure, plt.Axes]: """ Plot and compares given colour checkers. Parameters ---------- colour_checkers Color checker to plot, count must be less than or equal to 2. ``colour_checkers`` elements can be of any type or form supported by the :func:`colour.plotting.filter_colour_checkers` definition. Other Parameters ---------------- kwargs {:func:`colour.plotting.artist`, :func:`colour.plotting.plot_multi_colour_swatches`, :func:`colour.plotting.render`}, See the documentation of the previously listed definitions. Returns ------- :class:`tuple` Current figure and axes. Examples -------- >>> plot_multi_colour_checkers(['ColorChecker 1976', 'ColorChecker 2005']) ... # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, <...AxesSubplot...>) .. image:: ../_static/Plotting_Plot_Multi_Colour_Checkers.png :align: center :alt: plot_multi_colour_checkers """ filtered_colour_checkers = list( filter_colour_checkers(colour_checkers).values()) attest( len(filtered_colour_checkers) <= 2, "Only two colour checkers can be compared at a time!", ) _figure, axes = artist(**kwargs) compare_swatches = len(filtered_colour_checkers) == 2 colour_swatches = [] colour_checker_names = [] for colour_checker in filtered_colour_checkers: colour_checker_names.append(colour_checker.name) for label, xyY in colour_checker.data.items(): XYZ = xyY_to_XYZ(xyY) RGB = XYZ_to_plotting_colourspace(XYZ, colour_checker.illuminant) colour_swatches.append( ColourSwatch(np.clip(np.ravel(RGB), 0, 1), label.title())) if compare_swatches: colour_swatches = [ swatch for pairs in zip( colour_swatches[0:len(colour_swatches) // 2], colour_swatches[len(colour_swatches) // 2:], ) for swatch in pairs ] background_colour = "0.1" width = height = 1.0 spacing = 0.25 columns = 6 settings: Dict[str, Any] = { "axes": axes, "width": width, "height": height, "spacing": spacing, "columns": columns, "direction": "-y", "text_kwargs": { "size": 8 }, "background_colour": background_colour, "compare_swatches": "Stacked" if compare_swatches else None, } settings.update(kwargs) settings["standalone"] = False plot_multi_colour_swatches(colour_swatches, **settings) axes.text( 0.5, 0.005, (f"{', '.join(colour_checker_names)} - " f"{CONSTANTS_COLOUR_STYLE.colour.colourspace.name} - " f"Colour Rendition Chart"), transform=axes.transAxes, color=CONSTANTS_COLOUR_STYLE.colour.bright, ha="center", va="bottom", zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_label, ) settings.update({ "axes": axes, "standalone": True, "title": ", ".join(colour_checker_names), }) return render(**settings)
def plot_sds_in_chromaticity_diagram( sds, cmfs='CIE 1931 2 Degree Standard Observer', chromaticity_diagram_callable=plot_chromaticity_diagram, method='CIE 1931', annotate_kwargs=None, plot_kwargs=None, **kwargs): """ Plots given spectral distribution chromaticity coordinates into the *Chromaticity Diagram* using given method. Parameters ---------- sds : array_like or MultiSpectralDistributions Spectral distributions or multi-spectral distributions to plot. `sds` can be a single :class:`colour.MultiSpectralDistributions` class instance, a list of :class:`colour.MultiSpectralDistributions` class instances or a list of :class:`colour.SpectralDistribution` class instances. cmfs : unicode or XYZ_ColourMatchingFunctions, optional 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. chromaticity_diagram_callable : callable, optional Callable responsible for drawing the *Chromaticity Diagram*. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. annotate_kwargs : dict or array_like, optional Keyword arguments for the :func:`plt.annotate` definition, used to annotate the resulting chromaticity coordinates with their respective spectral distribution names. ``annotate_kwargs`` can be either a single dictionary applied to all the arrows with same settings or a sequence of dictionaries with different settings for each spectral distribution. The following special keyword arguments can also be used: - *annotate* : bool, whether to annotate the spectral distributions. plot_kwargs : dict or array_like, optional Keyword arguments for the :func:`plt.plot` definition, used to control the style of the plotted spectral distributions. ``plot_kwargs`` can be either a single dictionary applied to all the plotted spectral distributions with same settings or a sequence of dictionaries with different settings for each plotted spectral distributions. The following special keyword arguments can also be used: - *illuminant* : unicode or :class:`colour.SpectralDistribution`, the illuminant used to compute the spectral distributions colours. The default is the illuminant associated with the whitepoint of the default plotting colourspace. ``illuminant`` can be of any type or form supported by the :func:`colour.plotting.filter_cmfs` definition. - *cmfs* : unicode, the standard observer colour matching functions used for computing the spectral distributions colours. ``cmfs`` can be of any type or form supported by the :func:`colour.plotting.filter_cmfs` definition. - *normalise_sd_colours* : bool, whether to normalise the computed spectral distributions colours. The default is *True*. - *use_sd_colours* : bool, whether to use the computed spectral distributions colours under the plotting colourspace illuminant. Alternatively, it is possible to use the :func:`plt.plot` definition ``color`` argument with pre-computed values. The default is *True*. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Also handles keywords arguments for deprecation management. Returns ------- tuple Current figure and axes. Examples -------- >>> A = SDS_ILLUMINANTS['A'] >>> D65 = SDS_ILLUMINANTS['D65'] >>> annotate_kwargs = [ ... {'xytext': (-25, 15), 'arrowprops':{'arrowstyle':'-'}}, ... {} ... ] >>> plot_kwargs = [ ... { ... 'illuminant': SDS_ILLUMINANTS['E'], ... 'markersize' : 15, ... 'normalise_sd_colours': True, ... 'use_sd_colours': True ... }, ... {'illuminant': SDS_ILLUMINANTS['E']}, ... ] >>> plot_sds_in_chromaticity_diagram( ... [A, D65], annotate_kwargs=annotate_kwargs, plot_kwargs=plot_kwargs) ... # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, <...AxesSubplot...>) .. image:: ../_static/Plotting_Plot_SDS_In_Chromaticity_Diagram.png :align: center :alt: plot_sds_in_chromaticity_diagram """ annotate_kwargs = handle_arguments_deprecation( { 'ArgumentRenamed': [['annotate_parameters', 'annotate_kwargs']], }, **kwargs).get('annotate_kwargs', annotate_kwargs) sds = sds_and_msds_to_sds(sds) settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) method = method.upper() settings.update({ 'axes': axes, 'standalone': False, 'method': method, 'cmfs': cmfs, }) chromaticity_diagram_callable(**settings) if method == 'CIE 1931': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return XYZ_to_xy(XYZ) bounding_box = (-0.1, 0.9, -0.1, 0.9) elif method == 'CIE 1960 UCS': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return UCS_to_uv(XYZ_to_UCS(XYZ)) bounding_box = (-0.1, 0.7, -0.2, 0.6) elif method == 'CIE 1976 UCS': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return Luv_to_uv(XYZ_to_Luv(XYZ)) bounding_box = (-0.1, 0.7, -0.1, 0.7) else: raise ValueError( 'Invalid method: "{0}", must be one of ' '[\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\']'.format( method)) annotate_settings_collection = [{ 'annotate': True, 'xytext': (-50, 30), 'textcoords': 'offset points', 'arrowprops': CONSTANTS_ARROW_STYLE, } for _ in range(len(sds))] if annotate_kwargs is not None: update_settings_collection(annotate_settings_collection, annotate_kwargs, len(sds)) plot_settings_collection = [{ 'color': CONSTANTS_COLOUR_STYLE.colour.brightest, 'label': '{0}'.format(sd.strict_name), 'marker': 'o', 'markeredgecolor': CONSTANTS_COLOUR_STYLE.colour.dark, 'markeredgewidth': CONSTANTS_COLOUR_STYLE.geometry.short * 0.75, 'markersize': (CONSTANTS_COLOUR_STYLE.geometry.short * 6 + CONSTANTS_COLOUR_STYLE.geometry.short * 0.75), 'cmfs': cmfs, 'illuminant': SDS_ILLUMINANTS[ CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint_name], 'use_sd_colours': False, 'normalise_sd_colours': False, } for sd in sds] if plot_kwargs is not None: update_settings_collection(plot_settings_collection, plot_kwargs, len(sds)) for i, sd in enumerate(sds): plot_settings = plot_settings_collection[i] cmfs = first_item(filter_cmfs(plot_settings.pop('cmfs')).values()) illuminant = first_item( filter_illuminants(plot_settings.pop('illuminant')).values()) normalise_sd_colours = plot_settings.pop('normalise_sd_colours') use_sd_colours = plot_settings.pop('use_sd_colours') with domain_range_scale('1'): XYZ = sd_to_XYZ(sd, cmfs, illuminant) if use_sd_colours: if normalise_sd_colours: XYZ /= XYZ[..., 1] plot_settings['color'] = np.clip(XYZ_to_plotting_colourspace(XYZ), 0, 1) ij = XYZ_to_ij(XYZ) axes.plot(ij[0], ij[1], **plot_settings) if (sd.name is not None and annotate_settings_collection[i]['annotate']): annotate_settings = annotate_settings_collection[i] annotate_settings.pop('annotate') axes.annotate(sd.name, xy=ij, **annotate_settings) settings.update({'standalone': True, 'bounding_box': bounding_box}) settings.update(kwargs) return render(**settings)
def plot_colour_quality_bars(specifications, labels=True, hatching=None, hatching_repeat=2, **kwargs): """ Plots the colour quality data of given illuminants or light sources colour quality specifications. Parameters ---------- specifications : array_like Array of illuminants or light sources colour quality specifications. labels : bool, optional Add labels above bars. hatching : bool or None, optional Use hatching for the bars. hatching_repeat : int, optional Hatching pattern repeat. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.quality.plot_colour_quality_bars`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> from colour import (ILLUMINANTS_SDS, ... LIGHT_SOURCES_SDS, SpectralShape) >>> illuminant = ILLUMINANTS_SDS['FL2'] >>> light_source = LIGHT_SOURCES_SDS['Kinoton 75P'] >>> light_source = light_source.copy().align(SpectralShape(360, 830, 1)) >>> cqs_i = colour_quality_scale(illuminant, additional_data=True) >>> cqs_l = colour_quality_scale(light_source, additional_data=True) >>> plot_colour_quality_bars([cqs_i, cqs_l]) # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Colour_Quality_Bars.png :align: center :alt: plot_colour_quality_bars """ settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) bar_width = 0.5 y_ticks_interval = 10 count_s, count_Q_as = len(specifications), 0 patterns = cycle(COLOUR_STYLE_CONSTANTS.hatch.patterns) if hatching is None: hatching = False if count_s == 1 else True for i, specification in enumerate(specifications): Q_a, Q_as, colorimetry_data = (specification.Q_a, specification.Q_as, specification.colorimetry_data) count_Q_as = len(Q_as) colours = ([[1] * 3] + [ np.clip(XYZ_to_plotting_colourspace(x.XYZ), 0, 1) for x in colorimetry_data[0] ]) x = (i + np.arange( 0, (count_Q_as + 1) * (count_s + 1), (count_s + 1), dtype=DEFAULT_FLOAT_DTYPE)) * bar_width y = [s[1].Q_a for s in sorted(Q_as.items(), key=lambda s: s[0])] y = np.array([Q_a] + list(y)) bars = plt.bar( x, np.abs(y), color=colours, width=bar_width, edgecolor=COLOUR_STYLE_CONSTANTS.colour.dark, label=specification.name) hatches = ([next(patterns) * hatching_repeat] * (count_Q_as + 1) if hatching else np.where(y < 0, next(patterns), None).tolist()) for j, bar in enumerate(bars.patches): bar.set_hatch(hatches[j]) if labels: label_rectangles( y, bars, rotation='horizontal' if count_s == 1 else 'vertical', offset=(0 if count_s == 1 else 3 / 100 * count_s + 65 / 1000, 0.025), text_size=-5 / 7 * count_s + 12.5) axes.axhline( y=100, color=COLOUR_STYLE_CONSTANTS.colour.dark, linestyle='--') axes.set_xticks((np.arange( 0, (count_Q_as + 1) * (count_s + 1), (count_s + 1), dtype=DEFAULT_FLOAT_DTYPE) * bar_width + (count_s * bar_width / 2)), ['Qa'] + [ 'Q{0}'.format(index + 1) for index in range(0, count_Q_as + 1, 1) ]) axes.set_yticks(range(0, 100 + y_ticks_interval, y_ticks_interval)) aspect = 1 / (120 / (bar_width + len(Q_as) + bar_width * 2)) bounding_box = (-bar_width, ((count_Q_as + 1) * (count_s + 1)) / 2, 0, 120) settings = { 'axes': axes, 'aspect': aspect, 'bounding_box': bounding_box, 'legend': hatching, 'title': 'Colour Quality', } settings.update(kwargs) return render(**settings)
def plot_chromaticity_diagram_colours( samples=256, diagram_opacity=1.0, diagram_clipping_path=None, cmfs='CIE 1931 2 Degree Standard Observer', method='CIE 1931', **kwargs): """ Plots the *Chromaticity Diagram* colours according to given method. Parameters ---------- samples : numeric, optional Samples count on one axis. diagram_opacity : numeric, optional Opacity of the *Chromaticity Diagram* colours. diagram_clipping_path : array_like, optional Path of points used to clip the *Chromaticity Diagram* colours. cmfs : unicode, optional Standard observer colour matching functions used for *Chromaticity Diagram* bounds. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_chromaticity_diagram_colours() # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Chromaticity_Diagram_Colours.png :align: center :alt: plot_chromaticity_diagram_colours """ settings = {'uniform': True} settings.update(kwargs) figure, axes = artist(**settings) method = method.upper() cmfs = first_item(filter_cmfs(cmfs).values()) illuminant = COLOUR_STYLE_CONSTANTS.colour.colourspace.whitepoint ii, jj = np.meshgrid( np.linspace(0, 1, samples), np.linspace(1, 0, samples)) ij = tstack([ii, jj]) if method == 'CIE 1931': XYZ = xy_to_XYZ(ij) spectral_locus = XYZ_to_xy(cmfs.values, illuminant) elif method == 'CIE 1960 UCS': XYZ = xy_to_XYZ(UCS_uv_to_xy(ij)) spectral_locus = UCS_to_uv(XYZ_to_UCS(cmfs.values)) elif method == 'CIE 1976 UCS': XYZ = xy_to_XYZ(Luv_uv_to_xy(ij)) spectral_locus = Luv_to_uv( XYZ_to_Luv(cmfs.values, illuminant), illuminant) else: raise ValueError( 'Invalid method: "{0}", must be one of ' '{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}'.format( method)) RGB = 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', edgecolor='none') axes.add_patch(polygon) # Preventing bounding box related issues as per # https://github.com/matplotlib/matplotlib/issues/10529 image = axes.imshow( RGB, interpolation='bilinear', extent=(0, 1, 0, 1), clip_path=None, alpha=diagram_opacity) image.set_clip_path(polygon) settings = {'axes': axes} settings.update(kwargs) return render(**kwargs)
def plot_multi_sds(sds, cmfs='CIE 1931 2 Degree Standard Observer', use_sds_colours=False, normalise_sds_colours=False, **kwargs): """ Plots given spectral distributions. Parameters ---------- sds : array_like or MultiSpectralDistributions Spectral distributions or multi-spectral distributions to plot. `sds` can be a single :class:`colour.MultiSpectralDistributions` class instance, a list of :class:`colour.MultiSpectralDistributions` class instances or a list of :class:`colour.SpectralDistribution` class instances. cmfs : unicode, optional Standard observer colour matching functions used for spectrum creation. use_sds_colours : bool, optional Whether to use spectral distributions colours. normalise_sds_colours : bool Whether to normalise spectral distributions colours. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> from colour import SpectralDistribution >>> data_1 = { ... 500: 0.004900, ... 510: 0.009300, ... 520: 0.063270, ... 530: 0.165500, ... 540: 0.290400, ... 550: 0.433450, ... 560: 0.594500 ... } >>> data_2 = { ... 500: 0.323000, ... 510: 0.503000, ... 520: 0.710000, ... 530: 0.862000, ... 540: 0.954000, ... 550: 0.994950, ... 560: 0.995000 ... } >>> sd_1 = SpectralDistribution(data_1, name='Custom 1') >>> sd_2 = SpectralDistribution(data_2, name='Custom 2') >>> plot_multi_sds([sd_1, sd_2]) # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, \ <matplotlib.axes._subplots.AxesSubplot object at 0x...>) .. image:: ../_static/Plotting_Plot_Multi_SDS.png :align: center :alt: plot_multi_sds """ _figure, axes = artist(**kwargs) sds = sds_and_multi_sds_to_sds(sds) cmfs = first_item(filter_cmfs(cmfs).values()) illuminant = ILLUMINANTS_SDS[ COLOUR_STYLE_CONSTANTS.colour.colourspace.illuminant] x_limit_min, x_limit_max, y_limit_min, y_limit_max = [], [], [], [] for sd in sds: wavelengths, values = sd.wavelengths, sd.values shape = sd.shape x_limit_min.append(shape.start) x_limit_max.append(shape.end) y_limit_min.append(min(values)) y_limit_max.append(max(values)) if use_sds_colours: with domain_range_scale('1'): XYZ = sd_to_XYZ(sd, cmfs, illuminant) if normalise_sds_colours: XYZ = normalise_maximum(XYZ, clip=False) RGB = np.clip(XYZ_to_plotting_colourspace(XYZ), 0, 1) axes.plot(wavelengths, values, color=RGB, label=sd.strict_name) else: axes.plot(wavelengths, values, label=sd.strict_name) bounding_box = (min(x_limit_min), max(x_limit_max), min(y_limit_min), max(y_limit_max) + max(y_limit_max) * 0.05) settings = { 'axes': axes, 'bounding_box': bounding_box, 'legend': True, 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': 'Spectral Distribution', } settings.update(kwargs) return render(**settings)
def plot_RGB_colourspaces_in_chromaticity_diagram( colourspaces=None, cmfs='CIE 1931 2 Degree Standard Observer', chromaticity_diagram_callable=plot_chromaticity_diagram, method='CIE 1931', show_whitepoints=True, show_pointer_gamut=False, **kwargs): """ Plots given *RGB* colourspaces in the *Chromaticity Diagram* according to given method. Parameters ---------- colourspaces : array_like, optional *RGB* colourspaces to plot. cmfs : unicode, optional Standard observer colour matching functions used for *Chromaticity Diagram* bounds. chromaticity_diagram_callable : callable, optional Callable responsible for drawing the *Chromaticity Diagram*. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. show_whitepoints : bool, optional Whether to display the *RGB* colourspaces whitepoints. show_pointer_gamut : bool, optional Whether to display the *Pointer's Gamut*. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, :func:`colour.plotting.plot_pointer_gamut`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_RGB_colourspaces_in_chromaticity_diagram( ... ['ITU-R BT.709', 'ACEScg', 'S-Gamut']) ... # doctest: +SKIP .. image:: ../_static/Plotting_\ Plot_RGB_Colourspaces_In_Chromaticity_Diagram.png :align: center :alt: plot_RGB_colourspaces_in_chromaticity_diagram """ if colourspaces is None: colourspaces = ['ITU-R BT.709', 'ACEScg', 'S-Gamut'] colourspaces = filter_RGB_colourspaces(colourspaces).values() settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) method = method.upper() cmfs = first_item(filter_cmfs(cmfs).values()) title = '{0}\n{1} - {2} Chromaticity Diagram'.format( ', '.join([colourspace.name for colourspace in colourspaces]), cmfs.name, method) settings = {'axes': axes, 'title': title, 'method': method} settings.update(kwargs) settings['standalone'] = False chromaticity_diagram_callable(**settings) if show_pointer_gamut: settings = {'axes': axes, 'method': method} settings.update(kwargs) settings['standalone'] = False plot_pointer_gamut(**settings) if method == 'CIE 1931': def xy_to_ij(xy): """ Converts given *xy* chromaticity coordinates to *ij* chromaticity coordinates. """ return xy x_limit_min, x_limit_max = [-0.1], [0.9] y_limit_min, y_limit_max = [-0.1], [0.9] elif method == 'CIE 1960 UCS': def xy_to_ij(xy): """ Converts given *xy* chromaticity coordinates to *ij* chromaticity coordinates. """ return xy_to_UCS_uv(xy) x_limit_min, x_limit_max = [-0.1], [0.7] y_limit_min, y_limit_max = [-0.2], [0.6] elif method == 'CIE 1976 UCS': def xy_to_ij(xy): """ Converts given *xy* chromaticity coordinates to *ij* chromaticity coordinates. """ return xy_to_Luv_uv(xy) x_limit_min, x_limit_max = [-0.1], [0.7] y_limit_min, y_limit_max = [-0.1], [0.7] else: raise ValueError( 'Invalid method: "{0}", must be one of ' '{{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}}'.format( method)) settings = {'colour_cycle_count': len(colourspaces)} settings.update(kwargs) cycle = colour_cycle(**settings) for colourspace in colourspaces: R, G, B, _A = next(cycle) # RGB colourspaces such as *ACES2065-1* have primaries with # chromaticity coordinates set to 0 thus we prevent nan from being # yield by zero division in later colour transformations. P = np.where( colourspace.primaries == 0, EPSILON, colourspace.primaries, ) P = xy_to_ij(P) W = xy_to_ij(colourspace.whitepoint) axes.plot( (W[0], W[0]), (W[1], W[1]), color=(R, G, B), label=colourspace.name) if show_whitepoints: axes.plot((W[0], W[0]), (W[1], W[1]), 'o', color=(R, G, B)) axes.plot( (P[0, 0], P[1, 0]), (P[0, 1], P[1, 1]), 'o-', color=(R, G, B)) axes.plot( (P[1, 0], P[2, 0]), (P[1, 1], P[2, 1]), 'o-', color=(R, G, B)) axes.plot( (P[2, 0], P[0, 0]), (P[2, 1], P[0, 1]), 'o-', color=(R, G, B)) x_limit_min.append(np.amin(P[..., 0]) - 0.1) y_limit_min.append(np.amin(P[..., 1]) - 0.1) x_limit_max.append(np.amax(P[..., 0]) + 0.1) y_limit_max.append(np.amax(P[..., 1]) + 0.1) bounding_box = ( min(x_limit_min), max(x_limit_max), min(y_limit_min), max(y_limit_max), ) settings.update({ 'standalone': True, 'legend': True, 'bounding_box': bounding_box, }) settings.update(kwargs) return render(**settings)
def plot_corresponding_chromaticities_prediction(experiment=1, model='Von Kries', transform='CAT02', **kwargs): """ Plots given chromatic adaptation model corresponding chromaticities prediction. Parameters ---------- experiment : int, optional Corresponding chromaticities prediction experiment number. model : unicode, optional Corresponding chromaticities prediction model name. transform : unicode, optional Transformation to use with *Von Kries* chromatic adaptation model. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_corresponding_chromaticities_prediction(1, 'Von Kries', 'CAT02') ... # doctest: +SKIP .. image:: ../_static/Plotting_\ Plot_Corresponding_Chromaticities_Prediction.png :align: center :alt: plot_corresponding_chromaticities_prediction """ settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) title = (('Corresponding Chromaticities Prediction\n{0} ({1}) - ' 'Experiment {2}\nCIE 1976 UCS Chromaticity Diagram').format( model, transform, experiment) if model.lower() in ('von kries', 'vonkries') else ('Corresponding Chromaticities Prediction\n{0} - ' 'Experiment {1}\nCIE 1976 UCS Chromaticity Diagram').format( model, experiment)) settings = {'axes': axes, 'title': title} settings.update(kwargs) settings['standalone'] = False plot_chromaticity_diagram_CIE1976UCS(**settings) results = corresponding_chromaticities_prediction( experiment, transform=transform) for result in results: _name, uvp_t, uvp_m, uvp_p = result axes.arrow( uvp_t[0], uvp_t[1], uvp_p[0] - uvp_t[0] - 0.1 * (uvp_p[0] - uvp_t[0]), uvp_p[1] - uvp_t[1] - 0.1 * (uvp_p[1] - uvp_t[1]), color=COLOUR_STYLE_CONSTANTS.colour.dark, head_width=0.005, head_length=0.005) axes.plot( uvp_t[0], uvp_t[1], 'o', color=COLOUR_STYLE_CONSTANTS.colour.brightest, markeredgecolor=COLOUR_STYLE_CONSTANTS.colour.dark, markersize=(COLOUR_STYLE_CONSTANTS.geometry.short * 6 + COLOUR_STYLE_CONSTANTS.geometry.short * 0.75), markeredgewidth=COLOUR_STYLE_CONSTANTS.geometry.short * 0.75) axes.plot( uvp_m[0], uvp_m[1], '^', color=COLOUR_STYLE_CONSTANTS.colour.brightest, markeredgecolor=COLOUR_STYLE_CONSTANTS.colour.dark, markersize=(COLOUR_STYLE_CONSTANTS.geometry.short * 6 + COLOUR_STYLE_CONSTANTS.geometry.short * 0.75), markeredgewidth=COLOUR_STYLE_CONSTANTS.geometry.short * 0.75) axes.plot( uvp_p[0], uvp_p[1], '^', color=COLOUR_STYLE_CONSTANTS.colour.dark) settings.update({ 'standalone': True, 'bounding_box': (-0.1, 0.7, -0.1, 0.7), }) settings.update(kwargs) return render(**settings)
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)
def plot_planckian_locus_in_chromaticity_diagram( illuminants=None, annotate_parameters=None, chromaticity_diagram_callable=plot_chromaticity_diagram, method='CIE 1931', **kwargs): """ Plots the *Planckian Locus* and given illuminants in the *Chromaticity Diagram* according to given method. Parameters ---------- illuminants : array_like, optional Factory illuminants to plot. annotate_parameters : dict or array_like, optional Parameters for the :func:`plt.annotate` definition, used to annotate the resulting chromaticity coordinates with their respective illuminant names if ``annotate`` is set to *True*. ``annotate_parameters`` can be either a single dictionary applied to all the arrows with same settings or a sequence of dictionaries with different settings for each illuminant. chromaticity_diagram_callable : callable, optional Callable responsible for drawing the *Chromaticity Diagram*. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, :func:`colour.plotting.temperature.plot_planckian_locus`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_planckian_locus_in_chromaticity_diagram(['A', 'B', 'C']) ... # doctest: +SKIP .. image:: ../_static/Plotting_\ Plot_Planckian_Locus_In_Chromaticity_Diagram.png :align: center :alt: plot_planckian_locus_in_chromaticity_diagram """ cmfs = CMFS['CIE 1931 2 Degree Standard Observer'] if illuminants is None: illuminants = ('A', 'B', 'C') illuminants = filter_passthrough(ILLUMINANTS.get(cmfs.name), illuminants) settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) method = method.upper() settings = {'axes': axes, 'method': method} settings.update(kwargs) settings['standalone'] = False chromaticity_diagram_callable(**settings) plot_planckian_locus(**settings) if method == 'CIE 1931': def xy_to_ij(xy): """ Converts given *xy* chromaticity coordinates to *ij* chromaticity coordinates. """ return xy bounding_box = (-0.1, 0.9, -0.1, 0.9) elif method == 'CIE 1960 UCS': def xy_to_ij(xy): """ Converts given *xy* chromaticity coordinates to *ij* chromaticity coordinates. """ return UCS_to_uv(XYZ_to_UCS(xy_to_XYZ(xy))) bounding_box = (-0.1, 0.7, -0.2, 0.6) else: raise ValueError('Invalid method: "{0}", must be one of ' '{{\'CIE 1931\', \'CIE 1960 UCS\'}}'.format(method)) annotate_settings_collection = [{ 'annotate': True, 'xytext': (-50, 30), 'textcoords': 'offset points', 'arrowprops': COLOUR_ARROW_STYLE, } for _ in range(len(illuminants))] if annotate_parameters is not None: if not isinstance(annotate_parameters, dict): assert len(annotate_parameters) == len(illuminants), ( 'Multiple annotate parameters defined, but they do not match ' 'the illuminants count!') for i, annotate_settings in enumerate(annotate_settings_collection): if isinstance(annotate_parameters, dict): annotate_settings.update(annotate_parameters) else: annotate_settings.update(annotate_parameters[i]) for i, (illuminant, xy) in enumerate(illuminants.items()): ij = xy_to_ij(xy) axes.plot( ij[0], ij[1], 'o', color=COLOUR_STYLE_CONSTANTS.colour.brightest, markeredgecolor=COLOUR_STYLE_CONSTANTS.colour.dark, markersize=(COLOUR_STYLE_CONSTANTS.geometry.short * 6 + COLOUR_STYLE_CONSTANTS.geometry.short * 0.75), markeredgewidth=COLOUR_STYLE_CONSTANTS.geometry.short * 0.75, label=illuminant) if annotate_settings_collection[i]['annotate']: annotate_settings = annotate_settings_collection[i] annotate_settings.pop('annotate') plt.annotate(illuminant, xy=ij, **annotate_settings) title = (('{0} Illuminants - Planckian Locus\n' '{1} Chromaticity Diagram - ' 'CIE 1931 2 Degree Standard Observer').format( ', '.join(illuminants), method) if illuminants else ('Planckian Locus\n{0} Chromaticity Diagram - ' 'CIE 1931 2 Degree Standard Observer'.format(method))) settings.update({ 'axes': axes, 'standalone': True, 'bounding_box': bounding_box, 'title': title, }) settings.update(kwargs) return render(**settings)
def plot_RGB_chromaticities_in_chromaticity_diagram( RGB, colourspace='sRGB', chromaticity_diagram_callable=( plot_RGB_colourspaces_in_chromaticity_diagram), method='CIE 1931', scatter_parameters=None, **kwargs): """ Plots given *RGB* colourspace array in the *Chromaticity Diagram* according to given method. Parameters ---------- RGB : array_like *RGB* colourspace array. colourspace : optional, unicode *RGB* colourspace of the *RGB* array. chromaticity_diagram_callable : callable, optional Callable responsible for drawing the *Chromaticity Diagram*. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. scatter_parameters : dict, optional Parameters for the :func:`plt.scatter` definition, if ``c`` is set to *RGB*, the scatter will use given ``RGB`` colours. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, :func:`colour.plotting.diagrams.\ plot_RGB_colourspaces_in_chromaticity_diagram`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> RGB = np.random.random((128, 128, 3)) >>> plot_RGB_chromaticities_in_chromaticity_diagram( ... RGB, 'ITU-R BT.709') ... # doctest: +SKIP .. image:: ../_static/Plotting_\ Plot_RGB_Chromaticities_In_Chromaticity_Diagram_Plot.png :align: center :alt: plot_RGB_chromaticities_in_chromaticity_diagram """ RGB = as_float_array(RGB).reshape(-1, 3) settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) method = method.upper() scatter_settings = { 's': 40, 'c': 'RGB', 'marker': 'o', 'alpha': 0.85, } if scatter_parameters is not None: scatter_settings.update(scatter_parameters) settings = dict(kwargs) settings.update({'axes': axes, 'standalone': False}) colourspace = first_item(filter_RGB_colourspaces(colourspace).values()) settings['colourspaces'] = ( ['^{0}$'.format(colourspace.name)] + settings.get('colourspaces', [])) chromaticity_diagram_callable(**settings) use_RGB_colours = scatter_settings['c'].upper() == 'RGB' if use_RGB_colours: RGB = RGB[RGB[:, 1].argsort()] scatter_settings['c'] = np.clip( RGB_to_RGB( RGB, colourspace, COLOUR_STYLE_CONSTANTS.colour.colourspace, apply_encoding_cctf=True).reshape(-1, 3), 0, 1) XYZ = RGB_to_XYZ(RGB, colourspace.whitepoint, colourspace.whitepoint, colourspace.RGB_to_XYZ_matrix) if method == 'CIE 1931': ij = XYZ_to_xy(XYZ, colourspace.whitepoint) elif method == 'CIE 1960 UCS': ij = UCS_to_uv(XYZ_to_UCS(XYZ)) elif method == 'CIE 1976 UCS': ij = Luv_to_uv( XYZ_to_Luv(XYZ, colourspace.whitepoint), colourspace.whitepoint) axes.scatter(ij[..., 0], ij[..., 1], **scatter_settings) settings.update({'standalone': True}) settings.update(kwargs) return render(**settings)
def plot_single_sd(sd, cmfs='CIE 1931 2 Degree Standard Observer', out_of_gamut_clipping=True, modulate_colours_with_sd_amplitude=False, equalize_sd_amplitude=False, **kwargs): """ Plots given spectral distribution. Parameters ---------- sd : SpectralDistribution Spectral distribution to plot. out_of_gamut_clipping : bool, optional Whether to clip out of gamut colours otherwise, the colours will be offset by the absolute minimal colour leading to a rendering on gray background, less saturated and smoother. modulate_colours_with_sd_amplitude : bool, optional Whether to modulate the colours with the spectral distribution amplitude. equalize_sd_amplitude : bool, optional Whether to equalize the spectral distribution amplitude. Equalization occurs after the colours modulation thus setting both arguments to *True* will generate a spectrum strip where each wavelength colour is modulated by the spectral distribution amplitude. The usual 5% margin above the spectral distribution is also omitted. cmfs : unicode Standard observer colour matching functions used for spectrum creation. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. References ---------- :cite:`Spiker2015a` Examples -------- >>> from colour import SpectralDistribution >>> data = { ... 500: 0.0651, ... 520: 0.0705, ... 540: 0.0772, ... 560: 0.0870, ... 580: 0.1128, ... 600: 0.1360 ... } >>> sd = SpectralDistribution(data, name='Custom') >>> plot_single_sd(sd) # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, \ <matplotlib.axes._subplots.AxesSubplot object at 0x...>) .. image:: ../_static/Plotting_Plot_Single_SD.png :align: center :alt: plot_single_sd """ _figure, axes = artist(**kwargs) cmfs = first_item(filter_cmfs(cmfs).values()) sd = sd.copy() sd.interpolator = LinearInterpolator wavelengths = cmfs.wavelengths[np.logical_and( cmfs.wavelengths >= max(min(cmfs.wavelengths), min(sd.wavelengths)), cmfs.wavelengths <= min(max(cmfs.wavelengths), max(sd.wavelengths)), )] values = sd[wavelengths] colours = XYZ_to_plotting_colourspace( wavelength_to_XYZ(wavelengths, cmfs), ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['E'], apply_cctf_encoding=False) if not out_of_gamut_clipping: colours += np.abs(np.min(colours)) colours = normalise_maximum(colours) if modulate_colours_with_sd_amplitude: colours *= (values / np.max(values))[..., np.newaxis] colours = COLOUR_STYLE_CONSTANTS.colour.colourspace.cctf_encoding(colours) if equalize_sd_amplitude: values = np.ones(values.shape) margin = 0 if equalize_sd_amplitude else 0.05 x_min, x_max = min(wavelengths), max(wavelengths) y_min, y_max = 0, max(values) + max(values) * margin polygon = Polygon(np.vstack([ (x_min, 0), tstack([wavelengths, values]), (x_max, 0), ]), facecolor='none', edgecolor='none') axes.add_patch(polygon) padding = 0.1 axes.bar(x=wavelengths - padding, height=max(values), width=1 + padding, color=colours, align='edge', clip_path=polygon) axes.plot(wavelengths, values, color=COLOUR_STYLE_CONSTANTS.colour.dark) settings = { 'axes': axes, 'bounding_box': (x_min, x_max, y_min, y_max), 'title': '{0} - {1}'.format(sd.strict_name, cmfs.strict_name), 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': 'Spectral Distribution', } settings.update(kwargs) return render(**settings)
def plot_pointer_gamut(method='CIE 1931', **kwargs): """ Plots *Pointer's Gamut* according to given method. Parameters ---------- method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, Plotting method. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_pointer_gamut() # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Pointer_Gamut.png :align: center :alt: plot_pointer_gamut """ settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) method = method.upper() if method == 'CIE 1931': def XYZ_to_ij(XYZ, *args): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return XYZ_to_xy(XYZ, *args) def xy_to_ij(xy): """ Converts given *xy* chromaticity coordinates to *ij* chromaticity coordinates. """ return xy elif method == 'CIE 1960 UCS': def XYZ_to_ij(XYZ, *args): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return UCS_to_uv(XYZ_to_UCS(XYZ)) def xy_to_ij(xy): """ Converts given *xy* chromaticity coordinates to *ij* chromaticity coordinates. """ return xy_to_UCS_uv(xy) elif method == 'CIE 1976 UCS': def XYZ_to_ij(XYZ, *args): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return Luv_to_uv(XYZ_to_Luv(XYZ, *args), *args) def xy_to_ij(xy): """ Converts given *xy* chromaticity coordinates to *ij* chromaticity coordinates. """ return xy_to_Luv_uv(xy) else: raise ValueError( 'Invalid method: "{0}", must be one of ' '{{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}}'.format( method)) ij = xy_to_ij(as_float_array(POINTER_GAMUT_BOUNDARIES)) alpha_p = COLOUR_STYLE_CONSTANTS.opacity.high colour_p = COLOUR_STYLE_CONSTANTS.colour.darkest axes.plot( ij[..., 0], ij[..., 1], label='Pointer\'s Gamut', color=colour_p, alpha=alpha_p) axes.plot( (ij[-1][0], ij[0][0]), (ij[-1][1], ij[0][1]), color=colour_p, alpha=alpha_p) XYZ = Lab_to_XYZ( LCHab_to_Lab(POINTER_GAMUT_DATA), POINTER_GAMUT_ILLUMINANT) ij = XYZ_to_ij(XYZ, POINTER_GAMUT_ILLUMINANT) axes.scatter( ij[..., 0], ij[..., 1], alpha=alpha_p / 2, color=colour_p, marker='+') settings.update({'axes': axes}) settings.update(kwargs) return render(**settings)
def plot_spectral_locus(cmfs='CIE 1931 2 Degree Standard Observer', spectral_locus_colours=None, spectral_locus_labels=None, method='CIE 1931', **kwargs): """ Plots the *Spectral Locus* according to given method. Parameters ---------- cmfs : unicode, optional Standard observer colour matching functions defining the *Spectral Locus*. spectral_locus_colours : array_like or unicode, optional *Spectral Locus* colours, if ``spectral_locus_colours`` is set to *RGB*, the colours will be computed according to the corresponding chromaticity coordinates. spectral_locus_labels : array_like, optional 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 : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_spectral_locus(spectral_locus_colours='RGB') # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Spectral_Locus.png :align: center :alt: plot_spectral_locus """ if spectral_locus_colours is None: spectral_locus_colours = COLOUR_STYLE_CONSTANTS.colour.dark settings = {'uniform': True} settings.update(kwargs) figure, axes = artist(**settings) method = method.upper() cmfs = first_item(filter_cmfs(cmfs).values()) illuminant = COLOUR_STYLE_CONSTANTS.colour.colourspace.whitepoint wavelengths = cmfs.wavelengths equal_energy = np.array([1 / 3] * 2) if method == 'CIE 1931': ij = XYZ_to_xy(cmfs.values, illuminant) labels = ((390, 460, 470, 480, 490, 500, 510, 520, 540, 560, 580, 600, 620, 700) if spectral_locus_labels is None else spectral_locus_labels) elif method == 'CIE 1960 UCS': ij = UCS_to_uv(XYZ_to_UCS(cmfs.values)) labels = ((420, 440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590, 600, 610, 620, 630, 645, 680) if spectral_locus_labels is None else spectral_locus_labels) elif method == 'CIE 1976 UCS': ij = Luv_to_uv(XYZ_to_Luv(cmfs.values, illuminant), illuminant) labels = ((420, 440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590, 600, 610, 620, 630, 645, 680) if spectral_locus_labels is None else spectral_locus_labels) else: raise ValueError( 'Invalid method: "{0}", must be one of ' '{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}'.format( method)) pl_ij = tstack([ np.linspace(ij[0][0], ij[-1][0], 20), np.linspace(ij[0][1], ij[-1][1], 20) ]).reshape(-1, 1, 2) sl_ij = np.copy(ij).reshape(-1, 1, 2) if 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(XYZ.reshape(-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) axes.add_collection(line_collection) wl_ij = dict(tuple(zip(wavelengths, ij))) for label in labels: i, j = wl_ij[label] 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] ij = np.array([i, j]) direction = np.array([-dy, dx]) normal = (np.array([-dy, dx]) if np.dot( normalise_vector(ij - 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]) axes.plot( (i, i + normal[0] * 0.75), (j, j + normal[1] * 0.75), color=label_colour) axes.plot(i, j, 'o', color=label_colour) 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'}) settings = {'axes': axes} settings.update(kwargs) return render(**kwargs)
def plot_ellipses_MacAdam1942_in_chromaticity_diagram( chromaticity_diagram_callable=plot_chromaticity_diagram, method='CIE 1931', chromaticity_diagram_clipping=False, ellipse_parameters=None, **kwargs): """ Plots *MacAdam (1942) Ellipses (Observer PGN)* in the *Chromaticity Diagram* according to given method. Parameters ---------- chromaticity_diagram_callable : callable, optional Callable responsible for drawing the *Chromaticity Diagram*. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. chromaticity_diagram_clipping : bool, optional, Whether to clip the *Chromaticity Diagram* colours with the ellipses. ellipse_parameters : dict or array_like, optional Parameters for the :class:`Ellipse` class, ``ellipse_parameters`` can be either a single dictionary applied to all the ellipses with same settings or a sequence of dictionaries with different settings for each ellipse. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_ellipses_MacAdam1942_in_chromaticity_diagram() # doctest: +SKIP .. image:: ../_static/\ Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram.png :align: center :alt: plot_ellipses_MacAdam1942_in_chromaticity_diagram """ settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) settings = dict(kwargs) settings.update({'axes': axes, 'standalone': False}) ellipses_coefficients = ellipses_MacAdam1942(method=method) if chromaticity_diagram_clipping: diagram_clipping_path_x = [] diagram_clipping_path_y = [] for coefficients in ellipses_coefficients: coefficients = np.copy(coefficients) coefficients[2:4] /= 2 x, y = tsplit( point_at_angle_on_ellipse( np.linspace(0, 360, 36), coefficients, )) diagram_clipping_path_x.append(x) diagram_clipping_path_y.append(y) diagram_clipping_path = np.rollaxis( np.array([diagram_clipping_path_x, diagram_clipping_path_y]), 0, 3) diagram_clipping_path = Path.make_compound_path_from_polys( diagram_clipping_path).vertices settings.update({'diagram_clipping_path': diagram_clipping_path}) chromaticity_diagram_callable(**settings) ellipse_settings_collection = [{ 'color': COLOUR_STYLE_CONSTANTS.colour.cycle[4], 'alpha': 0.4, 'edgecolor': COLOUR_STYLE_CONSTANTS.colour.cycle[1], 'linewidth': colour_style()['lines.linewidth'] } for _ellipses_coefficient in ellipses_coefficients] if ellipse_parameters is not None: if not isinstance(ellipse_parameters, dict): assert len(ellipse_parameters) == len(ellipses_coefficients), ( 'Multiple ellipse parameters defined, but they do not match ' 'the ellipses count!') for i, ellipse_settings in enumerate(ellipse_settings_collection): if isinstance(ellipse_parameters, dict): ellipse_settings.update(ellipse_parameters) else: ellipse_settings.update(ellipse_parameters[i]) for i, coefficients in enumerate(ellipses_coefficients): x_c, y_c, a_a, a_b, theta_e = coefficients ellipse = Ellipse((x_c, y_c), a_a, a_b, theta_e, **ellipse_settings_collection[i]) axes.add_artist(ellipse) settings.update({'standalone': True}) settings.update(kwargs) return render(**settings)
def plot_chromaticity_diagram_colours( samples=256, diagram_opacity=1.0, diagram_clipping_path=None, cmfs='CIE 1931 2 Degree Standard Observer', method='CIE 1931', **kwargs): """ Plots the *Chromaticity Diagram* colours according to given method. Parameters ---------- samples : numeric, optional Samples count on one axis. diagram_opacity : numeric, optional Opacity of the *Chromaticity Diagram* colours. diagram_clipping_path : array_like, optional Path of points used to clip the *Chromaticity Diagram* colours. cmfs : unicode or XYZ_ColourMatchingFunctions, optional 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 : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_chromaticity_diagram_colours() # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, <...AxesSubplot...>) .. image:: ../_static/Plotting_Plot_Chromaticity_Diagram_Colours.png :align: center :alt: plot_chromaticity_diagram_colours """ settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) method = method.upper() cmfs = first_item(filter_cmfs(cmfs).values()) illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint 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) spectral_locus = XYZ_to_xy(cmfs.values, illuminant) elif method == 'CIE 1960 UCS': XYZ = xy_to_XYZ(UCS_uv_to_xy(ij)) spectral_locus = UCS_to_uv(XYZ_to_UCS(cmfs.values)) elif method == 'CIE 1976 UCS': XYZ = xy_to_XYZ(Luv_uv_to_xy(ij)) spectral_locus = Luv_to_uv(XYZ_to_Luv(cmfs.values, illuminant), illuminant) else: raise ValueError( 'Invalid method: "{0}", must be one of ' '[\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\']'.format( method)) RGB = 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', edgecolor='none') axes.add_patch(polygon) # Preventing bounding box related issues as per # https://github.com/matplotlib/matplotlib/issues/10529 image = axes.imshow(RGB, interpolation='bilinear', extent=(0, 1, 0, 1), clip_path=None, alpha=diagram_opacity) image.set_clip_path(polygon) settings = {'axes': axes} settings.update(kwargs) return render(**kwargs)
def plot_chromaticity_diagram_colours( samples=256, diagram_opacity=1.0, diagram_clipping_path=None, cmfs='CIE 1931 2 Degree Standard Observer', method='CIE 1931', **kwargs): """ Plots the *Chromaticity Diagram* colours according to given method. Parameters ---------- samples : numeric, optional Samples count on one axis. diagram_opacity : numeric, optional Opacity of the *Chromaticity Diagram* colours. diagram_clipping_path : array_like, optional Path of points used to clip the *Chromaticity Diagram* colours. cmfs : unicode, optional Standard observer colour matching functions used for *Chromaticity Diagram* bounds. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_chromaticity_diagram_colours() # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Chromaticity_Diagram_Colours.png :align: center :alt: plot_chromaticity_diagram_colours """ settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) method = method.upper() cmfs = first_item(filter_cmfs(cmfs).values()) illuminant = COLOUR_STYLE_CONSTANTS.colour.colourspace.whitepoint ii, jj = np.meshgrid( np.linspace(0, 1, samples), np.linspace(1, 0, samples)) ij = tstack([ii, jj]) # Avoiding zero division in later colour transformations. ij = np.where(ij == 0, EPSILON, ij) if method == 'CIE 1931': XYZ = xy_to_XYZ(ij) spectral_locus = XYZ_to_xy(cmfs.values, illuminant) elif method == 'CIE 1960 UCS': XYZ = xy_to_XYZ(UCS_uv_to_xy(ij)) spectral_locus = UCS_to_uv(XYZ_to_UCS(cmfs.values)) elif method == 'CIE 1976 UCS': XYZ = xy_to_XYZ(Luv_uv_to_xy(ij)) spectral_locus = Luv_to_uv( XYZ_to_Luv(cmfs.values, illuminant), illuminant) else: raise ValueError( 'Invalid method: "{0}", must be one of ' '{{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}}'.format( method)) RGB = 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', edgecolor='none') axes.add_patch(polygon) # Preventing bounding box related issues as per # https://github.com/matplotlib/matplotlib/issues/10529 image = axes.imshow( RGB, interpolation='bilinear', extent=(0, 1, 0, 1), clip_path=None, alpha=diagram_opacity) image.set_clip_path(polygon) settings = {'axes': axes} settings.update(kwargs) return render(**kwargs)
def plot_colour_quality_bars(specifications, labels=True, hatching=None, hatching_repeat=2, **kwargs): """ Plots the colour quality data of given illuminants or light sources colour quality specifications. Parameters ---------- specifications : array_like Array of illuminants or light sources colour quality specifications. labels : bool, optional Add labels above bars. hatching : bool or None, optional Use hatching for the bars. hatching_repeat : int, optional Hatching pattern repeat. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.quality.plot_colour_quality_bars`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> from colour import (ILLUMINANTS_SDS, ... LIGHT_SOURCES_SDS, SpectralShape) >>> illuminant = ILLUMINANTS_SDS['FL2'] >>> light_source = LIGHT_SOURCES_SDS['Kinoton 75P'] >>> light_source = light_source.copy().align(SpectralShape(360, 830, 1)) >>> cqs_i = colour_quality_scale(illuminant, additional_data=True) >>> cqs_l = colour_quality_scale(light_source, additional_data=True) >>> plot_colour_quality_bars([cqs_i, cqs_l]) # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, \ <matplotlib.axes._subplots.AxesSubplot object at 0x...>) .. image:: ../_static/Plotting_Plot_Colour_Quality_Bars.png :align: center :alt: plot_colour_quality_bars """ settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) bar_width = 0.5 y_ticks_interval = 10 count_s, count_Q_as = len(specifications), 0 patterns = cycle(COLOUR_STYLE_CONSTANTS.hatch.patterns) if hatching is None: hatching = False if count_s == 1 else True for i, specification in enumerate(specifications): Q_a, Q_as, colorimetry_data = (specification.Q_a, specification.Q_as, specification.colorimetry_data) count_Q_as = len(Q_as) colours = ([[1] * 3] + [ np.clip(XYZ_to_plotting_colourspace(x.XYZ), 0, 1) for x in colorimetry_data[0] ]) x = (i + np.arange(0, (count_Q_as + 1) * (count_s + 1), (count_s + 1), dtype=DEFAULT_FLOAT_DTYPE)) * bar_width y = [s[1].Q_a for s in sorted(Q_as.items(), key=lambda s: s[0])] y = np.array([Q_a] + list(y)) bars = axes.bar(x, np.abs(y), color=colours, width=bar_width, edgecolor=COLOUR_STYLE_CONSTANTS.colour.dark, label=specification.name) hatches = ([next(patterns) * hatching_repeat] * (count_Q_as + 1) if hatching else np.where( y < 0, next(patterns), None).tolist()) for j, bar in enumerate(bars.patches): bar.set_hatch(hatches[j]) if labels: label_rectangles( ['{0:.1f}'.format(y_v) for y_v in y], bars, rotation='horizontal' if count_s == 1 else 'vertical', offset=(0 if count_s == 1 else 3 / 100 * count_s + 65 / 1000, 0.025), text_size=-5 / 7 * count_s + 12.5, axes=axes) axes.axhline(y=100, color=COLOUR_STYLE_CONSTANTS.colour.dark, linestyle='--') axes.set_xticks( (np.arange(0, (count_Q_as + 1) * (count_s + 1), (count_s + 1), dtype=DEFAULT_FLOAT_DTYPE) * bar_width + (count_s * bar_width / 2)), ['Qa'] + ['Q{0}'.format(index + 1) for index in range(0, count_Q_as + 1, 1)]) axes.set_yticks(range(0, 100 + y_ticks_interval, y_ticks_interval)) aspect = 1 / (120 / (bar_width + len(Q_as) + bar_width * 2)) bounding_box = (-bar_width, ((count_Q_as + 1) * (count_s + 1)) / 2, 0, 120) settings = { 'axes': axes, 'aspect': aspect, 'bounding_box': bounding_box, 'legend': hatching, 'title': 'Colour Quality', } settings.update(kwargs) return render(**settings)
def plot_chromaticity_diagram(cmfs='CIE 1931 2 Degree Standard Observer', show_diagram_colours=True, show_spectral_locus=True, method='CIE 1931', **kwargs): """ Plots the *Chromaticity Diagram* according to given method. Parameters ---------- cmfs : unicode, optional Standard observer colour matching functions used for *Chromaticity Diagram* bounds. show_diagram_colours : bool, optional Whether to display the *Chromaticity Diagram* background colours. show_spectral_locus : bool, optional Whether to display the *Spectral Locus*. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.diagrams.plot_spectral_locus`, :func:`colour.plotting.diagrams.plot_chromaticity_diagram_colours`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_chromaticity_diagram() # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Chromaticity_Diagram.png :align: center :alt: plot_chromaticity_diagram """ settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) method = method.upper() cmfs = first_item(filter_cmfs(cmfs).values()) if show_diagram_colours: settings = {'axes': axes, 'method': method} settings.update(kwargs) settings['standalone'] = False plot_chromaticity_diagram_colours(**settings) if show_spectral_locus: settings = {'axes': axes, 'method': method} settings.update(kwargs) settings['standalone'] = False plot_spectral_locus(**settings) if method == 'CIE 1931': x_label, y_label = 'CIE x', 'CIE y' elif method == 'CIE 1960 UCS': x_label, y_label = 'CIE u', 'CIE v' elif method == 'CIE 1976 UCS': x_label, y_label = 'CIE u\'', 'CIE v\'', else: raise ValueError( 'Invalid method: "{0}", must be one of ' '{{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}}'.format( method)) title = '{0} Chromaticity Diagram - {1}'.format(method, cmfs.strict_name) settings.update({ 'axes': axes, 'standalone': True, 'bounding_box': (0, 1, 0, 1), 'title': title, 'x_label': x_label, 'y_label': y_label, }) settings.update(kwargs) return render(**settings)
def plot_spectral_locus(cmfs='CIE 1931 2 Degree Standard Observer', spectral_locus_colours=None, spectral_locus_labels=None, method='CIE 1931', **kwargs): """ Plots the *Spectral Locus* according to given method. Parameters ---------- cmfs : unicode, optional Standard observer colour matching functions defining the *Spectral Locus*. spectral_locus_colours : array_like or unicode, optional *Spectral Locus* colours, if ``spectral_locus_colours`` is set to *RGB*, the colours will be computed according to the corresponding chromaticity coordinates. spectral_locus_labels : array_like, optional 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 : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_spectral_locus(spectral_locus_colours='RGB') # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Spectral_Locus.png :align: center :alt: plot_spectral_locus """ if spectral_locus_colours is None: spectral_locus_colours = COLOUR_STYLE_CONSTANTS.colour.dark settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) method = method.upper() cmfs = first_item(filter_cmfs(cmfs).values()) illuminant = COLOUR_STYLE_CONSTANTS.colour.colourspace.whitepoint wavelengths = cmfs.wavelengths equal_energy = np.array([1 / 3] * 2) if method == 'CIE 1931': ij = XYZ_to_xy(cmfs.values, illuminant) labels = ((390, 460, 470, 480, 490, 500, 510, 520, 540, 560, 580, 600, 620, 700) if spectral_locus_labels is None else spectral_locus_labels) elif method == 'CIE 1960 UCS': ij = UCS_to_uv(XYZ_to_UCS(cmfs.values)) labels = ((420, 440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590, 600, 610, 620, 630, 645, 680) if spectral_locus_labels is None else spectral_locus_labels) elif method == 'CIE 1976 UCS': ij = Luv_to_uv(XYZ_to_Luv(cmfs.values, illuminant), illuminant) labels = ((420, 440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590, 600, 610, 620, 630, 645, 680) if spectral_locus_labels is None else spectral_locus_labels) else: raise ValueError( 'Invalid method: "{0}", must be one of ' '{{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}}'.format( method)) pl_ij = tstack([ np.linspace(ij[0][0], ij[-1][0], 20), np.linspace(ij[0][1], ij[-1][1], 20) ]).reshape(-1, 1, 2) sl_ij = np.copy(ij).reshape(-1, 1, 2) if 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(XYZ.reshape(-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) axes.add_collection(line_collection) wl_ij = dict(tuple(zip(wavelengths, ij))) for label in labels: i, j = wl_ij[label] 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] ij = np.array([i, j]) direction = np.array([-dy, dx]) normal = (np.array([-dy, dx]) if np.dot( normalise_vector(ij - 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]) axes.plot( (i, i + normal[0] * 0.75), (j, j + normal[1] * 0.75), color=label_colour) axes.plot(i, j, 'o', color=label_colour) 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'}) settings = {'axes': axes} settings.update(kwargs) return render(**kwargs)
def plot_sds_in_chromaticity_diagram( sds, cmfs='CIE 1931 2 Degree Standard Observer', annotate_parameters=None, chromaticity_diagram_callable=plot_chromaticity_diagram, method='CIE 1931', **kwargs): """ Plots given spectral distribution chromaticity coordinates into the *Chromaticity Diagram* using given method. Parameters ---------- sds : array_like, optional Spectral distributions to plot. cmfs : unicode, optional Standard observer colour matching functions used for *Chromaticity Diagram* bounds. annotate_parameters : dict or array_like, optional Parameters for the :func:`plt.annotate` definition, used to annotate the resulting chromaticity coordinates with their respective spectral distribution names if ``annotate`` is set to *True*. ``annotate_parameters`` can be either a single dictionary applied to all the arrows with same settings or a sequence of dictionaries with different settings for each spectral distribution. chromaticity_diagram_callable : callable, optional Callable responsible for drawing the *Chromaticity Diagram*. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> from colour import ILLUMINANTS_SDS >>> A = ILLUMINANTS_SDS['A'] >>> D65 = ILLUMINANTS_SDS['D65'] >>> plot_sds_in_chromaticity_diagram([A, D65]) # doctest: +SKIP .. image:: ../_static/Plotting_Plot_SDs_In_Chromaticity_Diagram.png :align: center :alt: plot_sds_in_chromaticity_diagram """ settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) method = method.upper() settings.update({ 'axes': axes, 'standalone': False, 'method': method, 'cmfs': cmfs, }) chromaticity_diagram_callable(**settings) if method == 'CIE 1931': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return XYZ_to_xy(XYZ) bounding_box = (-0.1, 0.9, -0.1, 0.9) elif method == 'CIE 1960 UCS': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return UCS_to_uv(XYZ_to_UCS(XYZ)) bounding_box = (-0.1, 0.7, -0.2, 0.6) elif method == 'CIE 1976 UCS': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return Luv_to_uv(XYZ_to_Luv(XYZ)) bounding_box = (-0.1, 0.7, -0.1, 0.7) else: raise ValueError( 'Invalid method: "{0}", must be one of ' '{{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}}'.format( method)) annotate_settings_collection = [{ 'annotate': True, 'xytext': (-50, 30), 'textcoords': 'offset points', 'arrowprops': COLOUR_ARROW_STYLE, } for _ in range(len(sds))] if annotate_parameters is not None: if not isinstance(annotate_parameters, dict): assert len(annotate_parameters) == len(sds), ( 'Multiple annotate parameters defined, but they do not match ' 'the spectral distributions count!') for i, annotate_settings in enumerate(annotate_settings_collection): if isinstance(annotate_parameters, dict): annotate_settings.update(annotate_parameters) else: annotate_settings.update(annotate_parameters[i]) for i, sd in enumerate(sds): with domain_range_scale('1'): XYZ = sd_to_XYZ(sd) ij = XYZ_to_ij(XYZ) axes.plot( ij[0], ij[1], 'o', color=COLOUR_STYLE_CONSTANTS.colour.brightest, markeredgecolor=COLOUR_STYLE_CONSTANTS.colour.dark, markersize=(COLOUR_STYLE_CONSTANTS.geometry.short * 6 + COLOUR_STYLE_CONSTANTS.geometry.short * 0.75), markeredgewidth=COLOUR_STYLE_CONSTANTS.geometry.short * 0.75, label=sd.strict_name) if (sd.name is not None and annotate_settings_collection[i]['annotate']): annotate_settings = annotate_settings_collection[i] annotate_settings.pop('annotate') axes.annotate(sd.name, xy=ij, **annotate_settings) settings.update({'standalone': True, 'bounding_box': bounding_box}) settings.update(kwargs) return render(**settings)
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)
def plot_multi_sds(sds, cmfs='CIE 1931 2 Degree Standard Observer', use_sds_colours=False, normalise_sds_colours=False, **kwargs): """ Plots given spectral distributions. Parameters ---------- sds : array_like or MultiSpectralDistribution Spectral distributions or multi-spectral distributions to plot. `sds` can be a single :class:`colour.MultiSpectralDistribution` class instance, a list of :class:`colour.MultiSpectralDistribution` class instances or a list of :class:`colour.SpectralDistribution` class instances. cmfs : unicode, optional Standard observer colour matching functions used for spectrum creation. use_sds_colours : bool, optional Whether to use spectral distributions colours. normalise_sds_colours : bool Whether to normalise spectral distributions colours. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> from colour import SpectralDistribution >>> data_1 = { ... 500: 0.004900, ... 510: 0.009300, ... 520: 0.063270, ... 530: 0.165500, ... 540: 0.290400, ... 550: 0.433450, ... 560: 0.594500 ... } >>> data_2 = { ... 500: 0.323000, ... 510: 0.503000, ... 520: 0.710000, ... 530: 0.862000, ... 540: 0.954000, ... 550: 0.994950, ... 560: 0.995000 ... } >>> spd1 = SpectralDistribution(data_1, name='Custom 1') >>> spd2 = SpectralDistribution(data_2, name='Custom 2') >>> plot_multi_sds([spd1, spd2]) # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Multi_SDs.png :align: center :alt: plot_multi_sds """ _figure, axes = artist(**kwargs) if isinstance(sds, MultiSpectralDistribution): sds = sds.to_sds() else: sds = list(sds) for i, sd in enumerate(sds[:]): if isinstance(sd, MultiSpectralDistribution): sds.remove(sd) sds[i:i] = sd.to_sds() cmfs = first_item(filter_cmfs(cmfs).values()) illuminant = ILLUMINANTS_SDS[ COLOUR_STYLE_CONSTANTS.colour.colourspace.illuminant] x_limit_min, x_limit_max, y_limit_min, y_limit_max = [], [], [], [] for sd in sds: wavelengths, values = sd.wavelengths, sd.values shape = sd.shape x_limit_min.append(shape.start) x_limit_max.append(shape.end) y_limit_min.append(min(values)) y_limit_max.append(max(values)) if use_sds_colours: with domain_range_scale('1'): XYZ = sd_to_XYZ(sd, cmfs, illuminant) if normalise_sds_colours: XYZ = normalise_maximum(XYZ, clip=False) RGB = np.clip(XYZ_to_plotting_colourspace(XYZ), 0, 1) axes.plot(wavelengths, values, color=RGB, label=sd.strict_name) else: axes.plot(wavelengths, values, label=sd.strict_name) bounding_box = (min(x_limit_min), max(x_limit_max), min(y_limit_min), max(y_limit_max) + max(y_limit_max) * 0.05) settings = { 'axes': axes, 'bounding_box': bounding_box, 'legend': True, 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': 'Spectral Distribution', } settings.update(kwargs) return render(**settings)
def plot_RGB_colourspace_section( colourspace: Union[RGB_Colourspace, str, Sequence[Union[RGB_Colourspace, str]]], 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, show_section_colours: Boolean = True, show_section_contour: Boolean = True, **kwargs: Any, ) -> Tuple[plt.Figure, plt.Axes]: """ Plot given *RGB* colourspace section colours along given axis and origin. Parameters ---------- 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. 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. show_section_colours Whether to show the hull section colours. show_section_contour Whether to show the hull section contour. Other Parameters ---------------- kwargs {:func:`colour.plotting.artist`, :func:`colour.plotting.render`, :func:`colour.plotting.section.plot_hull_section_colours` :func:`colour.plotting.section.plot_hull_section_contour`}, See the documentation of the previously listed definitions. Returns ------- :class:`tuple` Current figure and axes. Examples -------- >>> from colour.utilities import is_trimesh_installed >>> if is_trimesh_installed: ... plot_RGB_colourspace_section( ... 'sRGB', section_colours='RGB', section_opacity=0.15) ... # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, <...AxesSubplot...>) .. image:: ../_static/Plotting_Plot_RGB_Colourspace_Section.png :align: center :alt: plot_RGB_colourspace_section """ import trimesh settings: Dict[str, Any] = {"uniform": True} settings.update(kwargs) _figure, axes = artist(**settings) colourspace = cast( RGB_Colourspace, first_item(filter_RGB_colourspaces(colourspace).values()), ) vertices, faces, _outline = primitive_cube(1, 1, 1, 64, 64, 64) XYZ_vertices = RGB_to_XYZ( vertices["position"] + 0.5, colourspace.whitepoint, colourspace.whitepoint, colourspace.matrix_RGB_to_XYZ, ) hull = trimesh.Trimesh(XYZ_vertices, faces, process=False) if show_section_colours: settings = {"axes": axes} settings.update(kwargs) settings["standalone"] = False plot_hull_section_colours(hull, model, axis, origin, normalise, **settings) if show_section_contour: settings = {"axes": axes} settings.update(kwargs) settings["standalone"] = False plot_hull_section_contour(hull, model, axis, origin, normalise, **settings) title = (f"{colourspace.name} Section - " f"{f'{origin * 100}%' if normalise else origin} - " f"{model}") plane = MAPPING_AXIS_TO_PLANE[axis] labels = np.array(COLOURSPACE_MODELS_AXIS_LABELS[model])[as_int_array( colourspace_model_axis_reorder([0, 1, 2], model))] x_label, y_label = labels[plane[0]], labels[plane[1]] settings.update({ "axes": axes, "standalone": True, "title": title, "x_label": x_label, "y_label": y_label, }) settings.update(kwargs) return render(**settings)
def plot_single_sd(sd, cmfs='CIE 1931 2 Degree Standard Observer', out_of_gamut_clipping=True, modulate_colours_with_sd_amplitude=False, equalize_sd_amplitude=False, **kwargs): """ Plots given spectral distribution. Parameters ---------- sd : SpectralDistribution Spectral distribution to plot. out_of_gamut_clipping : bool, optional Whether to clip out of gamut colours otherwise, the colours will be offset by the absolute minimal colour leading to a rendering on gray background, less saturated and smoother. modulate_colours_with_sd_amplitude : bool, optional Whether to modulate the colours with the spectral distribution amplitude. equalize_sd_amplitude : bool, optional Whether to equalize the spectral distribution amplitude. Equalization occurs after the colours modulation thus setting both arguments to *True* will generate a spectrum strip where each wavelength colour is modulated by the spectral distribution amplitude. The usual 5% margin above the spectral distribution is also omitted. cmfs : unicode Standard observer colour matching functions used for spectrum creation. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. References ---------- :cite:`Spiker2015a` Examples -------- >>> from colour import SpectralDistribution >>> data = { ... 500: 0.0651, ... 520: 0.0705, ... 540: 0.0772, ... 560: 0.0870, ... 580: 0.1128, ... 600: 0.1360 ... } >>> sd = SpectralDistribution(data, name='Custom') >>> plot_single_sd(sd) # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Single_SD.png :align: center :alt: plot_single_sd """ _figure, axes = artist(**kwargs) cmfs = first_item(filter_cmfs(cmfs).values()) sd = sd.copy() sd.interpolator = LinearInterpolator wavelengths = cmfs.wavelengths[np.logical_and( cmfs.wavelengths >= max(min(cmfs.wavelengths), min(sd.wavelengths)), cmfs.wavelengths <= min(max(cmfs.wavelengths), max(sd.wavelengths)), )] values = sd[wavelengths] colours = XYZ_to_plotting_colourspace( wavelength_to_XYZ(wavelengths, cmfs), ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['E'], apply_encoding_cctf=False) if not out_of_gamut_clipping: colours += np.abs(np.min(colours)) colours = normalise_maximum(colours) if modulate_colours_with_sd_amplitude: colours *= (values / np.max(values))[..., np.newaxis] colours = COLOUR_STYLE_CONSTANTS.colour.colourspace.encoding_cctf(colours) if equalize_sd_amplitude: values = np.ones(values.shape) margin = 0 if equalize_sd_amplitude else 0.05 x_min, x_max = min(wavelengths), max(wavelengths) y_min, y_max = 0, max(values) + max(values) * margin polygon = Polygon( np.vstack([ (x_min, 0), tstack([wavelengths, values]), (x_max, 0), ]), facecolor='none', edgecolor='none') axes.add_patch(polygon) padding = 0.1 axes.bar( x=wavelengths - padding, height=max(values), width=1 + padding, color=colours, align='edge', clip_path=polygon) axes.plot(wavelengths, values, color=COLOUR_STYLE_CONSTANTS.colour.dark) settings = { 'axes': axes, 'bounding_box': (x_min, x_max, y_min, y_max), 'title': '{0} - {1}'.format(sd.strict_name, cmfs.strict_name), 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': 'Spectral Distribution', } settings.update(kwargs) return render(**settings)
def plot_planckian_locus_in_chromaticity_diagram( illuminants=None, annotate_parameters=None, chromaticity_diagram_callable=plot_chromaticity_diagram, planckian_locus_callable=plot_planckian_locus, method='CIE 1931', **kwargs): """ Plots the *Planckian Locus* and given illuminants in the *Chromaticity Diagram* according to given method. Parameters ---------- illuminants : array_like, optional Factory illuminants to plot. annotate_parameters : dict or array_like, optional Parameters for the :func:`plt.annotate` definition, used to annotate the resulting chromaticity coordinates with their respective illuminant names if ``annotate`` is set to *True*. ``annotate_parameters`` can be either a single dictionary applied to all the arrows with same settings or a sequence of dictionaries with different settings for each illuminant. chromaticity_diagram_callable : callable, optional Callable responsible for drawing the *Chromaticity Diagram*. planckian_locus_callable : callable, optional Callable responsible for drawing the *Planckian Locus*. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, :func:`colour.plotting.temperature.plot_planckian_locus`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_planckian_locus_in_chromaticity_diagram(['A', 'B', 'C']) ... # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, \ <matplotlib.axes._subplots.AxesSubplot object at 0x...>) .. image:: ../_static/Plotting_\ Plot_Planckian_Locus_In_Chromaticity_Diagram.png :align: center :alt: plot_planckian_locus_in_chromaticity_diagram """ cmfs = CMFS['CIE 1931 2 Degree Standard Observer'] if illuminants is None: illuminants = ('A', 'B', 'C') illuminants = filter_passthrough(ILLUMINANTS.get(cmfs.name), illuminants) settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) method = method.upper() settings = {'axes': axes, 'method': method} settings.update(kwargs) settings['standalone'] = False chromaticity_diagram_callable(**settings) planckian_locus_callable(**settings) if method == 'CIE 1931': def xy_to_ij(xy): """ Converts given *CIE xy* chromaticity coordinates to *ij* chromaticity coordinates. """ return xy bounding_box = (-0.1, 0.9, -0.1, 0.9) elif method == 'CIE 1960 UCS': def xy_to_ij(xy): """ Converts given *CIE xy* chromaticity coordinates to *ij* chromaticity coordinates. """ return UCS_to_uv(XYZ_to_UCS(xy_to_XYZ(xy))) bounding_box = (-0.1, 0.7, -0.2, 0.6) else: raise ValueError('Invalid method: "{0}", must be one of ' '{{\'CIE 1931\', \'CIE 1960 UCS\'}}'.format(method)) annotate_settings_collection = [{ 'annotate': True, 'xytext': (-50, 30), 'textcoords': 'offset points', 'arrowprops': COLOUR_ARROW_STYLE, } for _ in range(len(illuminants))] if annotate_parameters is not None: if not isinstance(annotate_parameters, dict): assert len(annotate_parameters) == len(illuminants), ( 'Multiple annotate parameters defined, but they do not match ' 'the illuminants count!') for i, annotate_settings in enumerate(annotate_settings_collection): if isinstance(annotate_parameters, dict): annotate_settings.update(annotate_parameters) else: annotate_settings.update(annotate_parameters[i]) for i, (illuminant, xy) in enumerate(illuminants.items()): ij = xy_to_ij(xy) axes.plot(ij[0], ij[1], 'o', color=COLOUR_STYLE_CONSTANTS.colour.brightest, markeredgecolor=COLOUR_STYLE_CONSTANTS.colour.dark, markersize=(COLOUR_STYLE_CONSTANTS.geometry.short * 6 + COLOUR_STYLE_CONSTANTS.geometry.short * 0.75), markeredgewidth=COLOUR_STYLE_CONSTANTS.geometry.short * 0.75, label=illuminant) if annotate_settings_collection[i]['annotate']: annotate_settings = annotate_settings_collection[i] annotate_settings.pop('annotate') axes.annotate(illuminant, xy=ij, **annotate_settings) title = (('{0} Illuminants - Planckian Locus\n' '{1} Chromaticity Diagram - ' 'CIE 1931 2 Degree Standard Observer').format( ', '.join(illuminants), method) if illuminants else ('Planckian Locus\n{0} Chromaticity Diagram - ' 'CIE 1931 2 Degree Standard Observer'.format(method))) settings.update({ 'axes': axes, 'standalone': True, 'bounding_box': bounding_box, 'title': title, }) settings.update(kwargs) return render(**settings)
def plot_planckian_locus(planckian_locus_colours=None, method='CIE 1931', **kwargs): """ Plots the *Planckian Locus* according to given method. Parameters ---------- planckian_locus_colours : array_like or unicode, optional *Planckian Locus* colours. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_planckian_locus() # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Planckian_Locus.png :align: center :alt: plot_planckian_locus """ if planckian_locus_colours is None: planckian_locus_colours = COLOUR_STYLE_CONSTANTS.colour.dark settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) if method == 'CIE 1931': def uv_to_ij(uv): """ Converts given *uv* chromaticity coordinates to *ij* chromaticity coordinates. """ return UCS_uv_to_xy(uv) D_uv = 0.025 elif method == 'CIE 1960 UCS': def uv_to_ij(uv): """ Converts given *uv* chromaticity coordinates to *ij* chromaticity coordinates. """ return uv D_uv = 0.025 else: raise ValueError('Invalid method: "{0}", must be one of ' '{{\'CIE 1931\', \'CIE 1960 UCS\'}}'.format(method)) start, end = 1667, 100000 CCT = np.arange(start, end + 250, 250) CCT_D_uv = tstack([CCT, np.zeros(CCT.shape)]) ij = uv_to_ij(CCT_to_uv(CCT_D_uv, 'Robertson 1968')) axes.plot(ij[..., 0], ij[..., 1], color=planckian_locus_colours) for i in (1667, 2000, 2500, 3000, 4000, 6000, 10000): i0, j0 = uv_to_ij(CCT_to_uv(np.array([i, -D_uv]), 'Robertson 1968')) i1, j1 = uv_to_ij(CCT_to_uv(np.array([i, D_uv]), 'Robertson 1968')) axes.plot((i0, i1), (j0, j1), color=planckian_locus_colours) axes.annotate( '{0}K'.format(i), xy=(i0, j0), xytext=(0, -10), textcoords='offset points', size='x-small') settings = {'axes': axes} settings.update(kwargs) return render(**settings)
def plot_multi_cmfs(cmfs=None, **kwargs): """ Plots given colour matching functions. Parameters ---------- cmfs : array_like, optional Colour matching functions to plot. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> cmfs = ('CIE 1931 2 Degree Standard Observer', ... 'CIE 1964 10 Degree Standard Observer') >>> plot_multi_cmfs(cmfs) # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Multi_CMFS.png :align: center :alt: plot_multi_cmfs """ if cmfs is None: cmfs = ('CIE 1931 2 Degree Standard Observer', 'CIE 1964 10 Degree Standard Observer') cmfs = filter_cmfs(cmfs).values() _figure, axes = artist(**kwargs) axes.axhline(color=COLOUR_STYLE_CONSTANTS.colour.dark, linestyle='--') x_limit_min, x_limit_max, y_limit_min, y_limit_max = [], [], [], [] for i, cmfs_i in enumerate(cmfs): for j, RGB in enumerate([(1, 0, 0), (0, 1, 0), (0, 0, 1)]): RGB = [reduce(lambda y, _: y * 0.5, range(i), x) for x in RGB] values = cmfs_i.values[:, j] shape = cmfs_i.shape x_limit_min.append(shape.start) x_limit_max.append(shape.end) y_limit_min.append(min(values)) y_limit_max.append(max(values)) axes.plot( cmfs_i.wavelengths, values, color=RGB, label='{0} - {1}'.format(cmfs_i.strict_labels[j], cmfs_i.strict_name)) bounding_box = (min(x_limit_min), max(x_limit_max), min(y_limit_min) - abs(min(y_limit_min)) * 0.05, max(y_limit_max) + abs(max(y_limit_max)) * 0.05) title = '{0} - Colour Matching Functions'.format(', '.join( [cmfs_i.strict_name for cmfs_i in cmfs])) settings = { 'axes': axes, 'bounding_box': bounding_box, 'legend': True, 'title': title, 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': 'Tristimulus Values', } settings.update(kwargs) return render(**settings)
def plot_planckian_locus(planckian_locus_colours=None, method='CIE 1931', **kwargs): """ Plots the *Planckian Locus* according to given method. Parameters ---------- planckian_locus_colours : array_like or unicode, optional *Planckian Locus* colours. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_planckian_locus() # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, \ <matplotlib.axes._subplots.AxesSubplot object at 0x...>) .. image:: ../_static/Plotting_Plot_Planckian_Locus.png :align: center :alt: plot_planckian_locus """ if planckian_locus_colours is None: planckian_locus_colours = COLOUR_STYLE_CONSTANTS.colour.dark settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) if method == 'CIE 1931': def uv_to_ij(uv): """ Converts given *uv* chromaticity coordinates to *ij* chromaticity coordinates. """ return UCS_uv_to_xy(uv) D_uv = 0.025 elif method == 'CIE 1960 UCS': def uv_to_ij(uv): """ Converts given *uv* chromaticity coordinates to *ij* chromaticity coordinates. """ return uv D_uv = 0.025 else: raise ValueError('Invalid method: "{0}", must be one of ' '{{\'CIE 1931\', \'CIE 1960 UCS\'}}'.format(method)) start, end = 1667, 100000 CCT = np.arange(start, end + 250, 250) CCT_D_uv = tstack([CCT, np.zeros(CCT.shape)]) ij = uv_to_ij(CCT_to_uv(CCT_D_uv, 'Robertson 1968')) axes.plot(ij[..., 0], ij[..., 1], color=planckian_locus_colours) for i in (1667, 2000, 2500, 3000, 4000, 6000, 10000): i0, j0 = uv_to_ij(CCT_to_uv(np.array([i, -D_uv]), 'Robertson 1968')) i1, j1 = uv_to_ij(CCT_to_uv(np.array([i, D_uv]), 'Robertson 1968')) axes.plot((i0, i1), (j0, j1), color=planckian_locus_colours) axes.annotate('{0}K'.format(i), xy=(i0, j0), xytext=(0, -10), textcoords='offset points', size='x-small') settings = {'axes': axes} settings.update(kwargs) return render(**settings)
def plot_blackbody_colours( shape=SpectralShape(150, 12500, 50), cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots blackbody colours. Parameters ---------- shape : SpectralShape, optional Spectral shape to use as plot boundaries. cmfs : unicode, optional Standard observer colour matching functions. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_blackbody_colours(SpectralShape(150, 12500, 50)) # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Blackbody_Colours.png :align: center :alt: plot_blackbody_colours """ _figure, axes = artist(**kwargs) cmfs = first_item(filter_cmfs(cmfs).values()) colours = [] temperatures = [] for temperature in shape: sd = sd_blackbody(temperature, cmfs.shape) with domain_range_scale('1'): XYZ = sd_to_XYZ(sd, cmfs) RGB = normalise_maximum(XYZ_to_plotting_colourspace(XYZ)) colours.append(RGB) temperatures.append(temperature) x_min, x_max = min(temperatures), max(temperatures) y_min, y_max = 0, 1 padding = 0.1 axes.bar( x=np.array(temperatures) - padding, height=1, width=shape.interval + (padding * shape.interval), color=colours, align='edge') settings = { 'axes': axes, 'bounding_box': (x_min, x_max, y_min, y_max), 'title': 'Blackbody Colours', 'x_label': 'Temperature K', 'y_label': None, } settings.update(kwargs) return render(**settings)
def plot_multi_colour_checkers(colour_checkers=None, **kwargs): """ Plots and compares given colour checkers. Parameters ---------- colour_checkers : array_like, optional Color checker names, must be less than or equal to 2 names. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.plot_multi_colour_swatches`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_multi_colour_checkers(['ColorChecker 1976', 'ColorChecker 2005']) ... # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Multi_Colour_Checkers.png :align: center :alt: plot_multi_colour_checkers """ if colour_checkers is None: colour_checkers = ['ColorChecker 1976', 'ColorChecker 2005'] else: assert len(colour_checkers) <= 2, ( 'Only two colour checkers can be compared at a time!') colour_checkers = filter_colour_checkers(colour_checkers).values() _figure, axes = artist(**kwargs) compare_swatches = len(colour_checkers) == 2 colour_swatches = [] colour_checker_names = [] for colour_checker in colour_checkers: colour_checker_names.append(colour_checker.name) for label, xyY in colour_checker.data.items(): XYZ = xyY_to_XYZ(xyY) RGB = XYZ_to_plotting_colourspace(XYZ, colour_checker.illuminant) colour_swatches.append( ColourSwatch(label.title(), np.clip(np.ravel(RGB), 0, 1))) if compare_swatches: colour_swatches = [ swatch for pairs in zip(colour_swatches[0:len(colour_swatches) // 2], colour_swatches[len(colour_swatches) // 2:]) for swatch in pairs ] background_colour = '0.1' width = height = 1.0 spacing = 0.25 columns = 6 settings = { 'axes': axes, 'width': width, 'height': height, 'spacing': spacing, 'columns': columns, 'text_parameters': { 'size': 8 }, 'background_colour': background_colour, 'compare_swatches': 'Stacked' if compare_swatches else None, } settings.update(kwargs) settings['standalone'] = False plot_multi_colour_swatches(colour_swatches, **settings) axes.text( 0.5, 0.005, '{0} - {1} - Colour Rendition Chart'.format( ', '.join(colour_checker_names), COLOUR_STYLE_CONSTANTS.colour.colourspace.name), transform=axes.transAxes, color=COLOUR_STYLE_CONSTANTS.colour.bright, ha='center', va='bottom') settings.update({ 'axes': axes, 'standalone': True, 'title': ', '.join(colour_checker_names), }) return render(**settings)
def plot_multi_cmfs(cmfs=None, **kwargs): """ Plots given colour matching functions. Parameters ---------- cmfs : array_like, optional Colour matching functions to plot. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> cmfs = ('CIE 1931 2 Degree Standard Observer', ... 'CIE 1964 10 Degree Standard Observer') >>> plot_multi_cmfs(cmfs) # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, \ <matplotlib.axes._subplots.AxesSubplot object at 0x...>) .. image:: ../_static/Plotting_Plot_Multi_CMFS.png :align: center :alt: plot_multi_cmfs """ if cmfs is None: cmfs = ('CIE 1931 2 Degree Standard Observer', 'CIE 1964 10 Degree Standard Observer') cmfs = filter_cmfs(cmfs).values() _figure, axes = artist(**kwargs) axes.axhline(color=COLOUR_STYLE_CONSTANTS.colour.dark, linestyle='--') x_limit_min, x_limit_max, y_limit_min, y_limit_max = [], [], [], [] for i, cmfs_i in enumerate(cmfs): for j, RGB in enumerate([(1, 0, 0), (0, 1, 0), (0, 0, 1)]): RGB = [reduce(lambda y, _: y * 0.5, range(i), x) for x in RGB] values = cmfs_i.values[:, j] shape = cmfs_i.shape x_limit_min.append(shape.start) x_limit_max.append(shape.end) y_limit_min.append(min(values)) y_limit_max.append(max(values)) axes.plot(cmfs_i.wavelengths, values, color=RGB, label='{0} - {1}'.format(cmfs_i.strict_labels[j], cmfs_i.strict_name)) bounding_box = (min(x_limit_min), max(x_limit_max), min(y_limit_min) - abs(min(y_limit_min)) * 0.05, max(y_limit_max) + abs(max(y_limit_max)) * 0.05) title = '{0} - Colour Matching Functions'.format(', '.join( [cmfs_i.strict_name for cmfs_i in cmfs])) settings = { 'axes': axes, 'bounding_box': bounding_box, 'legend': True, 'title': title, 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': 'Tristimulus Values', } settings.update(kwargs) return render(**settings)
def plot_spectra_ANSIIESTM3018( specification: ColourQuality_Specification_ANSIIESTM3018, **kwargs: Any) -> Tuple[plt.Figure, plt.Axes]: """ Plot a comparison of the spectral distributions of a test emission source and a reference illuminant for *ANSI/IES TM-30-18 Colour Rendition Report*. Parameters ---------- specification *ANSI/IES TM-30-18 Colour Rendition Report* specification. 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 >>> from colour.quality import colour_fidelity_index_ANSIIESTM3018 >>> sd = SDS_ILLUMINANTS['FL2'] >>> specification = colour_fidelity_index_ANSIIESTM3018(sd, True) >>> plot_spectra_ANSIIESTM3018(specification) ... # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, <...AxesSubplot...>) """ settings: Dict[str, Any] = dict(kwargs) _figure, axes = artist(**settings) Y_reference = sd_to_XYZ(specification.sd_reference)[1] Y_test = sd_to_XYZ(specification.sd_test)[1] axes.plot( specification.sd_reference.wavelengths, specification.sd_reference.values / Y_reference, "black", label="Reference", zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line, ) axes.plot( specification.sd_test.wavelengths, specification.sd_test.values / Y_test, "#F05046", label="Test", zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_line, ) axes.tick_params(axis="y", which="both", length=0) axes.set_yticklabels([]) settings = { "axes": axes, "legend": True, "legend_columns": 2, "x_label": "Wavelength (nm)", "y_label": "Radiant Power\n(Equal Luminous Flux)", } settings.update(kwargs) return render(**settings)
def plot_blackbody_colours(shape=SpectralShape(150, 12500, 50), cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots blackbody colours. Parameters ---------- shape : SpectralShape, optional Spectral shape to use as plot boundaries. cmfs : unicode, optional Standard observer colour matching functions. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_blackbody_colours(SpectralShape(150, 12500, 50)) ... # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, \ <matplotlib.axes._subplots.AxesSubplot object at 0x...>) .. image:: ../_static/Plotting_Plot_Blackbody_Colours.png :align: center :alt: plot_blackbody_colours """ _figure, axes = artist(**kwargs) cmfs = first_item(filter_cmfs(cmfs).values()) colours = [] temperatures = [] for temperature in shape: sd = sd_blackbody(temperature, cmfs.shape) with domain_range_scale('1'): XYZ = sd_to_XYZ(sd, cmfs) RGB = normalise_maximum(XYZ_to_plotting_colourspace(XYZ)) colours.append(RGB) temperatures.append(temperature) x_min, x_max = min(temperatures), max(temperatures) y_min, y_max = 0, 1 padding = 0.1 axes.bar(x=np.array(temperatures) - padding, height=1, width=shape.interval + (padding * shape.interval), color=colours, align='edge') settings = { 'axes': axes, 'bounding_box': (x_min, x_max, y_min, y_max), 'title': 'Blackbody Colours', 'x_label': 'Temperature K', 'y_label': None, } settings.update(kwargs) return render(**settings)
def plot_16_bin_bars( values: ArrayLike, label_template: str, x_ticker: Boolean = False, label_orientation: Union[Literal["Horizontal", "Vertical"], str] = "Vertical", **kwargs: Any, ) -> Tuple[plt.Figure, plt.Axes]: """ Plot the 16 bin bars for given values according to *ANSI/IES TM-30-18 Colour Rendition Report*. Parameters ---------- values Values to generate the bin bars for. label_template Template to format the labels. x_ticker Whether to show the *X* axis ticker and the associated label. label_orientation Orientation of the labels. 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_16_bin_bars(np.arange(16), '{0}') ... # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, <...AxesSubplot...>) """ values = as_float_array(values) label_orientation = validate_method(label_orientation, ["Horizontal", "Vertical"]) _figure, axes = artist(**kwargs) bar_count = len(_COLOURS_BIN_BAR) axes.bar( np.arange(bar_count) + 1, values, color=_COLOURS_BIN_BAR, width=1, edgecolor="black", linewidth=CONSTANTS_COLOUR_STYLE.geometry.short / 3, zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon, ) axes.set_xlim(0.5, bar_count + 0.5) if x_ticker: axes.set_xticks(np.arange(1, bar_count + 1)) axes.set_xlabel("Hue-Angle Bin (j)") else: axes.set_xticks([]) label_orientation = label_orientation.lower() value_max = np.max(values) for i, value in enumerate(values): if label_orientation == "vertical": va, vo = (("bottom", value_max * 0.15) if value > 0 else ("top", -value_max * 0.15)) axes.annotate( label_template.format(value), xy=(i + 1, value + vo), rotation=90, fontsize="xx-small", ha="center", va=va, zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_label, ) elif label_orientation == "horizontal": va, vo = (("bottom", value_max * 0.025) if value < 90 else ("top", -value_max * 0.025)) axes.annotate( label_template.format(value), xy=(i + 1, value + vo), fontsize="xx-small", ha="center", va=va, zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_label, ) return render(**kwargs)
def plot_chromaticity_diagram(cmfs='CIE 1931 2 Degree Standard Observer', show_diagram_colours=True, show_spectral_locus=True, method='CIE 1931', **kwargs): """ Plots the *Chromaticity Diagram* according to given method. Parameters ---------- cmfs : unicode, optional Standard observer colour matching functions used for *Chromaticity Diagram* bounds. show_diagram_colours : bool, optional Whether to display the *Chromaticity Diagram* background colours. show_spectral_locus : bool, optional Whether to display the *Spectral Locus*. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.diagrams.plot_spectral_locus`, :func:`colour.plotting.diagrams.plot_chromaticity_diagram_colours`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_chromaticity_diagram() # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Chromaticity_Diagram.png :align: center :alt: plot_chromaticity_diagram """ settings = {'uniform': True} settings.update(kwargs) figure, axes = artist(**settings) method = method.upper() cmfs = first_item(filter_cmfs(cmfs).values()) if show_diagram_colours: settings = {'axes': axes, 'method': method} settings.update(kwargs) settings['standalone'] = False plot_chromaticity_diagram_colours(**settings) if show_spectral_locus: settings = {'axes': axes, 'method': method} settings.update(kwargs) settings['standalone'] = False plot_spectral_locus(**settings) if method == 'CIE 1931': x_label, y_label = 'CIE x', 'CIE y' elif method == 'CIE 1960 UCS': x_label, y_label = 'CIE u', 'CIE v' elif method == 'CIE 1976 UCS': x_label, y_label = 'CIE u\'', 'CIE v\'', else: raise ValueError( 'Invalid method: "{0}", must be one of ' '{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}'.format( method)) title = '{0} Chromaticity Diagram - {1}'.format(method, cmfs.strict_name) settings.update({ 'axes': axes, 'standalone': True, 'bounding_box': (0, 1, 0, 1), 'title': title, 'x_label': x_label, 'y_label': y_label, }) settings.update(kwargs) return render(**settings)
def fraunhofer_lines_plot(image, measured_Fraunhofer_lines, show_luminance_spd=True, **kwargs): """ Plots the *Fraunhofer* lines of given image. Parameters ---------- image : unicode Path to read the image from. measured_Fraunhofer_lines : dict, optional Measured *Fraunhofer* lines locations. show_luminance_spd : bool, optional Whether to show the *Luminance* sd for given image. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. """ settings = {} settings.update(kwargs) figure, axes = artist(**settings) spectrum = calibrated_RGB_spectrum(image, FRAUNHOFER_LINES_PUBLISHED, measured_Fraunhofer_lines) wavelengths = spectrum.wavelengths input, output = wavelengths[0], wavelengths[-1] width, height = figure.get_size_inches() ratio = width / height height = (output - input) * (1 / ratio) axes.imshow(COLOUR_STYLE_CONSTANTS.colour.colourspace.cctf_encoding( np.clip(spectrum.values[np.newaxis, ...], 0, 1)), extent=[input, output, 0, height]) sd = luminance_sd(spectrum).normalise(height - height * 0.05) if show_luminance_spd: axes.plot(sd.wavelengths, sd.values, color='black', linewidth=1) fraunhofer_wavelengths = np.array( sorted(FRAUNHOFER_LINES_PUBLISHED.values())) fraunhofer_wavelengths = fraunhofer_wavelengths[np.where( np.logical_and(fraunhofer_wavelengths >= input, fraunhofer_wavelengths <= output))] fraunhofer_lines_labels = [ tuple(FRAUNHOFER_LINES_PUBLISHED.keys())[tuple( FRAUNHOFER_LINES_PUBLISHED.values()).index(i)] for i in fraunhofer_wavelengths ] y0, y1 = 0, height * .5 for i, label in enumerate(fraunhofer_lines_labels): # Trick to cluster siblings *Fraunhofer* lines. from_siblings = False for pattern, (first, siblings, specific_label) in FRAUNHOFER_LINES_CLUSTERED.items(): if re.match(pattern, label): if label in siblings: from_siblings = True label = specific_label break power = bisect.bisect_left(wavelengths, fraunhofer_wavelengths[i]) scale = (sd[wavelengths[power]] / height) is_large_line = label in FRAUNHOFER_LINES_NOTABLE axes.vlines(fraunhofer_wavelengths[i], y0, y1 * scale, linewidth=1 if is_large_line else 1) axes.vlines(fraunhofer_wavelengths[i], y0, height, linewidth=1 if is_large_line else 1, alpha=0.075) if not from_siblings: axes.text(fraunhofer_wavelengths[i], y1 * scale + (y1 * 0.025), label, clip_on=True, ha='center', va='bottom', fontdict={'size': 'large' if is_large_line else 'small'}) r = lambda x: int(x / 100) * 100 plt.xticks(np.arange(r(input), r(output * 1.5), 20)) plt.yticks([]) settings = { 'title': 'The Solar Spectrum - Fraunhofer Lines', 'bounding_box': [input, output, 0, height], 'x_label': u'Wavelength λ (nm)', 'y_label': False, } settings.update(**kwargs) return render(**settings)
def plot_sds_in_chromaticity_diagram( sds, cmfs='CIE 1931 2 Degree Standard Observer', annotate_parameters=None, chromaticity_diagram_callable=plot_chromaticity_diagram, method='CIE 1931', **kwargs): """ Plots given spectral distribution chromaticity coordinates into the *Chromaticity Diagram* using given method. Parameters ---------- sds : array_like, optional Spectral distributions to plot. cmfs : unicode, optional Standard observer colour matching functions used for *Chromaticity Diagram* bounds. annotate_parameters : dict or array_like, optional Parameters for the :func:`plt.annotate` definition, used to annotate the resulting chromaticity coordinates with their respective spectral distribution names if ``annotate`` is set to *True*. ``annotate_parameters`` can be either a single dictionary applied to all the arrows with same settings or a sequence of dictionaries with different settings for each spectral distribution. chromaticity_diagram_callable : callable, optional Callable responsible for drawing the *Chromaticity Diagram*. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> from colour import ILLUMINANTS_SDS >>> A = ILLUMINANTS_SDS['A'] >>> D65 = ILLUMINANTS_SDS['D65'] >>> plot_sds_in_chromaticity_diagram([A, D65]) # doctest: +SKIP .. image:: ../_static/Plotting_Plot_SDs_In_Chromaticity_Diagram.png :align: center :alt: plot_sds_in_chromaticity_diagram """ settings = {'uniform': True} settings.update(kwargs) figure, axes = artist(**settings) method = method.upper() settings.update({ 'axes': axes, 'standalone': False, 'method': method, 'cmfs': cmfs, }) chromaticity_diagram_callable(**settings) if method == 'CIE 1931': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return XYZ_to_xy(XYZ) bounding_box = (-0.1, 0.9, -0.1, 0.9) elif method == 'CIE 1960 UCS': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return UCS_to_uv(XYZ_to_UCS(XYZ)) bounding_box = (-0.1, 0.7, -0.2, 0.6) elif method == 'CIE 1976 UCS': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return Luv_to_uv(XYZ_to_Luv(XYZ)) bounding_box = (-0.1, 0.7, -0.1, 0.7) else: raise ValueError( 'Invalid method: "{0}", must be one of ' '{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}'.format( method)) annotate_settings_collection = [{ 'annotate': True, 'xytext': (-50, 30), 'textcoords': 'offset points', 'arrowprops': COLOUR_ARROW_STYLE, } for _ in range(len(sds))] if annotate_parameters is not None: if not isinstance(annotate_parameters, dict): assert len(annotate_parameters) == len(sds), ( 'Multiple annotate parameters defined, but they do not match ' 'the spectral distributions count!') for i, annotate_settings in enumerate(annotate_settings_collection): if isinstance(annotate_parameters, dict): annotate_settings.update(annotate_parameters) else: annotate_settings.update(annotate_parameters[i]) for i, sd in enumerate(sds): with domain_range_scale('1'): XYZ = sd_to_XYZ(sd) ij = XYZ_to_ij(XYZ) axes.plot( ij[0], ij[1], 'o', color=COLOUR_STYLE_CONSTANTS.colour.brightest, markeredgecolor=COLOUR_STYLE_CONSTANTS.colour.dark, markersize=(COLOUR_STYLE_CONSTANTS.geometry.short * 6 + COLOUR_STYLE_CONSTANTS.geometry.short * 0.75), markeredgewidth=COLOUR_STYLE_CONSTANTS.geometry.short * 0.75, label=sd.strict_name) if (sd.name is not None and annotate_settings_collection[i]['annotate']): annotate_settings = annotate_settings_collection[i] annotate_settings.pop('annotate') axes.annotate(sd.name, xy=ij, **annotate_settings) settings.update({'standalone': True, 'bounding_box': bounding_box}) settings.update(kwargs) return render(**settings)
def fraunhofer_lines_plot(image, measured_Fraunhofer_lines, show_luminance_spd=True, **kwargs): """ Plots the *Fraunhofer* lines of given image. Parameters ---------- image : unicode Path to read the image from. measured_Fraunhofer_lines : dict, optional Measured *Fraunhofer* lines locations. show_luminance_spd : bool, optional Whether to show the *Luminance* sd for given image. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. """ settings = {} settings.update(kwargs) figure, axes = artist(**settings) spectrum = calibrated_RGB_spectrum(image, FRAUNHOFER_LINES_PUBLISHED, measured_Fraunhofer_lines) wavelengths = spectrum.wavelengths input, output = wavelengths[0], wavelengths[-1] width, height = figure.get_size_inches() ratio = width / height height = (output - input) * (1 / ratio) axes.imshow( COLOUR_STYLE_CONSTANTS.colour.colourspace.encoding_cctf( np.clip(spectrum.values[np.newaxis, ...], 0, 1)), extent=[input, output, 0, height]) sd = luminance_sd(spectrum).normalise(height - height * 0.05) if show_luminance_spd: axes.plot(sd.wavelengths, sd.values, color='black', linewidth=1) fraunhofer_wavelengths = np.array( sorted(FRAUNHOFER_LINES_PUBLISHED.values())) fraunhofer_wavelengths = fraunhofer_wavelengths[np.where( np.logical_and(fraunhofer_wavelengths >= input, fraunhofer_wavelengths <= output))] fraunhofer_lines_labels = [ tuple(FRAUNHOFER_LINES_PUBLISHED.keys())[tuple( FRAUNHOFER_LINES_PUBLISHED.values()).index(i)] for i in fraunhofer_wavelengths ] y0, y1 = 0, height * .5 for i, label in enumerate(fraunhofer_lines_labels): # Trick to cluster siblings *Fraunhofer* lines. from_siblings = False for pattern, (first, siblings, specific_label) in FRAUNHOFER_LINES_CLUSTERED.items(): if re.match(pattern, label): if label in siblings: from_siblings = True label = specific_label break power = bisect.bisect_left(wavelengths, fraunhofer_wavelengths[i]) scale = (sd[wavelengths[power]] / height) is_large_line = label in FRAUNHOFER_LINES_NOTABLE axes.vlines( fraunhofer_wavelengths[i], y0, y1 * scale, linewidth=1 if is_large_line else 1) axes.vlines( fraunhofer_wavelengths[i], y0, height, linewidth=1 if is_large_line else 1, alpha=0.075) if not from_siblings: axes.text( fraunhofer_wavelengths[i], y1 * scale + (y1 * 0.025), label, clip_on=True, ha='center', va='bottom', fontdict={'size': 'large' if is_large_line else 'small'}) r = lambda x: int(x / 100) * 100 plt.xticks(np.arange(r(input), r(output * 1.5), 20)) plt.yticks([]) settings = { 'title': 'The Solar Spectrum - Fraunhofer Lines', 'bounding_box': [input, output, 0, height], 'x_label': u'Wavelength λ (nm)', 'y_label': False, } settings.update(**kwargs) return render(**settings)