def test_intersect_line_segments(self): """ Test :func:`colour.algebra.geometry.intersect_line_segments` definition. """ l_1 = np.array([ [[0.15416284, 0.7400497], [0.26331502, 0.53373939]], [[0.01457496, 0.91874701], [0.90071485, 0.03342143]], ]) l_2 = np.array([ [[0.95694934, 0.13720932], [0.28382835, 0.60608318]], [[0.94422514, 0.85273554], [0.00225923, 0.52122603]], [[0.55203763, 0.48537741], [0.76813415, 0.16071675]], [[0.01457496, 0.91874701], [0.90071485, 0.03342143]], ]) s = intersect_line_segments(l_1, l_2) np.testing.assert_almost_equal( s.xy, np.array([ [ [np.nan, np.nan], [0.22791841, 0.60064309], [np.nan, np.nan], [np.nan, np.nan], ], [ [0.42814517, 0.50555685], [0.30560559, 0.62798382], [0.7578749, 0.17613012], [np.nan, np.nan], ], ]), decimal=7, ) np.testing.assert_array_equal( s.intersect, np.array([[False, True, False, False], [True, True, True, False]]), ) np.testing.assert_array_equal( s.parallel, np.array([[False, False, False, False], [False, False, False, True]]), ) np.testing.assert_array_equal( s.coincident, np.array([[False, False, False, False], [False, False, False, True]]), )
def test_intersect_line_segments(self): """ Tests :func:`colour.algebra.geometry.intersect_line_segments` definition. """ l_1 = np.array([[[0.15416284, 0.7400497], [0.26331502, 0.53373939]], [[0.01457496, 0.91874701], [0.90071485, 0.03342143]]]) l_2 = np.array([[[0.95694934, 0.13720932], [0.28382835, 0.60608318]], [[0.94422514, 0.85273554], [0.00225923, 0.52122603]], [[0.55203763, 0.48537741], [0.76813415, 0.16071675]], [[0.01457496, 0.91874701], [0.90071485, 0.03342143]]]) s = intersect_line_segments(l_1, l_2) np.testing.assert_almost_equal( s.xy, np.array([[[np.nan, np.nan], [0.22791841, 0.60064309], [np.nan, np.nan], [np.nan, np.nan]], [[0.42814517, 0.50555685], [0.30560559, 0.62798382], [0.7578749, 0.17613012], [np.nan, np.nan]]]), decimal=7) np.testing.assert_array_equal( s.intersect, np.array([[False, True, False, False], [True, True, True, False]], dtype=bool)) np.testing.assert_array_equal( s.parallel, np.array([[False, False, False, False], [False, False, False, True]], dtype=bool)) np.testing.assert_array_equal( s.coincident, np.array([[False, False, False, False], [False, False, False, True]], dtype=bool))
def closest_spectral_locus_wavelength(xy, xy_n, xy_s, inverse=False): """ Returns the coordinates and closest spectral locus wavelength index to the point where the line defined by the given achromatic stimulus :math:`xy_n` to colour stimulus :math:`xy_n` *CIE xy* chromaticity coordinates intersects the spectral locus. Parameters ---------- xy : array_like Colour stimulus *CIE xy* chromaticity coordinates. xy_n : array_like Achromatic stimulus *CIE xy* chromaticity coordinates. xy_s : array_like Spectral locus *CIE xy* chromaticity coordinates. inverse : bool, optional The intersection will be computed using the colour stimulus :math:`xy` to achromatic stimulus :math:`xy_n` inverse direction. Returns ------- tuple Closest wavelength index, intersection point *CIE xy* chromaticity coordinates. Raises ------ ValueError If no closest spectral locus wavelength index and coordinates found. Examples -------- >>> xy = np.array([0.54369557, 0.32107944]) >>> xy_n = np.array([0.31270000, 0.32900000]) >>> xy_s = XYZ_to_xy(CMFS['CIE 1931 2 Degree Standard Observer'].values) >>> ix, intersect = closest_spectral_locus_wavelength(xy, xy_n, xy_s) >>> print(ix) # 256 >>> print(intersect) # doctest: +ELLIPSIS [ 0.6835474... 0.3162840...] """ xy = as_float_array(xy) xy_n = np.resize(xy_n, xy.shape) xy_s = as_float_array(xy_s) xy_e = (extend_line_segment(xy, xy_n) if inverse else extend_line_segment( xy_n, xy)) # Closing horse-shoe shape to handle line of purples intersections. xy_s = np.vstack([xy_s, xy_s[0, :]]) xy_wl = intersect_line_segments( np.concatenate((xy_n, xy_e), -1), np.hstack([xy_s, np.roll(xy_s, 1, axis=0)])).xy xy_wl = xy_wl[~np.isnan(xy_wl).any(axis=-1)] if not len(xy_wl): raise ValueError( 'No closest spectral locus wavelength index and coordinates found ' 'for "{0}" colour stimulus and "{1}" achromatic stimulus "xy" ' 'chromaticity coordinates!'.format(xy, xy_n)) i_wl = np.argmin(scipy.spatial.distance.cdist(xy_wl, xy_s), axis=-1) i_wl = np.reshape(i_wl, xy.shape[0:-1]) xy_wl = np.reshape(xy_wl, xy.shape) return i_wl, xy_wl
def dominant_wavelength(xy, xy_n, cmfs=CMFS['CIE 1931 2 Degree Standard Observer'], inverse=False): """ Returns the *dominant wavelength* :math:`\\lambda_d` for given colour stimulus :math:`xy` and the related :math:`xy_wl` first and :math:`xy_{cw}` second intersection coordinates with the spectral locus. In the eventuality where the :math:`xy_wl` first intersection coordinates are on the line of purples, the *complementary wavelength* will be computed in lieu. The *complementary wavelength* is indicated by a negative sign and the :math:`xy_{cw}` second intersection coordinates which are set by default to the same value than :math:`xy_wl` first intersection coordinates will be set to the *complementary dominant wavelength* intersection coordinates with the spectral locus. Parameters ---------- xy : array_like Colour stimulus *CIE xy* chromaticity coordinates. xy_n : array_like Achromatic stimulus *CIE xy* chromaticity coordinates. cmfs : XYZ_ColourMatchingFunctions, optional Standard observer colour matching functions. inverse : bool, optional Inverse the computation direction to retrieve the *complementary wavelength*. Returns ------- tuple *Dominant wavelength*, first intersection point *CIE xy* chromaticity coordinates, second intersection point *CIE xy* chromaticity coordinates. References ---------- :cite:`CIETC1-482004o`, :cite:`Erdogana` Examples -------- *Dominant wavelength* computation: >>> from pprint import pprint >>> xy = np.array([0.54369557, 0.32107944]) >>> xy_n = np.array([0.31270000, 0.32900000]) >>> cmfs = CMFS['CIE 1931 2 Degree Standard Observer'] >>> pprint(dominant_wavelength(xy, xy_n, cmfs)) # doctest: +ELLIPSIS (array(616...), array([ 0.6835474..., 0.3162840...]), array([ 0.6835474..., 0.3162840...])) *Complementary dominant wavelength* is returned if the first intersection is located on the line of purples: >>> xy = np.array([0.37605506, 0.24452225]) >>> pprint(dominant_wavelength(xy, xy_n, cmfs)) # doctest: +ELLIPSIS (array(-509.0), array([ 0.4572314..., 0.1362814...]), array([ 0.0104096..., 0.7320745...])) """ xy = as_float_array(xy) xy_n = np.resize(xy_n, xy.shape) xy_s = XYZ_to_xy(cmfs.values) i_wl, xy_wl = closest_spectral_locus_wavelength(xy, xy_n, xy_s, inverse) xy_cwl = xy_wl wl = cmfs.wavelengths[i_wl] xy_e = (extend_line_segment(xy, xy_n) if inverse else extend_line_segment( xy_n, xy)) intersect = intersect_line_segments(np.concatenate((xy_n, xy_e), -1), np.hstack([xy_s[0], xy_s[-1]])).intersect intersect = np.reshape(intersect, wl.shape) i_wl_r, xy_cwl_r = closest_spectral_locus_wavelength( xy, xy_n, xy_s, not inverse) wl_r = -cmfs.wavelengths[i_wl_r] wl = np.where(intersect, wl_r, wl) xy_cwl = np.where(intersect[..., np.newaxis], xy_cwl_r, xy_cwl) return wl, np.squeeze(xy_wl), np.squeeze(xy_cwl)
def closest_spectral_locus_wavelength(xy, xy_n, xy_s, reverse=False): """ Returns the coordinates and closest spectral locus wavelength index to the point where the line defined by the given achromatic stimulus :math:`xy_n` to colour stimulus :math:`xy_n` *xy* chromaticity coordinates intersects the spectral locus. Parameters ---------- xy : array_like Colour stimulus *xy* chromaticity coordinates. xy_n : array_like Achromatic stimulus *xy* chromaticity coordinates. xy_s : array_like Spectral locus *xy* chromaticity coordinates. reverse : bool, optional The intersection will be computed using the colour stimulus :math:`xy` to achromatic stimulus :math:`xy_n` reverse direction. Returns ------- tuple Closest wavelength index, intersection point *xy* chromaticity coordinates. Raises ------ ValueError If no closest spectral locus wavelength index and coordinates found. Examples -------- >>> xy = np.array([0.54369557, 0.32107944]) >>> xy_n = np.array([0.31270000, 0.32900000]) >>> xy_s = XYZ_to_xy(CMFS['CIE 1931 2 Degree Standard Observer'].values) >>> ix, intersect = closest_spectral_locus_wavelength(xy, xy_n, xy_s) >>> print(ix) # 256 >>> print(intersect) # doctest: +ELLIPSIS [ 0.6835474... 0.3162840...] """ xy = as_float_array(xy) xy_n = np.resize(xy_n, xy.shape) xy_s = as_float_array(xy_s) xy_e = (extend_line_segment(xy, xy_n) if reverse else extend_line_segment(xy_n, xy)) # Closing horse-shoe shape to handle line of purples intersections. xy_s = np.vstack([xy_s, xy_s[0, :]]) xy_wl = intersect_line_segments( np.concatenate((xy_n, xy_e), -1), np.hstack([xy_s, np.roll(xy_s, 1, axis=0)])).xy xy_wl = xy_wl[~np.isnan(xy_wl).any(axis=-1)] if not len(xy_wl): raise ValueError( 'No closest spectral locus wavelength index and coordinates found ' 'for "{0}" colour stimulus and "{1}" achromatic stimulus "xy" ' 'chromaticity coordinates!'.format(xy, xy_n)) i_wl = np.argmin(scipy.spatial.distance.cdist(xy_wl, xy_s), axis=-1) i_wl = np.reshape(i_wl, xy.shape[0:-1]) xy_wl = np.reshape(xy_wl, xy.shape) return i_wl, xy_wl
def dominant_wavelength(xy, xy_n, cmfs=CMFS['CIE 1931 2 Degree Standard Observer'], reverse=False): """ Returns the *dominant wavelength* :math:`\\lambda_d` for given colour stimulus :math:`xy` and the related :math:`xy_wl` first and :math:`xy_{cw}` second intersection coordinates with the spectral locus. In the eventuality where the :math:`xy_wl` first intersection coordinates are on the line of purples, the *complementary wavelength* will be computed in lieu. The *complementary wavelength* is indicated by a negative sign and the :math:`xy_{cw}` second intersection coordinates which are set by default to the same value than :math:`xy_wl` first intersection coordinates will be set to the *complementary dominant wavelength* intersection coordinates with the spectral locus. Parameters ---------- xy : array_like Colour stimulus *xy* chromaticity coordinates. xy_n : array_like Achromatic stimulus *xy* chromaticity coordinates. cmfs : XYZ_ColourMatchingFunctions, optional Standard observer colour matching functions. reverse : bool, optional Reverse the computation direction to retrieve the *complementary wavelength*. Returns ------- tuple *Dominant wavelength*, first intersection point *xy* chromaticity coordinates, second intersection point *xy* chromaticity coordinates. References ---------- :cite:`CIETC1-482004o`, :cite:`Erdogana` Examples -------- *Dominant wavelength* computation: >>> from pprint import pprint >>> xy = np.array([0.54369557, 0.32107944]) >>> xy_n = np.array([0.31270000, 0.32900000]) >>> cmfs = CMFS['CIE 1931 2 Degree Standard Observer'] >>> pprint(dominant_wavelength(xy, xy_n, cmfs)) # doctest: +ELLIPSIS (array(616...), array([ 0.6835474..., 0.3162840...]), array([ 0.6835474..., 0.3162840...])) *Complementary dominant wavelength* is returned if the first intersection is located on the line of purples: >>> xy = np.array([0.37605506, 0.24452225]) >>> pprint(dominant_wavelength(xy, xy_n, cmfs)) # doctest: +ELLIPSIS (array(-509.0), array([ 0.4572314..., 0.1362814...]), array([ 0.0104096..., 0.7320745...])) """ xy = as_float_array(xy) xy_n = np.resize(xy_n, xy.shape) xy_s = XYZ_to_xy(cmfs.values) i_wl, xy_wl = closest_spectral_locus_wavelength(xy, xy_n, xy_s, reverse) xy_cwl = xy_wl wl = cmfs.wavelengths[i_wl] xy_e = (extend_line_segment(xy, xy_n) if reverse else extend_line_segment(xy_n, xy)) intersect = intersect_line_segments( np.concatenate((xy_n, xy_e), -1), np.hstack([xy_s[0], xy_s[-1]])).intersect intersect = np.reshape(intersect, wl.shape) i_wl_r, xy_cwl_r = closest_spectral_locus_wavelength( xy, xy_n, xy_s, not reverse) wl_r = -cmfs.wavelengths[i_wl_r] wl = np.where(intersect, wl_r, wl) xy_cwl = np.where(intersect[..., np.newaxis], xy_cwl_r, xy_cwl) return wl, np.squeeze(xy_wl), np.squeeze(xy_cwl)
def dominant_wavelength(xy, xy_n, cmfs=CMFS['CIE 1931 2 Degree Standard Observer'], reverse=False): """ Returns the *dominant wavelength* :math:`\lambda_d` for given colour stimulus :math:`xy` and the related :math:`xy_wl` first and :math:`xy_{cw}` second intersection coordinates with the spectral locus. In the eventuality where the :math:`xy_wl` first intersection coordinates are on the line of purples, the *complementary wavelength* will be computed in lieu. The *complementary wavelength* is indicated by a negative sign and the :math:`xy_{cw}` second intersection coordinates which are set by default to the same value than :math:`xy_wl` first intersection coordinates will be set to the *complementary dominant wavelength* intersection coordinates with the spectral locus. Parameters ---------- xy : array_like Colour stimulus *xy* chromaticity coordinates. xy_n : array_like Achromatic stimulus *xy* chromaticity coordinates. cmfs : XYZ_ColourMatchingFunctions, optional Standard observer colour matching functions. reverse : bool, optional Reverse the computation direction to retrieve the *complementary wavelength*. Returns ------- tuple *Dominant wavelength*, first intersection point *xy* chromaticity coordinates, second intersection point *xy* chromaticity coordinates. See Also -------- complementary_wavelength Examples -------- *Dominant wavelength* computation: >>> from pprint import pprint >>> xy = np.array([0.26415, 0.37770]) >>> xy_n = np.array([0.31270, 0.32900]) >>> cmfs = CMFS['CIE 1931 2 Degree Standard Observer'] >>> pprint(dominant_wavelength(xy, xy_n, cmfs)) # doctest: +ELLIPSIS (array(504...), array([ 0.0036969..., 0.6389577...]), array([ 0.0036969..., 0.6389577...])) *Complementary dominant wavelength* is returned if the first intersection is located on the line of purples: >>> xy = np.array([0.35000, 0.25000]) >>> pprint(dominant_wavelength(xy, xy_n, cmfs)) # doctest: +ELLIPSIS (array(-520...), array([ 0.4133314..., 0.1158663...]), array([ 0.0743553..., 0.8338050...])) """ xy = np.asarray(xy) xy_n = np.resize(xy_n, xy.shape) xy_s = XYZ_to_xy(cmfs.values) i_wl, xy_wl = closest_spectral_locus_wavelength(xy, xy_n, xy_s, reverse) xy_cwl = xy_wl wl = cmfs.wavelengths[i_wl] xy_e = (extend_line_segment(xy, xy_n) if reverse else extend_line_segment(xy_n, xy)) intersect = intersect_line_segments( np.concatenate((xy_n, xy_e), -1), np.hstack((xy_s[0], xy_s[-1]))).intersect intersect = np.reshape(intersect, wl.shape) i_wl_r, xy_cwl_r = closest_spectral_locus_wavelength( xy, xy_n, xy_s, not reverse) wl_r = -cmfs.wavelengths[i_wl_r] wl = np.where(intersect, wl_r, wl) xy_cwl = np.where(intersect[..., np.newaxis], xy_cwl_r, xy_cwl) return wl, np.squeeze(xy_wl), np.squeeze(xy_cwl)
def closest_spectral_locus_wavelength( xy: ArrayLike, xy_n: ArrayLike, xy_s: ArrayLike, inverse: Boolean = False) -> Tuple[NDArray, NDArray]: """ Return the coordinates and closest spectral locus wavelength index to the point where the line defined by the given achromatic stimulus :math:`xy_n` to colour stimulus :math:`xy_n` *CIE xy* chromaticity coordinates intersects the spectral locus. Parameters ---------- xy Colour stimulus *CIE xy* chromaticity coordinates. xy_n Achromatic stimulus *CIE xy* chromaticity coordinates. xy_s Spectral locus *CIE xy* chromaticity coordinates. inverse The intersection will be computed using the colour stimulus :math:`xy` to achromatic stimulus :math:`xy_n` inverse direction. Returns ------- :class:`tuple` Closest wavelength index, intersection point *CIE xy* chromaticity coordinates. Raises ------ ValueError If no closest spectral locus wavelength index and coordinates found. Examples -------- >>> from colour.colorimetry import MSDS_CMFS >>> cmfs = MSDS_CMFS['CIE 1931 2 Degree Standard Observer'] >>> xy = np.array([0.54369557, 0.32107944]) >>> xy_n = np.array([0.31270000, 0.32900000]) >>> xy_s = XYZ_to_xy(cmfs.values) >>> ix, intersect = closest_spectral_locus_wavelength(xy, xy_n, xy_s) >>> print(ix) # 256 >>> print(intersect) # doctest: +ELLIPSIS [ 0.6835474... 0.3162840...] """ xy = as_float_array(xy) xy_n = np.resize(xy_n, xy.shape) xy_s = as_float_array(xy_s) xy_e = (extend_line_segment(xy, xy_n) if inverse else extend_line_segment( xy_n, xy)) # Closing horse-shoe shape to handle line of purples intersections. xy_s = np.vstack([xy_s, xy_s[0, :]]) xy_wl = intersect_line_segments( np.concatenate((xy_n, xy_e), -1), np.hstack([xy_s, np.roll(xy_s, 1, axis=0)]), ).xy # Extracting the first intersection per-wavelength. xy_wl = np.sort(xy_wl, 1)[:, 0, :] if not len(xy_wl): raise ValueError( f"No closest spectral locus wavelength index and coordinates " f'found for "{xy}" colour stimulus and "{xy_n}" achromatic ' f'stimulus "xy" chromaticity coordinates!') i_wl = np.argmin(scipy.spatial.distance.cdist(xy_wl, xy_s), axis=-1) i_wl = np.reshape(i_wl, xy.shape[0:-1]) xy_wl = np.reshape(xy_wl, xy.shape) return i_wl, xy_wl