示例#1
0
def line_intersect(a1, a2, b1, b2):
    """
    Line intersections of series of two line segments a and b. 
        
    Args:
        :a1: 
            | ndarray (.shape  = (N,2)) specifying end-point 1 of line a
        :a2: 
            | ndarray (.shape  = (N,2)) specifying end-point 2 of line a
        :b1: 
            | ndarray (.shape  = (N,2)) specifying end-point 1 of line b
        :b2: 
            | ndarray (.shape  = (N,2)) specifying end-point 2 of line b
    
    Note: 
        N is the number of line segments a and b.
    
    Returns:
        :returns: 
            | ndarray with line-intersections (.shape = (N,2))
    
    References:
        1. https://stackoverflow.com/questions/3252194/numpy-and-line-intersections
    """
    T = np.array([[0.0, -1.0], [1.0, 0.0]])
    da = np.atleast_2d(a2 - a1)
    db = np.atleast_2d(b2 - b1)
    dp = np.atleast_2d(a1 - b1)
    dap = np.dot(da, T)
    denom = np.sum(dap * db, axis=1)
    num = np.sum(dap * dp, axis=1)
    return np.atleast_2d(num / denom).T * db + b1
示例#2
0
def v_to_cik(v, inverse = False):
    """
    Calculate 2x2 '(covariance matrix)^-1' elements cik 
    
    Args:
        :v: 
            | (Nx5) np.ndarray
            | ellipse parameters [Rmax,Rmin,xc,yc,theta]
        :inverse:
            | If True: return inverse of cik.
    
    Returns:
        :cik: 
            | 'Nx2x2' (covariance matrix)^-1
    
    Notes:
        | cik is not actually a covariance matrix,
        | only for a Gaussian or normal distribution!

    """
    v = np.atleast_2d(v)
    g11 = (1/v[:,0]*np.cos(v[:,4]))**2 + (1/v[:,1]*np.sin(v[:,4]))**2
    g22 = (1/v[:,0]*np.sin(v[:,4]))**2 + (1/v[:,1]*np.cos(v[:,4]))**2
    g12 = (1/v[:,0]**2 - 1/v[:,1]**2)*np.sin(v[:,4])*np.cos(v[:,4])
    cik = np.zeros((g11.shape[0],2,2))

    for i in range(g11.shape[0]):
        cik[i,:,:] = np.vstack((np.hstack((g11[i],g12[i])), np.hstack((g12[i],g22[i]))))
        if inverse == True:
            cik[i,:,:] = np.linalg.inv(cik[i,:,:])
    return cik
示例#3
0
def rgb_to_spec_smits(rgb, intent='rfl', bitdepth=8, wlr=_WL3, rgb2spec=None):
    """
    Convert an array of RGB values to a spectrum using a Smits like conversion as implemented in Mitsuba.
    
    Args:
        :rgb: 
            | ndarray of list of rgb values
        :intent:
            | 'rfl' (or 'spd'), optional
            | type of requested spectrum conversion .
        :bitdepth:
            | 8, optional
            | bit depth of rgb values
        :wlr: 
            | _WL3, optional
            | desired wavelength (nm) range of spectrum.
        :rgb2spec:
            | None, optional
            | Dict with base spectra for white, cyan, magenta, yellow, blue, green and red for each intent.
            | If None: use _BASESPEC_SMITS.
        
    Returns:
        :spec: 
            | ndarray with spectrum or spectra (one for each rgb value, first row are the wavelengths)
    """
    if isinstance(rgb, list):
        rgb = np.atleast_2d(rgb)
    if rgb.max() > 1:
        rgb = rgb / (2**bitdepth - 1)
    if rgb2spec is None:
        rgb2spec = _BASESPEC_SMITS
    if not np.array_equal(rgb2spec['wlr'], getwlr(wlr)):
        rgb2spec = _convert_to_wlr(entries=copy.deepcopy(rgb2spec), wlr=wlr)
    spec = np.zeros((rgb.shape[0], rgb2spec['wlr'].shape[0]))
    for i in range(rgb.shape[0]):
        spec[i, :] = _fromLinearRGB(rgb[i, :],
                                    intent=intent,
                                    rgb2spec=rgb2spec,
                                    wlr=wlr)
    return np.vstack((rgb2spec['wlr'], spec))
