def correlate_colourspace(self, value): """ Setter for **self._correlate_colourspace** private attribute. Parameters ---------- value : unicode Attribute value. """ if value is not None: assert is_string(value), (('"{0}" attribute: "{1}" is not a ' '"string" like object!').format( 'correlate_colourspace', value)) assert value in RGB_COLOURSPACES, ( '"{0}" colourspace not found in factory RGB colourspaces: ' '"{1}".').format(value, ', '.join( sorted(RGB_COLOURSPACES.keys()))) self._correlate_colourspace = value if self._initialised: self._detach_visuals() self._create_correlate_colourspace_visual( self._visuals_style_presets[ 'correlate_colourspace_visual'].current_item()) self._attach_visuals() self._label_text()
def reference_colourspace(self, value): """ Setter for **self._reference_colourspace** private attribute. Parameters ---------- value : unicode Attribute value. """ if value is not None: assert is_string(value), (('"{0}" attribute: "{1}" is not a ' '"string" like object!').format( 'reference_colourspace', value)) assert value in REFERENCE_COLOURSPACES, ( '"{0}" reference colourspace not found in factory reference ' 'colourspaces: "{1}".').format(value, ', '.join( sorted(REFERENCE_COLOURSPACES.keys()))) self._reference_colourspace = value if self._initialised: self._store_visuals_visibility() self._detach_visuals() self._create_visuals() self._attach_visuals() self._restore_visuals_visibility() self._create_camera() self._label_text()
def diagram(self, value): """ Setter for **self._diagram** private attribute. Parameters ---------- value : unicode Attribute value. """ if value is not None: assert is_string(value), (('"{0}" attribute: "{1}" is not a ' '"string" like object!').format( 'input_colourspace', value)) assert value in CHROMATICITY_DIAGRAMS, ( '"{0}" diagram not found in factory chromaticity diagrams: ' '"{1}".').format(value, ', '.join( sorted(CHROMATICITY_DIAGRAMS.keys()))) if self._initialised: self._store_visuals_visibility() self._detach_visuals() self._diagram = value if self._initialised: self._create_visuals() self._attach_visuals() self._restore_visuals_visibility() self._label_text()
def parse_array(a, separator=' ', dtype=DEFAULT_FLOAT_DTYPE): """ Converts given string or array of strings to :class:`ndarray` class. Parameters ---------- a : unicode or array_like String or array of strings to convert. separator : unicode Separator to split the string with. dtype : object Type to use for conversion. Returns ------- ndarray Converted string or array of strings. Examples -------- >>> parse_array('-0.25 0.5 0.75') array([-0.25, 0.5 , 0.75]) >>> parse_array(['-0.25', '0.5', '0.75']) array([-0.25, 0.5 , 0.75]) """ if is_string(a): a = a.split(separator) return as_array([dtype(token) for token in a], dtype)
def name(self, value): """ Setter for **self.name** property. """ if value is not None: assert is_string(value), ( ('"{0}" attribute: "{1}" type is not "str" or "unicode"!' ).format('name', value)) self._name = value
def whitepoint_name(self, value): """ Setter for the **self.whitepoint_name** property. """ if value is not None: assert is_string(value), ( '"{0}" attribute: "{1}" is not a "string" like object!'.format( 'whitepoint_name', value)) self._whitepoint_name = value
def method(self, value): """ Setter for the **self.method** property. """ if value is not None: assert is_string(value), ( ('"{0}" attribute: "{1}" is not a "string" like object!' ).format('method', value)) value = value.lower() self._method = value
def reflection_geometry( self, value: Optional[Literal["di:8", "de:8", "8:di", "8:de", "d:d", "d:0", "45a:0", "45c:0", "0:45a", "45x:0", "0:45x", "other", ]], ): """Setter for the **self.reflection_geometry** property.""" if value is not None: attest( is_string(value), f'"reflection_geometry" property: "{value}" type is not "str"!', ) self._reflection_geometry = value
def catalog_number(self, value): """ Setter for **self._catalog_number** private attribute. Parameters ---------- value : unicode Attribute value. """ if value is not None: assert is_string(value), (('"{0}" attribute: "{1}" is not a ' '"string" like object!').format( 'catalog_number', value)) self._catalog_number = value
def illuminant(self, value): """ Setter for **self._illuminant** private attribute. Parameters ---------- value : unicode Attribute value. """ if value is not None: assert is_string(value), (('"{0}" attribute: "{1}" is not a ' '"string" like object!').format( 'illuminant', value)) self._illuminant = value
def name(self, value): """ Setter for **self._name** private attribute. Parameters ---------- value : unicode Attribute value. """ if value is not None: assert is_string(value), ( ('"{0}" attribute: "{1}" is not a "string" like object!' ).format('name', value)) self._name = value
def layout(self, value): """ Setter for **self._layout** private attribute. Parameters ---------- value : unicode Attribute value. """ if value is not None: assert is_string(value), (('"{0}" attribute: "{1}" is not a ' '"string" like object!').format( 'layout', value)) self._layout = value
def spectral_quantity( self, value: Optional[Literal["absorptance", "exitance", "flux", "intensity", "irradiance", "radiance", "reflectance", "relative", "transmittance", "R-Factor", "T-Factor", "other", ]], ): """Setter for the **self.spectral_quantity** property.""" if value is not None: attest( is_string(value), f'"spectral_quantity" property: "{value}" type is not "str"!', ) self._spectral_quantity = value
def document_creation_date(self, value): """ Setter for **self._document_creation_date** private attribute. Parameters ---------- value : unicode Attribute value. """ if value is not None: assert is_string(value), ( ('"{0}" attribute: "{1}" is not a ' '"string" like object!').format( 'document_creation_date', value)) self._document_creation_date = value
def transmission_geometry(self, value): """ Setter for **self._transmission_geometry** private attribute. Parameters ---------- value : unicode Attribute value. """ if value is not None: assert is_string(value), ( ('"{0}" attribute: "{1}" is not a ' '"string" like object!').format( 'transmission_geometry', value)) self._transmission_geometry = value
def method(self, value): """ Setter for **self._method** private attribute. Parameters ---------- value : unicode Attribute value. """ if value is not None: assert is_string(value), ( ('"{0}" attribute: "{1}" is not a ' '"string" like object!').format('method', value)) value = value.lower() self._method = value
def image_path(self, value): """ Setter for **self._image_path** private attribute. Parameters ---------- value : unicode Attribute value. """ if value is not None: assert is_string(value), (('"{0}" attribute: "{1}" is not a ' '"string" like object!').format( 'image_path', value)) assert os.path.exists(value), ( '"{0}" input image doesn\'t exists!'.format(value)) self._image_path = value
def correlate_colourspace(self, value): """ Setter for **self._correlate_colourspace** private attribute. Parameters ---------- value : unicode Attribute value. """ if value is not None: assert is_string(value), (('"{0}" attribute: "{1}" is not a ' '"string" like object!').format( 'correlate_colourspace', value)) assert value in RGB_COLOURSPACES, ( '"{0}" colourspace not found in factory RGB colourspaces: ' '"{1}".').format(value, ', '.join( sorted(RGB_COLOURSPACES.keys()))) self._correlate_colourspace = value
def input_oecf(self, value): """ Setter for **self._input_oecf** private attribute. Parameters ---------- value : unicode Attribute value. """ if value is not None: assert is_string(value), (('"{0}" attribute: "{1}" is not a ' '"string" like object!').format( 'input_oecf', value)) assert value in RGB_COLOURSPACES, ( '"{0}" OECF is not associated with any factory ' 'RGB colourspaces: "{1}".').format(value, ', '.join( sorted(RGB_COLOURSPACES.keys()))) self._input_oecf = value
def test_is_string(self): """ Tests :func:`colour.utilities.common.is_string` definition. """ self.assertTrue(is_string(str('Hello World!'))) self.assertTrue(is_string('Hello World!')) self.assertTrue(is_string(r'Hello World!')) self.assertFalse(is_string(1)) self.assertFalse(is_string([1])) self.assertFalse(is_string({1: None}))
def wavelength_to_XYZ(wavelength, cmfs=STANDARD_OBSERVERS_CMFS[ 'CIE 1931 2 Degree Standard Observer'], method=None): """ Converts given wavelength :math:`\lambda` to *CIE XYZ* tristimulus values using given colour matching functions. If the wavelength :math:`\lambda` is not available in the colour matching function, its value will be calculated using *CIE* recommendations: The method developed by *Sprague (1880)* should be used for interpolating functions having a uniformly spaced independent variable and a *Cubic Spline* method for non-uniformly spaced independent variable. Parameters ---------- wavelength : numeric or array_like Wavelength :math:`\lambda` in nm. cmfs : XYZ_ColourMatchingFunctions, optional Standard observer colour matching functions. method : unicode, optional {None, 'Cubic Spline', 'Linear', 'Pchip', 'Sprague'}, Enforce given interpolation method. Returns ------- ndarray *CIE XYZ* tristimulus values. Raises ------ RuntimeError If *Sprague (1880)* interpolation method is forced with a non-uniformly spaced independent variable. ValueError If the interpolation method is not defined or if wavelength :math:`\lambda` is not contained in the colour matching functions domain. Notes ----- - Output *CIE XYZ* tristimulus values are in range [0, 1]. - If *scipy* is not unavailable the *Cubic Spline* method will fallback to legacy *Linear* interpolation. - *Sprague (1880)* interpolator cannot be used for interpolating functions having a non-uniformly spaced independent variable. Warning ------- - If *scipy* is not unavailable the *Cubic Spline* method will fallback to legacy *Linear* interpolation. - *Cubic Spline* interpolator requires at least 3 wavelengths :math:`\lambda_n` for interpolation. - *Linear* interpolator requires at least 2 wavelengths :math:`\lambda_n` for interpolation. - *Pchip* interpolator requires at least 2 wavelengths :math:`\lambda_n` for interpolation. - *Sprague (1880)* interpolator requires at least 6 wavelengths :math:`\lambda_n` for interpolation. Examples -------- Uniform data is using *Sprague (1880)* interpolation by default: >>> from colour import CMFS >>> cmfs = CMFS['CIE 1931 2 Degree Standard Observer'] >>> wavelength_to_XYZ(480, cmfs) # doctest: +ELLIPSIS array([ 0.09564 , 0.13902 , 0.812950...]) >>> wavelength_to_XYZ(480.5, cmfs) # doctest: +ELLIPSIS array([ 0.0914287..., 0.1418350..., 0.7915726...]) Enforcing *Cubic Spline* interpolation: >>> wavelength_to_XYZ(480.5, cmfs, 'Cubic Spline') # doctest: +ELLIPSIS array([ 0.0914288..., 0.1418351..., 0.7915729...]) Enforcing *Linear* interpolation: >>> wavelength_to_XYZ(480.5, cmfs, 'Linear') # doctest: +ELLIPSIS array([ 0.0914697..., 0.1418482..., 0.7917337...]) Enforcing *Pchip* interpolation: >>> wavelength_to_XYZ(480.5, cmfs, 'Pchip') # doctest: +ELLIPSIS array([ 0.0914280..., 0.1418341..., 0.7915711...]) """ cmfs_shape = cmfs.shape if (np.min(wavelength) < cmfs_shape.start or np.max(wavelength) > cmfs_shape.end): raise ValueError( '"{0} nm" wavelength is not in "[{1}, {2}]" domain!'.format( wavelength, cmfs_shape.start, cmfs_shape.end)) if wavelength not in cmfs: wavelengths, values, = cmfs.wavelengths, cmfs.values if is_string(method): method = method.lower() is_uniform = cmfs.is_uniform() if method is None: if is_uniform: interpolator = SpragueInterpolator else: interpolator = CubicSplineInterpolator elif method == 'cubic spline': interpolator = CubicSplineInterpolator elif method == 'linear': interpolator = LinearInterpolator elif method == 'pchip': interpolator = PchipInterpolator elif method == 'sprague': if is_uniform: interpolator = SpragueInterpolator else: raise RuntimeError( ('"Sprague" interpolator can only be used for ' 'interpolating functions having a uniformly spaced ' 'independent variable!')) else: raise ValueError( 'Undefined "{0}" interpolator!'.format(method)) interpolators = [interpolator(wavelengths, values[..., i]) for i in range(values.shape[-1])] XYZ = np.dstack([i(np.ravel(wavelength)) for i in interpolators]) else: XYZ = cmfs[wavelength] XYZ = np.reshape(XYZ, np.asarray(wavelength).shape + (3,)) return XYZ
def filter_passthrough(mapping, filterers, anchors=True, allow_non_siblings=True, flags=re.IGNORECASE): """ Returns mapping objects matching given filterers while passing through class instances whose type is one of the mapping element types. Parameters ---------- mapping : dict_like Mapping to filter. filterers : unicode or object or array_like Filterer or object class instance (which is passed through directly if its type is one of the mapping element types) or list of filterers. anchors : bool, optional Whether to use Regex line anchors, i.e. *^* and *$* are added, surrounding the filterers patterns. allow_non_siblings : bool, optional Whether to allow non-siblings to be also passed through. flags : int, optional Regex flags. Returns ------- dict_like Filtered mapping. """ if is_string(filterers): filterers = [filterers] elif not isinstance(filterers, (list, tuple)): filterers = [filterers] string_filterers = [ filterer for filterer in filterers if is_string(filterer) ] object_filterers = [ filterer for filterer in filterers if is_sibling(filterer, mapping) ] if allow_non_siblings: non_siblings = [ filterer for filterer in filterers if filterer not in string_filterers and filterer not in object_filterers ] if non_siblings: runtime_warning( 'Non-sibling elements are passed-through: "{0}"'.format( non_siblings)) object_filterers.extend(non_siblings) filtered_mapping = filter_mapping(mapping, string_filterers, anchors, flags) for filterer in object_filterers: try: name = filterer.name except AttributeError: try: name = filterer.__name__ except AttributeError: name = str(id(filterer)) filtered_mapping[name] = filterer return filtered_mapping
def wavelength_to_XYZ(wavelength, cmfs=STANDARD_OBSERVERS_CMFS.get( 'CIE 1931 2 Degree Standard Observer'), method=None): """ Converts given wavelength :math:`\lambda` to *CIE XYZ* tristimulus values using given colour matching functions. If the wavelength :math:`\lambda` is not available in the colour matching function, its value will be calculated using *CIE* recommendations: The method developed by Sprague (1880) should be used for interpolating functions having a uniformly spaced independent variable and a *Cubic Spline* method for non-uniformly spaced independent variable. Parameters ---------- wavelength : numeric or array_like Wavelength :math:`\lambda` in nm. cmfs : XYZ_ColourMatchingFunctions, optional Standard observer colour matching functions. method : unicode, optional {None, 'Cubic Spline', 'Linear', 'Pchip', 'Sprague'}, Enforce given interpolation method. Returns ------- ndarray *CIE XYZ* tristimulus values. Raises ------ RuntimeError If Sprague (1880) interpolation method is forced with a non-uniformly spaced independent variable. ValueError If the interpolation method is not defined or if wavelength :math:`\lambda` is not contained in the colour matching functions domain. Notes ----- - Output *CIE XYZ* tristimulus values are in domain [0, 1]. - If *scipy* is not unavailable the *Cubic Spline* method will fallback to legacy *Linear* interpolation. - Sprague (1880) interpolator cannot be used for interpolating functions having a non-uniformly spaced independent variable. Warning ------- - If *scipy* is not unavailable the *Cubic Spline* method will fallback to legacy *Linear* interpolation. - *Cubic Spline* interpolator requires at least 3 wavelengths :math:`\lambda_n` for interpolation. - *Linear* interpolator requires at least 2 wavelengths :math:`\lambda_n` for interpolation. - *Pchip* interpolator requires at least 2 wavelengths :math:`\lambda_n` for interpolation. - Sprague (1880) interpolator requires at least 6 wavelengths :math:`\lambda_n` for interpolation. Examples -------- Uniform data is using Sprague (1880) interpolation by default: >>> from colour import CMFS >>> cmfs = CMFS.get('CIE 1931 2 Degree Standard Observer') >>> wavelength_to_XYZ(480, cmfs) # doctest: +ELLIPSIS array([ 0.09564 , 0.13902 , 0.812950...]) >>> wavelength_to_XYZ(480.5, cmfs) # doctest: +ELLIPSIS array([ 0.0914287..., 0.1418350..., 0.7915726...]) Enforcing *Cubic Spline* interpolation: >>> wavelength_to_XYZ(480.5, cmfs, 'Cubic Spline') # doctest: +ELLIPSIS array([ 0.0914288..., 0.1418351..., 0.7915729...]) Enforcing *Linear* interpolation: >>> wavelength_to_XYZ(480.5, cmfs, 'Linear') # doctest: +ELLIPSIS array([ 0.0914697..., 0.1418482..., 0.7917337...]) Enforcing *Pchip* interpolation: >>> wavelength_to_XYZ(480.5, cmfs, 'Pchip') # doctest: +ELLIPSIS array([ 0.0914280..., 0.1418341..., 0.7915711...]) """ cmfs_shape = cmfs.shape if (np.min(wavelength) < cmfs_shape.start or np.max(wavelength) > cmfs_shape.end): raise ValueError( '"{0} nm" wavelength is not in "[{1}, {2}]" domain!'.format( wavelength, cmfs_shape.start, cmfs_shape.end)) if wavelength not in cmfs: wavelengths, values, = cmfs.wavelengths, cmfs.values if is_string(method): method = method.lower() is_uniform = cmfs.is_uniform() if method is None: if is_uniform: interpolator = SpragueInterpolator else: interpolator = CubicSplineInterpolator elif method == 'cubic spline': interpolator = CubicSplineInterpolator elif method == 'linear': interpolator = LinearInterpolator elif method == 'pchip': interpolator = PchipInterpolator elif method == 'sprague': if is_uniform: interpolator = SpragueInterpolator else: raise RuntimeError( ('"Sprague" interpolator can only be used for ' 'interpolating functions having a uniformly spaced ' 'independent variable!')) else: raise ValueError( 'Undefined "{0}" interpolator!'.format(method)) interpolators = [interpolator(wavelengths, values[..., i]) for i in range(values.shape[-1])] XYZ = np.dstack([i(np.ravel(wavelength)) for i in interpolators]) else: XYZ = cmfs.get(wavelength) XYZ = np.reshape(XYZ, np.asarray(wavelength).shape + (3,)) return XYZ
def filter_passthrough( mapping: Mapping, filterers: Union[Any, str, Sequence[Union[Any, str]]], anchors: Boolean = True, allow_non_siblings: Boolean = True, flags: Union[Integer, RegexFlag] = re.IGNORECASE, ) -> Dict: """ Return mapping objects matching given filterers while passing through class instances whose type is one of the mapping element types. This definition allows passing custom but compatible objects to the various plotting definitions that by default expect the key from a dataset element. For example, a typical call to :func:`colour.plotting.\ plot_multi_illuminant_sds` definition with a regex pattern automatically anchored at boundaries by default is as follows: >>> import colour >>> colour.plotting.plot_multi_illuminant_sds(['A']) ... # doctest: +SKIP Here, `'A'` is by default anchored at boundaries and transformed into `'^A$'`. Note that because it is a regex pattern, special characters such as parenthesis must be escaped: `'Adobe RGB (1998)'` must be written `'Adobe RGB \\(1998\\)'` instead. With the previous example, t is also possible to pass a custom spectral distribution as follows: >>> data = { ... 500: 0.0651, ... 520: 0.0705, ... 540: 0.0772, ... 560: 0.0870, ... 580: 0.1128, ... 600: 0.1360 ... } >>> colour.plotting.plot_multi_illuminant_sds( ... ['A', colour.SpectralDistribution(data)]) ... # doctest: +SKIP Similarly, a typical call to :func:`colour.plotting.\ plot_planckian_locus_in_chromaticity_diagram_CIE1931` definition is as follows: >>> colour.plotting.plot_planckian_locus_in_chromaticity_diagram_CIE1931( ... ['A']) ... # doctest: +SKIP But it is also possible to pass a custom whitepoint as follows: >>> colour.plotting.plot_planckian_locus_in_chromaticity_diagram_CIE1931( ... ['A', {'Custom': np.array([1 / 3 + 0.05, 1 / 3 + 0.05])}]) ... # doctest: +SKIP Parameters ---------- mapping Mapping to filter. filterers Filterer or object class instance (which is passed through directly if its type is one of the mapping element types) or list of filterers. anchors Whether to use Regex line anchors, i.e. *^* and *$* are added, surrounding the filterers patterns. allow_non_siblings Whether to allow non-siblings to be also passed through. flags Regex flags. Returns ------- :class:`dict` Filtered mapping. """ if is_string(filterers): filterers = [filterers] elif not isinstance(filterers, (list, tuple)): filterers = [filterers] string_filterers: List[str] = [ cast(str, filterer) for filterer in filterers if is_string(filterer) ] object_filterers: List[Any] = [ filterer for filterer in filterers if is_sibling(filterer, mapping) ] if allow_non_siblings: non_siblings = [ filterer for filterer in filterers if filterer not in string_filterers and filterer not in object_filterers ] if non_siblings: runtime_warning( f'Non-sibling elements are passed-through: "{non_siblings}"') object_filterers.extend(non_siblings) filtered_mapping = filter_mapping(mapping, string_filterers, anchors, flags) for filterer in object_filterers: # TODO: Consider using "MutableMapping" here. if isinstance(filterer, (dict, CaseInsensitiveMapping)): for key, value in filterer.items(): filtered_mapping[key] = value else: try: name = filterer.name except AttributeError: try: name = filterer.__name__ except AttributeError: name = str(id(filterer)) filtered_mapping[name] = filterer return filtered_mapping
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 filter_warnings(colour_runtime_warnings=None, colour_usage_warnings=None, colour_warnings=None, python_warnings=None): """ Filters *Colour* and also optionally overall Python warnings. The possible values for all the actions, i.e. each argument, are as follows: - *None* (No action is taken) - *True* (*ignore*) - *False* (*default*) - *error* - *ignore* - *always* - *default* - *module* - *once* Parameters ---------- colour_runtime_warnings : bool or unicode, optional Whether to filter *Colour* runtime warnings according to the action value. colour_usage_warnings : bool or unicode, optional Whether to filter *Colour* usage warnings according to the action value. colour_warnings : bool or unicode, optional Whether to filter *Colour* warnings, this also filters *Colour* usage and runtime warnings according to the action value. python_warnings : bool or unicode, optional Whether to filter *Python* warnings according to the action value. Examples -------- Filtering *Colour* runtime warnings: >>> filter_warnings(colour_runtime_warnings=True) Filtering *Colour* usage warnings: >>> filter_warnings(colour_usage_warnings=True) Filtering *Colour* warnings: >>> filter_warnings(colour_warnings=True) Filtering all the *Colour* and also Python warnings: >>> filter_warnings(python_warnings=True) Enabling all the *Colour* and Python warnings: >>> filter_warnings(*[False] * 4) Enabling all the *Colour* and Python warnings using the *default* action: >>> filter_warnings(*['default'] * 4) Setting back the default state: >>> filter_warnings(colour_runtime_warnings=True) """ for action, category in [ (colour_warnings, ColourWarning), (colour_runtime_warnings, ColourRuntimeWarning), (colour_usage_warnings, ColourUsageWarning), (python_warnings, Warning), ]: if action is None: continue if is_string(action): action = action else: action = 'ignore' if action else 'default' filterwarnings(action, category=category)
def filter_passthrough(mapping, filterers, anchors=True, allow_non_siblings=True, flags=re.IGNORECASE): """ Returns mapping objects matching given filterers while passing through class instances whose type is one of the mapping element types. This definition allows passing custom but compatible objects to the various plotting definitions that by default expect the key from a dataset element. For example, a typical call to :func:`colour.plotting.\ plot_multi_illuminant_sds` definition is as follows: >>> import colour >>> import colour.plotting >>> colour.plotting.plot_multi_illuminant_sds(['A']) ... # doctest: +SKIP But it is also possible to pass a custom spectral distribution as follows: >>> data = { ... 500: 0.0651, ... 520: 0.0705, ... 540: 0.0772, ... 560: 0.0870, ... 580: 0.1128, ... 600: 0.1360 ... } >>> colour.plotting.plot_multi_illuminant_sds( ... ['A', colour.SpectralDistribution(data)]) ... # doctest: +SKIP Similarly, a typical call to :func:`colour.plotting.\ plot_planckian_locus_in_chromaticity_diagram_CIE1931` definition is as follows: >>> colour.plotting.plot_planckian_locus_in_chromaticity_diagram_CIE1931( ... ['A']) ... # doctest: +SKIP But it is also possible to pass a custom whitepoint as follows: >>> colour.plotting.plot_planckian_locus_in_chromaticity_diagram_CIE1931( ... ['A', {'Custom': np.array([1 / 3 + 0.05, 1 / 3 + 0.05])}]) ... # doctest: +SKIP Parameters ---------- mapping : dict_like Mapping to filter. filterers : unicode or object or array_like Filterer or object class instance (which is passed through directly if its type is one of the mapping element types) or list of filterers. anchors : bool, optional Whether to use Regex line anchors, i.e. *^* and *$* are added, surrounding the filterers patterns. allow_non_siblings : bool, optional Whether to allow non-siblings to be also passed through. flags : int, optional Regex flags. Returns ------- dict_like Filtered mapping. """ if is_string(filterers): filterers = [filterers] elif not isinstance(filterers, (list, tuple)): filterers = [filterers] string_filterers = [ filterer for filterer in filterers if is_string(filterer) ] object_filterers = [ filterer for filterer in filterers if is_sibling(filterer, mapping) ] if allow_non_siblings: non_siblings = [ filterer for filterer in filterers if filterer not in string_filterers and filterer not in object_filterers ] if non_siblings: runtime_warning( 'Non-sibling elements are passed-through: "{0}"'.format( non_siblings)) object_filterers.extend(non_siblings) filtered_mapping = filter_mapping(mapping, string_filterers, anchors, flags) for filterer in object_filterers: if isinstance(filterer, (dict, OrderedDict, CaseInsensitiveMapping)): for key, value in filterer.items(): filtered_mapping[key] = value else: try: name = filterer.name except AttributeError: try: name = filterer.__name__ except AttributeError: name = str(id(filterer)) filtered_mapping[name] = filterer return filtered_mapping
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_spectral_locus( cmfs: Union[MultiSpectralDistributions, str, Sequence[Union[ MultiSpectralDistributions, str]], ] = "CIE 1931 2 Degree Standard Observer", spectral_locus_colours: Optional[Union[ArrayLike, str]] = None, spectral_locus_opacity: Floating = 1, spectral_locus_labels: Optional[Sequence] = None, method: Union[Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"], str] = "CIE 1931", **kwargs: Any, ) -> Tuple[plt.Figure, plt.Axes]: """ Plot the *Spectral Locus* according to given method. Parameters ---------- cmfs Standard observer colour matching functions used for computing the spectral locus boundaries. ``cmfs`` can be of any type or form supported by the :func:`colour.plotting.filter_cmfs` definition. spectral_locus_colours Colours of the *Spectral Locus*, if ``spectral_locus_colours`` is set to *RGB*, the colours will be computed according to the corresponding chromaticity coordinates. spectral_locus_opacity Opacity of the *Spectral Locus*. spectral_locus_labels Array of wavelength labels used to customise which labels will be drawn around the spectral locus. Passing an empty array will result in no wavelength labels being drawn. method *Chromaticity Diagram* method. Other Parameters ---------------- kwargs {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, See the documentation of the previously listed definitions. Returns ------- :class:`tuple` Current figure and axes. Examples -------- >>> plot_spectral_locus(spectral_locus_colours='RGB') # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, <...AxesSubplot...>) .. image:: ../_static/Plotting_Plot_Spectral_Locus.png :align: center :alt: plot_spectral_locus """ method = validate_method(method, ["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"]) spectral_locus_colours = optional(spectral_locus_colours, CONSTANTS_COLOUR_STYLE.colour.dark) settings: Dict[str, Any] = {"uniform": True} settings.update(kwargs) _figure, axes = artist(**settings) cmfs = cast(MultiSpectralDistributions, first_item(filter_cmfs(cmfs).values())) illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint wavelengths = list(cmfs.wavelengths) equal_energy = np.array([1 / 3] * 2) if method == "cie 1931": ij = XYZ_to_xy(cmfs.values, illuminant) labels = cast( Tuple, optional( spectral_locus_labels, ( 390, 460, 470, 480, 490, 500, 510, 520, 540, 560, 580, 600, 620, 700, ), ), ) elif method == "cie 1960 ucs": ij = UCS_to_uv(XYZ_to_UCS(cmfs.values)) labels = cast( Tuple, optional( spectral_locus_labels, ( 420, 440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590, 600, 610, 620, 630, 645, 680, ), ), ) elif method == "cie 1976 ucs": ij = Luv_to_uv(XYZ_to_Luv(cmfs.values, illuminant), illuminant) labels = cast( Tuple, optional( spectral_locus_labels, ( 420, 440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590, 600, 610, 620, 630, 645, 680, ), ), ) pl_ij = np.reshape( tstack([ np.linspace(ij[0][0], ij[-1][0], 20), np.linspace(ij[0][1], ij[-1][1], 20), ]), (-1, 1, 2), ) sl_ij = np.copy(ij).reshape(-1, 1, 2) purple_line_colours: Optional[Union[ArrayLike, str]] if str(spectral_locus_colours).upper() == "RGB": spectral_locus_colours = normalise_maximum(XYZ_to_plotting_colourspace( cmfs.values), axis=-1) if method == "cie 1931": XYZ = xy_to_XYZ(pl_ij) elif method == "cie 1960 ucs": XYZ = xy_to_XYZ(UCS_uv_to_xy(pl_ij)) elif method == "cie 1976 ucs": XYZ = xy_to_XYZ(Luv_uv_to_xy(pl_ij)) purple_line_colours = normalise_maximum(XYZ_to_plotting_colourspace( np.reshape(XYZ, (-1, 3))), axis=-1) else: purple_line_colours = spectral_locus_colours for slp_ij, slp_colours in ( (pl_ij, purple_line_colours), (sl_ij, spectral_locus_colours), ): line_collection = LineCollection( np.concatenate([slp_ij[:-1], slp_ij[1:]], axis=1), colors=slp_colours, alpha=spectral_locus_opacity, zorder=CONSTANTS_COLOUR_STYLE.zorder.midground_scatter, ) axes.add_collection(line_collection) wl_ij = dict(zip(wavelengths, ij)) for label in labels: ij_l = wl_ij.get(label) if ij_l is None: continue ij_l = as_float_array([ij_l]) i, j = tsplit(ij_l) index = bisect.bisect(wavelengths, label) left = wavelengths[index - 1] if index >= 0 else wavelengths[index] right = (wavelengths[index] if index < len(wavelengths) else wavelengths[-1]) dx = wl_ij[right][0] - wl_ij[left][0] dy = wl_ij[right][1] - wl_ij[left][1] direction = np.array([-dy, dx]) normal = (np.array([-dy, dx]) if np.dot( normalise_vector(ij_l - equal_energy), normalise_vector(direction), ) > 0 else np.array([dy, -dx])) normal = normalise_vector(normal) / 30 label_colour = ( spectral_locus_colours if is_string(spectral_locus_colours) else spectral_locus_colours[index] # type: ignore[index] ) axes.plot( (i, i + normal[0] * 0.75), (j, j + normal[1] * 0.75), color=label_colour, alpha=spectral_locus_opacity, zorder=CONSTANTS_COLOUR_STYLE.zorder.background_line, ) axes.plot( i, j, "o", color=label_colour, alpha=spectral_locus_opacity, zorder=CONSTANTS_COLOUR_STYLE.zorder.background_line, ) axes.text( i + normal[0], j + normal[1], label, clip_on=True, ha="left" if normal[0] >= 0 else "right", va="center", fontdict={"size": "small"}, zorder=CONSTANTS_COLOUR_STYLE.zorder.background_label, ) settings = {"axes": axes} settings.update(kwargs) return render(**kwargs)