def colour_rendering_index(spd_test, additional_data=False): """ Returns the *Colour Rendering Index* (CRI) :math:`Q_a` of given spectral power distribution. Parameters ---------- spd_test : SpectralPowerDistribution Test spectral power distribution. additional_data : bool, optional Output additional data. Returns ------- numeric or CRI_Specification *Colour Rendering Index* (CRI). Examples -------- >>> from colour import ILLUMINANTS_RELATIVE_SPDS >>> spd = ILLUMINANTS_RELATIVE_SPDS['F2'] >>> colour_rendering_index(spd) # doctest: +ELLIPSIS 64.1515202... """ cmfs = STANDARD_OBSERVERS_CMFS[ 'CIE 1931 2 Degree Standard Observer'].clone().trim_wavelengths( ASTME30815_PRACTISE_SHAPE) shape = cmfs.shape spd_test = spd_test.clone().align(shape) tcs_spds = { spd.name: spd.clone().align(shape) for spd in TCS_SPDS.values() } XYZ = spectral_to_XYZ(spd_test, cmfs) uv = UCS_to_uv(XYZ_to_UCS(XYZ)) CCT, _D_uv = uv_to_CCT_Robertson1968(uv) if CCT < 5000: spd_reference = blackbody_spd(CCT, shape) else: xy = CCT_to_xy_CIE_D(CCT) spd_reference = D_illuminant_relative_spd(xy) spd_reference.align(shape) test_tcs_colorimetry_data = tcs_colorimetry_data(spd_test, spd_reference, tcs_spds, cmfs, chromatic_adaptation=True) reference_tcs_colorimetry_data = tcs_colorimetry_data( spd_reference, spd_reference, tcs_spds, cmfs) Q_as = colour_rendering_indexes(test_tcs_colorimetry_data, reference_tcs_colorimetry_data) Q_a = np.average( [v.Q_a for k, v in Q_as.items() if k in (1, 2, 3, 4, 5, 6, 7, 8)]) if additional_data: return CRI_Specification( spd_test.name, Q_a, Q_as, (test_tcs_colorimetry_data, reference_tcs_colorimetry_data)) else: return Q_a
def CCT_to_uv_Ohno2013( CCT, D_uv=0, cmfs=STANDARD_OBSERVERS_CMFS.get('CIE 1931 2 Degree Standard Observer')): """ Returns the *CIE UCS* colourspace *uv* chromaticity coordinates from given correlated colour temperature :math:`T_{cp}`, :math:`\Delta_{uv}` and colour matching functions using Ohno (2013) method. Parameters ---------- CCT : numeric Correlated colour temperature :math:`T_{cp}`. D_uv : numeric, optional :math:`\Delta_{uv}`. cmfs : XYZ_ColourMatchingFunctions, optional Standard observer colour matching functions. Returns ------- ndarray *CIE UCS* colourspace *uv* chromaticity coordinates. References ---------- .. [4] Ohno, Y. (2014). Practical Use and Calculation of CCT and Duv. LEUKOS, 10(1), 47–55. doi:10.1080/15502724.2014.839020 Examples -------- >>> from colour import STANDARD_OBSERVERS_CMFS >>> cmfs = 'CIE 1931 2 Degree Standard Observer' >>> cmfs = STANDARD_OBSERVERS_CMFS.get(cmfs) >>> CCT = 6507.4342201047066 >>> D_uv = 0.003223690901512735 >>> CCT_to_uv_Ohno2013(CCT, D_uv, cmfs) # doctest: +ELLIPSIS array([ 0.1978003..., 0.3122005...]) """ shape = cmfs.shape delta = 0.01 spd = blackbody_spd(CCT, shape) XYZ = spectral_to_XYZ(spd, cmfs) XYZ *= 1 / np.max(XYZ) UVW = XYZ_to_UCS(XYZ) u0, v0 = UCS_to_uv(UVW) if D_uv == 0: return np.array([u0, v0]) else: spd = blackbody_spd(CCT + delta, shape) XYZ = spectral_to_XYZ(spd, cmfs) XYZ *= 1 / np.max(XYZ) UVW = XYZ_to_UCS(XYZ) u1, v1 = UCS_to_uv(UVW) du = u0 - u1 dv = v0 - v1 u = u0 - D_uv * (dv / np.sqrt(du**2 + dv**2)) v = v0 + D_uv * (du / np.sqrt(du**2 + dv**2)) return np.array([u, v])
def XYZ_to_UVW( XYZ, illuminant=ILLUMINANTS['CIE 1931 2 Degree Standard Observer']['D65']): """ Converts from *CIE XYZ* tristimulus values to *CIE 1964 U\\*V\\*W\\** colourspace. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. illuminant : array_like, optional Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY* colourspace array. Returns ------- ndarray *CIE 1964 U\\*V\\*W\\** colourspace array. Warning ------- The input domain and output range of that definition are non standard! Notes ----- +----------------+-----------------------+-----------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``XYZ`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ | ``illuminant`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ +----------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``UVW`` | ``U`` : [-100, 100] | ``U`` : [-1, 1] | | | | | | | ``V`` : [-100, 100] | ``V`` : [-1, 1] | | | | | | | ``W`` : [0, 100] | ``W`` : [0, 1] | +----------------+-----------------------+-----------------+ References ---------- :cite:`Wikipedia2008a` Examples -------- >>> import numpy as np >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) * 100 >>> XYZ_to_UVW(XYZ) # doctest: +ELLIPSIS array([ 94.5503572..., 11.5553652..., 40.5475740...]) """ XYZ = to_domain_100(XYZ) xy = xyY_to_xy(illuminant) xyY = XYZ_to_xyY(XYZ, xy) _x, _y, Y = tsplit(xyY) u, v = tsplit(UCS_to_uv(XYZ_to_UCS(XYZ))) u_0, v_0 = tsplit(xy_to_UCS_uv(xy)) W = 25 * spow(Y, 1 / 3) - 17 U = 13 * W * (u - u_0) V = 13 * W * (v - v_0) UVW = tstack([U, V, W]) return from_range_100(UVW)
def RGB_chromaticity_coordinates_CIE_1960_UCS_chromaticity_diagram_plot( RGB, colourspace, **kwargs): """ Plots given *RGB* colourspace array in *CIE 1960 UCS Chromaticity Diagram*. Parameters ---------- RGB : array_like *RGB* colourspace array. colourspace : unicode *RGB* colourspace of the *RGB* array. Other Parameters ---------------- \**kwargs : dict, optional {:func:`boundaries`, :func:`canvas`, :func:`decorate`, :func:`display`}, Please refer to the documentation of the previously listed definitions. show_diagram_colours : bool, optional {:func:`CIE_1960_UCS_chromaticity_diagram_plot`}, Whether to display the chromaticity diagram background colours. Returns ------- Figure Current figure or None. Examples -------- >>> RGB = np.random.random((10, 10, 3)) >>> c = 'Rec. 709' >>> RGB_chromaticity_coordinates_CIE_1960_UCS_chromaticity_diagram_plot( ... RGB, c) # doctest: +SKIP """ settings = {} settings.update(kwargs) settings.update({'standalone': False}) colourspace, name = get_RGB_colourspace(colourspace), colourspace settings['colourspaces'] = ([name] + settings.get('colourspaces', [])) RGB_colourspaces_CIE_1960_UCS_chromaticity_diagram_plot(**settings) alpha_p, colour_p = 0.85, 'black' uv = UCS_to_uv( XYZ_to_UCS( RGB_to_XYZ(RGB, colourspace.whitepoint, colourspace.whitepoint, colourspace.RGB_to_XYZ_matrix))) pylab.scatter(uv[..., 0], uv[..., 1], alpha=alpha_p / 2, color=colour_p, marker='+') settings.update({'standalone': True}) settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**settings)
def camera_space_to_XYZ_matrix(xy, CCT_calibration_illuminant_1, CCT_calibration_illuminant_2, M_color_matrix_1, M_color_matrix_2, M_camera_calibration_1, M_camera_calibration_2, analog_balance, M_forward_matrix_1, M_forward_matrix_2, chromatic_adaptation_transform='Bradford'): """ Returns the *Camera Space* to *CIE XYZ* matrix for given *xy* white balance chromaticity coordinates. Parameters ---------- xy : array_like *xy* white balance chromaticity coordinates. CCT_calibration_illuminant_1 : numeric Correlated colour temperature of *CalibrationIlluminant1*. CCT_calibration_illuminant_2 : numeric Correlated colour temperature of *CalibrationIlluminant2*. M_color_matrix_1 : array_like *ColorMatrix1* tag matrix. M_color_matrix_2 : array_like *ColorMatrix2* tag matrix. M_camera_calibration_1 : array_like *CameraCalibration1* tag matrix. M_camera_calibration_2 : array_like *CameraCalibration2* tag matrix. analog_balance : array_like *AnalogBalance* tag vector. M_forward_matrix_1 : array_like *ForwardMatrix1* tag matrix. M_forward_matrix_2 : array_like *ForwardMatrix2* tag matrix. chromatic_adaptation_transform : unicode, optional **{'CAT02', 'XYZ Scaling', 'Von Kries', 'Bradford', 'Sharp', 'Fairchild', 'CMCCAT97', 'CMCCAT2000', 'CAT02_BRILL_CAT', 'Bianco', 'Bianco PC'}**, Chromatic adaptation transform. Returns ------- ndarray *Camera Space* to *CIE XYZ* matrix. Notes ----- - The reference illuminant is D50 as defined per :attr:`colour_hdri.models.dataset.dng.ADOBE_DNG_XYZ_ILLUMINANT` attribute. References ---------- - :cite:`AdobeSystems2012f` - :cite:`AdobeSystems2012g` - :cite:`AdobeSystems2015d` - :cite:`McGuffog2012a` Examples -------- >>> M_color_matrix_1 = np.array( ... [[0.5309, -0.0229, -0.0336], ... [-0.6241, 1.3265, 0.3337], ... [-0.0817, 0.1215, 0.6664]]) >>> M_color_matrix_2 = np.array( ... [[0.4716, 0.0603, -0.0830], ... [-0.7798, 1.5474, 0.2480], ... [-0.1496, 0.1937, 0.6651]]) >>> M_camera_calibration_1 = np.identity(3) >>> M_camera_calibration_2 = np.identity(3) >>> analog_balance = np.ones(3) >>> M_forward_matrix_1 = np.array( ... [[0.8924, -0.1041, 0.1760], ... [0.4351, 0.6621, -0.0972], ... [0.0505, -0.1562, 0.9308]]) >>> M_forward_matrix_2 = np.array( ... [[0.8924, -0.1041, 0.1760], ... [0.4351, 0.6621, -0.0972], ... [0.0505, -0.1562, 0.9308]]) >>> camera_space_to_XYZ_matrix( # doctest: +ELLIPSIS ... np.array([0.32816244, 0.34698169]), ... 2850, ... 6500, ... M_color_matrix_1, ... M_color_matrix_2, ... M_camera_calibration_1, ... M_camera_calibration_2, ... analog_balance, ... M_forward_matrix_1, ... M_forward_matrix_2) array([[ 2.1604087..., -0.1041... , 0.2722498...], [ 1.0533324..., 0.6621... , -0.1503561...], [ 0.1222553..., -0.1562... , 1.4398304...]]) """ # *ForwardMatrix1* and *ForwardMatrix2* are not included in the camera # profile. if is_identity(M_forward_matrix_1) and is_identity(M_forward_matrix_2): M_camera_to_XYZ = np.linalg.inv( XYZ_to_camera_space_matrix(xy, CCT_calibration_illuminant_1, CCT_calibration_illuminant_2, M_color_matrix_1, M_color_matrix_2, M_camera_calibration_1, M_camera_calibration_2, analog_balance)) M_CAT = chromatic_adaptation_matrix_VonKries( xy_to_XYZ(xy), xy_to_XYZ(ADOBE_DNG_XYZ_ILLUMINANT), chromatic_adaptation_transform) M_camera_space_to_XYZ = dot_matrix(M_CAT, M_camera_to_XYZ) else: uv = UCS_to_uv(XYZ_to_UCS(xy_to_XYZ(xy))) CCT, _D_uv = uv_to_CCT_Robertson1968(uv) M_CC = interpolated_matrix(CCT, CCT_calibration_illuminant_1, CCT_calibration_illuminant_2, M_camera_calibration_1, M_camera_calibration_2) # The reference implementation :cite:`AdobeSystems2015d` diverges from # the white-paper :cite:`AdobeSystems2012f`: # The reference implementation directly computes the camera neutral by # multiplying directly the interpolated colour matrix :math:`CM` with # the tristimulus values of the *xy* white balance chromaticity # coordinates. # The current implementation is based on the white-paper so that the # interpolated camera calibration matrix :math:`CC` and the # analog balance matrix :math:`AB` are accounted for. camera_neutral = xy_to_camera_neutral( xy, CCT_calibration_illuminant_1, CCT_calibration_illuminant_2, M_color_matrix_1, M_color_matrix_2, M_camera_calibration_1, M_camera_calibration_2, analog_balance) M_AB = np.diagflat(analog_balance) M_reference_neutral = dot_vector(np.linalg.inv(dot_matrix(M_AB, M_CC)), camera_neutral) M_D = np.linalg.inv(np.diagflat(M_reference_neutral)) M_FM = interpolated_matrix(CCT, CCT_calibration_illuminant_1, CCT_calibration_illuminant_2, M_forward_matrix_1, M_forward_matrix_2) M_camera_space_to_XYZ = dot_matrix( dot_matrix(M_FM, M_D), np.linalg.inv(dot_matrix(M_AB, M_CC))) return M_camera_space_to_XYZ
def colour_rendering_index(sd_test, additional_data=False): """ Returns the *Colour Rendering Index* (CRI) :math:`Q_a` of given spectral distribution. Parameters ---------- sd_test : SpectralDistribution Test spectral distribution. additional_data : bool, optional Whether to output additional data. Returns ------- numeric or CRI_Specification *Colour Rendering Index* (CRI). References ---------- :cite:`Ohno2008a` Examples -------- >>> from colour import ILLUMINANTS_SDS >>> sd = ILLUMINANTS_SDS['F2'] >>> colour_rendering_index(sd) # doctest: +ELLIPSIS 64.1515202... """ cmfs = STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'].copy( ).trim(ASTME30815_PRACTISE_SHAPE) shape = cmfs.shape sd_test = sd_test.copy().align(shape) tcs_sds = {sd.name: sd.copy().align(shape) for sd in TCS_SDS.values()} with domain_range_scale('1'): XYZ = sd_to_XYZ(sd_test, cmfs) uv = UCS_to_uv(XYZ_to_UCS(XYZ)) CCT, _D_uv = uv_to_CCT_Robertson1968(uv) if CCT < 5000: sd_reference = sd_blackbody(CCT, shape) else: xy = CCT_to_xy_CIE_D(CCT) sd_reference = sd_CIE_illuminant_D_series(xy) sd_reference.align(shape) test_tcs_colorimetry_data = tcs_colorimetry_data(sd_test, sd_reference, tcs_sds, cmfs, chromatic_adaptation=True) reference_tcs_colorimetry_data = tcs_colorimetry_data( sd_reference, sd_reference, tcs_sds, cmfs) Q_as = colour_rendering_indexes(test_tcs_colorimetry_data, reference_tcs_colorimetry_data) Q_a = np.average( [v.Q_a for k, v in Q_as.items() if k in (1, 2, 3, 4, 5, 6, 7, 8)]) if additional_data: return CRI_Specification( sd_test.name, Q_a, Q_as, (test_tcs_colorimetry_data, reference_tcs_colorimetry_data)) else: return Q_a
def plot_RGB_chromaticities_in_chromaticity_diagram( RGB, colourspace='sRGB', chromaticity_diagram_callable=( plot_RGB_colourspaces_in_chromaticity_diagram), method='CIE 1931', scatter_parameters=None, **kwargs): """ Plots given *RGB* colourspace array in the *Chromaticity Diagram* according to given method. Parameters ---------- RGB : array_like *RGB* colourspace array. colourspace : optional, unicode *RGB* colourspace of the *RGB* array. chromaticity_diagram_callable : callable, optional Callable responsible for drawing the *Chromaticity Diagram*. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. scatter_parameters : dict, optional Parameters for the :func:`plt.scatter` definition, if ``c`` is set to *RGB*, the scatter will use given ``RGB`` colours. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> RGB = np.random.random((128, 128, 3)) >>> plot_RGB_chromaticities_in_chromaticity_diagram( ... RGB, 'ITU-R BT.709') ... # doctest: +SKIP .. image:: ../_static/Plotting_\ Plot_RGB_Chromaticities_In_Chromaticity_Diagram_Plot.png :align: center :alt: plot_RGB_chromaticities_in_chromaticity_diagram """ RGB = as_float_array(RGB).reshape(-1, 3) settings = {'uniform': True} settings.update(kwargs) figure, axes = artist(**settings) method = method.upper() scatter_settings = { 's': 40, 'c': 'RGB', 'marker': 'o', 'alpha': 0.85, } if scatter_parameters is not None: scatter_settings.update(scatter_parameters) settings = dict(kwargs) settings.update({'axes': axes, 'standalone': False}) colourspace = first_item(filter_RGB_colourspaces(colourspace).values()) settings['colourspaces'] = (['^{0}$'.format(colourspace.name)] + settings.get('colourspaces', [])) chromaticity_diagram_callable(**settings) use_RGB_colours = scatter_settings['c'].upper() == 'RGB' if use_RGB_colours: RGB = RGB[RGB[:, 1].argsort()] scatter_settings['c'] = np.clip( RGB_to_RGB(RGB, colourspace, COLOUR_STYLE_CONSTANTS.colour.colourspace, apply_encoding_cctf=True).reshape(-1, 3), 0, 1) XYZ = RGB_to_XYZ(RGB, colourspace.whitepoint, colourspace.whitepoint, colourspace.RGB_to_XYZ_matrix) if method == 'CIE 1931': ij = XYZ_to_xy(XYZ, colourspace.whitepoint) elif method == 'CIE 1960 UCS': ij = UCS_to_uv(XYZ_to_UCS(XYZ)) elif method == 'CIE 1976 UCS': ij = Luv_to_uv(XYZ_to_Luv(XYZ, colourspace.whitepoint), colourspace.whitepoint) axes.scatter(ij[..., 0], ij[..., 1], **scatter_settings) settings.update({'standalone': True}) settings.update(kwargs) return render(**settings)
def CCT_to_uv_Ohno2013( CCT, D_uv=0, cmfs=STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer']): """ Returns the *CIE UCS* colourspace *uv* chromaticity coordinates from given correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}` and colour matching functions using *Ohno (2013)* method. Parameters ---------- CCT : numeric Correlated colour temperature :math:`T_{cp}`. D_uv : numeric, optional :math:`\\Delta_{uv}`. cmfs : XYZ_ColourMatchingFunctions, optional Standard observer colour matching functions. Returns ------- ndarray *CIE UCS* colourspace *uv* chromaticity coordinates. References ---------- :cite:`Ohno2014a` Examples -------- >>> from colour import STANDARD_OBSERVERS_CMFS >>> cmfs = STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'] >>> CCT = 6507.4342201047066 >>> D_uv = 0.003223690901513 >>> CCT_to_uv_Ohno2013(CCT, D_uv, cmfs) # doctest: +ELLIPSIS array([ 0.1977999..., 0.3122004...]) """ cmfs = cmfs.copy().trim(ASTME30815_PRACTISE_SHAPE) shape = cmfs.shape delta = 0.01 sd = sd_blackbody(CCT, shape) XYZ = sd_to_XYZ(sd, cmfs) XYZ *= 1 / np.max(XYZ) UVW = XYZ_to_UCS(XYZ) u0, v0 = UCS_to_uv(UVW) if D_uv == 0: return np.array([u0, v0]) else: sd = sd_blackbody(CCT + delta, shape) XYZ = sd_to_XYZ(sd, cmfs) XYZ *= 1 / np.max(XYZ) UVW = XYZ_to_UCS(XYZ) u1, v1 = UCS_to_uv(UVW) du = u0 - u1 dv = v0 - v1 u = u0 - D_uv * (dv / np.hypot(du, dv)) v = v0 + D_uv * (du / np.hypot(du, dv)) return np.array([u, v])
def planckian_table(uv, cmfs, start, end, count): """ Returns a planckian table from given *CIE UCS* colourspace *uv* chromaticity coordinates, colour matching functions and temperature range using *Ohno (2013)* method. Parameters ---------- uv : array_like *uv* chromaticity coordinates. cmfs : XYZ_ColourMatchingFunctions Standard observer colour matching functions. start : numeric Temperature range start in kelvins. end : numeric Temperature range end in kelvins. count : int Temperatures count in the planckian table. Returns ------- list Planckian table. Examples -------- >>> from colour.colorimetry import ( ... SPECTRAL_SHAPE_DEFAULT, MSDS_CMFS_STANDARD_OBSERVER) >>> from pprint import pprint >>> cmfs = ( ... MSDS_CMFS_STANDARD_OBSERVER['CIE 1931 2 Degree Standard Observer']. ... copy().align(SPECTRAL_SHAPE_DEFAULT) ... ) >>> uv = np.array([0.1978, 0.3122]) >>> pprint(planckian_table(uv, cmfs, 1000, 1010, 10)) ... # doctest: +ELLIPSIS [PlanckianTable_Tuvdi(Ti=1000.0, \ ui=0.4479628..., vi=0.3546296..., di=0.2537355...), PlanckianTable_Tuvdi(Ti=1001.1111111..., \ ui=0.4477030..., vi=0.3546521..., di=0.2534831...), PlanckianTable_Tuvdi(Ti=1002.2222222..., \ ui=0.4474434..., vi=0.3546746..., di=0.2532310...), PlanckianTable_Tuvdi(Ti=1003.3333333..., \ ui=0.4471842..., vi=0.3546970..., di=0.2529792...), PlanckianTable_Tuvdi(Ti=1004.4444444..., \ ui=0.4469252..., vi=0.3547194..., di=0.2527277...), PlanckianTable_Tuvdi(Ti=1005.5555555..., \ ui=0.4466666..., vi=0.3547417..., di=0.2524765...), PlanckianTable_Tuvdi(Ti=1006.6666666..., \ ui=0.4464083..., vi=0.3547640..., di=0.2522256...), PlanckianTable_Tuvdi(Ti=1007.7777777..., \ ui=0.4461502..., vi=0.3547862..., di=0.2519751...), PlanckianTable_Tuvdi(Ti=1008.8888888..., \ ui=0.4458925..., vi=0.3548084..., di=0.2517248...), PlanckianTable_Tuvdi(Ti=1010.0, \ ui=0.4456351..., vi=0.3548306..., di=0.2514749...)] """ ux, vx = uv cmfs = cmfs.copy().trim(SPECTRAL_SHAPE_DEFAULT) shape = cmfs.shape table = [] for Ti in np.linspace(start, end, count): sd = sd_blackbody(Ti, shape) XYZ = sd_to_XYZ(sd, cmfs) XYZ /= np.max(XYZ) UVW = XYZ_to_UCS(XYZ) ui, vi = UCS_to_uv(UVW) di = np.hypot(ux - ui, vx - vi) table.append(PLANCKIAN_TABLE_TUVD(Ti, ui, vi, di)) return table
def RGB_chromaticity_coordinates_chromaticity_diagram_plot_CIE1960UCS( RGB, colourspace='sRGB', chromaticity_diagram_callable_CIE1960UCS=( RGB_colourspaces_chromaticity_diagram_plot_CIE1960UCS), **kwargs): """ Plots given *RGB* colourspace array in *CIE 1960 UCS Chromaticity Diagram*. Parameters ---------- RGB : array_like *RGB* colourspace array. colourspace : optional, unicode *RGB* colourspace of the *RGB* array. chromaticity_diagram_callable_CIE1960UCS : callable, optional Callable responsible for drawing the *CIE 1960 UCS Chromaticity Diagram*. Other Parameters ---------------- \**kwargs : dict, optional {:func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definition. show_diagram_colours : bool, optional {:func:`colour.plotting.chromaticity_diagram_plot_CIE1960UCS`}, Whether to display the chromaticity diagram background colours. use_cached_diagram_colours : bool, optional {:func:`colour.plotting.chromaticity_diagram_plot_CIE1960UCS`}, Whether to used the cached chromaticity diagram background colours image. Returns ------- Figure Current figure or None. Examples -------- >>> RGB = np.random.random((10, 10, 3)) >>> c = 'ITU-R BT.709' >>> RGB_chromaticity_coordinates_chromaticity_diagram_plot_CIE1960UCS( ... RGB, c) # doctest: +SKIP """ settings = {} settings.update(kwargs) settings.update({'standalone': False}) colourspace, name = get_RGB_colourspace(colourspace), colourspace settings['colourspaces'] = ([name] + settings.get('colourspaces', [])) chromaticity_diagram_callable_CIE1960UCS(**settings) alpha_p, colour_p = 0.85, 'black' uv = UCS_to_uv( XYZ_to_UCS( RGB_to_XYZ(RGB, colourspace.whitepoint, colourspace.whitepoint, colourspace.RGB_to_XYZ_matrix))) pylab.scatter(uv[..., 0], uv[..., 1], alpha=alpha_p / 2, color=colour_p, marker='+') settings.update({'standalone': True}) settings.update(kwargs) return render(**settings)
def colour_quality_scale(sd_test, additional_data=False, method='NIST CQS 9.0'): """ Returns the *Colour Quality Scale* (CQS) of given spectral distribution using given method. Parameters ---------- sd_test : SpectralDistribution Test spectral distribution. additional_data : bool, optional Whether to output additional data. method : unicode, optional **{'NIST CQS 9.0', 'NIST CQS 7.4'}**, Computation method. Returns ------- numeric or CQS_Specification Color quality scale. References ---------- :cite:`Davis2010a`, :cite:`Ohno2008a`, :cite:`Ohno2013` Examples -------- >>> from colour import ILLUMINANTS_SDS >>> sd = ILLUMINANTS_SDS['FL2'] >>> colour_quality_scale(sd) # doctest: +ELLIPSIS 64.0172835... """ method = method.lower() assert method.lower() in [ m.lower() for m in COLOUR_QUALITY_SCALE_METHODS ], ('"{0}" method is invalid, must be one of {1}!'.format( method, COLOUR_QUALITY_SCALE_METHODS)) cmfs = STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'].copy( ).trim(ASTME30815_PRACTISE_SHAPE) shape = cmfs.shape sd_test = sd_test.copy().align(shape) vs_sds = { sd.name: sd.copy().align(shape) for sd in VS_SDS[method].values() } with domain_range_scale('1'): XYZ = sd_to_XYZ(sd_test, cmfs) uv = UCS_to_uv(XYZ_to_UCS(XYZ)) CCT, _D_uv = uv_to_CCT_Ohno2013(uv) if CCT < 5000: sd_reference = sd_blackbody(CCT, shape) else: xy = CCT_to_xy_CIE_D(CCT) sd_reference = sd_CIE_illuminant_D_series(xy) sd_reference.align(shape) test_vs_colorimetry_data = vs_colorimetry_data(sd_test, sd_reference, vs_sds, cmfs, chromatic_adaptation=True) reference_vs_colorimetry_data = vs_colorimetry_data( sd_reference, sd_reference, vs_sds, cmfs) if method == 'nist cqs 9.0': CCT_f = 1 scaling_f = 3.2 else: XYZ_r = sd_to_XYZ(sd_reference, cmfs) XYZ_r /= XYZ_r[1] CCT_f = CCT_factor(reference_vs_colorimetry_data, XYZ_r) scaling_f = 3.104 Q_as = colour_quality_scales(test_vs_colorimetry_data, reference_vs_colorimetry_data, scaling_f, CCT_f) D_E_RMS = delta_E_RMS(Q_as, 'D_E_ab') D_Ep_RMS = delta_E_RMS(Q_as, 'D_Ep_ab') Q_a = scale_conversion(D_Ep_RMS, CCT_f, scaling_f) if method == 'nist cqs 9.0': scaling_f = 2.93 * 1.0343 else: scaling_f = 2.928 Q_f = scale_conversion(D_E_RMS, CCT_f, scaling_f) G_t = gamut_area( [vs_CQS_data.Lab for vs_CQS_data in test_vs_colorimetry_data]) G_r = gamut_area( [vs_CQS_data.Lab for vs_CQS_data in reference_vs_colorimetry_data]) Q_g = G_t / D65_GAMUT_AREA * 100 if method == 'nist cqs 9.0': Q_d = Q_p = None else: p_delta_C = np.average([ sample_data.D_C_ab if sample_data.D_C_ab > 0 else 0 for sample_data in Q_as.values() ]) Q_p = 100 - 3.6 * (D_Ep_RMS - p_delta_C) Q_d = G_t / G_r * CCT_f * 100 if additional_data: return CQS_Specification( sd_test.name, Q_a, Q_f, Q_p, Q_g, Q_d, Q_as, (test_vs_colorimetry_data, reference_vs_colorimetry_data)) else: return Q_a
def spds_CIE_1960_UCS_chromaticity_diagram_plot( spds, cmfs='CIE 1931 2 Degree Standard Observer', annotate=True, **kwargs): """ Plots given spectral power distribution chromaticity coordinates into the *CIE 1960 UCS Chromaticity Diagram*. Parameters ---------- spds : array_like, optional Spectral power distributions to plot. cmfs : unicode, optional Standard observer colour matching functions used for diagram bounds. annotate : bool Should resulting chromaticity coordinates annotated with their respective spectral power distribution names. \**kwargs : dict, optional Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> from colour import ILLUMINANTS_RELATIVE_SPDS >>> A = ILLUMINANTS_RELATIVE_SPDS['A'] >>> D65 = ILLUMINANTS_RELATIVE_SPDS['D65'] >>> spds_CIE_1960_UCS_chromaticity_diagram_plot([A, D65]) # doctest: +SKIP True """ settings = {} settings.update(kwargs) settings.update({'standalone': False}) CIE_1960_UCS_chromaticity_diagram_plot(**settings) cmfs = get_cmfs(cmfs) cmfs_shape = cmfs.shape for spd in spds: spd = spd.clone().align(cmfs_shape) XYZ = spectral_to_XYZ(spd) / 100 uv = UCS_to_uv(XYZ_to_UCS(XYZ)) pylab.plot(uv[0], uv[1], 'o', color='white') if spd.name is not None and annotate: pylab.annotate(spd.name, xy=uv, xytext=(50, 30), textcoords='offset points', arrowprops=dict(arrowstyle='->', connectionstyle='arc3, rad=0.2')) settings.update({ 'x_tighten': True, 'y_tighten': True, 'limits': (-0.1, 0.7, -0.2, 0.6), 'standalone': True }) settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**settings)
def CIE_1960_UCS_chromaticity_diagram_plot( cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots the *CIE 1960 UCS Chromaticity Diagram*. Parameters ---------- cmfs : unicode, optional Standard observer colour matching functions used for diagram bounds. \**kwargs : dict, optional Keywords arguments. Returns ------- bool Definition success. Examples -------- >>> CIE_1960_UCS_chromaticity_diagram_plot() # doctest: +SKIP True """ settings = {'figure_size': (DEFAULT_FIGURE_WIDTH, DEFAULT_FIGURE_WIDTH)} settings.update(kwargs) canvas(**settings) cmfs = get_cmfs(cmfs) image = matplotlib.image.imread( os.path.join( PLOTTING_RESOURCES_DIRECTORY, 'CIE_1960_UCS_Chromaticity_Diagram_{0}.png'.format( cmfs.name.replace(' ', '_')))) pylab.imshow(image, interpolation=None, extent=(0, 1, 0, 1)) labels = (420, 430, 440, 450, 460, 470, 480, 490, 500, 510, 520, 530, 540, 550, 560, 570, 580, 590, 600, 610, 620, 630, 640, 680) wavelengths = cmfs.wavelengths equal_energy = np.array([1 / 3] * 2) uv = UCS_to_uv(XYZ_to_UCS(cmfs.values)) wavelengths_chromaticity_coordinates = dict(tuple(zip(wavelengths, uv))) pylab.plot(uv[..., 0], uv[..., 1], color='black', linewidth=2) pylab.plot((uv[-1][0], uv[0][0]), (uv[-1][1], uv[0][1]), color='black', linewidth=2) for label in labels: u, v = wavelengths_chromaticity_coordinates.get(label) pylab.plot(u, v, 'o', color='black', linewidth=2) 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 = (wavelengths_chromaticity_coordinates.get(right)[0] - wavelengths_chromaticity_coordinates.get(left)[0]) dy = (wavelengths_chromaticity_coordinates.get(right)[1] - wavelengths_chromaticity_coordinates.get(left)[1]) norme = lambda x: x / np.linalg.norm(x) uv = np.array([u, v]) direction = np.array([-dy, dx]) normal = (np.array([-dy, dx]) if np.dot(norme(uv - equal_energy), norme(direction)) > 0 else np.array([dy, -dx])) normal = norme(normal) normal /= 25 pylab.plot((u, u + normal[0] * 0.75), (v, v + normal[1] * 0.75), color='black', linewidth=1.5) pylab.text(u + normal[0], v + normal[1], label, clip_on=True, ha='left' if normal[0] >= 0 else 'right', va='center', fontdict={'size': 'small'}) ticks = np.arange(-10, 10, 0.1) pylab.xticks(ticks) pylab.yticks(ticks) settings.update({ 'title': 'CIE 1960 UCS Chromaticity Diagram - {0}'.format(cmfs.title), 'x_label': 'CIE u', 'y_label': 'CIE v', 'grid': True, 'bounding_box': (0, 1, 0, 1), 'bbox_inches': 'tight', 'pad_inches': 0 }) settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**settings)
def CIE_1960_UCS_chromaticity_diagram_colours_plot( surface=1, samples=4096, cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots the *CIE 1960 UCS 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 ------- bool Definition success. Examples -------- >>> CIE_1960_UCS_chromaticity_diagram_colours_plot() # doctest: +SKIP True """ if is_scipy_installed(raise_exception=True): from scipy.spatial import Delaunay settings = {'figure_size': (64, 64)} settings.update(kwargs) canvas(**settings) cmfs = get_cmfs(cmfs) illuminant = DEFAULT_PLOTTING_ILLUMINANT triangulation = Delaunay(UCS_to_uv(XYZ_to_UCS(cmfs.values)), 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(UCS_uv_to_xy(xy)) RGB = normalise(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), 'bbox_inches': 'tight', 'pad_inches': 0 }) settings.update(kwargs) ax = matplotlib.pyplot.gca() matplotlib.pyplot.setp(ax, frame_on=False) boundaries(**settings) decorate(**settings) return display(**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 colour_rendering_index(test_spd, additional_data=False): """ Returns the *colour rendering index* of given spectral power distribution. Parameters ---------- test_spd : SpectralPowerDistribution Test spectral power distribution. additional_data : bool, optional Output additional data. Returns ------- numeric or (numeric, dict) Colour rendering index, Tsc data. Examples -------- >>> from colour import ILLUMINANTS_RELATIVE_SPDS >>> spd = ILLUMINANTS_RELATIVE_SPDS.get('F2') >>> colour_rendering_index(spd) # doctest: +ELLIPSIS 64.1507331... """ cmfs = STANDARD_OBSERVERS_CMFS.get('CIE 1931 2 Degree Standard Observer') shape = cmfs.shape test_spd = test_spd.clone().align(shape) tcs_spds = {} for index, tcs_spd in sorted(TCS_SPDS.items()): tcs_spds[index] = tcs_spd.clone().align(shape) XYZ = spectral_to_XYZ(test_spd, cmfs) uv = UCS_to_uv(XYZ_to_UCS(XYZ)) CCT, Duv = uv_to_CCT_robertson1968(uv) if CCT < 5000: reference_spd = blackbody_spd(CCT, shape) else: xy = CCT_to_xy_illuminant_D(CCT) reference_spd = D_illuminant_relative_spd(xy) reference_spd.align(shape) test_tcs_colorimetry_data = _tcs_colorimetry_data( test_spd, reference_spd, tcs_spds, cmfs, chromatic_adaptation=True) reference_tcs_colorimetry_data = _tcs_colorimetry_data( reference_spd, reference_spd, tcs_spds, cmfs) colour_rendering_indexes = _colour_rendering_indexes( test_tcs_colorimetry_data, reference_tcs_colorimetry_data) colour_rendering_index = np.average([ v for k, v in colour_rendering_indexes.items() if k in (1, 2, 3, 4, 5, 6, 7, 8) ]) if additional_data: return (colour_rendering_index, colour_rendering_indexes, [test_tcs_colorimetry_data, reference_tcs_colorimetry_data]) else: return colour_rendering_index
def tcs_colorimetry_data(sd_t, sd_r, sds_tcs, cmfs, chromatic_adaptation=False): """ Returns the *test colour samples* colorimetry data. Parameters ---------- sd_t : SpectralDistribution Test spectral distribution. sd_r : SpectralDistribution Reference spectral distribution. sds_tcs : dict *Test colour samples* spectral distributions. cmfs : XYZ_ColourMatchingFunctions Standard observer colour matching functions. chromatic_adaptation : bool, optional Perform chromatic adaptation. Returns ------- list *Test colour samples* colorimetry data. """ XYZ_t = sd_to_XYZ(sd_t, cmfs) uv_t = UCS_to_uv(XYZ_to_UCS(XYZ_t)) u_t, v_t = uv_t[0], uv_t[1] XYZ_r = sd_to_XYZ(sd_r, cmfs) uv_r = UCS_to_uv(XYZ_to_UCS(XYZ_r)) u_r, v_r = uv_r[0], uv_r[1] tcs_data = [] for _key, value in sorted(TCS_INDEXES_TO_NAMES.items()): sd_tcs = sds_tcs[value] XYZ_tcs = sd_to_XYZ(sd_tcs, cmfs, sd_t) xyY_tcs = XYZ_to_xyY(XYZ_tcs) uv_tcs = UCS_to_uv(XYZ_to_UCS(XYZ_tcs)) u_tcs, v_tcs = uv_tcs[0], uv_tcs[1] if chromatic_adaptation: def c(x, y): """ Computes the :math:`c` term. """ return (4 - x - 10 * y) / y def d(x, y): """ Computes the :math:`d` term. """ return (1.708 * y + 0.404 - 1.481 * x) / y c_t, d_t = c(u_t, v_t), d(u_t, v_t) c_r, d_r = c(u_r, v_r), d(u_r, v_r) tcs_c, tcs_d = c(u_tcs, v_tcs), d(u_tcs, v_tcs) u_tcs = ( (10.872 + 0.404 * c_r / c_t * tcs_c - 4 * d_r / d_t * tcs_d) / (16.518 + 1.481 * c_r / c_t * tcs_c - d_r / d_t * tcs_d)) v_tcs = (5.52 / (16.518 + 1.481 * c_r / c_t * tcs_c - d_r / d_t * tcs_d)) W_tcs = 25 * spow(xyY_tcs[-1], 1 / 3) - 17 U_tcs = 13 * W_tcs * (u_tcs - u_r) V_tcs = 13 * W_tcs * (v_tcs - v_r) tcs_data.append( TCS_ColorimetryData(sd_tcs.name, XYZ_tcs, uv_tcs, np.array([U_tcs, V_tcs, W_tcs]))) return tcs_data
def _tcs_colorimetry_data(test_spd, reference_spd, tsc_spds, cmfs, chromatic_adaptation=False): """ Returns the *test colour samples* colorimetry data. Parameters ---------- test_spd : SpectralPowerDistribution Test spectral power distribution. reference_spd : SpectralPowerDistribution Reference spectral power distribution. tsc_spds : dict Test colour samples. cmfs : XYZ_ColourMatchingFunctions Standard observer colour matching functions. chromatic_adaptation : bool, optional Perform chromatic adaptation. Returns ------- list *Test colour samples* colorimetry data. """ test_XYZ = spectral_to_XYZ(test_spd, cmfs) test_uv = np.ravel(UCS_to_uv(XYZ_to_UCS(test_XYZ))) test_u, test_v = test_uv[0], test_uv[1] reference_XYZ = spectral_to_XYZ(reference_spd, cmfs) reference_uv = np.ravel(UCS_to_uv(XYZ_to_UCS(reference_XYZ))) reference_u, reference_v = reference_uv[0], reference_uv[1] tcs_data = [] for key, value in sorted(TCS_INDEXES_TO_NAMES.items()): tcs_spd = tsc_spds.get(value) tcs_XYZ = spectral_to_XYZ(tcs_spd, cmfs, test_spd) tcs_xyY = np.ravel(XYZ_to_xyY(tcs_XYZ)) tcs_uv = np.ravel(UCS_to_uv(XYZ_to_UCS(tcs_XYZ))) tcs_u, tcs_v = tcs_uv[0], tcs_uv[1] if chromatic_adaptation: c = lambda x, y: (4 - x - 10 * y) / y d = lambda x, y: (1.708 * y + 0.404 - 1.481 * x) / y test_c, test_d = c(test_u, test_v), d(test_u, test_v) reference_c, reference_d = (c(reference_u, reference_v), d(reference_u, reference_v)) tcs_c, tcs_d = c(tcs_u, tcs_v), d(tcs_u, tcs_v) tcs_u = ((10.872 + 0.404 * reference_c / test_c * tcs_c - 4 * reference_d / test_d * tcs_d) / (16.518 + 1.481 * reference_c / test_c * tcs_c - reference_d / test_d * tcs_d)) tcs_v = (5.52 / (16.518 + 1.481 * reference_c / test_c * tcs_c - reference_d / test_d * tcs_d)) tcs_W = 25 * tcs_xyY[-1]**(1 / 3) - 17 tcs_U = 13 * tcs_W * (tcs_u - reference_u) tcs_V = 13 * tcs_W * (tcs_v - reference_v) tcs_data.append( TSC_COLORIMETRY_DATA_NXYZUVUVW(tcs_spd.name, tcs_XYZ, tcs_uv, np.array([tcs_U, tcs_V, tcs_W]))) return tcs_data
def planckian_table( uv: ArrayLike, cmfs: MultiSpectralDistributions, start: Floating, end: Floating, count: Integer, ) -> List[PlanckianTableRow]: """ Return a planckian table from given *CIE UCS* colourspace *uv* chromaticity coordinates, colour matching functions and temperature range using *Ohno (2013)* method. Parameters ---------- uv *uv* chromaticity coordinates. cmfs Standard observer colour matching functions. start Temperature range start in kelvin degrees. end Temperature range end in kelvin degrees. count Temperatures count in the planckian table. Returns ------- :class:`list` Planckian table. Examples -------- >>> from colour import MSDS_CMFS, SPECTRAL_SHAPE_DEFAULT >>> cmfs = ( ... MSDS_CMFS['CIE 1931 2 Degree Standard Observer']. ... copy().align(SPECTRAL_SHAPE_DEFAULT) ... ) >>> uv = np.array([0.1978, 0.3122]) >>> pprint(planckian_table(uv, cmfs, 1000, 1010, 10)) ... # doctest: +SKIP [PlanckianTableRow(Ti=1000.0, ui=0.4479628..., \ vi=0.3546296..., di=0.2537355...), PlanckianTableRow(Ti=1001.1111111..., ui=0.4477030..., \ vi=0.3546521..., di=0.2534831...), PlanckianTableRow(Ti=1002.2222222..., ui=0.4474434..., \ vi=0.3546746..., di=0.2532310...), PlanckianTableRow(Ti=1003.3333333..., ui=0.4471842..., \ vi=0.3546970..., di=0.2529792...), PlanckianTableRow(Ti=1004.4444444..., ui=0.4469252..., \ vi=0.3547194..., di=0.2527277...), PlanckianTableRow(Ti=1005.5555555..., ui=0.4466666..., \ vi=0.3547417..., di=0.2524765...), PlanckianTableRow(Ti=1006.6666666..., ui=0.4464083..., \ vi=0.3547640..., di=0.2522256...), PlanckianTableRow(Ti=1007.7777777..., ui=0.4461502..., \ vi=0.3547862..., di=0.2519751...), PlanckianTableRow(Ti=1008.8888888..., ui=0.4458925..., \ vi=0.3548084..., di=0.2517248...), PlanckianTableRow(Ti=1010.0, ui=0.4456351..., \ vi=0.3548306..., di=0.2514749...)] """ ux, vx = tsplit(uv) table = [] for Ti in np.linspace(start, end, count): sd = sd_blackbody(Ti, cmfs.shape) XYZ = sd_to_XYZ(sd, cmfs) XYZ /= np.max(XYZ) UVW = XYZ_to_UCS(XYZ) ui, vi = UCS_to_uv(UVW) di = np.hypot(ux - ui, vx - vi) table.append(PlanckianTableRow(Ti, ui, vi, di)) return table
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)
def RGB_colourspaces_CIE_1960_UCS_chromaticity_diagram_plot( colourspaces=None, cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots given *RGB* colourspaces in *CIE 1960 UCS Chromaticity Diagram*. Parameters ---------- colourspaces : array_like, optional *RGB* colourspaces to plot. cmfs : unicode, optional Standard observer colour matching functions used for diagram bounds. Other Parameters ---------------- \**kwargs : dict, optional {:func:`boundaries`, :func:`canvas`, :func:`decorate`, :func:`display`}, Please refer to the documentation of the previously listed definitions. show_diagram_colours : bool, optional {:func:`CIE_1960_UCS_chromaticity_diagram_plot`}, Whether to display the chromaticity diagram background colours. Returns ------- Figure Current figure or None. Examples -------- >>> c = ['Rec. 709', 'ACEScg', 'S-Gamut'] >>> RGB_colourspaces_CIE_1960_UCS_chromaticity_diagram_plot( ... c) # doctest: +SKIP """ settings = {'figure_size': (DEFAULT_FIGURE_WIDTH, DEFAULT_FIGURE_WIDTH)} settings.update(kwargs) canvas(**settings) if colourspaces is None: colourspaces = ('Rec. 709', 'ACEScg', 'S-Gamut', 'Pointer Gamut') cmfs, name = get_cmfs(cmfs), cmfs settings = { 'title': '{0} - {1} - CIE 1960 UCS Chromaticity Diagram'.format( ', '.join(colourspaces), name), 'standalone': False } settings.update(kwargs) CIE_1960_UCS_chromaticity_diagram_plot(**settings) x_limit_min, x_limit_max = [-0.1], [0.7] y_limit_min, y_limit_max = [-0.2], [0.6] settings = { 'colour_cycle_map': 'rainbow', 'colour_cycle_count': len(colourspaces) } settings.update(kwargs) cycle = colour_cycle(**settings) for colourspace in colourspaces: if colourspace == 'Pointer Gamut': uv = UCS_to_uv(XYZ_to_UCS(xy_to_XYZ(POINTER_GAMUT_BOUNDARIES))) alpha_p, colour_p = 0.85, '0.95' pylab.plot(uv[..., 0], uv[..., 1], label='Pointer\'s Gamut', color=colour_p, alpha=alpha_p, linewidth=2) pylab.plot((uv[-1][0], uv[0][0]), (uv[-1][1], uv[0][1]), color=colour_p, alpha=alpha_p, linewidth=2) XYZ = Lab_to_XYZ(LCHab_to_Lab(POINTER_GAMUT_DATA), POINTER_GAMUT_ILLUMINANT) uv = UCS_to_uv(XYZ_to_UCS(XYZ)) pylab.scatter(uv[..., 0], uv[..., 1], alpha=alpha_p / 2, color=colour_p, marker='+') else: colourspace, name = get_RGB_colourspace(colourspace), colourspace r, g, b, _a = next(cycle) # RGB colourspaces such as *ACES2065-1* have primaries with # chromaticity coordinates set to 0 thus we prevent nan from being # yield by zero division in later colour transformations. P = np.where(colourspace.primaries == 0, EPSILON, colourspace.primaries) P = UCS_to_uv(XYZ_to_UCS(xy_to_XYZ(P))) W = UCS_to_uv(XYZ_to_UCS(xy_to_XYZ(colourspace.whitepoint))) pylab.plot((W[0], W[0]), (W[1], W[1]), color=(r, g, b), label=colourspace.name, linewidth=2) pylab.plot((W[0], W[0]), (W[1], W[1]), 'o', color=(r, g, b), linewidth=2) pylab.plot((P[0, 0], P[1, 0]), (P[0, 1], P[1, 1]), 'o-', color=(r, g, b), linewidth=2) pylab.plot((P[1, 0], P[2, 0]), (P[1, 1], P[2, 1]), 'o-', color=(r, g, b), linewidth=2) pylab.plot((P[2, 0], P[0, 0]), (P[2, 1], P[0, 1]), 'o-', color=(r, g, b), linewidth=2) x_limit_min.append(np.amin(P[..., 0]) - 0.1) y_limit_min.append(np.amin(P[..., 1]) - 0.1) x_limit_max.append(np.amax(P[..., 0]) + 0.1) y_limit_max.append(np.amax(P[..., 1]) + 0.1) settings.update({ 'legend': True, 'legend_location': 'upper right', 'x_tighten': True, 'y_tighten': True, 'limits': (min(x_limit_min), max(x_limit_max), min(y_limit_min), max(y_limit_max)), 'standalone': True }) settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**settings)
def plot_chromaticity_diagram_colours( samples: Integer = 256, diagram_colours: Optional[Union[ArrayLike, str]] = None, diagram_opacity: Floating = 1, diagram_clipping_path: Optional[ArrayLike] = None, cmfs: Union[MultiSpectralDistributions, str, Sequence[Union[ MultiSpectralDistributions, str]], ] = "CIE 1931 2 Degree Standard Observer", method: Union[Literal["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"], str] = "CIE 1931", **kwargs: Any, ) -> Tuple[plt.Figure, plt.Axes]: """ Plot the *Chromaticity Diagram* colours according to given method. Parameters ---------- samples Samples count on one axis when computing the *Chromaticity Diagram* colours. diagram_colours Colours of the *Chromaticity Diagram*, if ``diagram_colours`` is set to *RGB*, the colours will be computed according to the corresponding coordinates. diagram_opacity Opacity of the *Chromaticity Diagram*. diagram_clipping_path Path of points used to clip the *Chromaticity Diagram* colours. cmfs Standard observer colour matching functions used for computing the spectral locus boundaries. ``cmfs`` can be of any type or form supported by the :func:`colour.plotting.filter_cmfs` definition. method *Chromaticity Diagram* method. Other Parameters ---------------- kwargs {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, See the documentation of the previously listed definitions. Returns ------- :class:`tuple` Current figure and axes. Examples -------- >>> plot_chromaticity_diagram_colours(diagram_colours='RGB') ... # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, <...AxesSubplot...>) .. image:: ../_static/Plotting_Plot_Chromaticity_Diagram_Colours.png :align: center :alt: plot_chromaticity_diagram_colours """ method = validate_method(method, ["CIE 1931", "CIE 1960 UCS", "CIE 1976 UCS"]) settings: Dict[str, Any] = {"uniform": True} settings.update(kwargs) _figure, axes = artist(**settings) diagram_colours = cast( ArrayLike, optional(diagram_colours, HEX_to_RGB(CONSTANTS_COLOUR_STYLE.colour.average)), ) cmfs = cast(MultiSpectralDistributions, first_item(filter_cmfs(cmfs).values())) illuminant = CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint if method == "cie 1931": spectral_locus = XYZ_to_xy(cmfs.values, illuminant) elif method == "cie 1960 ucs": spectral_locus = UCS_to_uv(XYZ_to_UCS(cmfs.values)) elif method == "cie 1976 ucs": spectral_locus = Luv_to_uv(XYZ_to_Luv(cmfs.values, illuminant), illuminant) use_RGB_diagram_colours = str(diagram_colours).upper() == "RGB" if use_RGB_diagram_colours: ii, jj = np.meshgrid(np.linspace(0, 1, samples), np.linspace(1, 0, samples)) ij = tstack([ii, jj]) # NOTE: Various values in the grid have potential to generate # zero-divisions, they could be avoided by perturbing the grid, e.g. # adding a small epsilon. It was decided instead to disable warnings. with suppress_warnings(python_warnings=True): if method == "cie 1931": XYZ = xy_to_XYZ(ij) elif method == "cie 1960 ucs": XYZ = xy_to_XYZ(UCS_uv_to_xy(ij)) elif method == "cie 1976 ucs": XYZ = xy_to_XYZ(Luv_uv_to_xy(ij)) diagram_colours = normalise_maximum(XYZ_to_plotting_colourspace( XYZ, illuminant), axis=-1) polygon = Polygon( spectral_locus if diagram_clipping_path is None else diagram_clipping_path, facecolor="none" if use_RGB_diagram_colours else np.hstack( [diagram_colours, diagram_opacity]), edgecolor="none" if use_RGB_diagram_colours else np.hstack( [diagram_colours, diagram_opacity]), zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon, ) axes.add_patch(polygon) if use_RGB_diagram_colours: # Preventing bounding box related issues as per # https://github.com/matplotlib/matplotlib/issues/10529 image = axes.imshow( diagram_colours, interpolation="bilinear", extent=(0, 1, 0, 1), clip_path=None, alpha=diagram_opacity, zorder=CONSTANTS_COLOUR_STYLE.zorder.background_polygon, ) image.set_clip_path(polygon) settings = {"axes": axes} settings.update(kwargs) return render(**kwargs)
def XYZ_to_camera_space_matrix(xy, CCT_calibration_illuminant_1, CCT_calibration_illuminant_2, M_color_matrix_1, M_color_matrix_2, M_camera_calibration_1, M_camera_calibration_2, analog_balance): """ Returns the *CIE XYZ* to *Camera Space* matrix for given *xy* white balance chromaticity coordinates. Parameters ---------- xy : array_like *xy* white balance chromaticity coordinates. CCT_calibration_illuminant_1 : numeric Correlated colour temperature of *CalibrationIlluminant1*. CCT_calibration_illuminant_2 : numeric Correlated colour temperature of *CalibrationIlluminant2*. M_color_matrix_1 : array_like *ColorMatrix1* tag matrix. M_color_matrix_2 : array_like *ColorMatrix2* tag matrix. M_camera_calibration_1 : array_like *CameraCalibration1* tag matrix. M_camera_calibration_2 : array_like *CameraCalibration2* tag matrix. analog_balance : array_like *AnalogBalance* tag vector. Returns ------- ndarray *CIE XYZ* to *Camera Space* matrix. Notes ----- - The reference illuminant is D50 as defined per :attr:`colour_hdri.models.dataset.dng.ADOBE_DNG_XYZ_ILLUMINANT` attribute. References ---------- - :cite:`AdobeSystems2012f` - :cite:`AdobeSystems2015d` - :cite:`McGuffog2012a` Examples -------- >>> M_color_matrix_1 = np.array( ... [[0.5309, -0.0229, -0.0336], ... [-0.6241, 1.3265, 0.3337], ... [-0.0817, 0.1215, 0.6664]]) >>> M_color_matrix_2 = np.array( ... [[0.4716, 0.0603, -0.0830], ... [-0.7798, 1.5474, 0.2480], ... [-0.1496, 0.1937, 0.6651]]) >>> M_camera_calibration_1 = np.identity(3) >>> M_camera_calibration_2 = np.identity(3) >>> analog_balance = np.ones(3) >>> XYZ_to_camera_space_matrix( # doctest: +ELLIPSIS ... np.array([0.34510414, 0.35162252]), ... 2850, ... 6500, ... M_color_matrix_1, ... M_color_matrix_2, ... M_camera_calibration_1, ... M_camera_calibration_2, ... analog_balance) array([[ 0.4854908..., 0.0408106..., -0.0714282...], [-0.7433278..., 1.4956549..., 0.2680749...], [-0.1336946..., 0.1767874..., 0.6654045...]]) """ M_AB = np.diagflat(analog_balance) uv = UCS_to_uv(XYZ_to_UCS(xy_to_XYZ(xy))) CCT, _D_uv = uv_to_CCT_Robertson1968(uv) if is_identity(M_color_matrix_1) or is_identity(M_color_matrix_2): M_CM = (M_color_matrix_1 if is_identity(M_color_matrix_2) else M_color_matrix_2) else: M_CM = interpolated_matrix(CCT, CCT_calibration_illuminant_1, CCT_calibration_illuminant_2, M_color_matrix_1, M_color_matrix_2) M_CC = interpolated_matrix(CCT, CCT_calibration_illuminant_1, CCT_calibration_illuminant_2, M_camera_calibration_1, M_camera_calibration_2) M_XYZ_to_camera_space = dot_matrix(dot_matrix(M_AB, M_CC), M_CM) return M_XYZ_to_camera_space
def XYZ_to_colourspace_model(XYZ, illuminant, model): """ Converts from *CIE XYZ* tristimulus values to given colourspace model. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. illuminant : array_like *CIE XYZ* tristimulus values *illuminant* *xy* chromaticity coordinates. model : unicode **{'CIE XYZ', 'CIE xyY', 'CIE xy', 'CIE Lab', 'CIE LCHab', 'CIE Luv', 'CIE Luv uv', 'CIE LCHuv', 'CIE UCS', 'CIE UCS uv', 'CIE UVW', 'IPT', 'Hunter Lab', 'Hunter Rdab'}**, Colourspace model to convert the *CIE XYZ* tristimulus values to. Returns ------- ndarray Colourspace model values. Examples -------- >>> import numpy as np >>> XYZ = np.array([0.07049534, 0.10080000, 0.09558313]) >>> W = np.array([0.34570, 0.35850]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE XYZ') array([ 0.0704953..., 0.1008 , 0.0955831...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE xyY') array([ 0.2641477..., 0.3777000..., 0.1008 ]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE xy') array([ 0.2641477..., 0.3777000...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE Lab') array([ 37.9856291..., -23.6290768..., -4.4174661...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE LCHab') array([ 37.9856291..., 24.0384542..., 190.5892337...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE Luv') array([ 37.9856291..., -28.8021959..., -1.3580070...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE Luv uv') array([ 0.1508531..., 0.4853297...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE LCHuv') array([ 37.9856291..., 28.8341927..., 182.6994640...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE UCS uv') array([ 0.1508531..., 0.32355314...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE UVW') array([-28.0579733..., -0.8819449..., 37.0041149...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'IPT') array([ 0.3657112..., -0.1111479..., 0.0159474...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'Hunter Lab') array([ 31.7490157..., -15.1351736..., -2.7709606...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'Hunter Rdab') array([ 10.08..., -18.7019271..., -3.4239649...]) """ values = None if model == 'CIE XYZ': values = XYZ elif model == 'CIE xyY': values = XYZ_to_xyY(XYZ, illuminant) elif model == 'CIE xy': values = XYZ_to_xy(XYZ, illuminant) elif model == 'CIE Lab': values = XYZ_to_Lab(XYZ, illuminant) elif model == 'CIE LCHab': values = Lab_to_LCHab(XYZ_to_Lab(XYZ, illuminant)) elif model == 'CIE Luv': values = XYZ_to_Luv(XYZ, illuminant) elif model == 'CIE Luv uv': values = Luv_to_uv(XYZ_to_Luv(XYZ, illuminant), illuminant) elif model == 'CIE LCHuv': values = Luv_to_LCHuv(XYZ_to_Luv(XYZ, illuminant)) elif model == 'CIE UCS': values = XYZ_to_UCS(XYZ) elif model == 'CIE UCS uv': values = UCS_to_uv(XYZ_to_UCS(XYZ)) elif model == 'CIE UVW': values = XYZ_to_UVW(XYZ * 100, illuminant) elif model == 'IPT': values = XYZ_to_IPT(XYZ) elif model == 'Hunter Lab': values = XYZ_to_Hunter_Lab(XYZ * 100, xy_to_XYZ(illuminant) * 100) elif model == 'Hunter Rdab': values = XYZ_to_Hunter_Rdab(XYZ * 100, xy_to_XYZ(illuminant) * 100) if values is None: raise ValueError( '"{0}" not found in colourspace models: "{1}".'.format( model, ', '.join(COLOURSPACE_MODELS))) return values
def planckian_table(uv, cmfs, start, end, count): """ Returns a planckian table from given *CIE UCS* colourspace *uv* chromaticity coordinates, colour matching functions and temperature range using Ohno (2013) method. Parameters ---------- uv : array_like *uv* chromaticity coordinates. cmfs : XYZ_ColourMatchingFunctions Standard observer colour matching functions. start : numeric Temperature range start in kelvins. end : numeric Temperature range end in kelvins. count : int Temperatures count in the planckian table. Returns ------- list Planckian table. Examples -------- >>> from colour import STANDARD_OBSERVERS_CMFS >>> from pprint import pprint >>> cmfs = 'CIE 1931 2 Degree Standard Observer' >>> cmfs = STANDARD_OBSERVERS_CMFS.get(cmfs) >>> uv = np.array([0.1978, 0.3122]) >>> pprint(planckian_table( # doctest: +ELLIPSIS ... uv, cmfs, 1000, 1010, 10)) [PlanckianTable_Tuvdi(\ Ti=1000.0, ui=0.4480108..., vi=0.3546249..., di=0.2537821...), PlanckianTable_Tuvdi(\ Ti=1001.1111111..., ui=0.4477508..., vi=0.3546475..., di=0.2535294...), PlanckianTable_Tuvdi(\ Ti=1002.2222222..., ui=0.4474910..., vi=0.3546700..., di=0.2532771...), PlanckianTable_Tuvdi(\ Ti=1003.3333333..., ui=0.4472316..., vi=0.3546924..., di=0.2530251...), PlanckianTable_Tuvdi(\ Ti=1004.4444444..., ui=0.4469724..., vi=0.3547148..., di=0.2527734...), PlanckianTable_Tuvdi(\ Ti=1005.5555555..., ui=0.4467136..., vi=0.3547372..., di=0.2525220...), PlanckianTable_Tuvdi(\ Ti=1006.6666666..., ui=0.4464550..., vi=0.3547595..., di=0.2522710...), PlanckianTable_Tuvdi(\ Ti=1007.7777777..., ui=0.4461968..., vi=0.3547817..., di=0.2520202...), PlanckianTable_Tuvdi(\ Ti=1008.8888888..., ui=0.4459389..., vi=0.3548040..., di=0.2517697...), PlanckianTable_Tuvdi(\ Ti=1010.0, ui=0.4456812..., vi=0.3548261..., di=0.2515196...)] """ ux, vx = uv shape = cmfs.shape table = [] for Ti in np.linspace(start, end, count): spd = blackbody_spd(Ti, shape) XYZ = spectral_to_XYZ(spd, cmfs) XYZ *= 1 / np.max(XYZ) UVW = XYZ_to_UCS(XYZ) ui, vi = UCS_to_uv(UVW) di = np.sqrt((ux - ui)**2 + (vx - vi)**2) table.append(PLANCKIAN_TABLE_TUVD(Ti, ui, vi, di)) return table
def planckian_locus_CIE_1960_UCS_chromaticity_diagram_plot( illuminants=None, **kwargs): """ Plots the planckian locus and given illuminants in *CIE 1960 UCS Chromaticity Diagram*. Parameters ---------- illuminants : array_like, optional Factory illuminants to plot. \**kwargs : dict, optional Keywords arguments. Returns ------- bool Definition success. Raises ------ KeyError If one of the given illuminant is not found in the factory illuminants. Examples -------- >>> ils = ['A', 'C', 'E'] >>> planckian_locus_CIE_1960_UCS_chromaticity_diagram_plot( ... ils) # doctest: +SKIP True """ if illuminants is None: illuminants = ('A', 'C', 'E') cmfs = CMFS.get('CIE 1931 2 Degree Standard Observer') settings = { 'title': ('{0} Illuminants - Planckian Locus\n' 'CIE 1960 UCS Chromaticity Diagram - ' 'CIE 1931 2 Degree Standard Observer').format( ', '.join(illuminants)) if illuminants else ('Planckian Locus\nCIE 1960 UCS Chromaticity Diagram - ' 'CIE 1931 2 Degree Standard Observer'), 'standalone': False } settings.update(kwargs) CIE_1960_UCS_chromaticity_diagram_plot(**settings) xy_to_uv = lambda x: UCS_to_uv(XYZ_to_UCS(xy_to_XYZ(x))) start, end = 1667, 100000 uv = np.array([ CCT_to_uv(x, 0, method='Robertson 1968') for x in np.arange(start, end + 250, 250) ]) pylab.plot(uv[..., 0], uv[..., 1], color='black', linewidth=2) for i in (1667, 2000, 2500, 3000, 4000, 6000, 10000): u0, v0 = CCT_to_uv(i, -0.05, method='Robertson 1968') u1, v1 = CCT_to_uv(i, 0.05, method='Robertson 1968') pylab.plot((u0, u1), (v0, v1), color='black', linewidth=2) pylab.annotate('{0}K'.format(i), xy=(u0, v0), xytext=(0, -10), textcoords='offset points', size='x-small') for illuminant in illuminants: xy = ILLUMINANTS.get(cmfs.name).get(illuminant) if xy is None: raise KeyError( ('Illuminant "{0}" not found in factory illuminants: ' '"{1}".').format(illuminant, sorted(ILLUMINANTS.get(cmfs.name).keys()))) uv = xy_to_uv(xy) pylab.plot(uv[0], uv[1], 'o', color='white', linewidth=2) pylab.annotate(illuminant, xy=(uv[0], uv[1]), xytext=(-50, 30), textcoords='offset points', arrowprops=dict(arrowstyle='->', connectionstyle='arc3, rad=-0.2')) settings.update({ 'x_tighten': True, 'y_tighten': True, 'limits': (-0.1, 0.7, -0.2, 0.6), 'standalone': True }) settings.update(kwargs) boundaries(**settings) decorate(**settings) return display(**settings)
def colour_quality_scale(spd_test, additional_data=False): """ Returns the *Colour Quality Scale* (CQS) of given spectral power distribution. Parameters ---------- spd_test : SpectralPowerDistribution Test spectral power distribution. additional_data : bool, optional Output additional data. Returns ------- numeric or CQS_Specification Color quality scale. Examples -------- >>> from colour import ILLUMINANTS_RELATIVE_SPDS >>> spd = ILLUMINANTS_RELATIVE_SPDS['F2'] >>> colour_quality_scale(spd) # doctest: +ELLIPSIS 64.6864169... """ cmfs = STANDARD_OBSERVERS_CMFS[ 'CIE 1931 2 Degree Standard Observer'].clone().trim_wavelengths( ASTME30815_PRACTISE_SHAPE) shape = cmfs.shape spd_test = spd_test.clone().align(shape) vs_spds = {spd.name: spd.clone().align(shape) for spd in VS_SPDS.values()} XYZ = spectral_to_XYZ(spd_test, cmfs) uv = UCS_to_uv(XYZ_to_UCS(XYZ)) CCT, _D_uv = uv_to_CCT_Ohno2013(uv) if CCT < 5000: spd_reference = blackbody_spd(CCT, shape) else: xy = CCT_to_xy_CIE_D(CCT) spd_reference = D_illuminant_relative_spd(xy) spd_reference.align(shape) test_vs_colorimetry_data = vs_colorimetry_data(spd_test, spd_reference, vs_spds, cmfs, chromatic_adaptation=True) reference_vs_colorimetry_data = vs_colorimetry_data( spd_reference, spd_reference, vs_spds, cmfs) XYZ_r = spectral_to_XYZ(spd_reference, cmfs) XYZ_r /= XYZ_r[1] CCT_f = CCT_factor(reference_vs_colorimetry_data, XYZ_r) Q_as = colour_quality_scales(test_vs_colorimetry_data, reference_vs_colorimetry_data, CCT_f) D_E_RMS = delta_E_RMS(Q_as, 'D_E_ab') D_Ep_RMS = delta_E_RMS(Q_as, 'D_Ep_ab') Q_a = scale_conversion(D_Ep_RMS, CCT_f) Q_f = scale_conversion(D_E_RMS, CCT_f, 2.928) p_delta_C = np.average( [sample_data.D_C_ab if sample_data.D_C_ab > 0 else 0 for sample_data in Q_as.values()]) # yapf: disable Q_p = 100 - 3.6 * (D_Ep_RMS - p_delta_C) G_t = gamut_area( [vs_CQS_data.Lab for vs_CQS_data in test_vs_colorimetry_data]) G_r = gamut_area( [vs_CQS_data.Lab for vs_CQS_data in reference_vs_colorimetry_data]) Q_g = G_t / D65_GAMUT_AREA * 100 Q_d = G_t / G_r * CCT_f * 100 if additional_data: return CQS_Specification( spd_test.name, Q_a, Q_f, Q_p, Q_g, Q_d, Q_as, (test_vs_colorimetry_data, reference_vs_colorimetry_data)) else: return Q_a
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 XYZ_to_colourspace_model(XYZ, illuminant, model, **kwargs): """ Converts from *CIE XYZ* tristimulus values to given colourspace model. Parameters ---------- XYZ : array_like *CIE XYZ* tristimulus values. illuminant : array_like *CIE XYZ* tristimulus values *illuminant* *xy* chromaticity coordinates. model : unicode **{'CIE XYZ', 'CIE xyY', 'CIE xy', 'CIE Lab', 'CIE LCHab', 'CIE Luv', 'CIE Luv uv', 'CIE LCHuv', 'CIE UCS', 'CIE UCS uv', 'CIE UVW', 'DIN 99', 'Hunter Lab', 'Hunter Rdab', 'IPT', 'JzAzBz, 'OSA UCS', 'hdr-CIELAB', 'hdr-IPT'}**, Colourspace model to convert the *CIE XYZ* tristimulus values to. Other Parameters ---------------- \\**kwargs : dict, optional Keywords arguments. Returns ------- ndarray Colourspace model values. Examples -------- >>> import numpy as np >>> XYZ = np.array([0.20654008, 0.12197225, 0.05136952]) >>> W = np.array([0.31270, 0.32900]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE XYZ') array([ 0.2065400..., 0.1219722..., 0.0513695...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE xyY') array([ 0.5436955..., 0.3210794..., 0.1219722...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE xy') array([ 0.5436955..., 0.3210794...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE Lab') array([ 0.4152787..., 0.5263858..., 0.2692317...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE LCHab') array([ 0.4152787..., 0.5912425..., 0.0752458...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE Luv') array([ 0.4152787..., 0.9683626..., 0.1775210...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE Luv uv') array([ 0.3772021..., 0.5012026...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE LCHuv') array([ 0.4152787..., 0.9844997..., 0.0288560...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE UCS uv') array([ 0.3772021..., 0.3341350...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'CIE UVW') array([ 0.9455035..., 0.1155536..., 0.4054757...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'DIN 99') array([ 0.5322822..., 0.2841634..., 0.0389839...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'Hunter Lab') array([ 0.3492452..., 0.4703302..., 0.1439330...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'Hunter Rdab') array([ 0.1219722..., 0.5709032..., 0.1747109...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'IPT') array([ 0.3842619..., 0.3848730..., 0.1888683...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'JzAzBz') array([ 0.0053504..., 0.0092430..., 0.0052600...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'OSA UCS') array([-0.0300499..., 0.0299713..., -0.0966784...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'hdr-CIELAB') array([ 0.5187002..., 0.6047633..., 0.3214551...]) >>> XYZ_to_colourspace_model( # doctest: +ELLIPSIS ... XYZ, W, 'hdr-IPT') array([ 0.4839376..., 0.4244990..., 0.2201954...]) """ with domain_range_scale(1): values = None if model == 'CIE XYZ': values = XYZ elif model == 'CIE xyY': values = XYZ_to_xyY(XYZ, illuminant) elif model == 'CIE xy': values = XYZ_to_xy(XYZ, illuminant) elif model == 'CIE Lab': values = XYZ_to_Lab(XYZ, illuminant) elif model == 'CIE LCHab': values = Lab_to_LCHab(XYZ_to_Lab(XYZ, illuminant)) elif model == 'CIE Luv': values = XYZ_to_Luv(XYZ, illuminant) elif model == 'CIE Luv uv': values = Luv_to_uv(XYZ_to_Luv(XYZ, illuminant), illuminant) elif model == 'CIE LCHuv': values = Luv_to_LCHuv(XYZ_to_Luv(XYZ, illuminant)) elif model == 'CIE UCS': values = XYZ_to_UCS(XYZ) elif model == 'CIE UCS uv': values = UCS_to_uv(XYZ_to_UCS(XYZ)) elif model == 'CIE UVW': values = XYZ_to_UVW(XYZ, illuminant) elif model == 'DIN 99': values = Lab_to_DIN99(XYZ_to_Lab(XYZ, illuminant)) elif model == 'Hunter Lab': values = XYZ_to_Hunter_Lab(XYZ, xy_to_XYZ(illuminant)) elif model == 'Hunter Rdab': values = XYZ_to_Hunter_Rdab(XYZ, xy_to_XYZ(illuminant)) elif model == 'IPT': values = XYZ_to_IPT(XYZ) elif model == 'JzAzBz': values = XYZ_to_JzAzBz(XYZ) elif model == 'OSA UCS': values = XYZ_to_OSA_UCS(XYZ) elif model == 'hdr-CIELAB': values = XYZ_to_hdr_CIELab(XYZ, illuminant, **kwargs) elif model == 'hdr-IPT': values = XYZ_to_hdr_IPT(XYZ, **kwargs) if values is None: raise ValueError( '"{0}" not found in colourspace models: "{1}".'.format( model, ', '.join(COLOURSPACE_MODELS))) return values
def tcs_colorimetry_data( sd_t: SpectralDistribution, sd_r: SpectralDistribution, sds_tcs: Dict[str, SpectralDistribution], cmfs: MultiSpectralDistributions, chromatic_adaptation: Boolean = False, ) -> Tuple[TCS_ColorimetryData, ...]: """ Return the *test colour samples* colorimetry data. Parameters ---------- sd_t Test spectral distribution. sd_r Reference spectral distribution. sds_tcs *Test colour samples* spectral distributions. cmfs Standard observer colour matching functions. chromatic_adaptation Perform chromatic adaptation. Returns ------- :class:`tuple` *Test colour samples* colorimetry data. """ XYZ_t = sd_to_XYZ(sd_t, cmfs) uv_t = UCS_to_uv(XYZ_to_UCS(XYZ_t)) u_t, v_t = uv_t[0], uv_t[1] XYZ_r = sd_to_XYZ(sd_r, cmfs) uv_r = UCS_to_uv(XYZ_to_UCS(XYZ_r)) u_r, v_r = uv_r[0], uv_r[1] tcs_data = [] for _key, value in sorted(INDEXES_TO_NAMES_TCS.items()): sd_tcs = sds_tcs[value] XYZ_tcs = sd_to_XYZ(sd_tcs, cmfs, sd_t) xyY_tcs = XYZ_to_xyY(XYZ_tcs) uv_tcs = UCS_to_uv(XYZ_to_UCS(XYZ_tcs)) u_tcs, v_tcs = uv_tcs[0], uv_tcs[1] if chromatic_adaptation: def c( x: FloatingOrNDArray, y: FloatingOrNDArray ) -> FloatingOrNDArray: """Compute the :math:`c` term.""" return (4 - x - 10 * y) / y def d( x: FloatingOrNDArray, y: FloatingOrNDArray ) -> FloatingOrNDArray: """Compute the :math:`d` term.""" return (1.708 * y + 0.404 - 1.481 * x) / y c_t, d_t = c(u_t, v_t), d(u_t, v_t) c_r, d_r = c(u_r, v_r), d(u_r, v_r) tcs_c, tcs_d = c(u_tcs, v_tcs), d(u_tcs, v_tcs) u_tcs = ( 10.872 + 0.404 * c_r / c_t * tcs_c - 4 * d_r / d_t * tcs_d ) / (16.518 + 1.481 * c_r / c_t * tcs_c - d_r / d_t * tcs_d) v_tcs = 5.52 / ( 16.518 + 1.481 * c_r / c_t * tcs_c - d_r / d_t * tcs_d ) W_tcs = 25 * spow(xyY_tcs[-1], 1 / 3) - 17 U_tcs = 13 * W_tcs * (u_tcs - u_r) V_tcs = 13 * W_tcs * (v_tcs - v_r) tcs_data.append( TCS_ColorimetryData( sd_tcs.name, XYZ_tcs, uv_tcs, np.array([U_tcs, V_tcs, W_tcs]) ) ) return tuple(tcs_data)