示例#4
0
文件: utils.py 项目: simongr2/luxpy
def hue_quadrature(h, unique_hue_data=None):
    """
    Get hue quadrature H from hue h.
    
    Args:
        :h: 
            | float or ndarray [(N,) or (N,1)] with hue data in degrees (!).
        :unique_hue data:
            | None or dict, optional
            |   - None: defaults to:
            |         {'hues': 'red yellow green blue red'.split(), 
            |        'i': np.arange(5.0), 
            |        'hi':[20.14, 90.0, 164.25,237.53,380.14],
            |        'ei':[0.8,0.7,1.0,1.2,0.8],
            |        'Hi':[0.0,100.0,200.0,300.0,400.0]}
            |   - dict: user specified unique hue data  
            |           (same structure as above)
    
    Returns:
        :H: 
            | ndarray of Hue quadrature value(s).
    """

    if unique_hue_data is None:
        unique_hue_data = {
            'hues': 'red yellow green blue red'.split(),
            'i': [0, 1, 2, 3, 4],
            'hi': [20.14, 90.0, 164.25, 237.53, 380.14],
            'ei': [0.8, 0.7, 1.0, 1.2, 0.8],
            'Hi': [0.0, 100.0, 200.0, 300.0, 400.0]
        }

    ndim = np.array(h).ndim

    hi = unique_hue_data['hi']
    Hi = unique_hue_data['Hi']
    ei = unique_hue_data['ei']

    h = np.atleast_2d(h)
    h[h < hi[0]] += 360.0
    if h.shape[0] == 1:
        h = h.T

    H = np.zeros_like(h)
    for j in range(h.shape[1]):
        h_j = h[..., j:j + 1]
        h_hi = np.repeat(h_j, repeats=len(hi), axis=1)
        hi_h = np.repeat(np.atleast_2d(hi), repeats=h.shape[0], axis=0)
        d = (h_hi - hi_h)
        d[d < 0] = 1000.0
        p = d.argmin(axis=1)
        p[p == (len(hi) -
                1)] = 0  # make sure last unique hue data is not selected
        H_j = np.array([
            Hi[pi] + (100.0 * (h_j[i] - hi[pi]) / ei[pi]) /
            ((h_j[i] - hi[pi]) / ei[pi] + (hi[pi + 1] - h_j[i]) / ei[pi + 1])
            for (i, pi) in enumerate(p)
        ])
        H[..., j:j + 1] = H_j

    if ndim == 0:
        return H[0][0]
    elif ndim == 1:
        return H[:, 0]
    else:
        return H
示例#5
0
def box_m(*X, ni = None, verbosity = 0, robust = False, robust_alpha = 0.01):
    """
    Perform Box's M test (p>=2) to check equality of covariance matrices or Bartlett's test (p==1) for equality of variances.
    
    Args:
        :X: 
            | A number  (k groups) or list of 2d-ndarrays (rows: samples, cols: variables) with data.
            | or a number of 2d-ndarrays with covariance matrices (supply ni!)
        :ni:
            | None, optional
            | If None: X contains data, else, X contains covariance matrices.
        :verbosity: 
            | 0, optional
            | If 1: print results.
        :robust:
            | False, optional
            | If True: remove outliers beyond the confidence ellipsoid before calculating
            |          the covariances.
        :robust_alpha:
            | 0.01, optional
            | Significance level of confidence ellipsoid marking the boundary for outliers.
    
    Returns:
        :statistic:
            | F or chi2 value (see len(dfs))
        :pval:
            | p-value
        :df:
            | degrees of freedom.
            | if len(dfs) == 2: F-test was used.
            | if len(dfs) == 1: chi2 approx. was used.
    
    Notes:
        1. If p==1: Reduces to Bartlett's test for equal variances.
        2. If (ni>20).all() & (p<6) & (k<6): then a more appropriate chi2 test is used in a some cases.
    """

    k = len(X) # groups
    p = np.atleast_2d(X[0]).shape[1] # variables
    if p == 1: # for p == 1: only variance!
        det = lambda x: np.array(x)
    else:
        det = lambda x: np.linalg.det(x)
    if ni is None: # samples in each group
        
        # remove outliers before calculation of box M:
        if robust == True:
            X = [remove_outliers(Xi, alpha = robust_alpha) for Xi in X]
            
        ni = np.array([Xi.shape[0] for Xi in X])
        Si = np.array([np.cov(Xi.T) for Xi in X])
        if p == 1:
            Si = np.atleast_2d(Si).T
    else:
        Si = np.array([Xi for Xi in X]) # input are already cov matrices!
        ni = np.array(ni)
        if ni.shape[0] == 1:
            ni = ni*np.ones((k,))
        
    N = ni.sum()
    S = np.array([(ni[i]-1)*Si[i] for i in range(len(ni))]).sum(axis=0)/(N - k)

    M = (N-k)*np.log(det(S)) - ((ni-1)*np.log(det(Si))).sum()
    if p == 1:
        M = M[0]
    A1 = (2*p**2 + 3*p -1)/(6*(p+1)*(k-1))*((1/(ni-1)) - 1/(N - k)).sum()
    v1 = p*(p+1)*(k-1)/2
    A2 = (p-1)*(p+2)/(6*(k-1))*((1/(ni-1)**2) - 1/(N - k)**2).sum()

    if (A2 - A1**2) > 0:
        v2 = (v1 + 2)/(A2 - A1**2)
        b = v1/(1 - A1 -(v1/v2))
        Fv1v2 = M/b
        statistic = Fv1v2
        pval = 1.0 - sp.stats.f.cdf(Fv1v2,v1,v2)
        dfs = [v1,v2]
        
        if verbosity == 1:
            print('M = {:1.4f}, F = {:1.4f}, df1 = {:1.1f}, df2 = {:1.1f}, p = {:1.4f}'.format(M,Fv1v2,v1,v2,pval))
    else:
        v2 = (v1 + 2)/(A1**2 - A2)
        b = v2/(1 - A1 + (2/v2))
        Fv1v2 = v2*M/(v1*(b - M))
        statistic = Fv1v2
        pval = 1.0 - sp.stats.f.cdf(Fv1v2,v1,v2)
        dfs = [v1,v2]

        if (ni>20).all() & (p<6) & (k<6): #use Chi2v1
            chi2v1 = M*(1-A1)
            statistic = chi2v1
            pval = 1.0 - sp.stats.chi2.cdf(chi2v1,v1)
            dfs = [v1]
            if verbosity == 1:
                print('M = {:1.4f}, chi2 = {:1.4f}, df1 = {:1.1f}, p = {:1.4f}'.format(M,chi2v1,v1,pval))

        else:
            if verbosity == 1:
                print('M = {:1.4f}, F = {:1.4f}, df1 = {:1.1f}, df2 = {:1.1f}, p = {:1.4f}'.format(M,Fv1v2,v1,v2,pval))

    return statistic, pval, dfs
