Esempio n. 1
0
 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()
Esempio n. 2
0
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
Esempio n. 3
0
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
Esempio n. 4
0
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
Esempio n. 5
0
File: cct.py Progetto: uhqinli/luxpy
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
Esempio n. 6
0
 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')
Esempio n. 7
0
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
Esempio n. 8
0
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)
Esempio n. 9
0
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
Esempio n. 10
0
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)
Esempio n. 11
0
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)