def plot_spectra_TM_30_18(ax, spec): """ Plots a comparison of spectral distributions of a test and a reference illuminant, for use in *TM-30-18* color rendition reports. Parameters ========== spec : TM_30_18_Specification *TM-30-18* color fidelity specification. """ Y_reference = sd_to_XYZ(spec.sd_reference)[1] Y_test = sd_to_XYZ(spec.sd_test)[1] ax.plot(spec.sd_reference.wavelengths, spec.sd_reference.values / Y_reference, 'k', label='Reference') ax.plot(spec.sd_test.wavelengths, spec.sd_test.values / Y_test, 'r', label='Test') ax.set_yticks([]) ax.grid() ax.set_xlabel('Wavelength (nm)') ax.set_ylabel('Radiant power') ax.legend()
def test_domain_range_scale_XYZ_to_sd_Otsu2018(self): """ Test :func:`colour.recovery.otsu2018.XYZ_to_sd_Otsu2018` definition domain and range scale support. """ XYZ_i = np.array([0.20654008, 0.12197225, 0.05136952]) XYZ_o = sd_to_XYZ( XYZ_to_sd_Otsu2018(XYZ_i, self._cmfs, self._sd_D65), self._cmfs, self._sd_D65, ) d_r = (("reference", 1, 1), ("1", 1, 0.01), ("100", 100, 1)) for scale, factor_a, factor_b in d_r: with domain_range_scale(scale): np.testing.assert_almost_equal( sd_to_XYZ( XYZ_to_sd_Otsu2018( XYZ_i * factor_a, self._cmfs, self._sd_D65 ), self._cmfs, self._sd_D65, ), XYZ_o * factor_b, decimal=7, )
def test_optimise(self): """Test :class:`colour.recovery.otsu2018.Tree_Otsu2018.optimise` method.""" node_tree = Tree_Otsu2018(self._reflectances, self._cmfs, self._sd_D65) node_tree.optimise(iterations=5) dataset = node_tree.to_dataset() dataset.write(self._path) dataset = Dataset_Otsu2018() dataset.read(self._path) for sd in SDS_COLOURCHECKERS["ColorChecker N Ohta"].values(): XYZ = sd_to_XYZ(sd, self._cmfs, self._sd_D65) / 100 Lab = XYZ_to_Lab(XYZ, self._xy_D65) recovered_sd = XYZ_to_sd_Otsu2018( XYZ, self._cmfs, self._sd_D65, dataset, False ) recovered_XYZ = ( sd_to_XYZ(recovered_sd, self._cmfs, self._sd_D65) / 100 ) recovered_Lab = XYZ_to_Lab(recovered_XYZ, self._xy_D65) error = metric_mse( reshape_sd(sd, SPECTRAL_SHAPE_OTSU2018).values, recovered_sd.values, ) self.assertLess(error, 0.075) delta_E = delta_E_CIE1976(Lab, recovered_Lab) self.assertLess(delta_E, 1e-12)
def plot_spectra_ANSIIESTM3018(specification, **kwargs): """ Plots a comparison of the spectral distributions of a test emission source and a reference illuminant for *ANSI/IES TM-30-18 Colour Rendition Report*. Parameters ---------- specification : ColourQuality_Specification_ANSIIESTM3018 *ANSI/IES TM-30-18 Colour Rendition Report* specification. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes Examples -------- >>> from colour import SDS_ILLUMINANTS >>> from colour.quality import colour_fidelity_index_ANSIIESTM3018 >>> sd = SDS_ILLUMINANTS['FL2'] >>> specification = colour_fidelity_index_ANSIIESTM3018(sd, True) >>> plot_spectra_ANSIIESTM3018(specification) ... # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, <...AxesSubplot...>) """ settings = kwargs.copy() _figure, axes = artist(**settings) Y_reference = sd_to_XYZ(specification.sd_reference)[1] Y_test = sd_to_XYZ(specification.sd_test)[1] axes.plot(specification.sd_reference.wavelengths, specification.sd_reference.values / Y_reference, 'black', label='Reference') axes.plot(specification.sd_test.wavelengths, specification.sd_test.values / Y_test, '#F05046', label='Test') axes.tick_params(axis='y', which='both', length=0) axes.set_yticklabels([]) settings = { 'axes': axes, 'legend': True, 'legend_columns': 2, 'x_label': 'Wavelength (nm)', 'y_label': 'Radiant Power\n(Equal Luminous Flux)', } settings.update(kwargs) return render(**settings)
def vs_colorimetry_data( sd_test: SpectralDistribution, sd_reference: SpectralDistribution, sds_vs: Dict[str, SpectralDistribution], cmfs: MultiSpectralDistributions, chromatic_adaptation: Boolean = False, ) -> Tuple[VS_ColorimetryData, ...]: """ Return the *VS test colour samples* colorimetry data. Parameters ---------- sd_test Test spectral distribution. sd_reference Reference spectral distribution. sds_vs *VS test colour samples* spectral distributions. cmfs Standard observer colour matching functions. chromatic_adaptation Whether to perform chromatic adaptation. Returns ------- :class:`tuple` *VS test colour samples* colorimetry data. """ XYZ_t = sd_to_XYZ(sd_test, cmfs) XYZ_t /= XYZ_t[1] XYZ_r = sd_to_XYZ(sd_reference, cmfs) XYZ_r /= XYZ_r[1] xy_r = XYZ_to_xy(XYZ_r) vs_data = [] for _key, value in sorted(INDEXES_TO_NAMES_VS.items()): sd_vs = sds_vs[value] with domain_range_scale("1"): XYZ_vs = sd_to_XYZ(sd_vs, cmfs, sd_test) if chromatic_adaptation: XYZ_vs = chromatic_adaptation_VonKries(XYZ_vs, XYZ_t, XYZ_r, transform="CMCCAT2000") Lab_vs = XYZ_to_Lab(XYZ_vs, illuminant=xy_r) _L_vs, C_vs, _Hab = Lab_to_LCHab(Lab_vs) vs_data.append(VS_ColorimetryData(sd_vs.name, XYZ_vs, Lab_vs, C_vs)) return tuple(vs_data)
def vs_colorimetry_data(sd_test, sd_reference, sds_vs, cmfs, chromatic_adaptation=False): """ Returns the *VS test colour samples* colorimetry data. Parameters ---------- sd_test : SpectralDistribution Test spectral distribution. sd_reference : SpectralDistribution Reference spectral distribution. sds_vs : dict *VS test colour samples* spectral distributions. cmfs : XYZ_ColourMatchingFunctions Standard observer colour matching functions. chromatic_adaptation : bool, optional Perform chromatic adaptation. Returns ------- list *VS test colour samples* colorimetry data. """ XYZ_t = sd_to_XYZ(sd_test, cmfs) XYZ_t /= XYZ_t[1] XYZ_r = sd_to_XYZ(sd_reference, cmfs) XYZ_r /= XYZ_r[1] xy_r = XYZ_to_xy(XYZ_r) vs_data = [] for _key, value in sorted(VS_INDEXES_TO_NAMES.items()): sd_vs = sds_vs[value] with domain_range_scale('1'): XYZ_vs = sd_to_XYZ(sd_vs, cmfs, sd_test) if chromatic_adaptation: XYZ_vs = chromatic_adaptation_VonKries(XYZ_vs, XYZ_t, XYZ_r, transform='CMCCAT2000') Lab_vs = XYZ_to_Lab(XYZ_vs, illuminant=xy_r) _L_vs, C_vs, _Hab = Lab_to_LCHab(Lab_vs) vs_data.append(VS_ColorimetryData(sd_vs.name, XYZ_vs, Lab_vs, C_vs)) return vs_data
def _CCT_to_uv_Ohno2013( CCT_D_uv, cmfs=MSDS_CMFS_STANDARD_OBSERVER['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_D_uv : ndarray Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`. cmfs : XYZ_ColourMatchingFunctions, optional Standard observer colour matching functions. Returns ------- ndarray *CIE UCS* colourspace *uv* chromaticity coordinates. """ CCT, D_uv = tsplit(CCT_D_uv) cmfs = cmfs.copy().trim(SPECTRAL_SHAPE_DEFAULT) 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 _CCT_to_uv_Ohno2013( CCT_D_uv, 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_D_uv : ndarray Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`. cmfs : XYZ_ColourMatchingFunctions, optional Standard observer colour matching functions. Returns ------- ndarray *CIE UCS* colourspace *uv* chromaticity coordinates. """ CCT, D_uv = tsplit(CCT_D_uv) 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 vs_colorimetry_data(sd_test, sd_reference, sds_vs, cmfs, chromatic_adaptation=False): """ Returns the *VS test colour samples* colorimetry data. Parameters ---------- sd_test : SpectralDistribution Test spectral distribution. sd_reference : SpectralDistribution Reference spectral distribution. sds_vs : dict *VS test colour samples* spectral distributions. cmfs : XYZ_ColourMatchingFunctions Standard observer colour matching functions. chromatic_adaptation : bool, optional Perform chromatic adaptation. Returns ------- list *VS test colour samples* colorimetry data. """ XYZ_t = sd_to_XYZ(sd_test, cmfs) XYZ_t /= XYZ_t[1] XYZ_r = sd_to_XYZ(sd_reference, cmfs) XYZ_r /= XYZ_r[1] xy_r = XYZ_to_xy(XYZ_r) vs_data = [] for _key, value in sorted(VS_INDEXES_TO_NAMES.items()): sd_vs = sds_vs[value] with domain_range_scale('1'): XYZ_vs = sd_to_XYZ(sd_vs, cmfs, sd_test) if chromatic_adaptation: XYZ_vs = chromatic_adaptation_VonKries( XYZ_vs, XYZ_t, XYZ_r, transform='CMCCAT2000') Lab_vs = XYZ_to_Lab(XYZ_vs, illuminant=xy_r) _L_vs, C_vs, _Hab = Lab_to_LCHab(Lab_vs) vs_data.append(VS_ColorimetryData(sd_vs.name, XYZ_vs, Lab_vs, C_vs)) return vs_data
def _CCT_to_uv_Ohno2013( CCT_D_uv: ArrayLike, cmfs: Optional[MultiSpectralDistributions] = None) -> NDArray: """ Return 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_D_uv Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`. cmfs Standard observer colour matching functions, default to the *CIE 1931 2 Degree Standard Observer*. Returns ------- :class:`numpy.ndarray` *CIE UCS* colourspace *uv* chromaticity coordinates. """ CCT, D_uv = tsplit(CCT_D_uv) cmfs, _illuminant = handle_spectral_arguments(cmfs) delta = 0.01 sd = sd_blackbody(CCT, cmfs.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, cmfs.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 tcs_colorimetry_data( sd_irradiance: SpectralDistribution, sds_tcs: MultiSpectralDistributions, cmfs: MultiSpectralDistributions, ) -> Tuple[TCS_ColorimetryData_CIE2017, ...]: """ Return the *test colour samples* colorimetry data under given test light source or reference illuminant spectral distribution for the *CIE 2017 Colour Fidelity Index* (CFI) computations. Parameters ---------- sd_irradiance Test light source or reference illuminant spectral distribution, i.e. the irradiance emitter. sds_tcs *Test colour samples* spectral distributions. cmfs Standard observer colour matching functions. Returns ------- :class:`tuple` *Test colour samples* colorimetry data under the given test light source or reference illuminant spectral distribution. Examples -------- >>> delta_E_to_R_f(4.4410383190) # doctest: +ELLIPSIS 70.1208254... """ XYZ_w = sd_to_XYZ(sd_ones(), cmfs, sd_irradiance) Y_b = 20 L_A = 100 surround = VIEWING_CONDITIONS_CIECAM02["Average"] tcs_data = [] for sd_tcs in sds_tcs.to_sds(): XYZ = sd_to_XYZ(sd_tcs, cmfs, sd_irradiance) CAM = XYZ_to_CIECAM02(XYZ, XYZ_w, L_A, Y_b, surround, True) JMh = tstack([CAM.J, CAM.M, CAM.h]) Jpapbp = JMh_CIECAM02_to_CAM02UCS(JMh) tcs_data.append( TCS_ColorimetryData_CIE2017(sd_tcs.name, XYZ, CAM, JMh, Jpapbp)) return tuple(tcs_data)
def CCT_reference_illuminant(sd: SpectralDistribution) -> NDArray: """ Compute the reference illuminant correlated colour temperature :math:`T_{cp}` and :math:`\\Delta_{uv}` for given test spectral distribution using *Ohno (2013)* method. Parameters ---------- sd Test spectral distribution. Returns ------- :class:`numpy.ndarray` Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`. Examples -------- >>> from colour import SDS_ILLUMINANTS >>> sd = SDS_ILLUMINANTS['FL2'] >>> CCT_reference_illuminant(sd) # doctest: +ELLIPSIS array([ 4.2244697...e+03, 1.7871111...e-03]) """ XYZ = sd_to_XYZ(sd) return uv_to_CCT_Ohno2013(UCS_to_uv(XYZ_to_UCS(XYZ)))
def setUp(self): """Initialise the common tests attributes.""" self._shape = SPECTRAL_SHAPE_OTSU2018 self._cmfs, self._sd_D65 = handle_spectral_arguments( shape_default=self._shape ) self._reflectances = sds_and_msds_to_msds( list(SDS_COLOURCHECKERS["ColorChecker N Ohta"].values()) + list(SDS_COLOURCHECKERS["BabelColor Average"].values()) ) self._tree = Tree_Otsu2018( self._reflectances, self._cmfs, self._sd_D65 ) self._XYZ_D65 = sd_to_XYZ(self._sd_D65) self._xy_D65 = XYZ_to_xy(self._XYZ_D65) self._temporary_directory = tempfile.mkdtemp() self._path = os.path.join( self._temporary_directory, "Test_Otsu2018.npz" )
def CCT_reference_illuminant(sd): """ Computes the reference illuminant correlated colour temperature :math:`T_{cp}` and :math:`\\Delta_{uv}` for given test spectral distribution using *Ohno (2013)* method. Parameters ---------- sd : SpectralDistribution Test spectral distribution. Returns ------- ndarray Correlated colour temperature :math:`T_{cp}`, :math:`\\Delta_{uv}`. Examples -------- >>> from colour import SDS_ILLUMINANTS >>> sd = SDS_ILLUMINANTS['FL2'] >>> CCT_reference_illuminant(sd) # doctest: +ELLIPSIS (4224.4697052..., 0.0017871...) """ XYZ = sd_to_XYZ(sd) CCT, D_uv = uv_to_CCT_Ohno2013(UCS_to_uv(XYZ_to_UCS(XYZ))) return CCT, D_uv
def setUp(self): """Initialise the common tests attributes.""" self._shape = SPECTRAL_SHAPE_OTSU2018 self._cmfs, self._sd_D65 = handle_spectral_arguments( shape_default=self._shape ) self._XYZ_D65 = sd_to_XYZ(self._sd_D65) self._xy_D65 = XYZ_to_xy(self._XYZ_D65)
def test_sd_to_XYZ(self): """ Test :func:`colour.colorimetry.tristimulus_values.sd_to_XYZ` definition. """ # Testing that the cache returns a copy of the data. XYZ = sd_to_XYZ(self._sd, self._cmfs, self._A) np.testing.assert_almost_equal( XYZ, np.array([14.46372680, 10.85832950, 2.04663200]), decimal=7) XYZ *= 10 np.testing.assert_almost_equal( sd_to_XYZ(self._sd, self._cmfs, self._A), np.array([14.46372680, 10.85832950, 2.04663200]), decimal=7, )
def test_domain_range_scale_XYZ_to_sd_Jakob2019(self): """ Tests :func:`colour.recovery.jakob2019.XYZ_to_sd_Jakob2019` definition domain and range scale support. """ XYZ_i = np.array([0.20654008, 0.12197225, 0.05136952]) XYZ_o = sd_to_XYZ(XYZ_to_sd_Jakob2019(XYZ_i, self._cmfs, self._sd_D65), self._cmfs, self._sd_D65) d_r = (('reference', 1, 1), (1, 1, 0.01), (100, 100, 1)) for scale, factor_a, factor_b in d_r: with domain_range_scale(scale): np.testing.assert_almost_equal(sd_to_XYZ( XYZ_to_sd_Jakob2019(XYZ_i * factor_a, self._cmfs, self._sd_D65), self._cmfs, self._sd_D65), XYZ_o * factor_b, decimal=7)
def check_basis_functions(self): """ Test :func:`colour.recovery.RGB_to_sd_Mallett2019` definition or the more specialised :func:`colour.recovery.RGB_to_sd_Mallett2019` definition. """ # Make sure the white point is reconstructed as a perfectly flat # spectrum. RGB = np.full(3, 1.0) sd = RGB_to_sd_Mallett2019(RGB, self._basis) self.assertLess(np.var(sd.values), 1e-5) # Check if the primaries or their combination exceeds the [0, 1] range. lower = np.zeros_like(sd.values) - 1e-12 upper = np.ones_like(sd.values) + 1e12 for RGB in [[1, 1, 1], [1, 0, 0], [0, 1, 0], [0, 0, 1]]: sd = RGB_to_sd_Mallett2019(RGB, self._basis) np.testing.assert_array_less(sd.values, upper) np.testing.assert_array_less(lower, sd.values) # Check Delta E's using a colour checker. for name, sd in SDS_COLOURCHECKERS["ColorChecker N Ohta"].items(): XYZ = sd_to_XYZ(sd, self._cmfs, self._sd_D65) / 100 Lab = XYZ_to_Lab(XYZ, self._xy_D65) RGB = XYZ_to_RGB( XYZ, self._RGB_colourspace.whitepoint, self._xy_D65, self._RGB_colourspace.matrix_XYZ_to_RGB, ) recovered_sd = RGB_to_sd_Mallett2019(RGB, self._basis) recovered_XYZ = ( sd_to_XYZ(recovered_sd, self._cmfs, self._sd_D65) / 100 ) recovered_Lab = XYZ_to_Lab(recovered_XYZ, self._xy_D65) error = delta_E_CIE1976(Lab, recovered_Lab) if error > 4 * JND_CIE1976 / 100: # pragma: no cover self.fail(f'Delta E for "{name}" is {error}!')
def test_NodeTree_Otsu2018_and_Dataset_Otsu2018(self): """ Tests :class:`colour.recovery.otsu2018.NodeTree_Otsu2018` dataset generation and :class:`colour.recovery.otsu2018.Dataset_Otsu2018` input and output. The generated dataset is also tested for reconstruction errors. """ reflectances = [] for colourchecker in ['ColorChecker N Ohta', 'BabelColor Average']: for sd in SDS_COLOURCHECKERS[colourchecker].values(): reflectances.append(sd.copy().align(self._shape).values) node_tree = NodeTree_Otsu2018(reflectances, self._cmfs, self._sd_D65) node_tree.optimise(iterations=2) path = os.path.join(self._temporary_directory, 'Test_Otsu2018.npz') dataset = node_tree.to_dataset() dataset.write(path) dataset = Dataset_Otsu2018() dataset.read(path) for sd in SDS_COLOURCHECKERS['ColorChecker N Ohta'].values(): XYZ = sd_to_XYZ(sd, self._cmfs, self._sd_D65) / 100 Lab = XYZ_to_Lab(XYZ, self._xy_D65) recovered_sd = XYZ_to_sd_Otsu2018(XYZ, self._cmfs, self._sd_D65, dataset, False) recovered_XYZ = sd_to_XYZ(recovered_sd, self._cmfs, self._sd_D65) / 100 recovered_Lab = XYZ_to_Lab(recovered_XYZ, self._xy_D65) error = metric_mse(sd.copy().align(SPECTRAL_SHAPE_OTSU2018).values, recovered_sd.values) self.assertLess(error, 0.075) delta_E = delta_E_CIE1976(Lab, recovered_Lab) self.assertLess(delta_E, 1e-12)
def test_XYZ_to_sd_Otsu2018(self): """Test :func:`colour.recovery.otsu2018.XYZ_to_sd_Otsu2018` definition.""" # Tests the round-trip with values of a colour checker. for _name, sd in SDS_COLOURCHECKERS["ColorChecker N Ohta"].items(): XYZ = sd_to_XYZ(sd, self._cmfs, self._sd_D65) / 100 Lab = XYZ_to_Lab(XYZ, self._xy_D65) recovered_sd = XYZ_to_sd_Otsu2018( XYZ, self._cmfs, self._sd_D65, clip=False ) recovered_XYZ = ( sd_to_XYZ(recovered_sd, self._cmfs, self._sd_D65) / 100 ) recovered_Lab = XYZ_to_Lab(recovered_XYZ, self._xy_D65) error = metric_mse( reshape_sd(sd, SPECTRAL_SHAPE_OTSU2018).values, recovered_sd.values, ) self.assertLess(error, 0.02) delta_E = delta_E_CIE1976(Lab, recovered_Lab) self.assertLess(delta_E, 1e-12)
def test_XYZ_to_sd_Jakob2019(self): """ Tests :func:`colour.recovery.jakob2019.XYZ_to_sd_Jakob2019` definition. """ # Tests the round-trip with values of a colour checker. for name, sd in SDS_COLOURCHECKERS['ColorChecker N Ohta'].items(): XYZ = sd_to_XYZ(sd, self._cmfs, self._sd_D65) / 100 _recovered_sd, error = XYZ_to_sd_Jakob2019(XYZ, self._cmfs, self._sd_D65, additional_data=True) if error > JND_CIE1976 / 100: self.fail('Delta E for \'{0}\' is {1}!'.format(name, error))
def setUp(self): """ Initialises common tests attributes. """ self._shape = SPECTRAL_SHAPE_JAKOB2019 self._cmfs = MSDS_CMFS_STANDARD_OBSERVER[ 'CIE 1931 2 Degree Standard Observer'].copy().align(self._shape) self._sd_D65 = SDS_ILLUMINANTS['D65'].copy().align(self._shape) self._XYZ_D65 = sd_to_XYZ(self._sd_D65) self._XYZ_D65 /= self._XYZ_D65[1] self._xy_D65 = CCS_ILLUMINANTS['CIE 1931 2 Degree Standard Observer'][ 'D65'] self._Lab_e = np.array([72, -20, 61])
def test_intermediates(self): """ Tests intermediate results of :func:`colour.recovery.jakob2019.error_function` with :func:`colour.sd_to_XYZ`, :func:`colour.XYZ_to_Lab` and checks if the error is computed correctly by comparing it with :func:`colour.difference.delta_E_CIE1976`. """ # Quoted names refer to colours from ColorChecker N Ohta (using D65). coefficient_list = [ np.array([0, 0, 0]), # 50% gray np.array([0, 0, -1e+9]), # Pure black np.array([0, 0, +1e+9]), # Pure white np.array([1e+9, -1e+9, 2.1e+8]), # A pathological example np.array([2.2667394, -7.6313081, 1.03185]), # 'blue' np.array([-31.377077, 26.810094, -6.1139927]), # 'green' np.array([25.064246, -16.072039, 0.10431365]), # 'red' np.array([-19.325667, 22.242319, -5.8144924]), # 'yellow' np.array([21.909902, -17.227963, 2.142351]), # 'magenta' np.array([-15.864009, 8.6735071, -1.4012552]), # 'cyan' ] for coefficients in coefficient_list: error, _derror, R, XYZ, Lab = error_function(coefficients, self._Lab_e, self._cmfs, self._sd_D65, additional_data=True) sd = sd_Jakob2019( dimensionalise_coefficients(coefficients, self._shape), self._shape) sd_XYZ = sd_to_XYZ(sd, self._cmfs, self._sd_D65) / 100 sd_Lab = XYZ_to_Lab(XYZ, self._xy_D65) error_reference = delta_E_CIE1976(self._Lab_e, Lab) np.testing.assert_allclose(sd.values, R, atol=1e-14) np.testing.assert_allclose(XYZ, sd_XYZ, atol=1e-14) self.assertLess(abs(error_reference - error), JND_CIE1976 / 100) self.assertLess(delta_E_CIE1976(Lab, sd_Lab), JND_CIE1976 / 100)
def test_LUT3D_Jakob2019(self): """ Tests the entirety of the :class:`colour.recovery.jakob2019.LUT3D_Jakob2019`class. """ LUT = LUT3D_Jakob2019() LUT.generate(self._RGB_colourspace, self._cmfs, self._sd_D65, 5) path = os.path.join(self._temporary_directory, 'Test_Jakob2019.coeff') LUT.write(path) LUT.read(path) for RGB in [ np.array([1, 0, 0]), np.array([0, 1, 0]), np.array([0, 0, 1]), zeros(3), full(3, 0.5), ones(3), ]: XYZ = RGB_to_XYZ(RGB, self._RGB_colourspace.whitepoint, self._xy_D65, self._RGB_colourspace.matrix_RGB_to_XYZ) Lab = XYZ_to_Lab(XYZ, self._xy_D65) recovered_sd = LUT.RGB_to_sd(RGB) recovered_XYZ = sd_to_XYZ(recovered_sd, self._cmfs, self._sd_D65) / 100 recovered_Lab = XYZ_to_Lab(recovered_XYZ, self._xy_D65) error = delta_E_CIE1976(Lab, recovered_Lab) if error > 2 * JND_CIE1976 / 100: self.fail( 'Delta E for RGB={0} in colourspace {1} is {2}!'.format( RGB, self._RGB_colourspace.name, error))
def plot_blackbody_colours( shape=SpectralShape(150, 12500, 50), cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots blackbody colours. Parameters ---------- shape : SpectralShape, optional Spectral shape to use as plot boundaries. cmfs : unicode, optional Standard observer colour matching functions. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_blackbody_colours(SpectralShape(150, 12500, 50)) # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Blackbody_Colours.png :align: center :alt: plot_blackbody_colours """ _figure, axes = artist(**kwargs) cmfs = first_item(filter_cmfs(cmfs).values()) colours = [] temperatures = [] for temperature in shape: sd = sd_blackbody(temperature, cmfs.shape) with domain_range_scale('1'): XYZ = sd_to_XYZ(sd, cmfs) RGB = normalise_maximum(XYZ_to_plotting_colourspace(XYZ)) colours.append(RGB) temperatures.append(temperature) x_min, x_max = min(temperatures), max(temperatures) y_min, y_max = 0, 1 padding = 0.1 axes.bar( x=np.array(temperatures) - padding, height=1, width=shape.interval + (padding * shape.interval), color=colours, align='edge') settings = { 'axes': axes, 'bounding_box': (x_min, x_max, y_min, y_max), 'title': 'Blackbody Colours', 'x_label': 'Temperature K', 'y_label': None, } settings.update(kwargs) return render(**settings)
def plot_blackbody_spectral_radiance( temperature=3500, cmfs='CIE 1931 2 Degree Standard Observer', blackbody='VY Canis Major', **kwargs): """ Plots given blackbody spectral radiance. Parameters ---------- temperature : numeric, optional Blackbody temperature. cmfs : unicode, optional Standard observer colour matching functions. blackbody : unicode, optional Blackbody name. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.plot_single_sd`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_blackbody_spectral_radiance(3500, blackbody='VY Canis Major') ... # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Blackbody_Spectral_Radiance.png :align: center :alt: plot_blackbody_spectral_radiance """ figure = plt.figure() figure.subplots_adjust(hspace=COLOUR_STYLE_CONSTANTS.geometry.short / 2) cmfs = first_item(filter_cmfs(cmfs).values()) sd = sd_blackbody(temperature, cmfs.shape) axes = figure.add_subplot(211) settings = { 'axes': axes, 'title': '{0} - Spectral Radiance'.format(blackbody), 'y_label': 'W / (sr m$^2$) / m', } settings.update(kwargs) settings['standalone'] = False plot_single_sd(sd, cmfs.name, **settings) axes = figure.add_subplot(212) with domain_range_scale('1'): XYZ = sd_to_XYZ(sd, cmfs) RGB = normalise_maximum(XYZ_to_plotting_colourspace(XYZ)) settings = { 'axes': axes, 'aspect': None, 'title': '{0} - Colour'.format(blackbody), 'x_label': '{0}K'.format(temperature), 'y_label': '', 'x_ticker': False, 'y_ticker': False, } settings.update(kwargs) settings['standalone'] = False figure, axes = plot_single_colour_swatch( ColourSwatch(name='', RGB=RGB), **settings) settings = {'axes': axes, 'standalone': True} settings.update(kwargs) return render(**settings)
def plot_multi_sds(sds, cmfs='CIE 1931 2 Degree Standard Observer', use_sds_colours=False, normalise_sds_colours=False, **kwargs): """ Plots given spectral distributions. Parameters ---------- sds : array_like or MultiSpectralDistribution Spectral distributions or multi-spectral distributions to plot. `sds` can be a single :class:`colour.MultiSpectralDistribution` class instance, a list of :class:`colour.MultiSpectralDistribution` class instances or a list of :class:`colour.SpectralDistribution` class instances. cmfs : unicode, optional Standard observer colour matching functions used for spectrum creation. use_sds_colours : bool, optional Whether to use spectral distributions colours. normalise_sds_colours : bool Whether to normalise spectral distributions colours. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> from colour import SpectralDistribution >>> data_1 = { ... 500: 0.004900, ... 510: 0.009300, ... 520: 0.063270, ... 530: 0.165500, ... 540: 0.290400, ... 550: 0.433450, ... 560: 0.594500 ... } >>> data_2 = { ... 500: 0.323000, ... 510: 0.503000, ... 520: 0.710000, ... 530: 0.862000, ... 540: 0.954000, ... 550: 0.994950, ... 560: 0.995000 ... } >>> spd1 = SpectralDistribution(data_1, name='Custom 1') >>> spd2 = SpectralDistribution(data_2, name='Custom 2') >>> plot_multi_sds([spd1, spd2]) # doctest: +SKIP .. image:: ../_static/Plotting_Plot_Multi_SDs.png :align: center :alt: plot_multi_sds """ _figure, axes = artist(**kwargs) if isinstance(sds, MultiSpectralDistribution): sds = sds.to_sds() else: sds = list(sds) for i, sd in enumerate(sds[:]): if isinstance(sd, MultiSpectralDistribution): sds.remove(sd) sds[i:i] = sd.to_sds() cmfs = first_item(filter_cmfs(cmfs).values()) illuminant = ILLUMINANTS_SDS[ COLOUR_STYLE_CONSTANTS.colour.colourspace.illuminant] x_limit_min, x_limit_max, y_limit_min, y_limit_max = [], [], [], [] for sd in sds: wavelengths, values = sd.wavelengths, sd.values shape = sd.shape x_limit_min.append(shape.start) x_limit_max.append(shape.end) y_limit_min.append(min(values)) y_limit_max.append(max(values)) if use_sds_colours: with domain_range_scale('1'): XYZ = sd_to_XYZ(sd, cmfs, illuminant) if normalise_sds_colours: XYZ = normalise_maximum(XYZ, clip=False) RGB = np.clip(XYZ_to_plotting_colourspace(XYZ), 0, 1) axes.plot(wavelengths, values, color=RGB, label=sd.strict_name) else: axes.plot(wavelengths, values, label=sd.strict_name) bounding_box = (min(x_limit_min), max(x_limit_max), min(y_limit_min), max(y_limit_max) + max(y_limit_max) * 0.05) settings = { 'axes': axes, 'bounding_box': bounding_box, 'legend': True, 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': 'Spectral Distribution', } settings.update(kwargs) return render(**settings)
def 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['FL2'] >>> colour_rendering_index(sd) # doctest: +ELLIPSIS 64.1515202... """ cmfs = STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'].copy( ).trim(DEFAULT_SPECTRAL_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 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 generate_documentation_plots(output_directory): """ Generates documentation plots. Parameters ---------- output_directory : unicode Output directory. """ filter_warnings() colour_style() np.random.seed(0) # ************************************************************************* # "README.rst" # ************************************************************************* filename = os.path.join(output_directory, 'Examples_Colour_Automatic_Conversion_Graph.png') plot_automatic_colour_conversion_graph(filename) arguments = { 'tight_layout': True, 'transparent_background': True, 'filename': os.path.join(output_directory, 'Examples_Plotting_Visible_Spectrum.png') } plt.close( plot_visible_spectrum('CIE 1931 2 Degree Standard Observer', **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Examples_Plotting_Illuminant_F1_SD.png') plt.close(plot_single_illuminant_sd('FL1', **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Examples_Plotting_Blackbodies.png') blackbody_sds = [ sd_blackbody(i, SpectralShape(0, 10000, 10)) for i in range(1000, 15000, 1000) ] plt.close( plot_multi_sds(blackbody_sds, y_label='W / (sr m$^2$) / m', use_sds_colours=True, normalise_sds_colours=True, legend_location='upper right', bounding_box=(0, 1250, 0, 2.5e15), **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Examples_Plotting_Cone_Fundamentals.png') plt.close( plot_single_cmfs('Stockman & Sharpe 2 Degree Cone Fundamentals', y_label='Sensitivity', bounding_box=(390, 870, 0, 1.1), **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Examples_Plotting_Luminous_Efficiency.png') plt.close( plot_multi_sds((sd_mesopic_luminous_efficiency_function(0.2), PHOTOPIC_LEFS['CIE 1924 Photopic Standard Observer'], SCOTOPIC_LEFS['CIE 1951 Scotopic Standard Observer']), y_label='Luminous Efficiency', legend_location='upper right', y_tighten=True, margins=(0, 0, 0, .1), **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Examples_Plotting_BabelColor_Average.png') plt.close( plot_multi_sds(COLOURCHECKERS_SDS['BabelColor Average'].values(), use_sds_colours=True, title=('BabelColor Average - ' 'Spectral Distributions'), **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Examples_Plotting_ColorChecker_2005.png') plt.close( plot_single_colour_checker('ColorChecker 2005', text_parameters={'visible': False}, **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Examples_Plotting_Chromaticities_Prediction.png') plt.close( plot_corresponding_chromaticities_prediction(2, 'Von Kries', 'Bianco', **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Examples_Plotting_CCT_CIE_1960_UCS_Chromaticity_Diagram.png') plt.close( plot_planckian_locus_in_chromaticity_diagram_CIE1960UCS( ['A', 'B', 'C'], **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Examples_Plotting_Chromaticities_CIE_1931_Chromaticity_Diagram.png') RGB = np.random.random((32, 32, 3)) plt.close( plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931( RGB, 'ITU-R BT.709', colourspaces=['ACEScg', 'S-Gamut'], show_pointer_gamut=True, **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Examples_Plotting_CRI.png') plt.close( plot_single_sd_colour_rendering_index_bars(ILLUMINANTS_SDS['FL2'], **arguments)[0]) # ************************************************************************* # Documentation # ************************************************************************* arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_CVD_Simulation_Machado2009.png') plt.close(plot_cvd_simulation_Machado2009(RGB, **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Single_Colour_Checker.png') plt.close(plot_single_colour_checker('ColorChecker 2005', **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Multi_Colour_Checkers.png') plt.close( plot_multi_colour_checkers(['ColorChecker 1976', 'ColorChecker 2005'], **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Plotting_Plot_Single_SD.png') data = { 500: 0.0651, 520: 0.0705, 540: 0.0772, 560: 0.0870, 580: 0.1128, 600: 0.1360 } sd = SpectralDistribution(data, name='Custom') plt.close(plot_single_sd(sd, **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Plotting_Plot_Multi_SDS.png') data_1 = { 500: 0.004900, 510: 0.009300, 520: 0.063270, 530: 0.165500, 540: 0.290400, 550: 0.433450, 560: 0.594500 } data_2 = { 500: 0.323000, 510: 0.503000, 520: 0.710000, 530: 0.862000, 540: 0.954000, 550: 0.994950, 560: 0.995000 } spd1 = SpectralDistribution(data_1, name='Custom 1') spd2 = SpectralDistribution(data_2, name='Custom 2') plt.close(plot_multi_sds([spd1, spd2], **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Plotting_Plot_Single_CMFS.png') plt.close( plot_single_cmfs('CIE 1931 2 Degree Standard Observer', **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Plotting_Plot_Multi_CMFS.png') cmfs = ('CIE 1931 2 Degree Standard Observer', 'CIE 1964 10 Degree Standard Observer') plt.close(plot_multi_cmfs(cmfs, **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Single_Illuminant_SD.png') plt.close(plot_single_illuminant_sd('A', **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Multi_Illuminant_SDS.png') plt.close(plot_multi_illuminant_sds(['A', 'B', 'C'], **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Plotting_Plot_Visible_Spectrum.png') plt.close(plot_visible_spectrum(**arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Single_Lightness_Function.png') plt.close(plot_single_lightness_function('CIE 1976', **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Multi_Lightness_Functions.png') plt.close( plot_multi_lightness_functions(['CIE 1976', 'Wyszecki 1963'], **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Single_Luminance_Function.png') plt.close(plot_single_luminance_function('CIE 1976', **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Multi_Luminance_Functions.png') plt.close( plot_multi_luminance_functions(['CIE 1976', 'Newhall 1943'], **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Blackbody_Spectral_Radiance.png') plt.close( plot_blackbody_spectral_radiance(3500, blackbody='VY Canis Major', **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Blackbody_Colours.png') plt.close( plot_blackbody_colours(SpectralShape(150, 12500, 50), **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Single_Colour_Swatch.png') RGB = ColourSwatch(RGB=(0.45620519, 0.03081071, 0.04091952)) plt.close(plot_single_colour_swatch(RGB, **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Multi_Colour_Swatches.png') RGB_1 = ColourSwatch(RGB=(0.45293517, 0.31732158, 0.26414773)) RGB_2 = ColourSwatch(RGB=(0.77875824, 0.57726450, 0.50453169)) plt.close(plot_multi_colour_swatches([RGB_1, RGB_2], **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Plotting_Plot_Single_Function.png') plt.close(plot_single_function(lambda x: x**(1 / 2.2), **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Plotting_Plot_Multi_Functions.png') functions = { 'Gamma 2.2': lambda x: x**(1 / 2.2), 'Gamma 2.4': lambda x: x**(1 / 2.4), 'Gamma 2.6': lambda x: x**(1 / 2.6), } plt.close(plot_multi_functions(functions, **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Plotting_Plot_Image.png') path = os.path.join(output_directory, 'Logo_Medium_001.png') plt.close(plot_image(read_image(str(path)), **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Corresponding_Chromaticities_Prediction.png') plt.close( plot_corresponding_chromaticities_prediction(1, 'Von Kries', 'CAT02', **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Plotting_Plot_Spectral_Locus.png') plt.close( plot_spectral_locus(spectral_locus_colours='RGB', **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Chromaticity_Diagram_Colours.png') plt.close(plot_chromaticity_diagram_colours(**arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Chromaticity_Diagram.png') plt.close(plot_chromaticity_diagram(**arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Chromaticity_Diagram_CIE1931.png') plt.close(plot_chromaticity_diagram_CIE1931(**arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Chromaticity_Diagram_CIE1960UCS.png') plt.close(plot_chromaticity_diagram_CIE1960UCS(**arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Chromaticity_Diagram_CIE1976UCS.png') plt.close(plot_chromaticity_diagram_CIE1976UCS(**arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_SDS_In_Chromaticity_Diagram.png') A = ILLUMINANTS_SDS['A'] D65 = ILLUMINANTS_SDS['D65'] plt.close(plot_sds_in_chromaticity_diagram([A, D65], **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_SDS_In_Chromaticity_Diagram_CIE1931.png') plt.close( plot_sds_in_chromaticity_diagram_CIE1931([A, D65], **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_SDS_In_Chromaticity_Diagram_CIE1960UCS.png') plt.close( plot_sds_in_chromaticity_diagram_CIE1960UCS([A, D65], **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_SDS_In_Chromaticity_Diagram_CIE1976UCS.png') plt.close( plot_sds_in_chromaticity_diagram_CIE1976UCS([A, D65], **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Plotting_Plot_Pointer_Gamut.png') plt.close(plot_pointer_gamut(**arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_RGB_Colourspaces_In_Chromaticity_Diagram.png') plt.close( plot_RGB_colourspaces_in_chromaticity_diagram( ['ITU-R BT.709', 'ACEScg', 'S-Gamut'], **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_RGB_Colourspaces_In_Chromaticity_Diagram_CIE1931.png') plt.close( plot_RGB_colourspaces_in_chromaticity_diagram_CIE1931( ['ITU-R BT.709', 'ACEScg', 'S-Gamut'], **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_RGB_Colourspaces_In_' 'Chromaticity_Diagram_CIE1960UCS.png') plt.close( plot_RGB_colourspaces_in_chromaticity_diagram_CIE1960UCS( ['ITU-R BT.709', 'ACEScg', 'S-Gamut'], **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_RGB_Colourspaces_In_' 'Chromaticity_Diagram_CIE1976UCS.png') plt.close( plot_RGB_colourspaces_in_chromaticity_diagram_CIE1976UCS( ['ITU-R BT.709', 'ACEScg', 'S-Gamut'], **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_RGB_Chromaticities_In_' 'Chromaticity_Diagram.png') RGB = np.random.random((128, 128, 3)) plt.close( plot_RGB_chromaticities_in_chromaticity_diagram( RGB, 'ITU-R BT.709', **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_RGB_Chromaticities_In_' 'Chromaticity_Diagram_CIE1931.png') plt.close( plot_RGB_chromaticities_in_chromaticity_diagram_CIE1931( RGB, 'ITU-R BT.709', **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_RGB_Chromaticities_In_' 'Chromaticity_Diagram_CIE1960UCS.png') plt.close( plot_RGB_chromaticities_in_chromaticity_diagram_CIE1960UCS( RGB, 'ITU-R BT.709', **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_RGB_Chromaticities_In_' 'Chromaticity_Diagram_CIE1976UCS.png') plt.close( plot_RGB_chromaticities_in_chromaticity_diagram_CIE1976UCS( RGB, 'ITU-R BT.709', **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Ellipses_MacAdam1942_In_Chromaticity_Diagram.png') plt.close( plot_ellipses_MacAdam1942_in_chromaticity_diagram(**arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Ellipses_MacAdam1942_In_' 'Chromaticity_Diagram_CIE1931.png') plt.close( plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1931( **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Ellipses_MacAdam1942_In_' 'Chromaticity_Diagram_CIE1960UCS.png') plt.close( plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1960UCS( **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Ellipses_MacAdam1942_In_' 'Chromaticity_Diagram_CIE1976UCS.png') plt.close( plot_ellipses_MacAdam1942_in_chromaticity_diagram_CIE1976UCS( **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Plotting_Plot_Single_CCTF.png') plt.close(plot_single_cctf('ITU-R BT.709', **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Plotting_Plot_Multi_CCTFs.png') plt.close(plot_multi_cctfs(['ITU-R BT.709', 'sRGB'], **arguments)[0]) data = np.array([ [ None, np.array([0.95010000, 1.00000000, 1.08810000]), np.array([0.40920000, 0.28120000, 0.30600000]), np.array([ [0.02495100, 0.01908600, 0.02032900], [0.10944300, 0.06235900, 0.06788100], [0.27186500, 0.18418700, 0.19565300], [0.48898900, 0.40749400, 0.44854600], ]), None, ], [ None, np.array([0.95010000, 1.00000000, 1.08810000]), np.array([0.30760000, 0.48280000, 0.42770000]), np.array([ [0.02108000, 0.02989100, 0.02790400], [0.06194700, 0.11251000, 0.09334400], [0.15255800, 0.28123300, 0.23234900], [0.34157700, 0.56681300, 0.47035300], ]), None, ], [ None, np.array([0.95010000, 1.00000000, 1.08810000]), np.array([0.39530000, 0.28120000, 0.18450000]), np.array([ [0.02436400, 0.01908600, 0.01468800], [0.10331200, 0.06235900, 0.02854600], [0.26311900, 0.18418700, 0.12109700], [0.43158700, 0.40749400, 0.39008600], ]), None, ], [ None, np.array([0.95010000, 1.00000000, 1.08810000]), np.array([0.20510000, 0.18420000, 0.57130000]), np.array([ [0.03039800, 0.02989100, 0.06123300], [0.08870000, 0.08498400, 0.21843500], [0.18405800, 0.18418700, 0.40111400], [0.32550100, 0.34047200, 0.50296900], [0.53826100, 0.56681300, 0.80010400], ]), None, ], [ None, np.array([0.95010000, 1.00000000, 1.08810000]), np.array([0.35770000, 0.28120000, 0.11250000]), np.array([ [0.03678100, 0.02989100, 0.01481100], [0.17127700, 0.11251000, 0.01229900], [0.30080900, 0.28123300, 0.21229800], [0.52976000, 0.40749400, 0.11720000], ]), None, ], ]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Constant_Hue_Loci.png') plt.close(plot_constant_hue_loci(data, 'IPT', **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Single_Munsell_Value_Function.png') plt.close(plot_single_munsell_value_function('ASTM D1535', **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Multi_Munsell_Value_Functions.png') plt.close( plot_multi_munsell_value_functions(['ASTM D1535', 'McCamy 1987'], **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Single_SD_Rayleigh_Scattering.png') plt.close(plot_single_sd_rayleigh_scattering(**arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Plotting_Plot_The_Blue_Sky.png') plt.close(plot_the_blue_sky(**arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Colour_Quality_Bars.png') illuminant = ILLUMINANTS_SDS['FL2'] light_source = LIGHT_SOURCES_SDS['Kinoton 75P'] light_source = light_source.copy().align(SpectralShape(360, 830, 1)) cqs_i = colour_quality_scale(illuminant, additional_data=True) cqs_l = colour_quality_scale(light_source, additional_data=True) plt.close(plot_colour_quality_bars([cqs_i, cqs_l], **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Single_SD_Colour_Rendering_Index_Bars.png') illuminant = ILLUMINANTS_SDS['FL2'] plt.close( plot_single_sd_colour_rendering_index_bars(illuminant, **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Multi_SDS_Colour_Rendering_Indexes_Bars.png') light_source = LIGHT_SOURCES_SDS['Kinoton 75P'] plt.close( plot_multi_sds_colour_rendering_indexes_bars( [illuminant, light_source], **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Single_SD_Colour_Quality_Scale_Bars.png') illuminant = ILLUMINANTS_SDS['FL2'] plt.close( plot_single_sd_colour_quality_scale_bars(illuminant, **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Multi_SDS_Colour_Quality_Scales_Bars.png') light_source = LIGHT_SOURCES_SDS['Kinoton 75P'] plt.close( plot_multi_sds_colour_quality_scales_bars([illuminant, light_source], **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Plotting_Plot_Planckian_Locus.png') plt.close(plot_planckian_locus(**arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Planckian_Locus_CIE1931.png') plt.close(plot_planckian_locus_CIE1931(**arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Planckian_Locus_CIE1960UCS.png') plt.close(plot_planckian_locus_CIE1960UCS(**arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Planckian_Locus_In_Chromaticity_Diagram.png') plt.close( plot_planckian_locus_in_chromaticity_diagram(['A', 'B', 'C'], **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Planckian_Locus_In_Chromaticity_Diagram_CIE1931.png') plt.close( plot_planckian_locus_in_chromaticity_diagram_CIE1931(['A', 'B', 'C'], **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_Planckian_Locus_In_Chromaticity_Diagram_CIE1960UCS.png') plt.close( plot_planckian_locus_in_chromaticity_diagram_CIE1960UCS( ['A', 'B', 'C'], **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_RGB_Colourspaces_Gamuts.png') plt.close( plot_RGB_colourspaces_gamuts(['ITU-R BT.709', 'ACEScg', 'S-Gamut'], **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Plotting_Plot_RGB_Colourspaces_Gamuts.png') plt.close( plot_RGB_colourspaces_gamuts(['ITU-R BT.709', 'ACEScg', 'S-Gamut'], **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Plotting_Plot_RGB_Scatter.png') plt.close(plot_RGB_scatter(RGB, 'ITU-R BT.709', **arguments)[0]) filename = os.path.join( output_directory, 'Plotting_Plot_Colour_Automatic_Conversion_Graph.png') plot_automatic_colour_conversion_graph(filename) # ************************************************************************* # "tutorial.rst" # ************************************************************************* arguments['filename'] = os.path.join(output_directory, 'Tutorial_Visible_Spectrum.png') plt.close(plot_visible_spectrum(**arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Tutorial_Sample_SD.png') sample_sd_data = { 380: 0.048, 385: 0.051, 390: 0.055, 395: 0.060, 400: 0.065, 405: 0.068, 410: 0.068, 415: 0.067, 420: 0.064, 425: 0.062, 430: 0.059, 435: 0.057, 440: 0.055, 445: 0.054, 450: 0.053, 455: 0.053, 460: 0.052, 465: 0.052, 470: 0.052, 475: 0.053, 480: 0.054, 485: 0.055, 490: 0.057, 495: 0.059, 500: 0.061, 505: 0.062, 510: 0.065, 515: 0.067, 520: 0.070, 525: 0.072, 530: 0.074, 535: 0.075, 540: 0.076, 545: 0.078, 550: 0.079, 555: 0.082, 560: 0.087, 565: 0.092, 570: 0.100, 575: 0.107, 580: 0.115, 585: 0.122, 590: 0.129, 595: 0.134, 600: 0.138, 605: 0.142, 610: 0.146, 615: 0.150, 620: 0.154, 625: 0.158, 630: 0.163, 635: 0.167, 640: 0.173, 645: 0.180, 650: 0.188, 655: 0.196, 660: 0.204, 665: 0.213, 670: 0.222, 675: 0.231, 680: 0.242, 685: 0.251, 690: 0.261, 695: 0.271, 700: 0.282, 705: 0.294, 710: 0.305, 715: 0.318, 720: 0.334, 725: 0.354, 730: 0.372, 735: 0.392, 740: 0.409, 745: 0.420, 750: 0.436, 755: 0.450, 760: 0.462, 765: 0.465, 770: 0.448, 775: 0.432, 780: 0.421 } sd = SpectralDistribution(sample_sd_data, name='Sample') plt.close(plot_single_sd(sd, **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Tutorial_SD_Interpolation.png') sd_copy = sd.copy() sd_copy.interpolate(SpectralShape(400, 770, 1)) plt.close( plot_multi_sds([sd, sd_copy], bounding_box=[730, 780, 0.25, 0.5], **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Tutorial_Sample_Swatch.png') sd = SpectralDistribution(sample_sd_data) cmfs = STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'] illuminant = ILLUMINANTS_SDS['D65'] with domain_range_scale('1'): XYZ = sd_to_XYZ(sd, cmfs, illuminant) RGB = XYZ_to_sRGB(XYZ) plt.close( plot_single_colour_swatch(ColourSwatch('Sample', RGB), text_parameters={'size': 'x-large'}, **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Tutorial_Neutral5.png') patch_name = 'neutral 5 (.70 D)' patch_sd = COLOURCHECKERS_SDS['ColorChecker N Ohta'][patch_name] with domain_range_scale('1'): XYZ = sd_to_XYZ(patch_sd, cmfs, illuminant) RGB = XYZ_to_sRGB(XYZ) plt.close( plot_single_colour_swatch(ColourSwatch(patch_name.title(), RGB), text_parameters={'size': 'x-large'}, **arguments)[0]) arguments['filename'] = os.path.join(output_directory, 'Tutorial_Colour_Checker.png') plt.close( plot_single_colour_checker(colour_checker='ColorChecker 2005', text_parameters={'visible': False}, **arguments)[0]) arguments['filename'] = os.path.join( output_directory, 'Tutorial_CIE_1931_Chromaticity_Diagram.png') xy = XYZ_to_xy(XYZ) plot_chromaticity_diagram_CIE1931(standalone=False) x, y = xy plt.plot(x, y, 'o-', color='white') # Annotating the plot. plt.annotate(patch_sd.name.title(), xy=xy, xytext=(-50, 30), textcoords='offset points', arrowprops=dict(arrowstyle='->', connectionstyle='arc3, rad=-0.2')) plt.close( render(standalone=True, limits=(-0.1, 0.9, -0.1, 0.9), x_tighten=True, y_tighten=True, **arguments)[0]) # ************************************************************************* # "basics.rst" # ************************************************************************* arguments['filename'] = os.path.join(output_directory, 'Basics_Logo_Small_001_CIE_XYZ.png') RGB = read_image(os.path.join(output_directory, 'Logo_Small_001.png'))[..., 0:3] XYZ = sRGB_to_XYZ(RGB) plt.close( plot_image(XYZ, text_parameters={'text': 'sRGB to XYZ'}, **arguments)[0])
def plot_sds_in_chromaticity_diagram( sds, cmfs='CIE 1931 2 Degree Standard Observer', chromaticity_diagram_callable=plot_chromaticity_diagram, method='CIE 1931', annotate_kwargs=None, plot_kwargs=None, **kwargs): """ Plots given spectral distribution chromaticity coordinates into the *Chromaticity Diagram* using given method. Parameters ---------- sds : array_like or MultiSpectralDistributions Spectral distributions or multi-spectral distributions to plot. `sds` can be a single :class:`colour.MultiSpectralDistributions` class instance, a list of :class:`colour.MultiSpectralDistributions` class instances or a list of :class:`colour.SpectralDistribution` class instances. cmfs : unicode or XYZ_ColourMatchingFunctions, optional Standard observer colour matching functions used for computing the spectral locus boundaries. ``cmfs`` can be of any type or form supported by the :func:`colour.plotting.filter_cmfs` definition. chromaticity_diagram_callable : callable, optional Callable responsible for drawing the *Chromaticity Diagram*. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. annotate_kwargs : dict or array_like, optional Keyword arguments for the :func:`plt.annotate` definition, used to annotate the resulting chromaticity coordinates with their respective spectral distribution names. ``annotate_kwargs`` can be either a single dictionary applied to all the arrows with same settings or a sequence of dictionaries with different settings for each spectral distribution. The following special keyword arguments can also be used: - *annotate* : bool, whether to annotate the spectral distributions. plot_kwargs : dict or array_like, optional Keyword arguments for the :func:`plt.plot` definition, used to control the style of the plotted spectral distributions. ``plot_kwargs`` can be either a single dictionary applied to all the plotted spectral distributions with same settings or a sequence of dictionaries with different settings for each plotted spectral distributions. The following special keyword arguments can also be used: - *illuminant* : unicode or :class:`colour.SpectralDistribution`, the illuminant used to compute the spectral distributions colours. The default is the illuminant associated with the whitepoint of the default plotting colourspace. ``illuminant`` can be of any type or form supported by the :func:`colour.plotting.filter_cmfs` definition. - *cmfs* : unicode, the standard observer colour matching functions used for computing the spectral distributions colours. ``cmfs`` can be of any type or form supported by the :func:`colour.plotting.filter_cmfs` definition. - *normalise_sd_colours* : bool, whether to normalise the computed spectral distributions colours. The default is *True*. - *use_sd_colours* : bool, whether to use the computed spectral distributions colours under the plotting colourspace illuminant. Alternatively, it is possible to use the :func:`plt.plot` definition ``color`` argument with pre-computed values. The default is *True*. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Also handles keywords arguments for deprecation management. Returns ------- tuple Current figure and axes. Examples -------- >>> A = SDS_ILLUMINANTS['A'] >>> D65 = SDS_ILLUMINANTS['D65'] >>> annotate_kwargs = [ ... {'xytext': (-25, 15), 'arrowprops':{'arrowstyle':'-'}}, ... {} ... ] >>> plot_kwargs = [ ... { ... 'illuminant': SDS_ILLUMINANTS['E'], ... 'markersize' : 15, ... 'normalise_sd_colours': True, ... 'use_sd_colours': True ... }, ... {'illuminant': SDS_ILLUMINANTS['E']}, ... ] >>> plot_sds_in_chromaticity_diagram( ... [A, D65], annotate_kwargs=annotate_kwargs, plot_kwargs=plot_kwargs) ... # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, <...AxesSubplot...>) .. image:: ../_static/Plotting_Plot_SDS_In_Chromaticity_Diagram.png :align: center :alt: plot_sds_in_chromaticity_diagram """ annotate_kwargs = handle_arguments_deprecation( { 'ArgumentRenamed': [['annotate_parameters', 'annotate_kwargs']], }, **kwargs).get('annotate_kwargs', annotate_kwargs) sds = sds_and_msds_to_sds(sds) settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) method = method.upper() settings.update({ 'axes': axes, 'standalone': False, 'method': method, 'cmfs': cmfs, }) chromaticity_diagram_callable(**settings) if method == 'CIE 1931': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return XYZ_to_xy(XYZ) bounding_box = (-0.1, 0.9, -0.1, 0.9) elif method == 'CIE 1960 UCS': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return UCS_to_uv(XYZ_to_UCS(XYZ)) bounding_box = (-0.1, 0.7, -0.2, 0.6) elif method == 'CIE 1976 UCS': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return Luv_to_uv(XYZ_to_Luv(XYZ)) bounding_box = (-0.1, 0.7, -0.1, 0.7) else: raise ValueError( 'Invalid method: "{0}", must be one of ' '[\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\']'.format( method)) annotate_settings_collection = [{ 'annotate': True, 'xytext': (-50, 30), 'textcoords': 'offset points', 'arrowprops': CONSTANTS_ARROW_STYLE, } for _ in range(len(sds))] if annotate_kwargs is not None: update_settings_collection(annotate_settings_collection, annotate_kwargs, len(sds)) plot_settings_collection = [{ 'color': CONSTANTS_COLOUR_STYLE.colour.brightest, 'label': '{0}'.format(sd.strict_name), 'marker': 'o', 'markeredgecolor': CONSTANTS_COLOUR_STYLE.colour.dark, 'markeredgewidth': CONSTANTS_COLOUR_STYLE.geometry.short * 0.75, 'markersize': (CONSTANTS_COLOUR_STYLE.geometry.short * 6 + CONSTANTS_COLOUR_STYLE.geometry.short * 0.75), 'cmfs': cmfs, 'illuminant': SDS_ILLUMINANTS[ CONSTANTS_COLOUR_STYLE.colour.colourspace.whitepoint_name], 'use_sd_colours': False, 'normalise_sd_colours': False, } for sd in sds] if plot_kwargs is not None: update_settings_collection(plot_settings_collection, plot_kwargs, len(sds)) for i, sd in enumerate(sds): plot_settings = plot_settings_collection[i] cmfs = first_item(filter_cmfs(plot_settings.pop('cmfs')).values()) illuminant = first_item( filter_illuminants(plot_settings.pop('illuminant')).values()) normalise_sd_colours = plot_settings.pop('normalise_sd_colours') use_sd_colours = plot_settings.pop('use_sd_colours') with domain_range_scale('1'): XYZ = sd_to_XYZ(sd, cmfs, illuminant) if use_sd_colours: if normalise_sd_colours: XYZ /= XYZ[..., 1] plot_settings['color'] = np.clip(XYZ_to_plotting_colourspace(XYZ), 0, 1) ij = XYZ_to_ij(XYZ) axes.plot(ij[0], ij[1], **plot_settings) if (sd.name is not None and annotate_settings_collection[i]['annotate']): annotate_settings = annotate_settings_collection[i] annotate_settings.pop('annotate') axes.annotate(sd.name, xy=ij, **annotate_settings) settings.update({'standalone': True, 'bounding_box': bounding_box}) settings.update(kwargs) return render(**settings)
def plot_the_blue_sky(cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots the blue sky. Parameters ---------- cmfs : unicode, optional Standard observer colour matching functions. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.plot_single_sd`, :func:`colour.plotting.plot_multi_colour_swatches`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_the_blue_sky() # doctest: +SKIP .. image:: ../_static/Plotting_Plot_The_Blue_Sky.png :align: center :alt: plot_the_blue_sky """ figure = plt.figure() figure.subplots_adjust(hspace=COLOUR_STYLE_CONSTANTS.geometry.short / 2) cmfs = first_item(filter_cmfs(cmfs).values()) ASTM_G_173_sd = ASTM_G_173_ETR.copy() rayleigh_sd = sd_rayleigh_scattering() ASTM_G_173_sd.align(rayleigh_sd.shape) sd = rayleigh_sd * ASTM_G_173_sd axes = figure.add_subplot(211) settings = { 'axes': axes, 'title': 'The Blue Sky - Synthetic Spectral Distribution', 'y_label': u'W / m-2 / nm-1', } settings.update(kwargs) settings['standalone'] = False plot_single_sd(sd, cmfs, **settings) axes = figure.add_subplot(212) x_label = ('The sky is blue because molecules in the atmosphere ' 'scatter shorter wavelengths more than longer ones.\n' 'The synthetic spectral distribution is computed as ' 'follows: ' '(ASTM G-173 ETR * Standard Air Rayleigh Scattering).') settings = { 'axes': axes, 'aspect': None, 'title': 'The Blue Sky - Colour', 'x_label': x_label, 'y_label': '', 'x_ticker': False, 'y_ticker': False, } settings.update(kwargs) settings['standalone'] = False blue_sky_color = XYZ_to_plotting_colourspace(sd_to_XYZ(sd)) figure, axes = plot_single_colour_swatch( ColourSwatch('', normalise_maximum(blue_sky_color)), **settings) settings = {'axes': axes, 'standalone': True} settings.update(kwargs) return render(**settings)
def plot_blackbody_colours(shape=SpectralShape(150, 12500, 50), cmfs='CIE 1931 2 Degree Standard Observer', **kwargs): """ Plots blackbody colours. Parameters ---------- shape : SpectralShape, optional Spectral shape to use as plot boundaries. cmfs : unicode, optional Standard observer colour matching functions. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_blackbody_colours(SpectralShape(150, 12500, 50)) ... # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, \ <matplotlib.axes._subplots.AxesSubplot object at 0x...>) .. image:: ../_static/Plotting_Plot_Blackbody_Colours.png :align: center :alt: plot_blackbody_colours """ _figure, axes = artist(**kwargs) cmfs = first_item(filter_cmfs(cmfs).values()) colours = [] temperatures = [] for temperature in shape: sd = sd_blackbody(temperature, cmfs.shape) with domain_range_scale('1'): XYZ = sd_to_XYZ(sd, cmfs) RGB = normalise_maximum(XYZ_to_plotting_colourspace(XYZ)) colours.append(RGB) temperatures.append(temperature) x_min, x_max = min(temperatures), max(temperatures) y_min, y_max = 0, 1 padding = 0.1 axes.bar(x=np.array(temperatures) - padding, height=1, width=shape.interval + (padding * shape.interval), color=colours, align='edge') settings = { 'axes': axes, 'bounding_box': (x_min, x_max, y_min, y_max), 'title': 'Blackbody Colours', 'x_label': 'Temperature K', 'y_label': None, } settings.update(kwargs) return render(**settings)
def plot_multi_sds(sds, cmfs='CIE 1931 2 Degree Standard Observer', use_sds_colours=False, normalise_sds_colours=False, **kwargs): """ Plots given spectral distributions. Parameters ---------- sds : array_like or MultiSpectralDistributions Spectral distributions or multi-spectral distributions to plot. `sds` can be a single :class:`colour.MultiSpectralDistributions` class instance, a list of :class:`colour.MultiSpectralDistributions` class instances or a list of :class:`colour.SpectralDistribution` class instances. cmfs : unicode, optional Standard observer colour matching functions used for spectrum creation. use_sds_colours : bool, optional Whether to use spectral distributions colours. normalise_sds_colours : bool Whether to normalise spectral distributions colours. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> from colour import SpectralDistribution >>> data_1 = { ... 500: 0.004900, ... 510: 0.009300, ... 520: 0.063270, ... 530: 0.165500, ... 540: 0.290400, ... 550: 0.433450, ... 560: 0.594500 ... } >>> data_2 = { ... 500: 0.323000, ... 510: 0.503000, ... 520: 0.710000, ... 530: 0.862000, ... 540: 0.954000, ... 550: 0.994950, ... 560: 0.995000 ... } >>> sd_1 = SpectralDistribution(data_1, name='Custom 1') >>> sd_2 = SpectralDistribution(data_2, name='Custom 2') >>> plot_multi_sds([sd_1, sd_2]) # doctest: +ELLIPSIS (<Figure size ... with 1 Axes>, \ <matplotlib.axes._subplots.AxesSubplot object at 0x...>) .. image:: ../_static/Plotting_Plot_Multi_SDS.png :align: center :alt: plot_multi_sds """ _figure, axes = artist(**kwargs) sds = sds_and_multi_sds_to_sds(sds) cmfs = first_item(filter_cmfs(cmfs).values()) illuminant = ILLUMINANTS_SDS[ COLOUR_STYLE_CONSTANTS.colour.colourspace.illuminant] x_limit_min, x_limit_max, y_limit_min, y_limit_max = [], [], [], [] for sd in sds: wavelengths, values = sd.wavelengths, sd.values shape = sd.shape x_limit_min.append(shape.start) x_limit_max.append(shape.end) y_limit_min.append(min(values)) y_limit_max.append(max(values)) if use_sds_colours: with domain_range_scale('1'): XYZ = sd_to_XYZ(sd, cmfs, illuminant) if normalise_sds_colours: XYZ = normalise_maximum(XYZ, clip=False) RGB = np.clip(XYZ_to_plotting_colourspace(XYZ), 0, 1) axes.plot(wavelengths, values, color=RGB, label=sd.strict_name) else: axes.plot(wavelengths, values, label=sd.strict_name) bounding_box = (min(x_limit_min), max(x_limit_max), min(y_limit_min), max(y_limit_max) + max(y_limit_max) * 0.05) settings = { 'axes': axes, 'bounding_box': bounding_box, 'legend': True, 'x_label': 'Wavelength $\\lambda$ (nm)', 'y_label': 'Spectral Distribution', } settings.update(kwargs) return render(**settings)
def 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 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['FL2'] >>> 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 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 sd_to_aces_relative_exposure_values( sd, illuminant=ILLUMINANTS_SDS['D65'], apply_chromatic_adaptation=False, chromatic_adaptation_transform='CAT02'): """ Converts given spectral distribution to *ACES2065-1* colourspace relative exposure values. Parameters ---------- sd : SpectralDistribution Spectral distribution. illuminant : SpectralDistribution, optional *Illuminant* spectral distribution. apply_chromatic_adaptation : bool, optional Whether to apply chromatic adaptation using given transform. 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, (3,) *ACES2065-1* colourspace relative exposure values array. Notes ----- +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``XYZ`` | [0, 100] | [0, 1] | +------------+-----------------------+---------------+ - The chromatic adaptation method implemented here is a bit unusual as it involves building a new colourspace based on *ACES2065-1* colourspace primaries but using the whitepoint of the illuminant that the spectral distribution was measured under. References ---------- :cite:`Forsythe2018`, :cite:`TheAcademyofMotionPictureArtsandSciences2014q`, :cite:`TheAcademyofMotionPictureArtsandSciences2014r`, :cite:`TheAcademyofMotionPictureArtsandSciencese` Examples -------- >>> from colour import COLOURCHECKERS_SDS >>> sd = COLOURCHECKERS_SDS['ColorChecker N Ohta']['dark skin'] >>> sd_to_aces_relative_exposure_values(sd) # doctest: +ELLIPSIS array([ 0.1171785..., 0.0866347..., 0.0589707...]) >>> sd_to_aces_relative_exposure_values(sd, ... apply_chromatic_adaptation=True) # doctest: +ELLIPSIS array([ 0.1180766..., 0.0869023..., 0.0589104...]) """ shape = ACES_RICD.shape if sd.shape != ACES_RICD.shape: sd = sd.copy().align(shape) if illuminant.shape != ACES_RICD.shape: illuminant = illuminant.copy().align(shape) s_v = sd.values i_v = illuminant.values r_bar, g_bar, b_bar = tsplit(ACES_RICD.values) def k(x, y): """ Computes the :math:`K_r`, :math:`K_g` or :math:`K_b` scale factors. """ return 1 / np.sum(x * y) k_r = k(i_v, r_bar) k_g = k(i_v, g_bar) k_b = k(i_v, b_bar) E_r = k_r * np.sum(i_v * s_v * r_bar) E_g = k_g * np.sum(i_v * s_v * g_bar) E_b = k_b * np.sum(i_v * s_v * b_bar) E_rgb = np.array([E_r, E_g, E_b]) # Accounting for flare. E_rgb += FLARE_PERCENTAGE E_rgb *= S_FLARE_FACTOR if apply_chromatic_adaptation: xy = XYZ_to_xy(sd_to_XYZ(illuminant) / 100) NPM = normalised_primary_matrix(ACES_2065_1_COLOURSPACE.primaries, xy) XYZ = RGB_to_XYZ(E_rgb, xy, ACES_2065_1_COLOURSPACE.whitepoint, NPM, chromatic_adaptation_transform) E_rgb = XYZ_to_RGB(XYZ, ACES_2065_1_COLOURSPACE.whitepoint, ACES_2065_1_COLOURSPACE.whitepoint, ACES_2065_1_COLOURSPACE.XYZ_to_RGB_matrix) return from_range_1(E_rgb)
def plot_blackbody_spectral_radiance( temperature=3500, cmfs='CIE 1931 2 Degree Standard Observer', blackbody='VY Canis Major', **kwargs): """ Plots given blackbody spectral radiance. Parameters ---------- temperature : numeric, optional Blackbody temperature. cmfs : unicode, optional Standard observer colour matching functions. blackbody : unicode, optional Blackbody name. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.plot_single_sd`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> plot_blackbody_spectral_radiance(3500, blackbody='VY Canis Major') ... # doctest: +ELLIPSIS (<Figure size ... with 2 Axes>, \ <matplotlib.axes._subplots.AxesSubplot object at 0x...>) .. image:: ../_static/Plotting_Plot_Blackbody_Spectral_Radiance.png :align: center :alt: plot_blackbody_spectral_radiance """ figure = plt.figure() figure.subplots_adjust(hspace=COLOUR_STYLE_CONSTANTS.geometry.short / 2) cmfs = first_item(filter_cmfs(cmfs).values()) sd = sd_blackbody(temperature, cmfs.shape) axes = figure.add_subplot(211) settings = { 'axes': axes, 'title': '{0} - Spectral Radiance'.format(blackbody), 'y_label': 'W / (sr m$^2$) / m', } settings.update(kwargs) settings['standalone'] = False plot_single_sd(sd, cmfs.name, **settings) axes = figure.add_subplot(212) with domain_range_scale('1'): XYZ = sd_to_XYZ(sd, cmfs) RGB = normalise_maximum(XYZ_to_plotting_colourspace(XYZ)) settings = { 'axes': axes, 'aspect': None, 'title': '{0} - Colour'.format(blackbody), 'x_label': '{0}K'.format(temperature), 'y_label': '', 'x_ticker': False, 'y_ticker': False, } settings.update(kwargs) settings['standalone'] = False figure, axes = plot_single_colour_swatch(ColourSwatch(name='', RGB=RGB), **settings) settings = {'axes': axes, 'standalone': True} settings.update(kwargs) return render(**settings)
def 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 = STANDARD_OBSERVERS_CMFS['CIE 1931 2 Degree Standard Observer'] >>> 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(ASTME30815_PRACTISE_SHAPE) 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 plot_sds_in_chromaticity_diagram( sds, cmfs='CIE 1931 2 Degree Standard Observer', annotate_parameters=None, chromaticity_diagram_callable=plot_chromaticity_diagram, method='CIE 1931', **kwargs): """ Plots given spectral distribution chromaticity coordinates into the *Chromaticity Diagram* using given method. Parameters ---------- sds : array_like, optional Spectral distributions to plot. cmfs : unicode, optional Standard observer colour matching functions used for *Chromaticity Diagram* bounds. annotate_parameters : dict or array_like, optional Parameters for the :func:`plt.annotate` definition, used to annotate the resulting chromaticity coordinates with their respective spectral distribution names if ``annotate`` is set to *True*. ``annotate_parameters`` can be either a single dictionary applied to all the arrows with same settings or a sequence of dictionaries with different settings for each spectral distribution. chromaticity_diagram_callable : callable, optional Callable responsible for drawing the *Chromaticity Diagram*. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> from colour import ILLUMINANTS_SDS >>> A = ILLUMINANTS_SDS['A'] >>> D65 = ILLUMINANTS_SDS['D65'] >>> plot_sds_in_chromaticity_diagram([A, D65]) # doctest: +SKIP .. image:: ../_static/Plotting_Plot_SDs_In_Chromaticity_Diagram.png :align: center :alt: plot_sds_in_chromaticity_diagram """ settings = {'uniform': True} settings.update(kwargs) figure, axes = artist(**settings) method = method.upper() settings.update({ 'axes': axes, 'standalone': False, 'method': method, 'cmfs': cmfs, }) chromaticity_diagram_callable(**settings) if method == 'CIE 1931': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return XYZ_to_xy(XYZ) bounding_box = (-0.1, 0.9, -0.1, 0.9) elif method == 'CIE 1960 UCS': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return UCS_to_uv(XYZ_to_UCS(XYZ)) bounding_box = (-0.1, 0.7, -0.2, 0.6) elif method == 'CIE 1976 UCS': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return Luv_to_uv(XYZ_to_Luv(XYZ)) bounding_box = (-0.1, 0.7, -0.1, 0.7) else: raise ValueError( 'Invalid method: "{0}", must be one of ' '{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}'.format( method)) annotate_settings_collection = [{ 'annotate': True, 'xytext': (-50, 30), 'textcoords': 'offset points', 'arrowprops': COLOUR_ARROW_STYLE, } for _ in range(len(sds))] if annotate_parameters is not None: if not isinstance(annotate_parameters, dict): assert len(annotate_parameters) == len(sds), ( 'Multiple annotate parameters defined, but they do not match ' 'the spectral distributions count!') for i, annotate_settings in enumerate(annotate_settings_collection): if isinstance(annotate_parameters, dict): annotate_settings.update(annotate_parameters) else: annotate_settings.update(annotate_parameters[i]) for i, sd in enumerate(sds): with domain_range_scale('1'): XYZ = sd_to_XYZ(sd) ij = XYZ_to_ij(XYZ) axes.plot( ij[0], ij[1], 'o', color=COLOUR_STYLE_CONSTANTS.colour.brightest, markeredgecolor=COLOUR_STYLE_CONSTANTS.colour.dark, markersize=(COLOUR_STYLE_CONSTANTS.geometry.short * 6 + COLOUR_STYLE_CONSTANTS.geometry.short * 0.75), markeredgewidth=COLOUR_STYLE_CONSTANTS.geometry.short * 0.75, label=sd.strict_name) if (sd.name is not None and annotate_settings_collection[i]['annotate']): annotate_settings = annotate_settings_collection[i] annotate_settings.pop('annotate') axes.annotate(sd.name, xy=ij, **annotate_settings) settings.update({'standalone': True, 'bounding_box': bounding_box}) settings.update(kwargs) return render(**settings)
def plot_sds_in_chromaticity_diagram( sds, cmfs='CIE 1931 2 Degree Standard Observer', annotate_parameters=None, chromaticity_diagram_callable=plot_chromaticity_diagram, method='CIE 1931', **kwargs): """ Plots given spectral distribution chromaticity coordinates into the *Chromaticity Diagram* using given method. Parameters ---------- sds : array_like, optional Spectral distributions to plot. cmfs : unicode, optional Standard observer colour matching functions used for *Chromaticity Diagram* bounds. annotate_parameters : dict or array_like, optional Parameters for the :func:`plt.annotate` definition, used to annotate the resulting chromaticity coordinates with their respective spectral distribution names if ``annotate`` is set to *True*. ``annotate_parameters`` can be either a single dictionary applied to all the arrows with same settings or a sequence of dictionaries with different settings for each spectral distribution. chromaticity_diagram_callable : callable, optional Callable responsible for drawing the *Chromaticity Diagram*. method : unicode, optional **{'CIE 1931', 'CIE 1960 UCS', 'CIE 1976 UCS'}**, *Chromaticity Diagram* method. Other Parameters ---------------- \\**kwargs : dict, optional {:func:`colour.plotting.artist`, :func:`colour.plotting.diagrams.plot_chromaticity_diagram`, :func:`colour.plotting.render`}, Please refer to the documentation of the previously listed definitions. Returns ------- tuple Current figure and axes. Examples -------- >>> from colour import ILLUMINANTS_SDS >>> A = ILLUMINANTS_SDS['A'] >>> D65 = ILLUMINANTS_SDS['D65'] >>> plot_sds_in_chromaticity_diagram([A, D65]) # doctest: +SKIP .. image:: ../_static/Plotting_Plot_SDs_In_Chromaticity_Diagram.png :align: center :alt: plot_sds_in_chromaticity_diagram """ settings = {'uniform': True} settings.update(kwargs) _figure, axes = artist(**settings) method = method.upper() settings.update({ 'axes': axes, 'standalone': False, 'method': method, 'cmfs': cmfs, }) chromaticity_diagram_callable(**settings) if method == 'CIE 1931': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return XYZ_to_xy(XYZ) bounding_box = (-0.1, 0.9, -0.1, 0.9) elif method == 'CIE 1960 UCS': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return UCS_to_uv(XYZ_to_UCS(XYZ)) bounding_box = (-0.1, 0.7, -0.2, 0.6) elif method == 'CIE 1976 UCS': def XYZ_to_ij(XYZ): """ Converts given *CIE XYZ* tristimulus values to *ij* chromaticity coordinates. """ return Luv_to_uv(XYZ_to_Luv(XYZ)) bounding_box = (-0.1, 0.7, -0.1, 0.7) else: raise ValueError( 'Invalid method: "{0}", must be one of ' '{{\'CIE 1931\', \'CIE 1960 UCS\', \'CIE 1976 UCS\'}}'.format( method)) annotate_settings_collection = [{ 'annotate': True, 'xytext': (-50, 30), 'textcoords': 'offset points', 'arrowprops': COLOUR_ARROW_STYLE, } for _ in range(len(sds))] if annotate_parameters is not None: if not isinstance(annotate_parameters, dict): assert len(annotate_parameters) == len(sds), ( 'Multiple annotate parameters defined, but they do not match ' 'the spectral distributions count!') for i, annotate_settings in enumerate(annotate_settings_collection): if isinstance(annotate_parameters, dict): annotate_settings.update(annotate_parameters) else: annotate_settings.update(annotate_parameters[i]) for i, sd in enumerate(sds): with domain_range_scale('1'): XYZ = sd_to_XYZ(sd) ij = XYZ_to_ij(XYZ) axes.plot( ij[0], ij[1], 'o', color=COLOUR_STYLE_CONSTANTS.colour.brightest, markeredgecolor=COLOUR_STYLE_CONSTANTS.colour.dark, markersize=(COLOUR_STYLE_CONSTANTS.geometry.short * 6 + COLOUR_STYLE_CONSTANTS.geometry.short * 0.75), markeredgewidth=COLOUR_STYLE_CONSTANTS.geometry.short * 0.75, label=sd.strict_name) if (sd.name is not None and annotate_settings_collection[i]['annotate']): annotate_settings = annotate_settings_collection[i] annotate_settings.pop('annotate') axes.annotate(sd.name, xy=ij, **annotate_settings) settings.update({'standalone': True, 'bounding_box': bounding_box}) settings.update(kwargs) return render(**settings)