示例#6
0
def get_macadam_ellipse(xy = None, k_neighbours = 3, nsteps = 10, average_cik = True):
    """
    Estimate n-step MacAdam ellipse at CIE x,y coordinates xy by calculating 
    average inverse covariance ellipse of the k_neighbours closest ellipses.
    
    Args:
        :xy:
            | None or ndarray, optional
            | If None: output Macadam ellipses, if not None: xy are the 
            | CIE xy coordinates for which ellipses will be estimated.
        :k_neighbours:
            | 3, optional
            | Number of nearest ellipses to use to calculate ellipse at xy
        :nsteps:
            | 10, optional
            | Set number of MacAdam steps of ellipse.
        :average_cik:
            | True, optional
            | If True: take distance weighted average of inverse 
            |   'covariance ellipse' elements cik. 
            | If False: average major & minor axis lengths and 
            |   ellipse orientation angles directly.
            
    Returns:
        :v_mac_est:
            | estimated MacAdam ellipse(s) in v-format [Rmax,Rmin,xc,yc,theta]
    
    References:
        1. MacAdam DL. Visual Sensitivities to Color Differences in Daylight*. J Opt Soc Am. 1942;32(5):247-274.
    """
    # list of MacAdam ellipses (x10)
    v_mac = np.atleast_2d([
         [0.16, 0.057, 0.0085, 0.0035, 62.5],
         [0.187, 0.118, 0.022, 0.0055, 77],
         [0.253, 0.125, 0.025, 0.005, 55.5],
         [0.15, 0.68, 0.096, 0.023, 105],
         [0.131, 0.521, 0.047, 0.02, 112.5],
         [0.212, 0.55, 0.058, 0.023, 100],
         [0.258, 0.45, 0.05, 0.02, 92],
         [0.152, 0.365, 0.038, 0.019, 110],
         [0.28, 0.385, 0.04, 0.015, 75.5],
         [0.38, 0.498, 0.044, 0.012, 70],
         [0.16, 0.2, 0.021, 0.0095, 104],
         [0.228, 0.25, 0.031, 0.009, 72],
         [0.305, 0.323, 0.023, 0.009, 58],
         [0.385, 0.393, 0.038, 0.016, 65.5],
         [0.472, 0.399, 0.032, 0.014, 51],
         [0.527, 0.35, 0.026, 0.013, 20],
         [0.475, 0.3, 0.029, 0.011, 28.5],
         [0.51, 0.236, 0.024, 0.012, 29.5],
         [0.596, 0.283, 0.026, 0.013, 13],
         [0.344, 0.284, 0.023, 0.009, 60],
         [0.39, 0.237, 0.025, 0.01, 47],
         [0.441, 0.198, 0.028, 0.0095, 34.5],
         [0.278, 0.223, 0.024, 0.0055, 57.5],
         [0.3, 0.163, 0.029, 0.006, 54],
         [0.365, 0.153, 0.036, 0.0095, 40]
         ])
    
    # convert to v-format ([a,b, xc, yc, theta]):
    v_mac = v_mac[:,[2,3,0,1,4]]
    
    # convert last column to rad.:
    v_mac[:,-1] = v_mac[:,-1]*np.pi/180
    
    # convert to desired number of MacAdam-steps:
    v_mac[:,0:2] = v_mac[:,0:2]/10*nsteps
    
    if xy is not None:
        #calculate inverse covariance matrices:
        cik = math.v_to_cik(v_mac, inverse = True)
        if average_cik == True:
            cik_long = np.hstack((cik[:,0,:],cik[:,1,:]))
        
        # Calculate k_neighbours closest ellipses to xy:
        tree = sp.spatial.cKDTree(v_mac[:,2:4], copy_data = True)
        d, inds = tree.query(xy, k = k_neighbours)
    
        if k_neighbours  > 1:
            pd = 1
            w = (1.0 / np.abs(d)**pd)[:,:,None] # inverse distance weigthing
            if average_cik == True:
                cik_long_est = np.sum(w * cik_long[inds,:], axis=1) / np.sum(w, axis=1)
            else:
                v_mac_est = np.sum(w * v_mac[inds,:], axis=1) / np.sum(w, axis=1) # for average xyc

        else:
            v_mac_est = v_mac[inds,:].copy()
        
        # convert cik back to v:
        if (average_cik == True) & (k_neighbours >1):
            cik_est = np.dstack((cik_long_est[:,0:2],cik_long_est[:,2:4]))
            v_mac_est = math.cik_to_v(cik_est, inverse = True)
        v_mac_est[:,2:4] = xy
    else:
        v_mac_est = v_mac
        
    return v_mac_est
