def luminous_efficacy(sd: SpectralDistribution, lef: Optional[SpectralDistribution] = None) -> Floating: """ Return the *luminous efficacy* in :math:`lm\\cdot W^{-1}` of given spectral distribution using given luminous efficiency function. Parameters ---------- sd test spectral distribution lef :math:`V(\\lambda)` luminous efficiency function, default to the *CIE 1924 Photopic Standard Observer*. Returns ------- :class:`numpy.floating` Luminous efficacy in :math:`lm\\cdot W^{-1}`. References ---------- :cite:`Wikipedia2005c` Examples -------- >>> from colour import SDS_LIGHT_SOURCES >>> sd = SDS_LIGHT_SOURCES['Neodimium Incandescent'] >>> luminous_efficacy(sd) # doctest: +ELLIPSIS 136.2170803... """ efficacy = CONSTANT_K_M * luminous_efficiency(sd, lef) return as_float_scalar(efficacy)
def colour_rendering_indexes( test_data: Tuple[TCS_ColorimetryData, ...], reference_data: Tuple[TCS_ColorimetryData, ...], ) -> Dict[Integer, TCS_ColourQualityScaleData]: """ Return the *test colour samples* rendering indexes :math:`Q_a`. Parameters ---------- test_data Test data. reference_data Reference data. Returns ------- :class:`dict` *Test colour samples* *Colour Rendering Index* (CRI). """ Q_as = {} for i in range(len(test_data)): Q_as[i + 1] = TCS_ColourQualityScaleData( test_data[i].name, 100 - 4.6 * as_float_scalar( euclidean_distance(reference_data[i].UVW, test_data[i].UVW) ), ) return Q_as
def luminous_flux( sd: SpectralDistribution, lef: Optional[SpectralDistribution] = None, K_m: Floating = CONSTANT_K_M, ) -> Floating: """ Return the *luminous flux* for given spectral distribution using given luminous efficiency function. Parameters ---------- sd test spectral distribution lef :math:`V(\\lambda)` luminous efficiency function, default to the *CIE 1924 Photopic Standard Observer*. K_m :math:`lm\\cdot W^{-1}` maximum photopic luminous efficiency. Returns ------- :class:`numpy.floating` Luminous flux. References ---------- :cite:`Wikipedia2003b` Examples -------- >>> from colour import SDS_LIGHT_SOURCES >>> sd = SDS_LIGHT_SOURCES['Neodimium Incandescent'] >>> luminous_flux(sd) # doctest: +ELLIPSIS 23807.6555273... """ lef = cast( SpectralDistribution, optional(lef, SDS_LEFS_PHOTOPIC["CIE 1924 Photopic Standard Observer"]), ) lef = reshape_sd( lef, sd.shape, extrapolator_kwargs={ "method": "Constant", "left": 0, "right": 0 }, ) flux = K_m * np.trapz(lef.values * sd.values, sd.wavelengths) return as_float_scalar(flux)
def bandwidth_FWHM(self, value: Optional[Floating]): """Setter for the **self.bandwidth_FWHM** property.""" if value is not None: attest( is_numeric(value), f'"bandwidth_FWHM" property: "{value}" is not a "number"!', ) value = as_float_scalar(value) self._bandwidth_FWHM = value
def colour_quality_scales( test_data: Tuple[VS_ColorimetryData, ...], reference_data: Tuple[VS_ColorimetryData, ...], scaling_f: Floating, CCT_f: Floating, ) -> Dict[Integer, VS_ColourQualityScaleData]: """ Return the *VS test colour samples* rendering scales. Parameters ---------- test_data Test data. reference_data Reference data. scaling_f Scaling factor constant. CCT_f Factor penalizing lamps with extremely low correlated colour temperatures. Returns ------- :class:`dict` *VS Test colour samples* colour rendering scales. """ Q_as = {} for i in range(len(test_data)): D_C_ab = test_data[i].C - reference_data[i].C D_E_ab = as_float_scalar( euclidean_distance(test_data[i].Lab, reference_data[i].Lab)) if D_C_ab > 0: D_Ep_ab = np.sqrt(D_E_ab**2 - D_C_ab**2) else: D_Ep_ab = D_E_ab Q_a = scale_conversion(D_Ep_ab, CCT_f, scaling_f) Q_as[i + 1] = VS_ColourQualityScaleData(test_data[i].name, Q_a, D_C_ab, D_E_ab, D_Ep_ab) return Q_as
def uv_to_UCS(uv: ArrayLike, V: Floating = 1) -> NDArray: """ Return the *CIE 1960 UCS* colourspace array from given *uv* chromaticity coordinates. Parameters ---------- uv *uv* chromaticity coordinates. V Optional :math:`V` *luminance* value used to construct the *CIE 1960 UCS* colourspace array, the default :math:`V` *luminance* is set to 1. Returns ------- :class:`numpy.ndarray` *CIE 1960 UCS* colourspace array. References ---------- :cite:`Wikipedia2008c` Examples -------- >>> import numpy as np >>> uv = np.array([0.37720213, 0.33413508]) >>> uv_to_UCS(uv) # doctest: +ELLIPSIS array([ 1.1288911..., 1. , 0.8639104...]) """ u, v = tsplit(uv) V = as_float_scalar(to_domain_1(V)) UVW = tstack([V * u / v, full(u.shape, V), -V * (u + v - 1) / v]) return from_range_1(UVW)
def cctf_decoding_RIMMRGB( X_p: Union[FloatingOrArrayLike, IntegerOrArrayLike], bit_depth: Integer = 8, in_int: Boolean = False, E_clip: Floating = 2.0, ) -> FloatingOrNDArray: """ Define the *RIMM RGB* decoding colour component transfer function (Encoding CCTF). Parameters ---------- X_p Non-linear data :math:`X'_{RIMM}`. bit_depth Bit depth used for conversion. in_int Whether to treat the input value as integer code value or float equivalent of a code value at a given bit depth. E_clip Maximum exposure level. Returns ------- :class:`numpy.floating` or :class:`numpy.ndarray` Linear data :math:`X_{RIMM}`. Notes ----- +----------------+-----------------------+---------------+ | **Domain \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``X_p`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ +----------------+-----------------------+---------------+ | **Range \\*** | **Scale - Reference** | **Scale - 1** | +================+=======================+===============+ | ``X`` | [0, 1] | [0, 1] | +----------------+-----------------------+---------------+ \\* This definition has an input integer switch, thus the domain-range scale information is only given for the floating point mode. References ---------- :cite:`Spaulding2000b` Examples -------- >>> cctf_decoding_RIMMRGB(0.291673732475746) # doctest: +ELLIPSIS 0.1... >>> cctf_decoding_RIMMRGB(74, in_int=True) # doctest: +ELLIPSIS 0.1... """ X_p = to_domain_1(X_p) I_max = as_float_scalar(2**bit_depth - 1) if not in_int: X_p = X_p * I_max V_clip = 1.099 * spow(E_clip, 0.45) - 0.099 m = V_clip * X_p / I_max with domain_range_scale("ignore"): X = np.where( X_p / I_max < cctf_encoding_RIMMRGB(0.018, bit_depth, E_clip=E_clip), m / 4.5, spow((m + 0.099) / 1.099, 1 / 0.45), ) return as_float(from_range_1(X))
def hull_section( hull: trimesh.Trimesh, # type: ignore[name-defined] # noqa axis: Union[Literal["+z", "+x", "+y"], str] = "+z", origin: Floating = 0.5, normalise: Boolean = False, ) -> NDArray: """ Compute the hull section for given axis at given origin. Parameters ---------- hull *Trimesh* hull. axis Axis the hull section will be normal to. origin Coordinate along ``axis`` at which to plot the hull section. normalise Whether to normalise ``axis`` to the extent of the hull along it. Returns ------- :class:`numpy.ndarray` Hull section vertices. Examples -------- >>> from colour.geometry import primitive_cube >>> from colour.utilities import is_trimesh_installed >>> vertices, faces, outline = primitive_cube(1, 1, 1, 2, 2, 2) >>> if is_trimesh_installed: ... import trimesh ... hull = trimesh.Trimesh(vertices['position'], faces, process=False) ... hull_section(hull, origin=0) array([[-0. , -0.5, 0. ], [ 0.5, -0.5, 0. ], [ 0.5, 0. , -0. ], [ 0.5, 0.5, -0. ], [-0. , 0.5, 0. ], [-0.5, 0.5, 0. ], [-0.5, 0. , -0. ], [-0.5, -0.5, -0. ], [-0. , -0.5, 0. ]]) """ import trimesh axis = validate_method( axis, ["+z", "+x", "+y"], '"{0}" axis is invalid, it must be one of {1}!', ) if axis == "+x": normal, plane = np.array([1, 0, 0]), np.array([origin, 0, 0]) elif axis == "+y": normal, plane = np.array([0, 1, 0]), np.array([0, origin, 0]) elif axis == "+z": normal, plane = np.array([0, 0, 1]), np.array([0, 0, origin]) if normalise: vertices = hull.vertices * normal origin = as_float_scalar( linear_conversion( origin, [0, 1], [np.min(vertices), np.max(vertices)])) plane[plane != 0] = origin section = trimesh.intersections.mesh_plane(hull, normal, plane) if len(section) == 0: raise ValueError( f'No section exists on "{axis}" axis at {origin} origin!') section = close_chord(unique_vertices(edges_to_chord(section))) return section
def __init__(self, coefficients: ArrayLike, error: Floating): self._coefficients = as_float_array(coefficients) self._error = as_float_scalar(error)
def uv_to_Luv( uv: ArrayLike, illuminant: ArrayLike = CCS_ILLUMINANTS[ "CIE 1931 2 Degree Standard Observer"]["D65"], Y: Floating = 1, ) -> NDArray: """ Return the *CIE L\\*u\\*v\\** colourspace array from given :math:`uv^p` chromaticity coordinates by extending the array last dimension with given :math:`L` *Lightness*. Parameters ---------- uv :math:`uv^p` chromaticity coordinates. illuminant Reference *illuminant* *CIE xy* chromaticity coordinates or *CIE xyY* colourspace array. Y Optional :math:`Y` *luminance* value used to construct the intermediate *CIE XYZ* colourspace array, the default :math:`Y` *luminance* value is 1. Returns ------- :class:`numpy.ndarray` *CIE L\\*u\\*v\\** colourspace array. Notes ----- +----------------+-----------------------+-----------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +================+=======================+=================+ | ``Luv`` | ``L`` : [0, 100] | ``L`` : [0, 1] | | | | | | | ``u`` : [-100, 100] | ``u`` : [-1, 1] | | | | | | | ``v`` : [-100, 100] | ``v`` : [-1, 1] | +----------------+-----------------------+-----------------+ | ``illuminant`` | [0, 1] | [0, 1] | +----------------+-----------------------+-----------------+ References ---------- :cite:`CIETC1-482004j` Examples -------- >>> import numpy as np >>> uv = np.array([0.37720213, 0.50120264]) >>> uv_to_Luv(uv) # doctest: +ELLIPSIS array([ 100. , 233.1837603..., 42.7474385...]) """ u, v = tsplit(uv) Y = as_float_scalar(to_domain_1(Y)) X = 9 * u / (4 * v) Z = (-5 * Y * v - 3 * u / 4 + 3) / v XYZ = tstack([X, full(u.shape, Y), Z]) return XYZ_to_Luv(from_range_1(XYZ), illuminant)
def colour_fidelity_index_CIE2017( sd_test: SpectralDistribution, additional_data: Boolean = False ) -> Union[Floating, ColourRendering_Specification_CIE2017]: """ Return the *CIE 2017 Colour Fidelity Index* (CFI) :math:`R_f` of given spectral distribution. Parameters ---------- sd_test Test spectral distribution. additional_data Whether to output additional data. Returns ------- :class:`numpy.floating` or \ :class:`colour.quality.ColourRendering_Specification_CIE2017` *CIE 2017 Colour Fidelity Index* (CFI) :math:`R_f`. References ---------- :cite:`CIETC1-902017` Examples -------- >>> from colour.colorimetry import SDS_ILLUMINANTS >>> sd = SDS_ILLUMINANTS['FL2'] >>> colour_fidelity_index_CIE2017(sd) # doctest: +ELLIPSIS 70.1208254... """ if sd_test.shape.start > 380 or sd_test.shape.end < 780: usage_warning("Test spectral distribution shape does not span the" "recommended 380-780nm range, missing values will be" "filled with zeros!") # NOTE: "CIE 2017 Colour Fidelity Index" standard recommends filling # missing values with zeros. sd_test = cast(SpectralDistribution, sd_test.copy()) sd_test.extrapolator = Extrapolator sd_test.extrapolator_kwargs = { "method": "constant", "left": 0, "right": 0, } if sd_test.shape.interval > 5: raise ValueError("Test spectral distribution interval is greater than" "5nm which is the maximum recommended value " 'for computing the "CIE 2017 Colour Fidelity Index"!') shape = SpectralShape( SPECTRAL_SHAPE_CIE2017.start, SPECTRAL_SHAPE_CIE2017.end, sd_test.shape.interval, ) CCT, D_uv = tsplit(CCT_reference_illuminant(sd_test)) sd_reference = sd_reference_illuminant(CCT, shape) # NOTE: All computations except CCT calculation use the # "CIE 1964 10 Degree Standard Observer". # pylint: disable=E1102 cmfs_10 = reshape_msds(MSDS_CMFS["CIE 1964 10 Degree Standard Observer"], shape) # pylint: disable=E1102 sds_tcs = reshape_msds(load_TCS_CIE2017(shape), shape) test_tcs_colorimetry_data = tcs_colorimetry_data(sd_test, sds_tcs, cmfs_10) reference_tcs_colorimetry_data = tcs_colorimetry_data( sd_reference, sds_tcs, cmfs_10) delta_E_s = np.empty(len(sds_tcs.labels)) for i, _delta_E in enumerate(delta_E_s): delta_E_s[i] = euclidean_distance( test_tcs_colorimetry_data[i].Jpapbp, reference_tcs_colorimetry_data[i].Jpapbp, ) R_s = as_float_array(delta_E_to_R_f(delta_E_s)) R_f = as_float_scalar(delta_E_to_R_f(np.average(delta_E_s))) if additional_data: return ColourRendering_Specification_CIE2017( sd_test.name, sd_reference, R_f, R_s, CCT, D_uv, (test_tcs_colorimetry_data, reference_tcs_colorimetry_data), delta_E_s, ) else: return R_f
def test_as_float_scalar(self): """Test :func:`colour.utilities.array.as_float_scalar` definition.""" self.assertEqual(as_float_scalar(1), 1.0) self.assertEqual(as_float_scalar(1).dtype, DEFAULT_FLOAT_DTYPE)
def colour_rendering_index( sd_test: SpectralDistribution, additional_data: Boolean = False ) -> Union[Floating, ColourRendering_Specification_CRI]: """ Return the *Colour Rendering Index* (CRI) :math:`Q_a` of given spectral distribution. Parameters ---------- sd_test Test spectral distribution. additional_data Whether to output additional data. Returns ------- :class:`numpy.floating` or \ :class:`colour.quality.ColourRendering_Specification_CRI` *Colour Rendering Index* (CRI). References ---------- :cite:`Ohno2008a` Examples -------- >>> from colour import SDS_ILLUMINANTS >>> sd = SDS_ILLUMINANTS['FL2'] >>> colour_rendering_index(sd) # doctest: +ELLIPSIS 64.2337241... """ # pylint: disable=E1102 cmfs = reshape_msds( MSDS_CMFS["CIE 1931 2 Degree Standard Observer"], SPECTRAL_SHAPE_DEFAULT, ) shape = cmfs.shape sd_test = reshape_sd(sd_test, shape) tcs_sds = {sd.name: reshape_sd(sd, shape) for sd in SDS_TCS.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 = as_float_scalar( 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 ColourRendering_Specification_CRI( 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: SpectralDistribution, additional_data: Boolean = False, method: Union[Literal["NIST CQS 7.4", "NIST CQS 9.0"], str] = "NIST CQS 9.0", ) -> Union[Floating, ColourRendering_Specification_CQS]: """ Return the *Colour Quality Scale* (CQS) of given spectral distribution using given method. Parameters ---------- sd_test Test spectral distribution. additional_data Whether to output additional data. method Computation method. Returns ------- :class:`numpy.floating` or \ :class:`colour.quality.ColourRendering_Specification_CQS` *Colour Quality Scale* (CQS). References ---------- :cite:`Davis2010a`, :cite:`Ohno2008a`, :cite:`Ohno2013` Examples -------- >>> from colour import SDS_ILLUMINANTS >>> sd = SDS_ILLUMINANTS['FL2'] >>> colour_quality_scale(sd) # doctest: +ELLIPSIS 64.1117031... """ method = validate_method(method, COLOUR_QUALITY_SCALE_METHODS) # pylint: disable=E1102 cmfs = reshape_msds( MSDS_CMFS["CIE 1931 2 Degree Standard Observer"], SPECTRAL_SHAPE_DEFAULT, ) shape = cmfs.shape sd_test = reshape_sd(sd_test, shape) vs_sds = {sd.name: reshape_sd(sd, shape) for sd in SDS_VS[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) CCT_f: Floating 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 / GAMUT_AREA_D65 * 100 if method == "nist cqs 9.0": Q_p = Q_d = 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 = as_float_scalar(100 - 3.6 * (D_Ep_RMS - p_delta_C)) Q_d = as_float_scalar(G_t / G_r * CCT_f * 100) if additional_data: return ColourRendering_Specification_CQS( 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 xy_to_xyY(xy: ArrayLike, Y: Floating = 1) -> NDArray: """ Convert from *CIE xy* chromaticity coordinates to *CIE xyY* colourspace by extending the array last dimension with given :math:`Y` *luminance*. ``xy`` argument with last dimension being equal to 3 will be assumed to be a *CIE xyY* colourspace array argument and will be returned directly by the definition. Parameters ---------- xy *CIE xy* chromaticity coordinates or *CIE xyY* colourspace array. Y Optional :math:`Y` *luminance* value used to construct the *CIE xyY* colourspace array, the default :math:`Y` *luminance* value is 1. Returns ------- :class:`numpy.ndarray` *CIE xyY* colourspace array. Notes ----- +------------+-----------------------+---------------+ | **Domain** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``xy`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ +------------+-----------------------+---------------+ | **Range** | **Scale - Reference** | **Scale - 1** | +============+=======================+===============+ | ``xyY`` | [0, 1] | [0, 1] | +------------+-----------------------+---------------+ - This definition is a convenient object provided to implement support of illuminant argument *luminance* value in various :mod:`colour.models` package objects such as :func:`colour.Lab_to_XYZ` or :func:`colour.Luv_to_XYZ`. References ---------- :cite:`Wikipedia2005` Examples -------- >>> xy = np.array([0.54369557, 0.32107944]) >>> xy_to_xyY(xy) # doctest: +ELLIPSIS array([ 0.5436955..., 0.3210794..., 1. ]) >>> xy = np.array([0.54369557, 0.32107944, 1.00000000]) >>> xy_to_xyY(xy) # doctest: +ELLIPSIS array([ 0.5436955..., 0.3210794..., 1. ]) >>> xy = np.array([0.54369557, 0.32107944]) >>> xy_to_xyY(xy, 100) # doctest: +ELLIPSIS array([ 0.5436955..., 0.3210794..., 100. ]) """ xy = as_float_array(xy) Y = as_float_scalar(to_domain_1(Y)) # Assuming ``xy`` is actually a *CIE xyY* colourspace array argument and # returning it directly. if xy.shape[-1] == 3: return xy x, y = tsplit(xy) xyY = tstack([x, y, full(x.shape, Y)]) return from_range_1(xyY, np.array([1, 1, 100]))
def convert_experiment_results_Breneman1987( experiment: Literal[1, 2, 3, 4, 6, 8, 9, 11, 12] ) -> CorrespondingColourDataset: """ Convert *Breneman (1987)* experiment results to a :class:`colour.CorrespondingColourDataset` class instance. Parameters ---------- experiment *Breneman (1987)* experiment number. Returns ------- :class:`colour.CorrespondingColourDataset` :class:`colour.CorrespondingColourDataset` class instance. Examples -------- >>> from pprint import pprint >>> pprint(tuple(convert_experiment_results_Breneman1987(2))) ... # doctest: +ELLIPSIS (2, array([ 0.9582463..., 1. , 0.9436325...]), array([ 0.9587332..., 1. , 0.4385796...]), array([[ 388.125 , 405. , 345.625 ], [ 266.8957925..., 135. , 28.5983365...], [ 474.5717821..., 405. , 222.75 ...], [ 538.3899082..., 405. , 24.8944954...], [ 178.7430167..., 135. , 19.6089385...], [ 436.6749547..., 405. , 26.5483725...], [ 124.7746282..., 135. , 36.1965613...], [ 77.0794172..., 135. , 60.5850563...], [ 279.9390889..., 405. , 455.8395127...], [ 149.5808157..., 135. , 498.7046827...], [ 372.1113689..., 405. , 669.9883990...], [ 212.3638968..., 135. , 414.6704871...]]), array([[ 400.1039651..., 405. , 191.7287234...], [ 271.0384615..., 135. , 13.5 ...], [ 495.4705323..., 405. , 119.7290874...], [ 580.7967033..., 405. , 6.6758241...], [ 190.1933701..., 135. , 7.4585635...], [ 473.7184115..., 405. , 10.2346570...], [ 135.4936014..., 135. , 20.2376599...], [ 86.4689781..., 135. , 35.2281021...], [ 283.5396281..., 405. , 258.1775929...], [ 119.7044335..., 135. , 282.6354679...], [ 359.9532224..., 405. , 381.0031185...], [ 181.8271461..., 135. , 204.0661252...]]), 1500.0, 1500.0, 0.3, 0.3, {}) """ valid_experiment_results = [1, 2, 3, 4, 6, 8, 9, 11, 12] attest( experiment in valid_experiment_results, f'"Breneman (1987)" experiment result is invalid, it must be one of ' f'"{valid_experiment_results}"!', ) samples_luminance = [ 0.270, 0.090, 0.270, 0.270, 0.090, 0.270, 0.090, 0.090, 0.270, 0.090, 0.270, 0.090, ] experiment_results = list(BRENEMAN_EXPERIMENTS[experiment]) illuminant_chromaticities = experiment_results.pop(0) Y_r = Y_t = as_float( BRENEMAN_EXPERIMENT_PRIMARIES_CHROMATICITIES[experiment].Y) B_r = B_t = 0.3 XYZ_t, XYZ_r = (xy_to_XYZ( np.hstack([ Luv_uv_to_xy(illuminant_chromaticities[1:3]), full((2, 1), as_float_scalar(Y_r)), ])) / Y_r) xyY_cr, xyY_ct = [], [] for i, experiment_result in enumerate(experiment_results): xyY_cr.append( np.hstack([ Luv_uv_to_xy(experiment_result[2]), samples_luminance[i] * Y_r, ])) xyY_ct.append( np.hstack([ Luv_uv_to_xy(experiment_result[1]), samples_luminance[i] * Y_t, ])) XYZ_cr = xyY_to_XYZ(xyY_cr) XYZ_ct = xyY_to_XYZ(xyY_ct) return CorrespondingColourDataset(experiment, XYZ_r, XYZ_t, XYZ_cr, XYZ_ct, Y_r, Y_t, B_r, B_t, {})