def test_xyY(self, getvars): # check xyz_to_..., ..._to_xyz: S, spds, rfls, xyz, xyzw = getvars labw = lx.xyz_to_Yxy(xyzw) lab = lx.xyz_to_Yxy(xyz) xyzw_ = lx.Yxy_to_xyz(labw) xyz_ = lx.Yxy_to_xyz(lab) assert np.isclose(xyz, xyz_).all() assert np.isclose(xyzw, xyzw_).all()
def xyz_to_cct_mcamy(xyzw): """ Convert XYZ tristimulus values to correlated color temperature (CCT) using the mccamy approximation. | Only valid for approx. 3000 < T < 9000, if < 6500, error < 2 K. Args: :xyzw: | ndarray of tristimulus values Returns: :cct: | ndarray of correlated color temperatures estimates References: 1. `McCamy, Calvin S. (April 1992). "Correlated color temperature as an explicit function of chromaticity coordinates". Color Research & Application. 17 (2): 142–144. <http://onlinelibrary.wiley.com/doi/10.1002/col.5080170211/abstract>`_ """ Yxy = xyz_to_Yxy(xyzw) #axis_of_v3 = len(xyzw.shape)-1 n = (Yxy[:, 1] - 0.3320) / (Yxy[:, 2] - 0.1858) return np2d(-449.0 * (n**3) + 3525.0 * (n**2) - 6823.3 * n + 5520.33).T
def xyz_to_space(input_type='RGB', R=None, G=None, B=None, W=None, file='GW_2019-09-13.txt', space='uv'): """ [ADD THIS] Parameters ---------- input_type : TYPE, optional DESCRIPTION. The default is 'RGB'. R : TYPE, optional DESCRIPTION. The default is None. G : TYPE, optional DESCRIPTION. The default is None. B : TYPE, optional DESCRIPTION. The default is None. W : TYPE, optional DESCRIPTION. The default is None. file : TYPE, optional DESCRIPTION. The default is 'GW_2019-09-13.txt'. space : TYPE, optional DESCRIPTION. The default is 'uv'. Returns ------- TYPE DESCRIPTION. """ if input_type == 'RGB': R_Yuv = lx.xyz_to_Yuv(R) G_Yuv = lx.xyz_to_Yuv(G) B_Yuv = lx.xyz_to_Yuv(B) return R_Yuv, G_Yuv, B_Yuv elif input_type == 'RGBW': R_Yuv = lx.xyz_to_Yuv(R) G_Yuv = lx.xyz_to_Yuv(G) B_Yuv = lx.xyz_to_Yuv(B) W_Yuv = lx.xyz_to_Yuv(W) return R_Yuv, G_Yuv, B_Yuv, W_Yuv elif input_type == 'file': XYZs = np.loadtxt(file, delimiter='\t') if space == 'uv': output = lx.xyz_to_Yuv(XYZs) else: output = lx.xyz_to_Yxy(XYZs) for count, _ in enumerate(output): count = count + 1 return XYZs, output, count
def _plot_tm30_report_bottom(axh, spd, notes = '', max_len_notes_line = 40): """ Print some notes, the CIE x, y, u',v' and Ra, R9 values of the source in some empty axes. Args: :axh: | None, optional | Plot on specified axes. :spd: | ndarray or dict | If ndarray: single spectral power distribution. :notes: | string to be split :max_len_notes_line: | 40, optional | Maximum length of a single line when splitting the string. Returns: :axh: | handle to figure axes. """ ciera = spd_to_cri(spd, cri_type = 'ciera') cierai = spd_to_cri(spd, cri_type = 'ciera-14', out = 'Rfi') xyzw = spd_to_xyz(spd, cieobs = '1931_2', relative = True) Yxyw = xyz_to_Yxy(xyzw) Yuvw = xyz_to_Yuv(xyzw) notes_ = _split_notes(notes, max_len_notes_line = max_len_notes_line) axh.set_xticks(np.arange(10)) axh.set_xticklabels(['' for i in np.arange(10)]) axh.set_yticks(np.arange(4)) axh.set_yticklabels(['' for i in np.arange(4)]) axh.set_axis_off() axh.set_xlabel([]) axh.text(0,2.8, 'Notes: ', fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(0.75,2.8, notes_, fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(6,2.8, "x {:1.4f}".format(Yxyw[0,1]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(6,2.2, "y {:1.4f}".format(Yxyw[0,2]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(6,1.6, "u' {:1.4f}".format(Yuvw[0,1]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(6,1.0, "v' {:1.4f}".format(Yuvw[0,2]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(7.5,2.8, "CIE 13.3-1995", fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(7.5,2.2, " (CRI) ", fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(7.5,1.6, " $R_a$ {:1.0f}".format(ciera[0,0]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(7.5,1.0, " $R_9$ {:1.0f}".format(cierai[9,0]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') # Create a Rectangle patch rect = patches.Rectangle((7.2,0.5),1.7,2.5,linewidth=1,edgecolor='k',facecolor='none') # Add the patch to the Axes axh.add_patch(rect) return axh
def xyz_to_cct_HA(xyzw): """ Convert XYZ tristimulus values to correlated color temperature (CCT). Args: :xyzw: | ndarray of tristimulus values Returns: :cct: | ndarray of correlated color temperatures estimates References: 1. `Hernández-Andrés, Javier; Lee, RL; Romero, J (September 20, 1999). Calculating Correlated Color Temperatures Across the Entire Gamut of Daylight and Skylight Chromaticities. Applied Optics. 38 (27), 5703–5709. P <https://www.osapublishing.org/ao/abstract.cfm?uri=ao-38-27-5703>`_ Notes: According to paper small error from 3000 - 800 000 K, but a test with Planckians showed errors up to 20% around 500 000 K; e>0.05 for T>200 000, e>0.1 for T>300 000, ... """ if len(xyzw.shape)>2: raise Exception('xyz_to_cct_HA(): Input xyzw.ndim must be <= 2 !') out_of_range_code = np.nan xe = [0.3366, 0.3356] ye = [0.1735, 0.1691] A0 = [-949.86315, 36284.48953] A1 = [6253.80338, 0.00228] t1 = [0.92159, 0.07861] A2 = [28.70599, 5.4535*1e-36] t2 = [0.20039, 0.01543] A3 = [0.00004, 0.0] t3 = [0.07125,1.0] cct_ranges = np.array([[3000.0,50000.0],[50000.0,800000.0]]) Yxy = xyz_to_Yxy(xyzw) CCT = np.ones((1,Yxy.shape[0]))*out_of_range_code for i in range(2): n = (Yxy[:,1]-xe[i])/(Yxy[:,2]-ye[i]) CCT_i = np2d(np.array(A0[i] + A1[i]*np.exp(np.divide(-n,t1[i])) + A2[i]*np.exp(np.divide(-n,t2[i])) + A3[i]*np.exp(np.divide(-n,t3[i])))) p = (CCT_i >= (1.0-0.05*(i == 0))*cct_ranges[i][0]) & (CCT_i < (1.0+0.05*(i == 0))*cct_ranges[i][1]) CCT[p] = CCT_i[p] p = (CCT_i < (1.0-0.05)*cct_ranges[0][0]) #smaller than smallest valid CCT value CCT[p] = -1 if (np.isnan(CCT.sum()) == True) | (np.any(CCT == -1)): print("Warning: xyz_to_cct_HA(): one or more CCTs out of range! --> (CCT < 3 kK, CCT >800 kK) coded as (-1, NaN) 's") return CCT.T
def to_Yxy(self): """ Convert XYZ tristimulus values CIE Yxy chromaticity values. Returns: :Yxy: | luxpy.LAB with .value field that is a ndarray | with Yxy chromaticity values. | (Y value refers to luminance or luminance factor) """ return LAB(value=xyz_to_Yxy(self.value), relative=self.relative, cieobs=self.cieobs, dtype='Yxy')
def xyz_to_xy(xyz): """ Convert XYZ to x, y.| --------------- Parameters: :xyz: array (shape: (1, 3)) Returns: :xy_mean: array (shape: (1, 2)) """ y_xy = lx.xyz_to_Yxy(xyz) y_xy_mean = np.array( [[y_xy[:, 0].mean(), y_xy[:, 1].mean(), y_xy[:, 2].mean()]]) y_xy_mean = np.around(y_xy_mean, decimals=3) x_mean = y_xy_mean[:, 1] x_mean = str(x_mean)[1:-1] y_mean = y_xy_mean[:, 2] y_mean = str(y_mean)[1:-1] xy_mean = np.array([[x_mean], [y_mean]]) xy_mean = xy_mean.T return xy_mean
rfls_norm = lx.spd_normalize(rfls_np_cp, norm_type='max', norm_f=1) rfls_norm = lx.spd_normalize(rfls_np_cp, norm_type='pu', norm_f=1) rfls_norm = lx.spd_normalize(rfls_np_cp, norm_type='ru', norm_f=1) rfls_norm = lx.spd_normalize(rfls_np_cp, norm_type='qu', norm_f=1) # check spd_to_xyz: xyz = lx.spd_to_xyz(spds) xyz = lx.spd_to_xyz(spds, relative=False) xyz = lx.spd_to_xyz(spds, rfl=rfls) xyz, xyzw = lx.spd_to_xyz(spds, rfl=rfls, cieobs='1931_2', out=2) # check xyz_to_cct(): cct, duv = lx.xyz_to_cct(xyzw, cieobs='1931_2', out=2) # check xyz_to_..., ..._to_xyz: labw = lx.xyz_to_Yxy(xyzw) lab = lx.xyz_to_Yxy(xyz) xyzw_ = lx.Yxy_to_xyz(labw) xyz_ = lx.Yxy_to_xyz(lab) labw = lx.xyz_to_Yuv(xyzw) lab = lx.xyz_to_Yuv(xyz) xyzw_ = lx.Yuv_to_xyz(labw) xyz_ = lx.Yuv_to_xyz(lab) labw = lx.xyz_to_lab(xyzw, xyzw=xyzw[:1, :]) lab = lx.xyz_to_lab(xyz, xyzw=xyzw[:1, :]) xyzw_ = lx.lab_to_xyz(labw, xyzw=xyzw[:1, :]) xyz_ = lx.lab_to_xyz(lab, xyzw=xyzw[:1, :]) labw = lx.xyz_to_luv(xyzw, xyzw=xyzw)
def apply_ciecat94(xyz, xyzw, xyzwr=None, E=1000, Er=1000, Yb=20, D=1, cat94_old=True): """ Calculate corresponding color tristimulus values using the CIECAT94 chromatic adaptation transform. Args: :xyz: | ndarray with sample 1931 2° XYZ tristimulus values under the test illuminant :xyzw: | ndarray with white point tristimulus values of the test illuminant :xyzwr: | None, optional | ndarray with white point tristimulus values of the reference illuminant | None defaults to D65. :E: | 100, optional | Illuminance (lx) of test illumination :Er: | 63.66, optional | Illuminance (lx) of the reference illumination :Yb: | 20, optional | Relative luminance of the adaptation field (background) :D: | 1, optional | Degree of chromatic adaptation. | For object colours D = 1, | and for luminous colours (typically displays) D=0 Returns: :xyzc: | ndarray with corresponding tristimlus values. Reference: 1. CIE160-2004. (2004). A review of chromatic adaptation transforms (Vols. CIE160-200). CIE. """ #-------------------------------------------- # Define cone/chromatic adaptation sensor space: mcat = _MCATS['kries'] invmcat = np.linalg.inv(mcat) #-------------------------------------------- # Define default ref. white point: if xyzwr is None: xyzwr = np.array( [[9.5047e+01, 1.0000e+02, 1.0888e+02]] ) #spd_to_xyz(_CIE_D65, cieobs = '1931_2', relative = True, rfl = None) #-------------------------------------------- # Calculate Y,x,y of white: Yxyw = xyz_to_Yxy(xyzw) Yxywr = xyz_to_Yxy(xyzwr) #-------------------------------------------- # Calculate La, Lar: La = Yb * E / np.pi / 100 Lar = Yb * Er / np.pi / 100 #-------------------------------------------- # Calculate CIELAB L* of samples: Lstar = xyz_to_lab(xyz, xyzw)[..., 0] #-------------------------------------------- # Define xi_, eta_ and zeta_ functions: xi_ = lambda Yxy: (0.48105 * Yxy[..., 1] + 0.78841 * Yxy[..., 2] - 0.080811 ) / Yxy[..., 2] eta_ = lambda Yxy: (-0.27200 * Yxy[..., 1] + 1.11962 * Yxy[..., 2] + 0.04570) / Yxy[..., 2] zeta_ = lambda Yxy: 0.91822 * (1 - Yxy[..., 1] - Yxy[..., 2]) / Yxy[..., 2] #-------------------------------------------- # Calculate intermediate values for test and ref. illuminants: xit, etat, zetat = xi_(Yxyw), eta_(Yxyw), zeta_(Yxyw) xir, etar, zetar = xi_(Yxywr), eta_(Yxywr), zeta_(Yxywr) #-------------------------------------------- # Calculate alpha: if cat94_old == False: alpha = 0.1151 * np.log10(La) + 0.0025 * (Lstar - 50) + (0.22 * D + 0.510) alpha[alpha > 1] = 1 else: alpha = 1 #-------------------------------------------- # Calculate adapted intermediate xip, etap zetap: xip = alpha * xit - (1 - alpha) * xir etap = alpha * etat - (1 - alpha) * etar zetap = alpha * zetat - (1 - alpha) * zetar #-------------------------------------------- # Calculate effective adapting response Rw, Gw, Bw and Rwr, Gwr, Bwr: #Rw, Gw, Bw = La*xit, La*etat, La*zetat # according to westland's book: Computational Colour Science wirg Matlab Rw, Gw, Bw = La * xip, La * etap, La * zetap # according to CIE160-2004 Rwr, Gwr, Bwr = Lar * xir, Lar * etar, Lar * zetar #-------------------------------------------- # Calculate beta1_ and beta2_ exponents for (R,G) and B: beta1_ = lambda x: (6.469 + 6.362 * x**0.4495) / (6.469 + x**0.4495) beta2_ = lambda x: 0.7844 * (8.414 + 8.091 * x**0.5128) / (8.414 + x** 0.5128) b1Rw, b1Rwr, b1Gw, b1Gwr = beta1_(Rw), beta1_(Rwr), beta1_(Gw), beta1_(Gwr) b2Bw, b2Bwr = beta2_(Bw), beta2_(Bwr) #-------------------------------------------- # Noise term: n = 1 if cat94_old else 0.1 #-------------------------------------------- # K factor = p/q (for correcting the difference between # the illuminance of the test and references conditions) # calculate q: p = ((Yb * xip + n) / (20 * xip + n))**(2 / 3 * b1Rw) * ((Yb * etap + n) / (20 * etap + n))**(1 / 3 * b1Gw) q = ((Yb * xir + n) / (20 * xir + n))**(2 / 3 * b1Rwr) * ((Yb * etar + n) / (20 * etar + n))**(1 / 3 * b1Gwr) K = p / q #-------------------------------------------- # transform sample xyz to cat sensor space: rgb = math.dot23(mcat, xyz.T).T #-------------------------------------------- # Calculate corresponding colors: Rc = (Yb * xir + n) * K**(1 / b1Rwr) * ((rgb[..., 0] + n) / (Yb * xip + n))**(b1Rw / b1Rwr) - n Gc = (Yb * etar + n) * K**(1 / b1Gwr) * ( (rgb[..., 1] + n) / (Yb * etap + n))**(b1Gw / b1Gwr) - n Bc = (Yb * zetar + n) * K**(1 / b2Bwr) * ( (rgb[..., 2] + n) / (Yb * zetap + n))**(b2Bw / b2Bwr) - n #-------------------------------------------- # transform to xyz and return: xyzc = math.dot23(invmcat, ajoin((Rc, Gc, Bc)).T).T return xyzc
def xyz_to_Ydlep(xyz, cieobs = _CIEOBS, xyzw = _COLORTF_DEFAULT_WHITE_POINT, flip_axes = False, **kwargs): """ Convert XYZ tristimulus values to Y, dominant (complementary) wavelength and excitation purity. Args: :xyz: | ndarray with tristimulus values :xyzw: | None or ndarray with tristimulus values of a single (!) native white point, optional | None defaults to xyz of CIE D65 using the :cieobs: observer. :cieobs: | luxpy._CIEOBS, optional | CMF set to use when calculating spectrum locus coordinates. :flip_axes: | False, optional | If True: flip axis 0 and axis 1 in Ydelep to increase speed of loop in function. | (single xyzw with is not flipped!) Returns: :Ydlep: | ndarray with Y, dominant (complementary) wavelength and excitation purity """ xyz3 = np3d(xyz).copy().astype(np.float) # flip axis so that shortest dim is on axis0 (save time in looping): if (xyz3.shape[0] < xyz3.shape[1]) & (flip_axes == True): axes12flipped = True xyz3 = xyz3.transpose((1,0,2)) else: axes12flipped = False # convert xyz to Yxy: Yxy = xyz_to_Yxy(xyz3) Yxyw = xyz_to_Yxy(xyzw) # get spectrum locus Y,x,y and wavelengths: SL = _CMF[cieobs]['bar'] wlsl = SL[0] Yxysl = xyz_to_Yxy(SL[1:4].T)[:,None] # center on xyzw: Yxy = Yxy - Yxyw Yxysl = Yxysl - Yxyw Yxyw = Yxyw - Yxyw #split: Y, x, y = asplit(Yxy) Yw,xw,yw = asplit(Yxyw) Ysl,xsl,ysl = asplit(Yxysl) # calculate hue: h = math.positive_arctan(x,y, htype = 'deg') hsl = math.positive_arctan(xsl,ysl, htype = 'deg') hsl_max = hsl[0] # max hue angle at min wavelength hsl_min = hsl[-1] # min hue angle at max wavelength dominantwavelength = np.zeros(Y.shape) purity = dominantwavelength.copy() for i in range(xyz3.shape[1]): # find index of complementary wavelengths/hues: pc = np.where((h[:,i] >= hsl_max) & (h[:,i] <= hsl_min + 360.0)) # hue's requiring complementary wavelength (purple line) h[:,i][pc] = h[:,i][pc] - np.sign(h[:,i][pc] - 180.0)*180.0 # add/subtract 180° to get positive complementary wavelength # find 2 closest hues in sl: #hslb,hib = meshblock(hsl,h[:,i:i+1]) hib,hslb = np.meshgrid(h[:,i:i+1],hsl) dh = np.abs(hslb-hib) q1 = dh.argmin(axis=0) # index of closest hue dh[q1] = 1000.0 q2 = dh.argmin(axis=0) # index of second closest hue dominantwavelength[:,i] = wlsl[q1] + np.divide(np.multiply((wlsl[q2] - wlsl[q1]),(h[:,i] - hsl[q1,0])),(hsl[q2,0] - hsl[q1,0])) # calculate wl corresponding to h: y = y1 + (y2-y1)*(x-x1)/(x2-x1) dominantwavelength[:,i][pc] = - dominantwavelength[:,i][pc] #complementary wavelengths are specified by '-' sign # calculate excitation purity: x_dom_wl = xsl[q1,0] + (xsl[q2,0] - xsl[q1,0])*(h[:,i] - hsl[q1,0])/(hsl[q2,0] - hsl[q1,0]) # calculate x of dom. wl y_dom_wl = ysl[q1,0] + (ysl[q2,0] - ysl[q1,0])*(h[:,i] - hsl[q1,0])/(hsl[q2,0] - hsl[q1,0]) # calculate y of dom. wl d_wl = (x_dom_wl**2.0 + y_dom_wl**2.0)**0.5 # distance from white point to sl d = (x[:,i]**2.0 + y[:,i]**2.0)**0.5 # distance from white point to test point purity[:,i] = d/d_wl # correct for those test points that have a complementary wavelength # calculate intersection of line through white point and test point and purple line: xy = np.vstack((x[:,i],y[:,i])).T xyw = np.hstack((xw,yw)) xypl1 = np.hstack((xsl[0,None],ysl[0,None])) xypl2 = np.hstack((xsl[-1,None],ysl[-1,None])) da = (xy-xyw) db = (xypl2-xypl1) dp = (xyw - xypl1) T = np.array([[0.0, -1.0], [1.0, 0.0]]) dap = np.dot(da,T) denom = np.sum(dap * db,axis=1,keepdims=True) num = np.sum(dap * dp,axis=1,keepdims=True) xy_linecross = (num/denom) *db + xypl1 d_linecross = np.atleast_2d((xy_linecross[:,0]**2.0 + xy_linecross[:,1]**2.0)**0.5).T#[0] purity[:,i][pc] = d[pc]/d_linecross[pc][:,0] Ydlep = np.dstack((xyz3[:,:,1],dominantwavelength,purity)) if axes12flipped == True: Ydlep = Ydlep.transpose((1,0,2)) else: Ydlep = Ydlep.transpose((0,1,2)) return Ydlep.reshape(xyz.shape)
def Ydlep_to_xyz(Ydlep, cieobs = _CIEOBS, xyzw = _COLORTF_DEFAULT_WHITE_POINT, flip_axes = False, **kwargs): """ Convert Y, dominant (complementary) wavelength and excitation purity to XYZ tristimulus values. Args: :Ydlep: | ndarray with Y, dominant (complementary) wavelength and excitation purity :xyzw: | None or narray with tristimulus values of a single (!) native white point, optional | None defaults to xyz of CIE D65 using the :cieobs: observer. :cieobs: | luxpy._CIEOBS, optional | CMF set to use when calculating spectrum locus coordinates. :flip_axes: | False, optional | If True: flip axis 0 and axis 1 in Ydelep to increase speed of loop in function. | (single xyzw with is not flipped!) Returns: :xyz: | ndarray with tristimulus values """ Ydlep3 = np3d(Ydlep).copy().astype(np.float) # flip axis so that longest dim is on first axis (save time in looping): if (Ydlep3.shape[0] < Ydlep3.shape[1]) & (flip_axes == True): axes12flipped = True Ydlep3 = Ydlep3.transpose((1,0,2)) else: axes12flipped = False # convert xyzw to Yxyw: Yxyw = xyz_to_Yxy(xyzw) Yxywo = Yxyw.copy() # get spectrum locus Y,x,y and wavelengths: SL = _CMF[cieobs]['bar'] wlsl = SL[0,None].T Yxysl = xyz_to_Yxy(SL[1:4].T)[:,None] # center on xyzw: Yxysl = Yxysl - Yxyw Yxyw = Yxyw - Yxyw #split: Y, dom, pur = asplit(Ydlep3) Yw,xw,yw = asplit(Yxyw) Ywo,xwo,ywo = asplit(Yxywo) Ysl,xsl,ysl = asplit(Yxysl) # loop over longest dim: x = np.zeros(Y.shape) y = x.copy() for i in range(Ydlep3.shape[1]): # find closest wl's to dom: #wlslb,wlib = meshblock(wlsl,np.abs(dom[i,:])) #abs because dom<0--> complemtary wl wlib,wlslb = np.meshgrid(np.abs(dom[:,i]),wlsl) dwl = np.abs(wlslb-wlib) q1 = dwl.argmin(axis=0) # index of closest wl dwl[q1] = 10000.0 q2 = dwl.argmin(axis=0) # index of second closest wl # calculate x,y of dom: x_dom_wl = xsl[q1,0] + (xsl[q2,0] - xsl[q1,0])*(np.abs(dom[:,i]) - wlsl[q1,0])/(wlsl[q2,0] - wlsl[q1,0]) # calculate x of dom. wl y_dom_wl = ysl[q1,0] + (ysl[q2,0] - ysl[q1,0])*(np.abs(dom[:,i]) - wlsl[q1,0])/(wlsl[q2,0] - wlsl[q1,0]) # calculate y of dom. wl # calculate x,y of test: d_wl = (x_dom_wl**2.0 + y_dom_wl**2.0)**0.5 # distance from white point to dom d = pur[:,i]*d_wl hdom = math.positive_arctan(x_dom_wl,y_dom_wl,htype = 'deg') x[:,i] = d*np.cos(hdom*np.pi/180.0) y[:,i] = d*np.sin(hdom*np.pi/180.0) # complementary: pc = np.where(dom[:,i] < 0.0) hdom[pc] = hdom[pc] - np.sign(dom[:,i][pc] - 180.0)*180.0 # get positive hue angle # calculate intersection of line through white point and test point and purple line: xy = np.vstack((x_dom_wl,y_dom_wl)).T xyw = np.vstack((xw,yw)).T xypl1 = np.vstack((xsl[0,None],ysl[0,None])).T xypl2 = np.vstack((xsl[-1,None],ysl[-1,None])).T da = (xy-xyw) db = (xypl2-xypl1) dp = (xyw - xypl1) T = np.array([[0.0, -1.0], [1.0, 0.0]]) dap = np.dot(da,T) denom = np.sum(dap * db,axis=1,keepdims=True) num = np.sum(dap * dp,axis=1,keepdims=True) xy_linecross = (num/denom) *db + xypl1 d_linecross = np.atleast_2d((xy_linecross[:,0]**2.0 + xy_linecross[:,1]**2.0)**0.5).T[:,0] x[:,i][pc] = pur[:,i][pc]*d_linecross[pc]*np.cos(hdom[pc]*np.pi/180) y[:,i][pc] = pur[:,i][pc]*d_linecross[pc]*np.sin(hdom[pc]*np.pi/180) Yxy = np.dstack((Ydlep3[:,:,0],x + xwo, y + ywo)) if axes12flipped == True: Yxy = Yxy.transpose((1,0,2)) else: Yxy = Yxy.transpose((0,1,2)) return Yxy_to_xyz(Yxy).reshape(Ydlep.shape)