示例#7
0
def xyz_to_Ydlep(xyz,
                 cieobs=_CIEOBS,
                 xyzw=_COLORTF_DEFAULT_WHITE_POINT,
                 flip_axes=False,
                 SL_max_lambda=None,
                 **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!)
        :SL_max_lambda:
            | None or float, optional
            | Maximum wavelength of spectrum locus before it turns back on itelf in the high wavelength range (~700 nm)
    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']
    SL = SL[:, SL[1:].sum(axis=0) >
            0]  # avoid div by zero in xyz-to-Yxy conversion
    wlsl = SL[0]
    Yxysl = xyz_to_Yxy(SL[1:4].T)[:, None]

    # Get maximum wavelength of spectrum locus (before it turns back on itself)
    if SL_max_lambda is None:
        pmaxlambda = Yxysl[..., 1].argmax()  # lambda with largest x value
        dwl = np.diff(
            Yxysl[:, 0,
                  1])  # spectrumlocus in that range should have increasing x
        dwl[wlsl[:-1] < 600] = 10000
        pmaxlambda = np.where(
            dwl <= 0)[0][0]  # Take first element with zero or <zero slope
    else:
        pmaxlambda = np.abs(wlsl - SL_max_lambda).argmin()
    Yxysl = Yxysl[:(pmaxlambda + 1), :]
    wlsl = wlsl[:(pmaxlambda + 1)]

    # 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
    if hsl_min < hsl_max: hsl_min += 360

    dominantwavelength = np.empty(Y.shape)
    purity = np.empty(Y.shape)

    for i in range(xyz3.shape[1]):

        # find index of complementary wavelengths/hues:
        pc = np.where(
            (h[:, i] > hsl_max) & (h[:, i] < hsl_min)
        )  # 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 enclosing hues in sl:
        #hslb,hib = meshblock(hsl,h[:,i:i+1])
        hib, hslb = np.meshgrid(h[:, i:i + 1], hsl)
        dh = (hslb - hib)
        q1 = np.abs(dh).argmin(axis=0)  # index of closest hue
        sign_q1 = np.sign(dh[q1])[0]
        dh[np.sign(dh) ==
           sign_q1] = 1000000  # set all dh on the same side as q1 to a very large value
        q2 = np.abs(dh).argmin(
            axis=0)  # index of second  closest (enclosing) hue

        # # Test changes to code:
        # print('wls',i, wlsl[q1],wlsl[q2])
        # import matplotlib.pyplot as plt
        # plt.figure()
        # plt.plot(wlsl[:-1],np.diff(xsl[:,0]),'k.-')
        # plt.figure()
        # plt.plot(x[0,i],y[0,i],'k.'); plt.plot(xsl,ysl,'r.-');plt.plot(xsl[q1],ysl[q1],'b.');plt.plot(xsl[q2],ysl[q2],'g.');plt.plot(xsl[-1],ysl[-1],'c+')

        dominantwavelength[:, i] = wlsl[q1] + np.multiply(
            (h[:, i] - hsl[q1, 0]),
            np.divide((wlsl[q2] - wlsl[q1]), (hsl[q2, 0] - hsl[q1, 0]))
        )  # calculate wl corresponding to h: y = y1 + (x-x1)*(y2-y1)/(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)
