def chromaticity_diagram_visual(samples=256, cmfs='CIE 1931 2 Degree Standard Observer', transformation='CIE 1931', parent=None): """ Creates a chromaticity diagram visual based on :class:`colour_analysis.visuals.Primitive` class. Parameters ---------- samples : int, optional Inner samples count used to construct the chromaticity diagram triangulation. cmfs : unicode, optional Standard observer colour matching functions used for the chromaticity diagram boundaries. transformation : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, Chromaticity diagram transformation. parent : Node, optional Parent of the chromaticity diagram in the `SceneGraph`. Returns ------- Primitive Chromaticity diagram visual. """ cmfs = first_item(filter_cmfs(cmfs).values()) illuminant = DEFAULT_PLOTTING_ILLUMINANT XYZ_to_ij = ( CHROMATICITY_DIAGRAM_TRANSFORMATIONS[transformation]['XYZ_to_ij']) ij_to_XYZ = ( CHROMATICITY_DIAGRAM_TRANSFORMATIONS[transformation]['ij_to_XYZ']) ij_c = XYZ_to_ij(cmfs.values, illuminant) triangulation = Delaunay(ij_c, qhull_options='QJ') samples = np.linspace(0, 1, samples) ii, jj = np.meshgrid(samples, samples) ij = tstack([ii, jj]) ij = np.vstack([ij_c, ij[triangulation.find_simplex(ij) > 0]]) ij_p = np.hstack([ij, np.full((ij.shape[0], 1), 0, DEFAULT_FLOAT_DTYPE)]) triangulation = Delaunay(ij, qhull_options='QJ') RGB = normalise_maximum( XYZ_to_sRGB(ij_to_XYZ(ij, illuminant), illuminant), axis=-1) diagram = Primitive( vertices=ij_p, faces=triangulation.simplices, vertex_colours=RGB, parent=parent) return diagram
def visible_spectrum_plot(cmfs='CIE 1931 2 Degree Standard Observer', out_of_gamut_clipping=True, **kwargs): """ Plots the visible colours spectrum using given standard observer *CIE XYZ* colour matching functions. Parameters ---------- cmfs : unicode, optional Standard observer colour matching functions used for spectrum creation. out_of_gamut_clipping : bool, optional Out of gamut colours will be clipped if *True* otherwise, the colours will be offset by the absolute minimal colour leading to a rendering on gray background, less saturated and smoother. [1]_ \**kwargs : dict, optional Keywords arguments. Returns ------- Figure Current figure or None. Examples -------- >>> visible_spectrum_plot() # doctest: +SKIP """ cmfs = get_cmfs(cmfs) cmfs = cmfs.clone().align(DEFAULT_SPECTRAL_SHAPE) wavelengths = cmfs.shape.range() colours = XYZ_to_sRGB( 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 = DEFAULT_PLOTTING_ENCODING_CCTF(normalise_maximum(colours)) settings = { 'title': 'The Visible Spectrum - {0}'.format(cmfs.title), 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': False, 'x_tighten': True, 'y_ticker': False} settings.update(kwargs) return colour_parameters_plot([ColourParameter(x=x[0], RGB=x[1]) for x in tuple(zip(wavelengths, colours))], **settings)
def blackbody_colours_plot(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. \**kwargs : dict, optional Keywords arguments. Returns ------- Figure Current figure or None. Examples -------- >>> blackbody_colours_plot() # doctest: +SKIP """ cmfs = get_cmfs(cmfs) colours = [] temperatures = [] for temperature in shape: spd = blackbody_spd(temperature, cmfs.shape) XYZ = spectral_to_XYZ(spd, cmfs) RGB = normalise_maximum(XYZ_to_sRGB(XYZ / 100)) colours.append(RGB) temperatures.append(temperature) settings = { 'title': 'Blackbody Colours', 'x_label': 'Temperature K', 'y_label': '', 'x_tighten': True, 'y_ticker': False} settings.update(kwargs) return colour_parameters_plot([ColourParameter(x=x[0], RGB=x[1]) for x in tuple(zip(temperatures, colours))], **settings)
def test_normalise_maximum(self): """ Tests :func:`colour.utilities.array.normalise_maximum` definition. """ np.testing.assert_almost_equal( normalise_maximum(np.array([0.20654008, 0.12197225, 0.05136952])), np.array([1.00000000, 0.59055003, 0.24871454]), decimal=7) np.testing.assert_almost_equal( normalise_maximum( np.array([ [0.20654008, 0.12197225, 0.05136952], [0.14222010, 0.23042768, 0.10495772], [0.07818780, 0.06157201, 0.28099326], ])), np.array([ [0.73503571, 0.43407536, 0.18281406], [0.50613349, 0.82004700, 0.37352398], [0.27825507, 0.21912273, 1.00000000], ]), decimal=7) np.testing.assert_almost_equal( normalise_maximum( np.array([ [0.20654008, 0.12197225, 0.05136952], [0.14222010, 0.23042768, 0.10495772], [0.07818780, 0.06157201, 0.28099326], ]), axis=-1), np.array([ [1.00000000, 0.59055003, 0.24871454], [0.61720059, 1.00000000, 0.45549094], [0.27825507, 0.21912273, 1.00000000], ]), decimal=7) np.testing.assert_almost_equal( normalise_maximum( np.array([0.20654008, 0.12197225, 0.05136952]), factor=10), np.array([10.00000000, 5.90550028, 2.48714535]), decimal=7) np.testing.assert_almost_equal( normalise_maximum( np.array([-0.11518475, -0.10080000, 0.05089373])), np.array([0.00000000, 0.00000000, 1.00000000]), decimal=7) np.testing.assert_almost_equal( normalise_maximum( np.array([-0.20654008, -0.12197225, 0.05136952]), clip=False), np.array([-4.02067374, -2.37440899, 1.00000000]), decimal=7)
def test_normalise_maximum(self): """ Tests :func:`colour.utilities.array.normalise_maximum` definition. """ np.testing.assert_almost_equal( normalise_maximum( np.array([0.11518475, 0.10080000, 0.05089373])), np.array([1.00000000, 0.87511585, 0.4418443]), decimal=7) np.testing.assert_almost_equal( normalise_maximum( np.array([[0.11518475, 0.10080000, 0.05089373], [0.07049534, 0.10080000, 0.09558313], [0.17501358, 0.38818795, 0.32161955]])), np.array([[0.29672418, 0.25966803, 0.13110589], [0.18160105, 0.25966803, 0.246229], [0.45084753, 1.00000000, 0.82851503]]), decimal=7) np.testing.assert_almost_equal( normalise_maximum( np.array([[0.11518475, 0.10080000, 0.05089373], [0.07049534, 0.10080000, 0.09558313], [0.17501358, 0.38818795, 0.32161955]]), axis=-1), np.array([[1.00000000, 0.87511585, 0.4418443], [0.69935852, 1.00000000, 0.94824533], [0.45084753, 1.00000000, 0.82851503]]), decimal=7) np.testing.assert_almost_equal( normalise_maximum( np.array([0.11518475, 0.10080000, 0.05089373]), factor=10), np.array([10.00000000, 8.75115850, 4.4184434]), decimal=7) np.testing.assert_almost_equal( normalise_maximum( np.array([-0.11518475, -0.10080000, 0.05089373])), np.array([0.00000000, 0.00000000, 1.00000000]), decimal=7) np.testing.assert_almost_equal( normalise_maximum( np.array([-0.11518475, -0.10080000, 0.05089373]), clip=False), np.array([-2.26324048, -1.98059761, 1.00000000]), decimal=7)
def plot_single_sd(sd, cmfs='CIE 1931 2 Degree Standard Observer', out_of_gamut_clipping=True, **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. 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 = COLOUR_STYLE_CONSTANTS.colour.colourspace.encoding_cctf( normalise_maximum(colours)) x_min, x_max = min(wavelengths), max(wavelengths) y_min, y_max = 0, max(values) + max(values) * 0.05 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 visible_spectrum_plot(cmfs='CIE 1931 2 Degree Standard Observer', out_of_gamut_clipping=True, **kwargs): """ Plots the visible colours spectrum using given standard observer *CIE XYZ* colour matching functions. Parameters ---------- cmfs : unicode, optional Standard observer colour matching functions used for spectrum creation. 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. [1]_ Other Parameters ---------------- \**kwargs : dict, optional {:func:`boundaries`, :func:`canvas`, :func:`decorate`, :func:`display`}, Please refer to the documentation of the previously listed definitions. y0_plot : bool, optional {:func:`colour_parameters_plot`}, Whether to plot *y0* line. y1_plot : bool, optional {:func:`colour_parameters_plot`}, Whether to plot *y1* line. Returns ------- Figure Current figure or None. Examples -------- >>> visible_spectrum_plot() # doctest: +SKIP """ cmfs = get_cmfs(cmfs) cmfs = cmfs.clone().align(DEFAULT_SPECTRAL_SHAPE) wavelengths = cmfs.shape.range() colours = XYZ_to_sRGB( 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 = DEFAULT_PLOTTING_ENCODING_CCTF(normalise_maximum(colours)) settings = { 'title': 'The Visible Spectrum - {0}'.format(cmfs.title), 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': False, 'x_tighten': True, 'y_tighten': True, 'y_ticker': False } settings.update(kwargs) return colour_parameters_plot([ ColourParameter(x=x[0], RGB=x[1]) for x in tuple(zip(wavelengths, colours)) ], **settings)
def plot_blackbody_spectral_radiance( temperature=3500, cmfs='CIE 1931 2 Degree Standard Observer', blackbody='VY Canis Major', **kwargs): """ Plots given blackbody spectral radiance. Parameters ---------- temperature : numeric, optional Blackbody temperature. cmfs : unicode, optional Standard observer colour matching functions. blackbody : unicode, optional Blackbody name. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.plot_single_sd`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_blackbody_spectral_radiance(3500, blackbody='VY Canis Major') ... # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Blackbody_Spectral_Radiance.png :align: center :alt: plot_blackbody_spectral_radiance """ figure = plt.figure() figure.subplots_adjust(hspace=COLOUR_STYLE_CONSTANTS.geometry.short / 2) cmfs = first_item(filter_cmfs(cmfs).values()) sd = sd_blackbody(temperature, cmfs.shape) axes = figure.add_subplot(211) settings = { 'axes': axes, 'title': '{0} - Spectral Radiance'.format(blackbody), 'y_label': 'W / (sr m$^2$) / m', } settings.update(kwargs) settings['standalone'] = False plot_single_sd(sd, cmfs.name, **settings) axes = figure.add_subplot(212) with domain_range_scale('1'): XYZ = sd_to_XYZ(sd, cmfs) RGB = normalise_maximum(XYZ_to_plotting_colourspace(XYZ)) settings = { 'axes': axes, 'aspect': None, 'title': '{0} - Colour'.format(blackbody), 'x_label': '{0}K'.format(temperature), 'y_label': '', 'x_ticker': False, 'y_ticker': False, } settings.update(kwargs) settings['standalone'] = False figure, axes = plot_single_colour_swatch( ColourSwatch(name='', RGB=RGB), **settings) settings = {'axes': axes, 'standalone': True} 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 blackbody_colours_plot(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:`boundaries`, :func:`canvas`, :func:`decorate`, :func:`display`}, Please refer to the documentation of the previously listed definitions. y0_plot : bool, optional {:func:`colour_parameters_plot`}, Whether to plot *y0* line. y1_plot : bool, optional {:func:`colour_parameters_plot`}, Whether to plot *y1* line. Returns ------- Figure Current figure or None. Examples -------- >>> blackbody_colours_plot() # doctest: +SKIP """ cmfs = get_cmfs(cmfs) colours = [] temperatures = [] for temperature in shape: spd = blackbody_spd(temperature, cmfs.shape) XYZ = spectral_to_XYZ(spd, cmfs) RGB = normalise_maximum(XYZ_to_sRGB(XYZ / 100)) colours.append(RGB) temperatures.append(temperature) settings = { 'title': 'Blackbody Colours', 'x_label': 'Temperature K', 'y_label': '', 'x_tighten': True, 'y_tighten': True, 'y_ticker': False } settings.update(kwargs) return colour_parameters_plot([ ColourParameter(x=x[0], RGB=x[1]) for x in tuple(zip(temperatures, colours)) ], **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]) # 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 the_blue_sky_plot(cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots the blue sky. Parameters ---------- cmfs : unicode, optional Standard observer colour matching functions. Other Parameters ---------------- \**kwargs : dict, optional {:func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definition. Returns ------- Figure Current figure or None. Examples -------- >>> the_blue_sky_plot() # doctest: +SKIP """ canvas(**kwargs) cmfs, name = get_cmfs(cmfs), cmfs ASTM_G_173_spd = ASTM_G_173_ETR.copy() rayleigh_spd = rayleigh_scattering_spd() ASTM_G_173_spd.align(rayleigh_spd.shape) spd = rayleigh_spd * ASTM_G_173_spd matplotlib.pyplot.subplots_adjust(hspace=0.4) matplotlib.pyplot.figure(1) matplotlib.pyplot.subplot(211) settings = { 'title': 'The Blue Sky - Synthetic Spectral Power Distribution', 'y_label': u'W / m-2 / nm-1', 'standalone': False } settings.update(kwargs) single_spd_plot(spd, name, **settings) matplotlib.pyplot.subplot(212) settings = { 'title': 'The Blue Sky - Colour', 'x_label': ('The sky is blue because molecules in the atmosphere ' 'scatter shorter wavelengths more than longer ones.\n' 'The synthetic spectral power distribution is computed as ' 'follows: ' '(ASTM G-173 ETR * Standard Air Rayleigh Scattering).'), 'y_label': '', 'aspect': None, 'standalone': False } blue_sky_color = XYZ_to_sRGB(spectral_to_XYZ(spd)) single_colour_swatch_plot( ColourSwatch('', normalise_maximum(blue_sky_color)), **settings) settings = {'standalone': True} settings.update(kwargs) return render(**settings)
def single_spd_plot(spd, cmfs='CIE 1931 2 Degree Standard Observer', out_of_gamut_clipping=True, **kwargs): """ Plots given spectral power distribution. Parameters ---------- spd : SpectralPowerDistribution Spectral power 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. cmfs : unicode Standard observer colour matching functions used for spectrum creation. Other Parameters ---------------- \**kwargs : dict, optional {:func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definition. Returns ------- Figure Current figure or None. References ---------- - :cite:`Spiker2015a` Examples -------- >>> from colour import SpectralPowerDistribution >>> data = { ... 500: 0.0651, ... 520: 0.0705, ... 540: 0.0772, ... 560: 0.0870, ... 580: 0.1128, ... 600: 0.1360 ... } >>> spd = SpectralPowerDistribution(data, name='Custom') >>> single_spd_plot(spd) # doctest: +SKIP """ axes = canvas(**kwargs).gca() cmfs = get_cmfs(cmfs) spd = spd.copy() spd.interpolator = LinearInterpolator wavelengths = cmfs.wavelengths[np.logical_and( cmfs.wavelengths >= max(min(cmfs.wavelengths), min(spd.wavelengths)), cmfs.wavelengths <= min(max(cmfs.wavelengths), max(spd.wavelengths)), )] values = spd[wavelengths] colours = XYZ_to_sRGB( 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 = DEFAULT_PLOTTING_ENCODING_CCTF(normalise_maximum(colours)) x_min, x_max = min(wavelengths), max(wavelengths) y_min, y_max = 0, max(values) polygon = Polygon(np.vstack([ (x_min, 0), tstack((wavelengths, values)), (x_max, 0), ]), facecolor='none', edgecolor='none') axes.add_patch(polygon) axes.bar(x=wavelengths, height=max(values), width=1, color=colours, align='edge', clip_path=polygon) axes.plot(wavelengths, values, color='black', linewidth=1) settings = { 'title': '{0} - {1}'.format(spd.strict_name, cmfs.strict_name), 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': 'Spectral Power Distribution', 'limits': (x_min, x_max, y_min, y_max), 'x_tighten': True, 'y_tighten': True } settings.update(kwargs) return render(**settings)
def multi_spd_plot(spds, cmfs='CIE 1931 2 Degree Standard Observer', use_spds_colours=False, normalise_spds_colours=False, **kwargs): """ Plots given spectral power distributions. Parameters ---------- spds : list Spectral power distributions to plot. cmfs : unicode, optional Standard observer colour matching functions used for spectrum creation. use_spds_colours : bool, optional Whether to use spectral power distributions colours. normalise_spds_colours : bool Whether to normalise spectral power distributions colours. Other Parameters ---------------- \**kwargs : dict, optional {:func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definition. Returns ------- Figure Current figure or None. Examples -------- >>> from colour import SpectralPowerDistribution >>> 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 = SpectralPowerDistribution(data_1, name='Custom 1') >>> spd2 = SpectralPowerDistribution(data_2, name='Custom 2') >>> multi_spd_plot([spd1, spd2]) # doctest: +SKIP """ canvas(**kwargs) cmfs = get_cmfs(cmfs) if use_spds_colours: illuminant = ILLUMINANTS_RELATIVE_SPDS['D65'] x_limit_min, x_limit_max, y_limit_min, y_limit_max = [], [], [], [] for spd in spds: wavelengths, values = spd.wavelengths, spd.values shape = spd.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_spds_colours: XYZ = spectral_to_XYZ(spd, cmfs, illuminant) / 100 if normalise_spds_colours: XYZ = normalise_maximum(XYZ, clip=False) RGB = np.clip(XYZ_to_sRGB(XYZ), 0, 1) pylab.plot(wavelengths, values, color=RGB, label=spd.strict_name, linewidth=1) else: pylab.plot(wavelengths, values, label=spd.strict_name, linewidth=1) settings = { 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': 'Spectral Power Distribution', 'x_tighten': True, 'y_tighten': True, 'legend': True, 'legend_location': 'upper left', 'limits': (min(x_limit_min), max(x_limit_max), min(y_limit_min), max(y_limit_max)) } settings.update(kwargs) return render(**settings)
def plot_simple_rainbow(ax=None, wavelength=None, flux=None, cmfs="CIE 1931 2 Degree Standard Observer", **kwargs): """ Plot a simple horizontal rainbow, based off colour's plot_single_sd. Parameters ---------- ax : matplotlib.axes._subplots.AxesSubplot The ax into which the rainbow should be drawn. cmfs : string The color matching function(s?) to use. Returns ------- ax : matplotlib.axes._subplots.AxesSubplot The ax into which the rainbow was drawn. """ if ax is None: ax = plt.gca() # pull out the CMFss cmfs = first_item(filter_cmfs(cmfs).values()) if wavelength is None: # create a grid of wavelengths (at which CMFss are useful) wavelength = cmfs.wavelengths ok = (wavelength >= np.min(cmfs.wavelengths)) & (wavelength <= np.max( cmfs.wavelengths)) # create colors at theose wavelengths colours = XYZ_to_plotting_colourspace( wavelength_to_XYZ(wavelength[ok], cmfs), CCS_ILLUMINANTS["CIE 1931 2 Degree Standard Observer"]["E"], ) # normalize the colors to their maximum? colours = CONSTANTS_COLOUR_STYLE.colour.colourspace.cctf_encoding( normalise_maximum(colours)) # create y values that will be plotted (these could be spectrum) if flux is None: flux = np.ones_like(wavelength) x_min, x_max = min(wavelength), max(wavelength) y_min, y_max = 0, max(flux) + max(flux) * 0.05 # create a polygon to define the top of the bars? polygon = Polygon( np.vstack([ (x_min, 0), tstack([wavelength[ok], flux[ok]]), (x_max, 0), ]), facecolor="none", edgecolor="none", ) ax.add_patch(polygon) # draw bars, with the colors at each vertical stripe padding = 0.2 ax.bar( x=wavelength[ok] - padding / 2, height=max(flux[ok]), width=1 + padding, color=colours, align="edge", ) return ax
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. cmfs : unicode or LMS_ConeFundamentals or \ RGB_ColourMatchingFunctions or XYZ_ColourMatchingFunctions, optional Standard observer colour matching functions used for computing the spectrum domain and colours. ``cmfs`` can be of any type or form supported by the :func:`colour.plotting.filter_cmfs` definition. 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. 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>, <...AxesSubplot...>) .. 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] RGB = XYZ_to_plotting_colourspace( wavelength_to_XYZ(wavelengths, cmfs), CCS_ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['E'], apply_cctf_encoding=False) if not out_of_gamut_clipping: RGB += np.abs(np.min(RGB)) RGB = normalise_maximum(RGB) if modulate_colours_with_sd_amplitude: RGB *= (values / np.max(values))[..., np.newaxis] RGB = CONSTANTS_COLOUR_STYLE.colour.colourspace.cctf_encoding(RGB) if equalize_sd_amplitude: values = 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=RGB, align='edge', clip_path=polygon) axes.plot(wavelengths, values, color=CONSTANTS_COLOUR_STYLE.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 single_spd_plot(spd, cmfs='CIE 1931 2 Degree Standard Observer', out_of_gamut_clipping=True, **kwargs): """ Plots given spectral power distribution. Parameters ---------- spd : SpectralPowerDistribution Spectral power distribution to plot. out_of_gamut_clipping : bool, optional Out of gamut colours will be clipped if *True* otherwise, the colours will be offset by the absolute minimal colour leading to a rendering on gray background, less saturated and smoother. [1]_ cmfs : unicode Standard observer colour matching functions used for spectrum creation. \**kwargs : dict, optional Keywords arguments. Returns ------- Figure Current figure or None. Examples -------- >>> from colour import SpectralPowerDistribution >>> data = {400: 0.0641, 420: 0.0645, 440: 0.0562} >>> spd = SpectralPowerDistribution('Custom', data) >>> single_spd_plot(spd) # doctest: +SKIP """ cmfs = get_cmfs(cmfs) shape = cmfs.shape spd = spd.clone().interpolate(shape, 'Linear') wavelengths = spd.wavelengths values = spd.values y1 = values colours = XYZ_to_sRGB( 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 = DEFAULT_PLOTTING_ENCODING_CCTF(normalise_maximum(colours)) settings = { 'title': '{0} - {1}'.format(spd.title, cmfs.title), 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': 'Spectral Power Distribution', 'x_tighten': True} settings.update(kwargs) return colour_parameters_plot( [ColourParameter(x=x[0], y1=x[1], RGB=x[2]) for x in tuple(zip(wavelengths, y1, colours))], **settings)
def single_spd_plot(spd, cmfs='CIE 1931 2 Degree Standard Observer', out_of_gamut_clipping=True, **kwargs): """ Plots given spectral power distribution. Parameters ---------- spd : SpectralPowerDistribution Spectral power 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. [1]_ cmfs : unicode Standard observer colour matching functions used for spectrum creation. Other Parameters ---------------- \**kwargs : dict, optional {:func:`boundaries`, :func:`canvas`, :func:`decorate`, :func:`display`}, Please refer to the documentation of the previously listed definitions. y0_plot : bool, optional {:func:`colour_parameters_plot`}, Whether to plot *y0* line. y1_plot : bool, optional {:func:`colour_parameters_plot`}, Whether to plot *y1* line. Returns ------- Figure Current figure or None. Examples -------- >>> from colour import SpectralPowerDistribution >>> data = {400: 0.0641, 420: 0.0645, 440: 0.0562} >>> spd = SpectralPowerDistribution('Custom', data) >>> single_spd_plot(spd) # doctest: +SKIP """ cmfs = get_cmfs(cmfs) shape = cmfs.shape spd = spd.clone().interpolate(shape, 'Linear') wavelengths = spd.wavelengths values = spd.values y1 = values colours = XYZ_to_sRGB( 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 = DEFAULT_PLOTTING_ENCODING_CCTF(normalise_maximum(colours)) settings = { 'title': '{0} - {1}'.format(spd.title, cmfs.title), 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': 'Spectral Power Distribution', 'x_tighten': True, 'y_tighten': True } settings.update(kwargs) return colour_parameters_plot([ ColourParameter(x=x[0], y1=x[1], RGB=x[2]) for x in tuple(zip(wavelengths, y1, colours)) ], **settings)
def blackbody_spectral_radiance_plot( temperature=3500, cmfs='CIE 1931 2 Degree Standard Observer', blackbody='VY Canis Major', **kwargs): """ Plots given blackbody spectral radiance. Parameters ---------- temperature : numeric, optional Blackbody temperature. cmfs : unicode, optional Standard observer colour matching functions. blackbody : unicode, optional Blackbody name. \**kwargs : dict, optional Keywords arguments. Returns ------- Figure Current figure or None. Examples -------- >>> blackbody_spectral_radiance_plot() # doctest: +SKIP """ canvas(**kwargs) cmfs = get_cmfs(cmfs) matplotlib.pyplot.subplots_adjust(hspace=0.4) spd = blackbody_spd(temperature, cmfs.shape) matplotlib.pyplot.figure(1) matplotlib.pyplot.subplot(211) settings = { 'title': '{0} - Spectral Radiance'.format(blackbody), 'y_label': 'W / (sr m$^2$) / m', 'standalone': False} settings.update(kwargs) single_spd_plot(spd, cmfs.name, **settings) XYZ = spectral_to_XYZ(spd, cmfs) RGB = normalise_maximum(XYZ_to_sRGB(XYZ / 100)) matplotlib.pyplot.subplot(212) settings = {'title': '{0} - Colour'.format(blackbody), 'x_label': '{0}K'.format(temperature), 'y_label': '', 'aspect': None, 'standalone': False} single_colour_plot(ColourParameter(name='', RGB=RGB), **settings) settings = { 'standalone': True} settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**settings)
def plot_blackbody_spectral_radiance( temperature=3500, cmfs='CIE 1931 2 Degree Standard Observer', blackbody='VY Canis Major', **kwargs): """ Plots given blackbody spectral radiance. Parameters ---------- temperature : numeric, optional Blackbody temperature. cmfs : unicode, optional Standard observer colour matching functions. blackbody : unicode, optional Blackbody name. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.plot_single_sd`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_blackbody_spectral_radiance(3500, blackbody='VY Canis Major') ... # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Blackbody_Spectral_Radiance.png :align: center :alt: plot_blackbody_spectral_radiance """ figure = plt.figure() figure.subplots_adjust(hspace=COLOUR_STYLE_CONSTANTS.geometry.short / 2) cmfs = first_item(filter_cmfs(cmfs).values()) sd = sd_blackbody(temperature, cmfs.shape) axes = figure.add_subplot(211) settings = { 'axes': axes, 'title': '{0} - Spectral Radiance'.format(blackbody), 'y_label': 'W / (sr m$^2$) / m', } settings.update(kwargs) settings['standalone'] = False plot_single_sd(sd, cmfs.name, **settings) axes = figure.add_subplot(212) with domain_range_scale('1'): XYZ = sd_to_XYZ(sd, cmfs) RGB = normalise_maximum(XYZ_to_plotting_colourspace(XYZ)) settings = { 'axes': axes, 'aspect': None, 'title': '{0} - Colour'.format(blackbody), 'x_label': '{0}K'.format(temperature), 'y_label': '', 'x_ticker': False, 'y_ticker': False, } settings.update(kwargs) settings['standalone'] = False figure, axes = plot_single_colour_swatch(ColourSwatch(name='', RGB=RGB), **settings) settings = {'axes': axes, 'standalone': True} 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_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_the_blue_sky(cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots the blue sky. Parameters ---------- cmfs : unicode, optional Standard observer colour matching functions. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.plot_single_sd`, :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_the_blue_sky() # doctest: +ELLIPSIS (<Figure size ... with 2 Axes>, \ <matplotlib.axes._subplots.AxesSubplot object at 0x...>) .. image:: ../_static/Plotting_Plot_The_Blue_Sky.png :align: center :alt: plot_the_blue_sky """ figure = plt.figure() figure.subplots_adjust(hspace=COLOUR_STYLE_CONSTANTS.geometry.short / 2) cmfs = first_item(filter_cmfs(cmfs).values()) ASTMG173_sd = ASTMG173_ETR.copy() rayleigh_sd = sd_rayleigh_scattering() ASTMG173_sd.align(rayleigh_sd.shape) sd = rayleigh_sd * ASTMG173_sd axes = figure.add_subplot(211) settings = { 'axes': axes, 'title': 'The Blue Sky - Synthetic Spectral Distribution', 'y_label': u'W / m-2 / nm-1', } settings.update(kwargs) settings['standalone'] = False plot_single_sd(sd, cmfs, **settings) axes = figure.add_subplot(212) x_label = ('The sky is blue because molecules in the atmosphere ' 'scatter shorter wavelengths more than longer ones.\n' 'The synthetic spectral distribution is computed as ' 'follows: ' '(ASTM G-173 ETR * Standard Air Rayleigh Scattering).') settings = { 'axes': axes, 'aspect': None, 'title': 'The Blue Sky - Colour', 'x_label': x_label, 'y_label': '', 'x_ticker': False, 'y_ticker': False, } settings.update(kwargs) settings['standalone'] = False blue_sky_color = XYZ_to_plotting_colourspace(sd_to_XYZ(sd)) figure, axes = plot_single_colour_swatch( ColourSwatch('', normalise_maximum(blue_sky_color)), **settings) settings = {'axes': axes, 'standalone': True} settings.update(kwargs) return render(**settings)
def CIE_1931_chromaticity_diagram_colours_plot( surface=1, samples=4096, cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots the *CIE 1931 Chromaticity Diagram* colours. Parameters ---------- surface : numeric, optional Generated markers surface. samples : numeric, optional Samples count on one axis. cmfs : unicode, optional Standard observer colour matching functions used for diagram bounds. \**kwargs : dict, optional Keywords arguments. Returns ------- Figure Current figure or None. Examples -------- >>> CIE_1931_chromaticity_diagram_colours_plot() # doctest: +SKIP """ settings = {'figure_size': (64, 64)} settings.update(kwargs) canvas(**settings) cmfs = get_cmfs(cmfs) illuminant = DEFAULT_PLOTTING_ILLUMINANT triangulation = Delaunay(XYZ_to_xy(cmfs.values, illuminant), qhull_options='QJ') xx, yy = np.meshgrid(np.linspace(0, 1, samples), np.linspace(0, 1, samples)) xy = tstack((xx, yy)) xy = xy[triangulation.find_simplex(xy) > 0] XYZ = xy_to_XYZ(xy) RGB = normalise_maximum(XYZ_to_sRGB(XYZ, illuminant), axis=-1) x_dot, y_dot = tsplit(xy) pylab.scatter(x_dot, y_dot, color=RGB, s=surface) settings.update({ 'x_ticker': False, 'y_ticker': False, 'bounding_box': (0, 1, 0, 1)}) settings.update(kwargs) ax = matplotlib.pyplot.gca() matplotlib.pyplot.setp(ax, frame_on=False) boundaries(**settings) decorate(**settings) return display(**settings)
def blackbody_spectral_radiance_plot( temperature=3500, cmfs='CIE 1931 2 Degree Standard Observer', blackbody='VY Canis Major', **kwargs): """ Plots given blackbody spectral radiance. Parameters ---------- temperature : numeric, optional Blackbody temperature. cmfs : unicode, optional Standard observer colour matching functions. blackbody : unicode, optional Blackbody name. Other Parameters ---------------- \**kwargs : dict, optional {:func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definition. Returns ------- Figure Current figure or None. Examples -------- >>> blackbody_spectral_radiance_plot() # doctest: +SKIP """ canvas(**kwargs) cmfs = get_cmfs(cmfs) matplotlib.pyplot.subplots_adjust(hspace=0.4) spd = blackbody_spd(temperature, cmfs.shape) matplotlib.pyplot.figure(1) matplotlib.pyplot.subplot(211) settings = { 'title': '{0} - Spectral Radiance'.format(blackbody), 'y_label': 'W / (sr m$^2$) / m', 'standalone': False } settings.update(kwargs) single_spd_plot(spd, cmfs.name, **settings) XYZ = spectral_to_XYZ(spd, cmfs) RGB = normalise_maximum(XYZ_to_sRGB(XYZ / 100)) matplotlib.pyplot.subplot(212) settings = { 'title': '{0} - Colour'.format(blackbody), 'x_label': '{0}K'.format(temperature), 'y_label': '', 'aspect': None, 'standalone': False } single_colour_swatch_plot(ColourSwatch(name='', RGB=RGB), **settings) settings = {'standalone': True} settings.update(kwargs) return render(**settings)
def multi_spd_plot(spds, cmfs='CIE 1931 2 Degree Standard Observer', use_spds_colours=False, normalise_spds_colours=False, **kwargs): """ Plots given spectral power distributions. Parameters ---------- spds : list Spectral power distributions to plot. cmfs : unicode, optional Standard observer colour matching functions used for spectrum creation. use_spds_colours : bool, optional Use spectral power distributions colours. normalise_spds_colours : bool Should spectral power distributions colours normalised. \**kwargs : dict, optional Keywords arguments. Returns ------- Figure Current figure or None. Examples -------- >>> from colour import SpectralPowerDistribution >>> data1 = {400: 0.0641, 420: 0.0645, 440: 0.0562} >>> data2 = {400: 0.134, 420: 0.789, 440: 1.289} >>> spd1 = SpectralPowerDistribution('Custom1', data1) >>> spd2 = SpectralPowerDistribution('Custom2', data2) >>> multi_spd_plot([spd1, spd2]) # doctest: +SKIP """ canvas(**kwargs) cmfs = get_cmfs(cmfs) if use_spds_colours: illuminant = ILLUMINANTS_RELATIVE_SPDS.get('D65') x_limit_min, x_limit_max, y_limit_min, y_limit_max = [], [], [], [] for spd in spds: wavelengths, values = tuple(zip(*spd.items)) shape = spd.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_spds_colours: XYZ = spectral_to_XYZ(spd, cmfs, illuminant) / 100 if normalise_spds_colours: XYZ = normalise_maximum(XYZ, clip=False) RGB = np.clip(XYZ_to_sRGB(XYZ), 0, 1) pylab.plot(wavelengths, values, color=RGB, label=spd.title, linewidth=2) else: pylab.plot(wavelengths, values, label=spd.title, linewidth=2) settings = { 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': 'Spectral Power Distribution', 'x_tighten': True, 'legend': True, 'legend_location': 'upper left', 'limits': (min(x_limit_min), max(x_limit_max), min(y_limit_min), max(y_limit_max))} settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**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]) # 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 blackbody_colours_plot(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.render`}, Please refer to the documentation of the previously listed definition. Returns ------- Figure Current figure or None. Examples -------- >>> blackbody_colours_plot() # doctest: +SKIP """ axes = canvas(**kwargs).gca() cmfs = get_cmfs(cmfs) colours = [] temperatures = [] with suppress_warnings(): for temperature in shape: spd = blackbody_spd(temperature, cmfs.shape) XYZ = spectral_to_XYZ(spd, cmfs) RGB = normalise_maximum(XYZ_to_sRGB(XYZ / 100)) colours.append(RGB) temperatures.append(temperature) x_min, x_max = min(temperatures), max(temperatures) y_min, y_max = 0, 1 axes.bar(x=temperatures, height=1, width=shape.interval, color=colours, align='edge') settings = { 'title': 'Blackbody Colours', 'x_label': 'Temperature K', 'y_label': None, 'limits': (x_min, x_max, y_min, y_max), 'x_tighten': True, 'y_tighten': True, 'y_ticker': False } settings.update(kwargs) return render(**settings)
def plot_the_blue_sky(cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots the blue sky. Parameters ---------- cmfs : unicode, optional Standard observer colour matching functions. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.plot_single_sd`, :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_the_blue_sky() # doctest: +SKIP .. image:: ../_static/Plotting_Plot_The_Blue_Sky.png :align: center :alt: plot_the_blue_sky """ figure = plt.figure() figure.subplots_adjust(hspace=COLOUR_STYLE_CONSTANTS.geometry.short / 2) cmfs = first_item(filter_cmfs(cmfs).values()) ASTM_G_173_sd = ASTM_G_173_ETR.copy() rayleigh_sd = sd_rayleigh_scattering() ASTM_G_173_sd.align(rayleigh_sd.shape) sd = rayleigh_sd * ASTM_G_173_sd axes = figure.add_subplot(211) settings = { 'axes': axes, 'title': 'The Blue Sky - Synthetic Spectral Distribution', 'y_label': u'W / m-2 / nm-1', } settings.update(kwargs) settings['standalone'] = False plot_single_sd(sd, cmfs, **settings) axes = figure.add_subplot(212) x_label = ('The sky is blue because molecules in the atmosphere ' 'scatter shorter wavelengths more than longer ones.\n' 'The synthetic spectral distribution is computed as ' 'follows: ' '(ASTM G-173 ETR * Standard Air Rayleigh Scattering).') settings = { 'axes': axes, 'aspect': None, 'title': 'The Blue Sky - Colour', 'x_label': x_label, 'y_label': '', 'x_ticker': False, 'y_ticker': False, } settings.update(kwargs) settings['standalone'] = False blue_sky_color = XYZ_to_plotting_colourspace(sd_to_XYZ(sd)) figure, axes = plot_single_colour_swatch( ColourSwatch('', normalise_maximum(blue_sky_color)), **settings) settings = {'axes': axes, 'standalone': True} 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 spectral_locus_visual(reference_colourspace='CIE xyY', cmfs='CIE 1931 2 Degree Standard Observer', width=2.0, uniform_colour=None, uniform_opacity=1.0, method='gl', parent=None): """ Returns a :class:`vispy.scene.visuals.Line` class instance representing the spectral locus. Parameters ---------- reference_colourspace : unicode, optional **{'CIE XYZ', 'CIE xyY', 'CIE Lab', 'CIE Luv', 'CIE UCS', 'CIE UVW', 'IPT', 'Hunter Lab', 'Hunter Rdab'}**, Reference colourspace to use for colour conversions / transformations. cmfs : unicode, optional Standard observer colour matching functions used to draw the spectral locus. width : numeric, optional Line width. uniform_colour : array_like, optional Uniform symbol colour. uniform_opacity : numeric, optional Uniform symbol opacity. method : unicode, optional **{'gl', 'agg'}**, Line drawing method. parent : Node, optional Parent of the spectral locus visual in the `SceneGraph`. Returns ------- Line Spectral locus visual. """ cmfs = first_item(filter_cmfs(cmfs).values()) XYZ = cmfs.values XYZ = np.vstack([XYZ, XYZ[0, ...]]) illuminant = DEFAULT_PLOTTING_ILLUMINANT points = common_colourspace_model_axis_reorder( XYZ_to_colourspace_model(XYZ, illuminant, reference_colourspace), reference_colourspace) points[np.isnan(points)] = 0 if uniform_colour is None: RGB = normalise_maximum(XYZ_to_sRGB(XYZ, illuminant), axis=-1) RGB = np.hstack([RGB, np.full((RGB.shape[0], 1), uniform_opacity, DEFAULT_FLOAT_DTYPE)]) else: RGB = ColorArray(uniform_colour, alpha=uniform_opacity).rgba line = Line( points, np.clip(RGB, 0, 1), width=width, method=method, parent=parent) return line
def spectral_locus_visual(reference_colourspace='CIE xyY', cmfs='CIE 1931 2 Degree Standard Observer', width=2.0, uniform_colour=None, uniform_opacity=1.0, method='gl', parent=None): """ Returns a :class:`vispy.scene.visuals.Line` class instance representing the spectral locus. Parameters ---------- reference_colourspace : unicode, optional **{'CIE XYZ', 'CIE xyY', 'CIE Lab', 'CIE Luv', 'CIE UCS', 'CIE UVW', 'IPT', 'Hunter Lab', 'Hunter Rdab'}**, Reference colourspace to use for colour conversions / transformations. cmfs : unicode, optional Standard observer colour matching functions used to draw the spectral locus. width : numeric, optional Line width. uniform_colour : array_like, optional Uniform symbol colour. uniform_opacity : numeric, optional Uniform symbol opacity. method : unicode, optional **{'gl', 'agg'}**, Line drawing method. parent : Node, optional Parent of the spectral locus visual in the `SceneGraph`. Returns ------- Line Spectral locus visual. """ cmfs = first_item(filter_cmfs(cmfs).values()) XYZ = cmfs.values XYZ = np.vstack([XYZ, XYZ[0, ...]]) illuminant = DEFAULT_PLOTTING_ILLUMINANT points = common_colourspace_model_axis_reorder( XYZ_to_colourspace_model(XYZ, illuminant, reference_colourspace), reference_colourspace) points[np.isnan(points)] = 0 if uniform_colour is None: RGB = normalise_maximum(XYZ_to_sRGB(XYZ, illuminant), axis=-1) RGB = np.hstack([ RGB, np.full((RGB.shape[0], 1), uniform_opacity, DEFAULT_FLOAT_DTYPE) ]) else: RGB = ColorArray(uniform_colour, alpha=uniform_opacity).rgba line = Line(points, np.clip(RGB, 0, 1), width=width, method=method, parent=parent) return line
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_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_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 multi_spd_plot(spds, cmfs='CIE 1931 2 Degree Standard Observer', use_spds_colours=False, normalise_spds_colours=False, **kwargs): """ Plots given spectral power distributions. Parameters ---------- spds : list Spectral power distributions to plot. cmfs : unicode, optional Standard observer colour matching functions used for spectrum creation. use_spds_colours : bool, optional Whether to use spectral power distributions colours. normalise_spds_colours : bool Whether to normalise spectral power distributions colours. Other Parameters ---------------- \**kwargs : dict, optional {:func:`boundaries`, :func:`canvas`, :func:`decorate`, :func:`display`}, Please refer to the documentation of the previously listed definitions. Returns ------- Figure Current figure or None. Examples -------- >>> from colour import SpectralPowerDistribution >>> data1 = {400: 0.0641, 420: 0.0645, 440: 0.0562} >>> data2 = {400: 0.134, 420: 0.789, 440: 1.289} >>> spd1 = SpectralPowerDistribution('Custom1', data1) >>> spd2 = SpectralPowerDistribution('Custom2', data2) >>> multi_spd_plot([spd1, spd2]) # doctest: +SKIP """ canvas(**kwargs) cmfs = get_cmfs(cmfs) if use_spds_colours: illuminant = ILLUMINANTS_RELATIVE_SPDS['D65'] x_limit_min, x_limit_max, y_limit_min, y_limit_max = [], [], [], [] for spd in spds: wavelengths, values = tuple(zip(*spd.items)) shape = spd.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_spds_colours: XYZ = spectral_to_XYZ(spd, cmfs, illuminant) / 100 if normalise_spds_colours: XYZ = normalise_maximum(XYZ, clip=False) RGB = np.clip(XYZ_to_sRGB(XYZ), 0, 1) pylab.plot(wavelengths, values, color=RGB, label=spd.title, linewidth=2) else: pylab.plot(wavelengths, values, label=spd.title, linewidth=2) settings = { 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': 'Spectral Power Distribution', 'x_tighten': True, 'y_tighten': True, 'legend': True, 'legend_location': 'upper left', 'limits': (min(x_limit_min), max(x_limit_max), min(y_limit_min), max(y_limit_max)) } settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**settings)