示例#8
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']
    SL = SL[:, SL[1:].sum(axis=0) >
            0]  # avoid div by zero in xyz-to-Yxy conversion
    wlsl = SL[0]
    Yxysl = xyz_to_Yxy(SL[1:4].T)[:, None]
    pmaxlambda = Yxysl[..., 1].argmax()
    maxlambda = wlsl[pmaxlambda]
    maxlambda = 700
    print(np.where(wlsl == maxlambda))
    pmaxlambda = np.where(wlsl == maxlambda)[0][0]
    Yxysl = Yxysl[:(pmaxlambda + 1), :]
    wlsl = wlsl[:(pmaxlambda + 1)]

    # 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')
    print(h)
    print('rh', h[0, 0] - h[0, 1])
    print(wlsl[0], wlsl[-1])

    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
    if hsl_min < hsl_max: hsl_min += 360

    dominantwavelength = np.empty(Y.shape)
    purity = np.empty(Y.shape)
    print('xyz:', xyz)
    for i in range(xyz3.shape[1]):
        print('\ni:', i, h[:, i], hsl_max, hsl_min)
        print(h)
        # find index of complementary wavelengths/hues:
        pc = np.where(
            (h[:, i] > hsl_max) & (h[:, i] < hsl_min)
        )  # hue's requiring complementary wavelength (purple line)
        print('pc', (h[:, i] > hsl_max) & (h[:, i] < hsl_min))
        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] = 1000000.0
        q2 = dh.argmin(axis=0)  # index of second closest hue
        print('q1q2', q2, q1)

        print('wls:', h[:, i], wlsl[q1], wlsl[q2])
        print('hsls:', hsl[q2, 0], hsl[q1, 0])
        print('d', (wlsl[q2] - wlsl[q1]), (hsl[q2, 0] - hsl[q1, 0]),
              (wlsl[q2] - wlsl[q1]) / (hsl[q2, 0] - hsl[q1, 0]))
        print('(h[:,i] - hsl[q1,0])', (h[:, i] - hsl[q1, 0]))
        print('div', np.divide((wlsl[q2] - wlsl[q1]),
                               (hsl[q2, 0] - hsl[q1, 0])))
        print(
            'mult(...)',
            np.multiply((h[:, i] - hsl[q1, 0]),
                        np.divide((wlsl[q2] - wlsl[q1]),
                                  (hsl[q2, 0] - hsl[q1, 0]))))
        dominantwavelength[:, i] = wlsl[q1] + np.multiply(
            (h[:, i] - hsl[q1, 0]),
            np.divide((wlsl[q2] - wlsl[q1]), (hsl[q2, 0] - hsl[q1, 0]))
        )  # calculate wl corresponding to h: y = y1 + (x-x1)*(y2-y1)/(x2-x1)
        print('dom', dominantwavelength[:, i])
        dominantwavelength[(dominantwavelength[:,
                                               i] > max(wlsl[q1], wlsl[q2])),
                           i] = max(wlsl[q1], wlsl[q2])
        dominantwavelength[(dominantwavelength[:,
                                               i] < min(wlsl[q1], wlsl[q2])),
                           i] = min(wlsl[q1], wlsl[q2])

        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)
示例#9
0
def Ydlep_to_xyz(Ydlep,
                 cieobs=_CIEOBS,
                 xyzw=_COLORTF_DEFAULT_WHITE_POINT,
                 flip_axes=False,
                 SL_max_lambda=None,
                 **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!)
        :SL_max_lambda:
            | None or float, optional
            | Maximum wavelength of spectrum locus before it turns back on itelf in the high wavelength range (~700 nm)

    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']
    SL = SL[:, SL[1:].sum(axis=0) >
            0]  # avoid div by zero in xyz-to-Yxy conversion
    wlsl = SL[0, None].T
    Yxysl = xyz_to_Yxy(SL[1:4].T)[:, None]

    # Get maximum wavelength of spectrum locus (before it turns back on itself)
    if SL_max_lambda is None:
        pmaxlambda = Yxysl[..., 1].argmax()  # lambda with largest x value
        dwl = np.diff(
            Yxysl[:, 0,
                  1])  # spectrumlocus in that range should have increasing x
        dwl[wlsl[:-1, 0] < 600] = 10000
        pmaxlambda = np.where(
            dwl <= 0)[0][0]  # Take first element with zero or <zero slope
    else:
        pmaxlambda = np.abs(wlsl - SL_max_lambda).argmin()
    Yxysl = Yxysl[:(pmaxlambda + 1), :]
    wlsl = wlsl[:(pmaxlambda + 1), :1]

    # 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.empty(Y.shape)
    y = np.empty(Y.shape)
    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 = wlslb - wlib
        q1 = np.abs(dwl).argmin(axis=0)  # index of closest wl
        sign_q1 = np.sign(dwl[q1])
        dwl[np.sign(dwl) ==
            sign_q1] = 1000000  # set all dwl on the same side as q1 to a very large value
        q2 = np.abs(dwl).argmin(
            axis=0)  # index of second closest (enclosing) 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)
示例#10
0
def selection(P, O, options):
    """
    Selects the next population.
    
    | Each parent is compared to its offspring. If the parent dominates its 
    | child, then it goes to the next population. If the offspring dominates 
    | the parent, that new member is added. However, if they are incomparable
    | (there is no mutual domination), them both are sent to the next 
    | population. After that, the new set of individuals must be truncated to 
    | mu, wherein mu is the original number of points.
    | This is accomplished by the use of "non-dominated sorting", that is,
    | ranks the individual in fronts of non-domination, and within each
    | front, measures them by using crowding distance. With regard to these
    | two metrics, the best individuals are kept in the new population.

   Args:
      :P: 
          | a dict with the parents (x and f)
      :O: 
          | a dict with the offspring
      :options: 
          | the dict with the algorithm's parameters

   Returns:
      :Pnew: 
          | the new population (a dict with x and f)
   """
   
    # ------ First part: checks dominance between parents and offspring
    # Verifies whether parent dominates offspring:
    aux1 = (P['f'] <= O['f']).all(axis = 0)
    aux2 = (P['f'] < O['f']).any(axis = 0)
    auxp = np.logical_and(aux1, aux2) #P dominates O
    
    # Now, where offspring dominates parent:
    aux1 = (P['f'] >= O['f']).all(axis = 0)
    aux2 = (P['f'] > O['f']).any(axis = 0)
    auxo = np.logical_and(aux1, aux2) #O dominates P
    auxpo = np.logical_and(~auxp, ~auxo); #P and O are incomparable

    # New population (where P dominates O, O dominates P and where they are 
    # incomparable)
    R = {'f' : np.hstack((P['f'][:,auxp].copy(), O['f'][:,auxo].copy(), P['f'][:,auxpo].copy(), O['f'][:,auxpo].copy()))}
    R['x'] = np.hstack((P['x'][:,auxp].copy(), O['x'][:,auxo].copy(), P['x'][:,auxpo].copy(), O['x'][:,auxpo].copy()))
    
    # ------- Second part: non-dominated sorting
    Pnew = {'x' : np.atleast_2d([])} 
    Pnew['f'] = np.atleast_2d([]) #prepares the new population
    while True:
       ispar = ndset(R['f']) #gets the non-dominated front

       # If the number of points in this front plus the current size of the new
       # population is smaller than mu, then include everything and keep going.
       # If it is greater, then stops and go to the truncation step:
       if ((Pnew['f'].shape[1] + ispar.sum()) < options['mu']):

          Pnew['f'] = np.hstack((Pnew['f'], R['f'][:,ispar].copy())) if (Pnew['f'].size) else R['f'][:,ispar].copy()
          Pnew['x'] = np.hstack((Pnew['x'], R['x'][:,ispar].copy())) if (Pnew['x'].size) else R['x'][:,ispar].copy()
          R['f'] = np.delete(R['f'],np.where(ispar)[0], axis = 1) #R['f'][:,ispar] = []; #removes this front
          R['x'] = np.delete(R['x'],np.where(ispar)[0], axis = 1) #R['x'][:,ispar] = []; #removes this front
       else:
          # Gets the points of this front and goes to the truncation part
          Frem = R['f'][:,ispar].copy()
          Xrem = R['x'][:,ispar].copy()
          break #don't forget this to stop this infinite loop

    # ------- Third part: truncates using crowding distance
    # If the remaining front has the exact number of points to fill the original
    # size, then just include them. If it has too many, remove some according to
    # the crowding distance (notice it cannot have too few!)
    aux = (Pnew['f'].shape[1] + Frem.shape[1]) - options['mu'] #remaining points to fill

    if aux == 0:
       Pnew['x'] = np.hstack((Pnew['x'], Xrem.copy())) #Xrem.copy()
       Pnew['f'] = np.hstack((Pnew['f'], Frem.copy())) #Frem.copy()
    elif aux > 0:
       for ii in range(aux):
          cdist = crowdingdistance(Frem)
          imin = cdist.argmin()#gets the point with smaller crowding distance
          Frem = np.delete(Frem, imin, axis = 1) # Frem(:,imin) = []; #and remove it
          Xrem = np.delete(Xrem, imin, axis = 1)  # Xrem(:,imin) = [];
       Pnew['x'] =  np.hstack((Pnew['x'], Xrem.copy())) if Pnew['x'].size else Xrem.copy()
       Pnew['f'] =  np.hstack((Pnew['f'], Frem.copy())) if Pnew['f'].size else Frem.copy()
    else: #if there are too few points... well, we're doomed!
       raise Exception('Run to the hills! This is not supposed to happen!')

    return Pnew
示例#11
0
def vlbar_cie_mesopic(m=[1],
                      wl_new=None,
                      kind='np',
                      out=1,
                      Lp=None,
                      Ls=None,
                      SP=None):
    """
    Get CIE mesopic luminous efficiency function Vmesm according to CIE191:2010
    
    Args:
        :m:
            | float or list or ndarray with mesopic adaptation coefficients
        :wl: 
            | None, optional
            | New wavelength range for interpolation. 
            | Defaults to wavelengths specified by luxpy._WL3.
        :out: 
            | 1 or 2, optional
            |     1: returns Vmesm
            |     2: returns (Vmes, Kmesm)
        :Lp: 
            | None, optional
            | float or ndarray with photopic adaptation luminance
            | If not None: use this (and SP or Ls) to calculate the 
            | mesopic adaptation coefficient
        :Ls: 
            | None, optional
            | float or ndarray with scotopic adaptation luminance
            | If None: SP must be supplied.
        :SP:
            | None, optional
            | S/P ratio
            | If None: Ls must be supplied.
            
    Returns:
        :Vmes: 
            | ndarray with mesopic luminous efficiency function 
            | for adaptation coefficient(s) m
        :Kmes:
            | ndarray with luminous efficacies of 555 nm monochromatic light
            | for for adaptation coefficient(s) m
    
    Reference:
        1. `CIE 191:2010 Recommended System for Mesopic Photometry Based on Visual Performance.
        (ISBN 978-3-901906-88-6  ), <http://cie.co.at/publications/recommended-system-mesopic-photometry-based-visual-performance>`_
    """
    if (Lp is not None) & ((Ls is not None) | (SP is not None)):
        Lmes, m = get_cie_mesopic_adaptation(Lp=Lp, Ls=Ls, SP=SP)
    else:
        Lmes = None
    m = np.atleast_2d(m).T
    m[m < 0] = 0
    m[m > 1] = 1
    Vl = vlbar(cieobs='1931_2')
    Vlp = vlbar(cieobs='1951_20_scotopic')
    Vlmes = m * (Vl[1:, :]) + (1 - m) * (Vlp[1:, :])
    Vlmes = np.vstack((Vl[:1, :], Vlmes))
    Kmes = 683 / Vlmes[1:, Vlmes[0, :] == 555]
    Vlmes[1:, :] = Vlmes[1:, :] / Vlmes[1:, :].max(
        axis=1, keepdims=True)  # normalize to max = 1

    if kind == 'df':
        columns = ['wl']
        for i in range(m.size):
            columns.append('Vmes{:0.2f}'.format(m[i, 0]))
    else:
        columns = ['wl', ['Vmes'] * m.size]
    Vlmes = spd(data=Vlmes,
                wl=wl_new,
                interpolation='linear',
                norm_type='max',
                norm_f=1,
                kind=kind,
                columns=columns)

    if out == 2:
        return Vlmes, Kmes
    elif out == 1:
        return Vlmes
    else:
        return eval(out)
示例#12
0
def spd_to_CS_CLa_lrc(El = None, E = None, \
                          sum_sources = False, interpolate_sources = True):
    """
    Calculate Circadian Stimulus (CS) and Circadian Light [LRC: Rea et al 2012].
    
    
    Args:
        :El:
            | ndarray, optional
            | Defaults to D65
            | light source spectral irradiance distribution
        :E: 
            | None, float or ndarray, optional
            | Illuminance of light sources.
            | If None: El is used as is, otherwise El is renormalized to have
            | an illuminance equal to E.
        :sum_sources:
            | False, optional
            |   - False: calculate CS and CLa for all sources in El array.
            |   - True: sum sources in El to a single source and perform calc.
        :interpolate_sources:
            | True, optional
            |  - True: El is interpolated to wavelength range of efficiency 
            |          functions (as in LRC calculator). 
            |  - False: interpolate efficiency functions to source range. 
            |           Source interpolation is not recommended due to possible
            |           errors for peaky spectra. 
            |           (see CIE15-2004, "Colorimetry").
            
    Returns:
        :CS:
            | ndarray with Circadian stimulus values
        :CLa:
            | ndarray with Circadian Light values
            
    Notes:
        1. The original 2012 (E.q. 1) had set the peak wavelength of the 
        melanopsin at 480 nm. Rea et al. later published a corrigendum with 
        updated model parameters for k, a_{b-y} and a_rod. The comparison table
        between showing values calculated for a number of sources with the old
        and updated parameters were very close (~1 unit voor CLa). 
        
        2. In that corrrection paper they did not mention a change in the
        factor (1622) that multiplies the (sum of) the integral(s) in Eq. 1. 
        HOWEVER, the excel calculator released in 2017 and the online 
        calculator show that factor to have a value of 1547.9. The change in
        values due to the new factor is much larger than their the updated 
        mentioned in note 1!
        
        3. For reasons of consistency the calculator uses the latest model 
        parameters, as could be read from the excel calculator. They values 
        adopted are: multiplier 1547.9, k = 0.2616, a_{b-y} = 0.7 and 
        a_rod = 3.3. 
        
        4. The parameter values to convert CLa to CS were also taken from the 
        2017 excel calculator.
        
    References:
        
        1. `LRC Online Circadian stimulus calculator <http://www.lrc.rpi.edu/cscalculator/>`_
        
        2. `LRC Excel based Circadian stimulus calculator. <http://www.lrc.rpi.edu/resources/CSCalculator_2017_10_03_Mac.xlsm>`_
        
        3. `Rea MS, Figueiro MG, Bierman A, and Hamner R (2012). 
        Modelling the spectral sensitivity of the human circadian system. 
        Light. Res. Technol. 44, 386–396.  
        <https://doi.org/10.1177/1477153511430474>`_
            
        4. `Rea MS, Figueiro MG, Bierman A, and Hamner R (2012). 
        Erratum: Modeling the spectral sensitivity of the human circadian system 
        (Lighting Research and Technology (2012) 44:4 (386-396)). 
        Light. Res. Technol. 44, 516.
        <https://doi.org/10.1177/1477153512467607>`_
    """
    # Create copy of dict with model parameters and spectral data:
    cs_cl_lrs = _LRC_CLA_CS_CONST['CLa'].copy()
    
 
    # Interpolate efficiency functions to light source wl-range:
    if interpolate_sources is False:
        cs_cl_lrs = interpolate_efficiency_functions(El[0], cs_cl_lrs)
    else:
        El = cie_interp(El, cs_cl_lrs['WL'], kind = 'spd')
    
    # Get wavelength spacing:
    dl = getwld(El[0])        
    
    # Separate wavelengths and data:
    wl = El[0]
    Elv = El[1:].copy()
      
    # define integral function:
    integral = lambda x: integrate.trapz(x, x = wl, axis = -1) 
    #integral = lambda x: np.sum(x,  axis = -1) 
    
    # Rescale El to E (if not None):
    if E is not None:

        # Calculate current E value of El:
        E_cv = np.atleast_2d(683.002 * integral(cs_cl_lrs['Vphotl']*Elv*dl))

        # Rescale El to supplied E:
        Elv = (E/E_cv).T*Elv
            
                
    # Sum all sources in array if requested:
    if sum_sources == True:
        Elv = Elv.sum(axis = 0, keepdims = True)  
    
    # Calculate Circadian light using model param. and spectral data:
    CLa = fCLa(wl, Elv, integral,  **cs_cl_lrs)
    
    # Calculate Circadian stimulus:
    CS = 0.7 * (1 - (1/(1 + (CLa/355.7)**1.1026)))
    
    return CS, CLa