Exemplo n.º 1
0
def pitman_morgan(X,Y, verbosity = 0):
    """
    Pitman-Morgan Test for the difference between correlated variances with paired samples.
     
    Args:
        :X,Y: 
            | ndarrays with data.
        :verbosity: 
            | 0, optional
            | If 1: print results. 
            
    Returns:
        :tval:
            | statistic
        :pval:
            | p-value
        :df:
            | degree of freedom.
        :ratio:
            | variance ratio var1/var2 (with var1 > var2).

    Note:
        1. Based on Gardner, R.C. (2001). Psychological Statistics Using SPSS for Windows. New Jersey, Prentice Hall.
        2. Python port from matlab code by Janne Kauttonen (https://nl.mathworks.com/matlabcentral/fileexchange/67910-pitmanmorgantest-x-y; accessed Sep 26, 2019)
    """
    N = X.shape[0]
    var1, var2 = X.var(axis=0),Y.var(axis=0)
    cor = np.corrcoef(X,Y)[0,1]
    
    # must have var1 > var2:
    if var1 < var2:
        var1, var2 = var2, var1

    ratio = var1/var2
    
    # formulas from Garder (2001, p.57):
    numerator1_S1minusS2 = var1-var2
    numerator2_SQRTnminus2 = np.sqrt(N-2)
    numerator3 = numerator1_S1minusS2*numerator2_SQRTnminus2
    denominator1_4timesS1timesS2 = 4*var1*var2
    denominator2_rSquared = cor**2
    denominator3_1minusrSquared = 1.0 - denominator2_rSquared
    denominator4_4timesS1timesS2div1minusrSquared = denominator1_4timesS1timesS2*denominator3_1minusrSquared
    denominator5 = np.sqrt(denominator4_4timesS1timesS2div1minusrSquared)
    df = N-2
    if denominator5 == 0:
        denominator5 = _EPS
    tval = numerator3/denominator5
    
    # compute stats:
    p = 2*(1.0-sp.stats.t.cdf(tval,df))
    if verbosity == 1:
        print('tval = {:1.4f}, df = {:1.1f}, p = {:1.4f}'.format(tval,df, p))

    return tval, p, df, ratio
Exemplo n.º 2
0
def cik_to_v(cik, xyc = None, inverse = False):
    """
    Calculate v-format ellipse descriptor from 2x2 'covariance matrix'^-1 cik 
    
    Args:
        :cik: 
            | 'Nx2x2' (covariance matrix)^-1
        :inverse:
            | If True: input is inverse of cik.
              
            
    Returns:
        :v: 
            | (Nx5) np.ndarray
            | ellipse parameters [Rmax,Rmin,xc,yc,theta]

    Notes:
        | cik is not actually the inverse covariance matrix,
        | only for a Gaussian or normal distribution!

    """
    if cik.ndim < 3:
        cik = cik[None,...]
    
    if inverse == True:
        for i in range(cik.shape[0]):
            cik[i,:,:] = np.linalg.inv(cik[i,:,:])
            
    g11 = cik[:,0,0]
    g22 = cik[:,1,1] 
    g12 = cik[:,0,1]

    theta = 0.5*np.arctan2(2*g12,(g11-g22)) + (np.pi/2)*(g12<0)
    #theta = theta2 + (np.pi/2)*(g12<0)
    #theta2 = theta
    cottheta = np.cos(theta)/np.sin(theta) #np.cot(theta)
    cottheta[np.isinf(cottheta)] = 0

    a = 1/np.sqrt((g22 + g12*cottheta))
    b = 1/np.sqrt((g11 - g12*cottheta))

    # ensure largest ellipse axis is first (correct angle):
    c = b>a; a[c], b[c], theta[c] = b[c],a[c],theta[c]+np.pi/2

    v = np.vstack((a, b, np.zeros(a.shape), np.zeros(a.shape), theta)).T
    
    # add center coordinates:
    if xyc is not None:
        v[:,2:4] = xyc
    
    return v
Exemplo n.º 3
0
def cart2spher(x,y,z, deg = True):
    """
    Convert cartesian to spherical coordinates.
    
    Args:        
        :x, y, z:
            | tuple of floats, ints or ndarrays
            | Cartesian coordinates
    Returns:
        :theta:
            | Float, int or ndarray
            | Angle with positive z-axis.
        :phi:
            | Float, int or ndarray
            | Angle around positive z-axis starting from x-axis.
        :r:
            | 1, optional
            | Float, int or ndarray
            | radius

    """
    r = np.sqrt(x*x + y*y + z*z)
    phi = np.arctan2(y,x)
    phi[phi<0.] = phi[phi<0.] + 2*np.pi
    zdr = z/r
    zdr[zdr > 1.] = 1.
    zdr[zdr<-1.] = -1
    theta = np.arccos(zdr)
    if deg == True:
        theta = theta*180/np.pi
        phi = phi *180/np.pi
    return theta, phi, r   
Exemplo n.º 4
0
def apply_poly_model_at_x(poly_model, pmodel, axr, bxr):
    """
    Applies base color shift model at cartesian coordinates axr, bxr.
    
    Args:
        :poly_model: 
            | function handle to model
        :pmodel:
            | ndarray with model parameters.
        :axr: 
            | ndarray with a-coordinates under the reference conditions
        :bxr:
            | ndarray with b-coordinates under the reference conditions
        
    Returns:
        :returns:
            | (axt,bxt,Cxt,hxt,
            |  axr,bxr,Cxr,hxr)
            | 
            | ndarrays with ab-coordinates, chroma and hue 
            | predicted by the model (xt), under the reference (xr).
    """

    # Calculate hxr and Cxr:
    Cxr = np.sqrt(axr**2 + bxr**2)
    hxr = np.arctan(bxr / (axr + _EPS))  #_eps avoid zero-division

    # B Set 2nd order color multipliers (shiftd parameters for a and b: pa & pb):
    pa = pmodel[0].copy()
    pb = pmodel[1].copy()
    isM6 = pa.shape[0] == 6
    pa[0 + isM6 * 1] = 1 + pa[0 + isM6 * 1]
    pb[1 + isM6 * 1] = 1 + pb[1 + isM6 * 1]

    # C Apply model to reference hues using 2nd order multipliers:
    axt = poly_model(axr, bxr, pa)
    bxt = poly_model(axr, bxr, pb)
    Cxt = np.sqrt(axt**2 + bxt**2)  #test chroma
    hxt = np.arctan(bxt / (axt + _EPS))

    return axt, bxt, Cxt, hxt, axr, bxr, Cxr, hxr
Exemplo n.º 5
0
def magnitude_v(v):
    """
    Calculates magnitude of vector.
    
    Args:
        :v: 
            | ndarray with vector
 
    Returns:
        :magnitude:
            | ndarray 
    """
    magnitude = np.sqrt(v[:,0]**2 + v[:,1]**2)
    return magnitude
Exemplo n.º 6
0
 def get_tpr(self, *args):
     """ get spherical coordinates tpr (theta, phi, radius) """
     if len(args) > 0:
         x, y, z = args
     else:
         x, y, z = self.x, self.y, self.z
     r = np.sqrt(x * x + y * y + z * z)
     zdr = np.asarray(z / r)
     zdr[zdr > 1.0] = 1.0
     zdr[zdr < -1.0] = -1.0
     theta = np.arccos(zdr)
     phi = np.arctan2(y, x)
     phi[phi < 0.0] = phi[phi < 0.0] + 2 * np.pi
     phi[r < self._TINY] = 0.0
     theta[r < self._TINY] = 0.0
     return theta, phi, r
Exemplo n.º 7
0
def _polyarea3D(xyz):

    x, y, z = asplit(xyz)

    RY = np.sqrt((x[0] - x[1])**2 + (y[0] - y[1])**2 + (z[0] - z[1])**2)
    YG = np.sqrt((x[1] - x[2])**2 + (y[1] - y[2])**2 + (z[1] - z[2])**2)
    GR = np.sqrt((x[2] - x[0])**2 + (y[2] - y[0])**2 + (z[2] - z[0])**2)
    RB = np.sqrt((x[0] - x[3])**2 + (y[0] - y[3])**2 + (z[0] - z[3])**2)
    BG = np.sqrt((x[2] - x[3])**2 + (y[2] - y[3])**2 + (z[2] - z[3])**2)
    S1 = (RY + YG + GR) / 2
    S2 = (RB + BG + GR) / 2
    GA1 = np.sqrt(S1 * (S1 - RY) * (S1 - YG) * (S1 - GR))
    GA2 = np.sqrt(S2 * (S2 - RB) * (S2 - BG) * (S2 - GR))
    GA = GA1 + GA2
    return GA
Exemplo n.º 8
0
def rms(data,axis = 0, keepdims = False):
    """
    Calculate root-mean-square along axis.
    
    Args:
        :data: 
            | list of values or ndarray
        :axis:
            | 0, optional
            | Axis along which to calculate rms.
        :keepdims:
            | False or True, optional
            | Keep original dimensions of array.
    
    Returns:
        :returns:
            | ndarray with rms values.
    """
    data = np2d(data)
    return np.sqrt(np.power(data,2).mean(axis=axis, keepdims = keepdims))
Exemplo n.º 9
0
def cart2pol(x,y = None, htype = 'deg'):
    """
    Convert Cartesion to polar coordinates.
    
    Args:
        :x: 
            | float or ndarray with x-coordinates
        :y: 
            | None or float or ndarray with x-coordinates, optional
            | If None, y-coordinates are assumed to be in :x:.
        :htype:
            | 'deg' or 'rad, optional
            | Output type of theta.
    
    Returns:
        :returns: 
            | (float or ndarray of theta, float or ndarray of r) values
    """
    if y is None:
        y = x[...,1].copy()
        x = x[...,0].copy()
    return positive_arctan(x,y, htype = htype), np.sqrt(x**2 + y**2)
Exemplo n.º 10
0
def _process_DEi(DEi, DEtype='jab', avg=None, avg_axis=0, out='DEi'):
    """
    Process color difference input DEi for output (helper function).
    
    Args:
        :DEi: 
            | tuple(J ndarray, ab ndarray).
        :DEtype:
            | 'jab' or str, optional
            | Options: 
            |    - 'jab' : calculates full color difference over all 3 dimensions.
            |    - 'ab'  : calculates chromaticity difference.
            |    - 'j'   : calculates lightness or brightness difference 
            |             (depending on :out:).
            |    - 'j,ab': calculates both 'j' and 'ab' options 
            |              and returns them as a tuple.
        :avg:
            | None, optional
            | None: don't calculate average DE, 
            |       otherwise use function handle in :avg:.
        :avg_axis:
            | axis to calculate average over, optional
        :out: 
            | 'DEi' or str, optional
            | Requested output.
        
    Note:
        For the other input arguments, see specific color space used.
        
    Returns:
        :returns: 
            | ndarray with DEi [, DEa] or other as specified by :out:
    """

    if (DEi[0].shape[-1] == 1) & (DEi[0].ndim == 3):
        DEi = tuple((map(lambda x: np.squeeze(x, axis=x.ndim - 1), DEi)))

    # Calculate correct type of DE:
    if DEtype == 'jab':
        DEi = np.sqrt(DEi[0] + DEi[1])
    elif DEtype == 'ab':
        DEi = np.sqrt(DEi[1])
    elif DEtype == 'j':
        DEi = np.sqrt(DEi[0])

    # Calculate average when requested:
    if (avg is not None) & ('DEa' in out.split(',')):
        if isinstance(DEi, tuple):
            DEa = (avg(DEi[0], axis=avg_axis,
                       keepdims=True), avg(DEi[1],
                                           axis=avg_axis,
                                           keepdims=True))
        else:
            DEa = avg(DEi, axis=avg_axis, keepdims=True)

    if out == 'DEi':
        return DEi
    elif out == 'DEi,DEa':
        return DEi, DEa
    else:
        return eval(out)
Exemplo n.º 11
0
def DE_cspace(xyzt, xyzr, dtype = 'xyz', tf = _CSPACE, DEtype = 'jab', avg = None, avg_axis = 0, out = 'DEi',
              xyzwt = None, xyzwr = None, fwtft = {}, fwtfr = {}, KLCH = None,\
              camtype = cam._CAM_DEFAULT_TYPE, ucstype = 'ucs'):
    """
    Calculate color difference DE in specific color space.
    
    Args:
        :xyzt: 
            | ndarray with tristimulus values of test data.
        :xyzr:
            | ndarray with tristimulus values of reference data.
        :dtype:
            | 'xyz' or 'jab', optional
            | Specifies data type in :xyzt: and :xyzr:.
        :xyzwt:
            | None or ndarray, optional
            |   White point tristimulus values of test data
            |   None defaults to the one set in :fwtft: 
            |   or else to the default of cspace.
        :xyzwr:
            | None or ndarray, optional
            |   Whitepoint tristimulus values of reference data
            |    None defaults to the one set in non-empty :fwtfr: 
            |    or else to default of cspace.
        :tf:
            | _CSPACE, optional
            | Color space to use for color difference calculation.
        :fwtft:
            | {}, optional
            | Dict with parameters for forward transform 
              from xyz to cspace for test data.
        :fwtfr: 
            | {}, optional 
            | Dict with parameters for forward transform 
            | from xyz to cspace for reference data.
        :KLCH:
            | None, optional
            | Weigths for L, C, H 
            | None: default to [1,1,1] 
            | KLCH is not used when tf == 'camucs'.
        :DEtype:
            | 'jab' or str, optional
            | Options: 
            |    - 'jab' : calculates full color difference over all 3 dimensions.
            |    - 'ab'  : calculates chromaticity difference.
            |    - 'j'   : calculates lightness or brightness difference 
            |             (depending on :outin:).
            |    - 'j,ab': calculates both 'j' and 'ab' options 
            |              and returns them as a tuple.
        :avg:
            | None, optional
            | None: don't calculate average DE, 
            |       otherwise use function handle in :avg:.
        :avg_axis:
            | axis to calculate average over, optional
        :out: 
            | 'DEi' or str, optional
            | Requested output.
        :camtype: 
            | luxpy.cam._CAM_DEFAULT_TYPE, optional
            | Str specifier for CAM type to use, options: 'ciecam02' or 'ciecam16'.
            | Only when DEtype == 'camucs'.
        :ucstype:
            | 'ucs' or 'lcd' or 'scd', optional
            | Str specifier for which type of color attribute compression 
            | parameters to use:
            |     -'ucs': uniform color space,
            |     -'lcd', large color differences,
            |     -'scd': small color differences
            | Only when DEtype == 'camucs'.
        
    Note:
        For the other input arguments, see specific color space used.
        
    Returns:
        :returns: 
            | ndarray with DEi [, DEa] or other as specified by :out:
    """

    # Get xyzw from dict if xyzw is None & dict is Not None
    if xyzwr is not None:
        fwtfr['xyzw'] = xyzwr
    else:
        if bool(fwtfr):
            xyzwr = fwtfr['xyzw']
    if xyzwt is not None:
        fwtft['xyzw'] = xyzwt
    else:
        if bool(fwtft):
            xyzwt = fwtft['xyzw']

    if tf == 'camucs':
        if dtype == 'xyz':
            if fwtfr['xyzw'] is None:
                fwtfr['xyzw'] = cam._CAM_DEFAULT_WHITE_POINT
            if fwtft['xyzw'] is None:
                fwtft['xyzw'] = cam._CAM_DEFAULT_WHITE_POINT
            jabt = cam.camXucs(xyzt, camtype=camtype, ucstype=ucstype, **fwtft)
            jabr = cam.camXucs(xyzr, camtype=camtype, ucstype=ucstype, **fwtfr)

        else:
            jabt = xyzt
            jabr = xyzr

        KL, c1, c2 = [
            cam._CAM_UCS_PARAMETERS[camtype][ucstype][x]
            for x in sorted(cam._CAM_UCS_PARAMETERS[camtype][ucstype].keys())
        ]

        # Calculate color difference and take account of KL:
        DEi = ((((jabt[...,0:1]-jabr[...,0:1])/KL)**2).sum(axis = jabt[...,0:1].ndim - 1, keepdims = True),\
                   ((jabt[...,1:3]-jabr[...,1:3])**2).sum(axis = jabt[...,1:3].ndim - 1, keepdims = True))

    elif (tf == 'DE2000') | (tf == 'DE00'):
        return DE2000(xyzt, xyzr, dtype = 'xyz', DEtype = DEtype, avg = avg,\
               avg_axis = avg_axis, out = out,
              xyzwt = xyzwt, xyzwr = xyzwr, KLCH = KLCH)

    else:
        if dtype == 'xyz':
            # Use colortf:
            jabt = colortf(xyzt, tf=tf, fwtf=fwtft)
            jabr = colortf(xyzr, tf=tf, fwtf=fwtfr)
        else:
            jabt = xyzt
            jabr = xyzr

        if (KLCH == None) | (KLCH == [1, 1, 1]):
            # Calculate color difference and take account of KL:
            DEi = (((jabt[...,0:1]-jabr[...,0:1])**2).sum(axis = jabt[...,0:1].ndim - 1, keepdims = True),\
                   ((jabt[...,1:3]-jabr[...,1:3])**2).sum(axis = jabt[...,1:3].ndim - 1, keepdims = True))

        else:  #using LCH specification for use with KLCH weights:
            Jt = jabt[..., 0:1]
            at = jabt[..., 1:2]
            bt = jabt[..., 2:3]
            Ct = np.sqrt(at**2 + bt**2)
            ht = cam.hue_angle(at, bt, htype='rad')

            Jr = jabr[..., 0:1]
            ar = jabr[..., 1:2]
            br = jabr[..., 2:3]
            Cr = np.sqrt(ar**2 + br**2)
            hr = cam.hue_angle(at, bt, htype='rad')

            dJ = Jt - Jr
            dC = Ct - Cr
            dH = ht - hr
            DEab2 = ((at - ar)**2 + (bt - br)**2)
            dH = np.sqrt(DEab2 - dC**2)

            DEi = ((dJ / KLCH[0])**2, (dC / KLCH[1])**2 + (dH / KLCH[2])**2)

    return _process_DEi(DEi,
                        DEtype=DEtype,
                        avg=avg,
                        avg_axis=avg_axis,
                        out=out)
Exemplo n.º 12
0
def DE2000(xyzt,
           xyzr,
           dtype='xyz',
           DEtype='jab',
           avg=None,
           avg_axis=0,
           out='DEi',
           xyzwt=None,
           xyzwr=None,
           KLCH=None):
    """
    Calculate DE2000 color difference.
    
    Args:
        :xyzt: 
            | ndarray with tristimulus values of test data.
        :xyzr:
            | ndarray with tristimulus values of reference data.
        :dtype:
            | 'xyz' or 'lab', optional
            | Specifies data type in :xyzt: and :xyzr:.
        :xyzwt:
            | None or ndarray, optional
            |   White point tristimulus values of test data
            |   None defaults to the one set in lx.xyz_to_lab()
        :xyzwr:
            | None or ndarray, optional
            |    Whitepoint tristimulus values of reference data
            |    None defaults to the one set in lx.xyz_to_lab()
        :DEtype:
            | 'jab' or str, optional
            | Options: 
            |    - 'jab' : calculates full color difference over all 3 dimensions.
            |    - 'ab'  : calculates chromaticity difference.
            |    - 'j'   : calculates lightness or brightness difference 
            |             (depending on :outin:).
            |    - 'j,ab': calculates both 'j' and 'ab' options 
            |              and returns them as a tuple.
        :KLCH: 
            | None, optional
            | Weigths for L, C, H 
            | None: default to [1,1,1] 
        :avg:
            | None, optional
            | None: don't calculate average DE, 
            |       otherwise use function handle in :avg:.
        :avg_axis:
            | axis to calculate average over, optional
        :out: 
            | 'DEi' or str, optional
            | Requested output.
        
    Note:
        For the other input arguments, see specific color space used.
        
    Returns:
        :returns: 
            | ndarray with DEi [, DEa] or other as specified by :out:
            
    References:
        1. `Sharma, G., Wu, W., & Dalal, E. N. (2005). 
        The CIEDE2000 color‐difference formula: Implementation notes, 
        supplementary test data, and mathematical observations. 
        Color Research & Application, 30(1), 21–30. 
        <https://doi.org/10.1002/col.20070>`_
    """

    if KLCH is None:
        KLCH = [1, 1, 1]

    if dtype == 'xyz':
        labt = xyz_to_lab(xyzt, xyzw=xyzwt)
        labr = xyz_to_lab(xyzr, xyzw=xyzwr)
    else:
        labt = xyzt
        labr = xyzr

    Lt = labt[..., 0:1]
    at = labt[..., 1:2]
    bt = labt[..., 2:3]
    Ct = np.sqrt(at**2 + bt**2)
    #ht = cam.hue_angle(at,bt,htype = 'rad')

    Lr = labr[..., 0:1]
    ar = labr[..., 1:2]
    br = labr[..., 2:3]
    Cr = np.sqrt(ar**2 + br**2)
    #hr = cam.hue_angle(at,bt,htype = 'rad')

    # Step 1:
    Cavg = (Ct + Cr) / 2
    G = 0.5 * (1 - np.sqrt((Cavg**7.0) / ((Cavg**7.0) + (25.0**7))))
    apt = (1 + G) * at
    apr = (1 + G) * ar

    Cpt = np.sqrt(apt**2 + bt**2)
    Cpr = np.sqrt(apr**2 + br**2)
    Cpprod = Cpt * Cpr

    hpt = cam.hue_angle(apt, bt, htype='deg')
    hpr = cam.hue_angle(apr, br, htype='deg')
    hpt[(apt == 0) * (bt == 0)] = 0
    hpr[(apr == 0) * (br == 0)] = 0

    # Step 2:
    dL = np.abs(Lr - Lt)
    dCp = np.abs(Cpr - Cpt)
    dhp_ = hpr - hpt

    dhp = dhp_.copy()
    dhp[np.where(np.abs(dhp_) > 180)] = dhp[np.where(np.abs(dhp_) > 180)] - 360
    dhp[np.where(
        np.abs(dhp_) < -180)] = dhp[np.where(np.abs(dhp_) < -180)] + 360
    dhp[np.where(Cpprod == 0)] = 0

    #dH = 2*np.sqrt(Cpprod)*np.sin(dhp/2*np.pi/180)
    dH = deltaH(dhp, Cpprod, htype='deg')

    # Step 3:
    Lp = (Lr + Lt) / 2
    Cp = (Cpr + Cpt) / 2

    hps = hpt + hpr
    hp = (hpt + hpr) / 2
    hp[np.where((np.abs(dhp_) > 180)
                & (hps < 360))] = hp[np.where((np.abs(dhp_) > 180)
                                              & (hps < 360))] + 180
    hp[np.where((np.abs(dhp_) > 180)
                & (hps >= 360))] = hp[np.where((np.abs(dhp_) > 180)
                                               & (hps >= 360))] - 180
    hp[np.where(Cpprod == 0)] = 0

    T = 1 - 0.17*np.cos((hp - 30)*np.pi/180) + 0.24*np.cos(2*hp*np.pi/180) +\
        0.32*np.cos((3*hp + 6)*np.pi/180) - 0.20*np.cos((4*hp - 63)*np.pi/180)
    dtheta = 30 * np.exp(-((hp - 275) / 25)**2)
    RC = 2 * np.sqrt((Cp**7) / ((Cp**7) + (25**7)))
    SL = 1 + ((0.015 * (Lp - 50)**2) / np.sqrt(20 + (Lp - 50)**2))
    SC = 1 + 0.045 * Cp
    SH = 1 + 0.015 * Cp * T
    RT = -np.sin(2 * dtheta * np.pi / 180) * RC

    kL, kC, kH = KLCH

    DEi = ((dL / (kL * SL))**2, (dCp / (kC * SC))**2 + (dH / (kH * SH))**2 +
           RT * (dCp / (kC * SC)) * (dH / (kH * SH)))

    return _process_DEi(DEi,
                        DEtype=DEtype,
                        avg=avg,
                        avg_axis=avg_axis,
                        out=out)
Exemplo n.º 13
0
def calculate_VF_PX_models(S, cri_type = _VF_CRI_DEFAULT, sampleset = None, pool = False, \
                           pcolorshift = {'href': np.arange(np.pi/10,2*np.pi,2*np.pi/10),\
                                          'Cref' : _VF_MAXR, 'sig' : _VF_SIG, 'labels' : '#'},\
                           vfcolor = 'k', verbosity = 0):
    """
    Calculate Vector Field and Pixel color shift models.
    
    Args:
        :cri_type: 
            | _VF_CRI_DEFAULT or str or dict, optional
            | Specifies type of color fidelity model to use. 
            | Controls choice of ref. ill., sample set, averaging, scaling, etc.
            | See luxpy.cri.spd_to_cri for more info.
        :sampleset:
            | None or str or ndarray, optional
            | Sampleset to be used when calculating vector field model.
        :pool:
            | False, optional
            | If :S: contains multiple spectra, True pools all jab data before 
            | modeling the vector field, while False models a different field 
            |  for each spectrum.
        :pcolorshift: 
            | default dict (see below) or user defined dict, optional
            | Dict containing the specification input 
            |  for apply_poly_model_at_hue_x().
            | Default dict = {'href': np.arange(np.pi/10,2*np.pi,2*np.pi/10),
            |                'Cref' : _VF_MAXR, 
            |                'sig' : _VF_SIG, 
            |                'labels' : '#'} 
            | The polynomial models of degree 5 and 6 can be fully specified or 
            | summarized by the model parameters themselved OR by calculating the
            | dCoverC and dH at resp. 5 and 6 hues.
        :vfcolor:
            | 'k', optional
            | For plotting the vector fields.
        :verbosity: 
            | 0, optional
            | Report warnings or not.
    
    Returns:
        :returns:
            | :dataVF:, :dataPX: 
            | Dicts, for more info, see output description of resp.: 
            | luxpy.cri.VF_colorshift_model() and luxpy.cri.PX_colorshift_model()
    """
    # calculate VectorField cri_color_shift model:
    dataVF = VF_colorshift_model(S,
                                 cri_type=cri_type,
                                 sampleset=sampleset,
                                 vfcolor=vfcolor,
                                 pcolorshift=pcolorshift,
                                 pool=pool,
                                 verbosity=verbosity)

    # Set jab_ranges and _deltas for PX-model pixel calculations:
    PX_jab_deltas = np.array([_VF_DELTAR, _VF_DELTAR, _VF_DELTAR
                              ])  #set same as for vectorfield generation
    PX_jab_ranges = np.vstack(
        ([0, 100, _VF_DELTAR], [-_VF_MAXR, _VF_MAXR + _VF_DELTAR, _VF_DELTAR],
         [-_VF_MAXR, _VF_MAXR + _VF_DELTAR, _VF_DELTAR]))  #IES4880 gamut

    # Calculate shift vectors using vectorfield and pixel methods:
    delta_SvsVF_vshift_ab_mean = np.zeros((len(dataVF), 1))
    delta_SvsVF_vshift_ab_mean.fill(np.nan)
    delta_SvsVF_vshift_ab_mean_normalized = delta_SvsVF_vshift_ab_mean.copy()
    delta_PXvsVF_vshift_ab_mean = np.zeros((len(dataVF), 1))
    delta_PXvsVF_vshift_ab_mean.fill(np.nan)
    delta_PXvsVF_vshift_ab_mean_normalized = delta_PXvsVF_vshift_ab_mean.copy()
    dataPX = [[] for k in range(len(dataVF))]
    for Snr in range(len(dataVF)):

        # Calculate shifts using pixel method, PX:
        dataPX[Snr] = PX_colorshift_model(dataVF[Snr]['Jab']['Jabt'][:, 0, :],
                                          dataVF[Snr]['Jab']['Jabr'][:, 0, :],
                                          jab_ranges=PX_jab_ranges,
                                          jab_deltas=PX_jab_deltas,
                                          limit_grid_radius=_VF_MAXR)

        # Calculate shift difference between Samples (S) and VectorField model predictions (VF):
        delta_SvsVF_vshift_ab = dataVF[Snr]['vshifts']['vshift_ab_s'] - dataVF[
            Snr]['vshifts']['vshift_ab_s_vf']
        delta_SvsVF_vshift_ab_mean[Snr] = np.nanmean(np.sqrt(
            (delta_SvsVF_vshift_ab[..., 1:3]**2).sum(
                axis=delta_SvsVF_vshift_ab[..., 1:3].ndim - 1)),
                                                     axis=0)
        delta_SvsVF_vshift_ab_mean_normalized[
            Snr] = delta_SvsVF_vshift_ab_mean[Snr] / dataVF[Snr]['Jab'][
                'DEi'].mean(axis=0)

        # Calculate shift difference between PiXel method (PX) and VectorField (VF):
        delta_PXvsVF_vshift_ab = dataPX[Snr]['vshifts'][
            'vectorshift_ab_J0'] - dataVF[Snr]['vshifts']['vshift_ab_vf']
        delta_PXvsVF_vshift_ab_mean[Snr] = np.nanmean(np.sqrt(
            (delta_PXvsVF_vshift_ab[..., 1:3]**2).sum(
                axis=delta_PXvsVF_vshift_ab[..., 1:3].ndim - 1)),
                                                      axis=0)
        delta_PXvsVF_vshift_ab_mean_normalized[
            Snr] = delta_PXvsVF_vshift_ab_mean[Snr] / dataVF[Snr]['Jab'][
                'DEi'].mean(axis=0)

        dataVF[Snr]['vshifts'][
            'delta_PXvsVF_vshift_ab_mean'] = delta_PXvsVF_vshift_ab_mean[Snr]
        dataVF[Snr]['vshifts'][
            'delta_SvsVF_vshift_ab_mean'] = delta_SvsVF_vshift_ab_mean[Snr]
        dataVF[Snr]['vshifts'][
            'delta_SvsVF_vshift_ab_mean_normalized'] = delta_SvsVF_vshift_ab_mean_normalized[
                Snr]
        dataVF[Snr]['vshifts'][
            'delta_PXvsVF_vshift_ab_mean_normalized'] = delta_PXvsVF_vshift_ab_mean_normalized[
                Snr]
        dataPX[Snr]['vshifts']['delta_PXvsVF_vshift_ab_mean'] = dataVF[Snr][
            'vshifts']['delta_PXvsVF_vshift_ab_mean']
        dataPX[Snr]['vshifts'][
            'delta_PXvsVF_vshift_ab_mean_normalized'] = dataVF[Snr]['vshifts'][
                'delta_PXvsVF_vshift_ab_mean_normalized']

    return dataVF, dataPX
Exemplo n.º 14
0
def xyz_to_rfl(xyz, CSF = None, rfl = None, out = 'rfl_est', \
                 refspd = None, D = None, cieobs = _CIEOBS, \
                 cspace = 'xyz', cspace_tf = {},\
                 interp_type = 'nd', k_neighbours = 4, verbosity = 0):
    """
    Approximate spectral reflectance of xyz values based on nd-dimensional linear interpolation 
    or k nearest neighbour interpolation of samples from a standard reflectance set.
    
    Args:
        :xyz: 
            | ndarray with xyz values of target points.
        :CSF:
            | None, optional
            | RGB camera response functions.
            | If None: input :xyz: contains raw rgb (float) values. Override :cspace:
            | argument and perform estimation directly in raw rgb space!!!
        :rfl: 
            | ndarray, optional
            | Reflectance set for color coordinate to rfl mapping.
        :out: 
            | 'rfl_est' or str, optional
        :refspd: 
            | None, optional
            | Refer ence spectrum for color coordinate to rfl mapping.
            | None defaults to D65.
        :cieobs:
            | _CIEOBS, optional
            | CMF set used for calculation of xyz from spectral data.
        :cspace:
            | 'xyz',  optional
            | Color space for color coordinate to rfl mapping.
            | Tip: Use linear space (e.g. 'xyz', 'Yuv',...) for (interp_type == 'nd'),
            |      and perceptually uniform space (e.g. 'ipt') for (interp_type == 'nearest')
        :cspace_tf:
            | {}, optional
            | Dict with parameters for xyz_to_cspace and cspace_to_xyz transform.
        :interp_type:
            | 'nd', optional
            | Options:
            | - 'nd': perform n-dimensional linear interpolation using Delaunay triangulation.
            | - 'nearest': perform nearest neighbour interpolation. 
        :k_neighbours:
            | 4 or int, optional
            | Number of nearest neighbours for reflectance spectrum interpolation.
            | Neighbours are found using scipy.spatial.cKDTree
        :verbosity:
            | 0, optional
            | If > 0: make a plot of the color coordinates of original and 
            | rendered image pixels.

    Returns:
        :returns: 
            | :rfl_est:
            | ndarrays with estimated reflectance spectra.
    """

    # get rfl set:
    if rfl is None:  # use IESTM30['4880'] set
        rfl = _CRI_RFL['ies-tm30']['4880']['5nm']

    wlr = rfl[0]

    # get Ref spd:
    if refspd is None:
        refspd = _CIE_ILLUMINANTS['D65'].copy()
    refspd = cie_interp(
        refspd, wlr,
        kind='linear')  # force spd to same wavelength range as rfl

    # Calculate rgb values of standard rfl set under refspd:
    if CSF is None:
        # Calculate lab coordinates:
        xyz_rr, xyz_wr = spd_to_xyz(refspd,
                                    relative=True,
                                    rfl=rfl,
                                    cieobs=cieobs,
                                    out=2)
        cspace_tf_copy = cspace_tf.copy()
        cspace_tf_copy[
            'xyzw'] = xyz_wr  # put correct white point in param. dict
        lab_rr = colortf(xyz_rr,
                         tf=cspace,
                         fwtf=cspace_tf_copy,
                         bwtf=cspace_tf_copy)[:, 0, :]
    else:
        # Calculate rgb coordinates from camera sensitivity functions
        rgb_rr = rfl_to_rgb(rfl, spd=refspd, CSF=CSF, wl=None)
        lab_rr = rgb_rr
        xyz = xyz
        lab_rr = np.round(lab_rr, _ROUNDING)  # speed up search

    # Convert xyz to lab-type values under refspd:
    if CSF is None:
        lab = colortf(xyz, tf=cspace, fwtf=cspace_tf_copy, bwtf=cspace_tf_copy)
    else:
        lab = xyz  # xyz contained rgb values !!!
        rgb = xyz
        lab = np.round(lab, _ROUNDING)  # speed up search

    if interp_type == 'nearest':
        # Find rfl (cfr. lab_rr) from rfl set that results in 'near' metameric
        # color coordinates for each value in lab_ur (i.e. smallest DE):
        # Construct cKDTree:
        tree = sp.spatial.cKDTree(lab_rr, copy_data=True)

        # Interpolate rfls using k nearest neightbours and inverse distance weigthing:
        d, inds = tree.query(lab, k=k_neighbours)
        if k_neighbours > 1:
            d += _EPS
            w = (1.0 / d**2)[:, :, None]  # inverse distance weigthing
            rfl_est = np.sum(w * rfl[inds + 1, :], axis=1) / np.sum(w, axis=1)
        else:
            rfl_est = rfl[inds + 1, :].copy()
    elif interp_type == 'nd':

        rfl_est = math.ndinterp1_scipy(lab_rr, rfl[1:], lab)

        _isnan = np.isnan(rfl_est[:, 0])

        if (
                _isnan.any()
        ):  #do nearest neigbour method for those that fail using Delaunay (i.e. ndinterp1_scipy)

            # Find rfl (cfr. lab_rr) from rfl set that results in 'near' metameric
            # color coordinates for each value in lab_ur (i.e. smallest DE):
            # Construct cKDTree:
            tree = sp.spatial.cKDTree(lab_rr, copy_data=True)

            # Interpolate rfls using k nearest neightbours and inverse distance weigthing:
            d, inds = tree.query(lab[_isnan, ...], k=k_neighbours)

            if k_neighbours > 1:
                d += _EPS
                w = (1.0 / d**2)[:, :, None]  # inverse distance weigthing
                rfl_est_isnan = np.sum(w * rfl[inds + 1, :], axis=1) / np.sum(
                    w, axis=1)
            else:
                rfl_est_isnan = rfl[inds + 1, :].copy()
            rfl_est[_isnan, :] = rfl_est_isnan

    else:
        raise Exception('xyz_to_rfl(): unsupported interp_type!')

    rfl_est[
        rfl_est <
        0] = 0  #can occur for points outside convexhull of standard rfl set.

    rfl_est = np.vstack((rfl[0], rfl_est))

    if ((verbosity > 0) | ('xyz_est' in out.split(',')) |
        ('lab_est' in out.split(',')) | ('DEi_ab' in out.split(',')) |
        ('DEa_ab' in out.split(','))) & (CSF is None):
        xyz_est, _ = spd_to_xyz(refspd,
                                rfl=rfl_est,
                                relative=True,
                                cieobs=cieobs,
                                out=2)
        cspace_tf_copy = cspace_tf.copy()
        cspace_tf_copy[
            'xyzw'] = xyz_wr  # put correct white point in param. dict
        lab_est = colortf(xyz_est, tf=cspace, fwtf=cspace_tf_copy)[:, 0, :]
        DEi_ab = np.sqrt(((lab_est[:, 1:3] - lab[:, 1:3])**2).sum(axis=1))
        DEa_ab = DEi_ab.mean()
    elif ((verbosity > 0) | ('xyz_est' in out.split(',')) |
          ('rgb_est' in out.split(',')) | ('DEi_rgb' in out.split(',')) |
          ('DEa_rgb' in out.split(','))) & (CSF is not None):
        rgb_est = rfl_to_rgb(rfl_est[1:], spd=refspd, CSF=CSF, wl=wlr)
        xyz_est = rgb_est
        DEi_rgb = np.sqrt(((rgb_est - rgb)**2).sum(axis=1))
        DEa_rgb = DEi_rgb.mean()

    if verbosity > 0:
        if CSF is None:
            ax = plot_color_data(lab[...,1], lab[...,2], z = lab[...,0], \
                            show = False, cieobs = cieobs, cspace = cspace, \
                            formatstr = 'ro', label = 'Original')
            plot_color_data(lab_est[...,1], lab_est[...,2], z = lab_est[...,0], \
                            show = True, axh = ax, cieobs = cieobs, cspace = cspace, \
                            formatstr = 'bd', label = 'Rendered')
        else:
            n = 100  #min(rfl.shape[0]-1,rfl_est.shape[0]-1)
            s = np.random.permutation(rfl.shape[0] -
                                      1)[:min(n, rfl.shape[0] - 1)]
            st = np.random.permutation(rfl_est.shape[0] -
                                       1)[:min(n, rfl_est.shape[0] - 1)]
            fig = plt.figure()
            ax = np.zeros((3, ), dtype=np.object)
            ax[0] = fig.add_subplot(131)
            ax[1] = fig.add_subplot(132)
            ax[2] = fig.add_subplot(133, projection='3d')
            ax[0].plot(rfl[0], rfl[1:][s].T, linestyle='-')
            ax[0].set_title('Original RFL set (random selection of all)')
            ax[0].set_ylim([0, 1])
            ax[1].plot(rfl_est[0], rfl_est[1:][st].T, linestyle='--')
            ax[0].set_title('Estimated RFL set (random selection of targets)')
            ax[1].set_ylim([0, 1])
            ax[2].plot(rgb[st, 0],
                       rgb[st, 1],
                       rgb[st, 2],
                       'ro',
                       label='Original')
            ax[2].plot(rgb_est[st, 0],
                       rgb_est[st, 1],
                       rgb_est[st, 2],
                       'bd',
                       label='Rendered')
            ax[2].legend()
    if out == 'rfl_est':
        return rfl_est
    elif out == 'rfl_est,xyz_est':
        return rfl_est, xyz_est
    else:
        return eval(out)
Exemplo n.º 15
0
def fit_ellipse(xy, center_on_mean_xy = False):
    """
    Fit an ellipse to supplied data points.

    Args:
        :xy: 
            | coordinates of points to fit (Nx2 array)
        :center_on_mean_xy:
            | False, optional
            | Center ellipse on mean of xy 
            | (otherwise it might be offset due to solving 
            | the contrained minization problem: aT*S*a, see ref below.)
            
    Returns:
        :v:
            | vector with ellipse parameters [Rmax,Rmin, xc,yc, theta (rad.)]
            
    Reference:
        1. Fitzgibbon, A.W., Pilu, M., and Fischer R.B., 
        Direct least squares fitting of ellipsees, 
        Proc. of the 13th Internation Conference on Pattern Recognition, 
        pp 253–257, Vienna, 1996.
    """
    # remove centroid:
#    center = xy.mean(axis=0)
#    xy = xy - center
    
    # Fit ellipse:
    x, y = xy[:,0:1], xy[:,1:2]
    D = np.hstack((x * x, x * y, y * y, x, y, np.ones_like(x)))
    S, C = np.dot(D.T, D), np.zeros([6, 6])
    C[0, 2], C[2, 0], C[1, 1] = 2, 2, -1
    U, s, V = np.linalg.svd(np.dot(np.linalg.inv(S), C))
    e = U[:, 0]
#    E, V =  np.linalg.eig(np.dot(np.linalg.inv(S), C))
#    n = np.argmax(np.abs(E))
#    e = V[:,n]
        
    # get ellipse axis lengths, center and orientation:
    b, c, d, f, g, a = e[1] / 2, e[2], e[3] / 2, e[4] / 2, e[5], e[0]
    
    # get ellipse center:
    num = b * b - a * c
    if num == 0:
        xc = 0
        yc = 0
    else:
        xc = ((c * d - b * f) / num) 
        yc = ((a * f - b * d) / num) 
    
    # get ellipse orientation:
    theta = np.arctan2(np.array(2 * b), np.array((a - c))) / 2
#    if b == 0:
#        if a > c:
#            theta = 0
#        else:
#            theta = np.pi/2
#    else:
#        if a > c:
#            theta = np.arctan2(2*b,(a-c))/2
#        else:
#            theta =  np.arctan2(2*b,(a-c))/2 + np.pi/2
        
    # axis lengths:
    up = 2 * (a * f * f + c * d * d + g * b * b - 2 * b * d * f - a * c * g)
    down1 = (b * b - a * c) * ((c - a) * np.sqrt(1 + 4 * b * b / ((a - c) * (a - c))) - (c + a))
    down2 = (b * b - a * c) * ((a - c) * np.sqrt(1 + 4 * b * b / ((a - c) * (a - c))) - (c + a))
    a, b  = np.sqrt((up / down1)), np.sqrt((up / down2))


    # assert that a is the major axis (otherwise swap and correct angle)
    if(b > a):
        b, a = a, b
        # ensure the angle is betwen 0 and 2*pi
        theta = fmod(theta, 2.0 * np.pi)
        
    if center_on_mean_xy == True:
        xc,yc = xy.mean(axis=0)

    return np.hstack((a, b, xc, yc, theta))
Exemplo n.º 16
0
def xyz_to_rfl(xyz, rfl = None, out = 'rfl_est', \
                 refspd = None, D = None, cieobs = _CIEOBS, \
                 cspace = 'xyz', cspace_tf = {},\
                 interp_type = 'nd', k_neighbours = 4, verbosity = 0):
    """
    Approximate spectral reflectance of xyz based on nd-dimensional linear interpolation 
    or k nearest neighbour interpolation of samples from a standard reflectance set.
    
    Args:
        :xyz: 
            | ndarray with tristimulus values of target points.
        :rfl: 
            | ndarray, optional
            | Reflectance set for color coordinate to rfl mapping.
        :out: 
            | 'rfl_est' or str, optional
        :refspd: 
            | None, optional
            | Refer ence spectrum for color coordinate to rfl mapping.
            | None defaults to D65.
        :cieobs:
            | _CIEOBS, optional
            | CMF set used for calculation of xyz from spectral data.
        :cspace:
            | 'xyz',  optional
            | Color space for color coordinate to rfl mapping.
            | Tip: Use linear space (e.g. 'xyz', 'Yuv',...) for (interp_type == 'nd'),
            |      and perceptually uniform space (e.g. 'ipt') for (interp_type == 'nearest')
        :cspace_tf:
            | {}, optional
            | Dict with parameters for xyz_to_cspace and cspace_to_xyz transform.
        :interp_type:
            | 'nd', optional
            | Options:
            | - 'nd': perform n-dimensional linear interpolation using Delaunay triangulation.
            | - 'nearest': perform nearest neighbour interpolation. 
        :k_neighbours:
            | 4 or int, optional
            | Number of nearest neighbours for reflectance spectrum interpolation.
            | Neighbours are found using scipy.spatial.cKDTree
        :verbosity:
            | 0, optional
            | If > 0: make a plot of the color coordinates of original and 
            | rendered image pixels.

    Returns:
        :returns: 
            | :rfl_est:
            | ndarrays with estimated reflectance spectra.
    """

    # get rfl set:
    if rfl is None:  # use IESTM30['4880'] set
        rfl = _CRI_RFL['ies-tm30']['4880']['5nm']

    # get Ref spd:
    if refspd is None:
        refspd = _CIE_ILLUMINANTS['D65'].copy()

    # Calculate lab-type coordinates of standard rfl set under refspd:
    xyz_rr, xyz_wr = spd_to_xyz(refspd,
                                relative=True,
                                rfl=rfl,
                                cieobs=cieobs,
                                out=2)
    cspace_tf_copy = cspace_tf.copy()
    cspace_tf_copy['xyzw'] = xyz_wr  # put correct white point in param. dict
    lab_rr = colortf(xyz_rr,
                     tf=cspace,
                     fwtf=cspace_tf_copy,
                     bwtf=cspace_tf_copy)[:, 0, :]

    # Convert xyz to lab-type values under refspd:
    lab = colortf(xyz, tf=cspace, fwtf=cspace_tf_copy, bwtf=cspace_tf_copy)

    if interp_type == 'nearest':
        # Find rfl (cfr. lab_rr) from rfl set that results in 'near' metameric
        # color coordinates for each value in lab_ur (i.e. smallest DE):
        # Construct cKDTree:
        tree = sp.spatial.cKDTree(lab_rr, copy_data=True)

        # Interpolate rfls using k nearest neightbours and inverse distance weigthing:
        d, inds = tree.query(lab, k=k_neighbours)
        if k_neighbours > 1:
            d += _EPS
            w = (1.0 / d**2)[:, :, None]  # inverse distance weigthing
            rfl_est = np.sum(w * rfl[inds + 1, :], axis=1) / np.sum(w, axis=1)
        else:
            rfl_est = rfl[inds + 1, :].copy()
    elif interp_type == 'nd':
        rfl_est = math.ndinterp1_scipy(lab_rr, rfl[1:], lab)

        _isnan = np.isnan(rfl_est[:, 0])

        if (
                _isnan.any()
        ):  #do nearest neigbour method for those that fail using Delaunay (i.e. ndinterp1_scipy)

            # Find rfl (cfr. lab_rr) from rfl set that results in 'near' metameric
            # color coordinates for each value in lab_ur (i.e. smallest DE):
            # Construct cKDTree:
            tree = sp.spatial.cKDTree(lab_rr, copy_data=True)

            # Interpolate rfls using k nearest neightbours and inverse distance weigthing:
            d, inds = tree.query(lab[_isnan, ...], k=k_neighbours)
            if k_neighbours > 1:
                d += _EPS
                w = (1.0 / d**2)[:, :, None]  # inverse distance weigthing
                rfl_est_isnan = np.sum(w * rfl[inds + 1, :], axis=1) / np.sum(
                    w, axis=1)
            else:
                rfl_est_isnan = rfl[inds + 1, :].copy()
            rfl_est[_isnan, :] = rfl_est_isnan
    else:
        raise Exception('xyz_to_rfl(): unsupported interp_type!')

    rfl_est[
        rfl_est <
        0] = 0  #can occur for points outside convexhull of standard rfl set.

    rfl_est = np.vstack((rfl[0], rfl_est))

    if (verbosity > 0) | ('xyz_est' in out.split(',')) | (
            'lab_est' in out.split(',')) | ('DEi_ab' in out.split(',')) | (
                'DEa_ab' in out.split(',')):
        xyz_est, _ = spd_to_xyz(refspd,
                                rfl=rfl_est,
                                relative=True,
                                cieobs=cieobs,
                                out=2)
        cspace_tf_copy = cspace_tf.copy()
        cspace_tf_copy[
            'xyzw'] = xyz_wr  # put correct white point in param. dict
        lab_est = colortf(xyz_est, tf=cspace, fwtf=cspace_tf_copy)[:, 0, :]
        DEi_ab = np.sqrt(((lab_est[:, 1:3] - lab[:, 1:3])**2).sum(axis=1))
        DEa_ab = DEi_ab.mean()

    if verbosity > 0:
        ax = plot_color_data(lab[...,1], lab[...,2], z = lab[...,0], \
                        show = False, cieobs = cieobs, cspace = cspace, \
                        formatstr = 'ro', label = 'Original')
        plot_color_data(lab_est[...,1], lab_est[...,2], z = lab_est[...,0], \
                        show = True, axh = ax, cieobs = cieobs, cspace = cspace, \
                        formatstr = 'bd', label = 'Rendered')

    if out == 'rfl_est':
        return rfl_est
    elif out == 'rfl_est,xyz_est':
        return rfl_est, xyz_est
    else:
        return eval(out)
Exemplo n.º 17
0
def spd_to_cqs(SPD, version='v9.0', out='Qa', wl=None):
    """
    Calculates CQS Qa (Qai) or Qf (Qfi) or Qp (Qpi) for versions v9.0 or v7.5.
    
    Args:
        :SPD: 
            | ndarray with spectral data (can be multiple SPDs, 
            | first axis are the wavelengths)
        :version: 
            | 'v9.0' or 'v7.5', optional
        :out: 
            | 'Qa' or str, optional
            | Specifies requested output (e.g. 'Qa,Qai,Qf,cct,duv') 
        :wl: 
            | None, optional
            | Wavelengths (or [start, end, spacing]) to interpolate the SPDs to. 
            | None: default to no interpolation   
    
    Returns:
        :returns:
            | float or ndarray with CQS Qa for :out: 'Qa'
            | Other output is also possible by changing the :out: str value. 
    
    References:
        1. `W. Davis and Y. Ohno, 
        “Color quality scale,” (2010), 
        Opt. Eng., vol. 49, no. 3, pp. 33602–33616.
        <http://spie.org/Publications/Journal/10.1117/1.3360335>`_
    
    """
    outlist = out.split()
    if isinstance(version, str):
        cri_type = 'cqs-' + version
    elif isinstance(version, dict):
        cri_type = version

    # calculate DEI, labti, labri and get cspace_pars and rg_pars:
    DEi, labti, labri, cct, duv, cri_type = spd_to_DEi(
        SPD, cri_type=cri_type, out='DEi,jabt,jabr,cct,duv,cri_type', wl=wl)

    # further unpack cri_type:
    scale_fcn = cri_type['scale']['fcn']
    scale_factor = cri_type['scale']['cfactor']
    avg = cri_type['avg']
    cri_specific_pars = cri_type['cri_specific_pars']
    rg_pars = cri_type['rg_pars']

    # get maxC: to limit chroma-enhancement:
    maxC = cri_specific_pars['maxC']

    # make 3d:
    test_original_shape = labti.shape
    if len(test_original_shape) < 3:
        labti = labti[:, None]
        labri = labri[:, None]
        DEi = DEi[:, None]
        cct = cct[:, None]

    # calculate Rg for each spd:
    Qf = np.zeros((1, labti.shape[1]))
    Qfi = np.zeros((labti.shape[0], labti.shape[1]))

    if version == 'v7.5':
        GA = (9.2672 * (1.0e-11)) * cct**3.0 - (
            8.3959 * (1.0e-7)) * cct**2.0 + 0.00255 * cct - 1.612
    elif version == 'v9.0':
        GA = np.ones(cct.shape)
    else:
        raise Exception('.cri.spd_to_cqs(): Unrecognized CQS version.')

    if ('Qf' in outlist) | ('Qfi' in outlist):

        # loop of light source spds
        for ii in range(labti.shape[1]):
            Qfi[:, ii] = GA[ii] * scale_fcn(DEi[:, ii], [scale_factor[0]])
            Qf[:, ii] = GA[ii] * scale_fcn(avg(DEi[:, ii, None], axis=0),
                                           [scale_factor[0]])

    if ('Qa' in outlist) | ('Qai' in outlist) | ('Qp' in outlist) | (
            'Qpi' in outlist):

        Qa = Qf.copy()
        Qai = Qfi.copy()
        Qp = Qf.copy()
        Qpi = Qfi.copy()

        # loop of light source spds
        for ii in range(labti.shape[1]):

            # calculate deltaC:
            deltaC = np.sqrt(
                np.power(labti[:, ii, 1:3], 2).sum(
                    axis=1, keepdims=True)) - np.sqrt(
                        np.power(labri[:, ii, 1:3], 2).sum(axis=1,
                                                           keepdims=True))
            # limit chroma increase:
            DEi_Climited = DEi[:, ii, None].copy()
            deltaC_Climited = deltaC.copy()
            if maxC is None:
                maxC = 10000.0
            limitC = np.where(deltaC >= maxC)[0]
            deltaC_Climited[limitC] = maxC
            p_deltaC_pos = np.where(deltaC > 0.0)[0]
            DEi_Climited[p_deltaC_pos] = np.sqrt(
                DEi_Climited[p_deltaC_pos]**2.0 - deltaC_Climited[p_deltaC_pos]
                **2.0)  # increase in chroma is not penalized!

            if ('Qa' in outlist) | ('Qai' in outlist):
                Qai[:, ii,
                    None] = GA[ii] * scale_fcn(DEi_Climited, [scale_factor[1]])
                Qa[:, ii] = GA[ii] * scale_fcn(avg(DEi_Climited, axis=0),
                                               [scale_factor[1]])

            if ('Qp' in outlist) | ('Qpi' in outlist):
                deltaC_pos = deltaC_Climited * (deltaC_Climited >= 0.0)
                deltaCmu = np.mean(deltaC_Climited * (deltaC_Climited >= 0.0))
                Qpi[:, ii, None] = GA[ii] * scale_fcn(
                    (DEi_Climited - deltaC_pos),
                    [scale_factor[2]
                     ])  # or ?? np.sqrt(DEi_Climited**2 - deltaC_pos**2) ??
                Qp[:, ii] = GA[ii] * scale_fcn(
                    (avg(DEi_Climited, axis=0) - deltaCmu), [scale_factor[2]])

    if ('Qg' in outlist):
        Qg = Qf.copy()
        for ii in range(labti.shape[1]):
            Qg[:, ii] = 100.0 * math.polyarea(
                labti[:, ii, 1], labti[:, ii, 2]) / math.polyarea(
                    labri[:, ii, 1], labri[:, ii, 2]
                )  # calculate Rg =  gamut area ratio of test and ref

    if out == 'Qa':
        return Qa
    else:
        return eval(out)
Exemplo n.º 18
0
def spd_to_ies_tm30_metrics(SPD, cri_type = None, \
                            hbins = 16, start_hue = 0.0,\
                            scalef = 100, \
                            vf_model_type = _VF_MODEL_TYPE, \
                            vf_pcolorshift = _VF_PCOLORSHIFT,\
                            scale_vf_chroma_to_sample_chroma = False):
    """
    Calculates IES TM30 metrics from spectral data.      
      
      Args:
        :data:
            | numpy.ndarray with spectral data 
        :cri_type:
            | None, optional
            | If None: defaults to cri_type = 'iesrf'.
            | Not none values of :hbins:, :start_hue: and :scalef: overwrite 
            | input in cri_type['rg_pars'] 
        :hbins:
            | None or numpy.ndarray with sorted hue bin centers (°), optional
        :start_hue: 
            | None, optional
        :scalef:
            | None, optional
            | Scale factor for reference circle.
        :vf_pcolorshift:
            | _VF_PCOLORSHIFT or user defined dict, optional
            | The polynomial models of degree 5 and 6 can be fully specified or 
            | summarized by the model parameters themselved OR by calculating the
            | dCoverC and dH at resp. 5 and 6 hues. :VF_pcolorshift: specifies 
            | these hues and chroma level.
        :scale_vf_chroma_to_sample_chroma: 
            | False, optional
            | Scale chroma of reference and test vf fields such that average of 
            | binned reference chroma equals that of the binned sample chroma
            | before calculating hue bin metrics.
            
    Returns:
        :data: 
            | dict with color rendering data:
            | - 'SPD'  : ndarray test SPDs
            | - 'bjabt': ndarray with binned jab data under test SPDs
            | - 'bjabr': ndarray with binned jab data under reference SPDs
            | - 'jabti': ndarray with individual jab data under test SPDs (scaled such that bjabr are on a circle)
            | - 'jabri': ndarray with individual jab data under reference SPDs (scaled such that bjabr are on a circle)
            | - 'hbinnr': ndarray with the hue bin number the samples belong to.
            | - 'cct'  : ndarray with CCT of test SPD
            | - 'duv'  : ndarray with distance to blackbody locus of test SPD
            | - 'Rf'   : ndarray with general color fidelity indices
            | - 'Rg'   : ndarray with gamut area indices
            | - 'Rfi'  : ndarray with specific color fidelity indices
            | - 'Rfhi' : ndarray with local (hue binned) fidelity indices
            | - 'Rcshi': ndarray with local chroma shifts indices
            | - 'Rhshi': ndarray with local hue shifts indices
            | - 'Rt'  : ndarray with general metameric uncertainty index Rt
            | - 'Rti' : ndarray with specific metameric uncertainty indices Rti
            | - 'Rfhi_vf' : ndarray with local (hue binned) fidelity indices 
            |               obtained from VF model predictions at color space
            |               pixel coordinates
            | - 'Rcshi_vf': ndarray with local chroma shifts indices 
            |               (same as above)
            | - 'Rhshi_vf': ndarray with local hue shifts indices 
            |               (same as above)
    """
    if cri_type is None:
        cri_type = 'iesrf'

    #Calculate color rendering measures for SPDs in data:
    out = 'Rf,Rg,cct,duv,Rfi,jabt,jabr,Rfhi,Rcshi,Rhshi,cri_type'
    if isinstance(cri_type, str):  # get dict
        cri_type = copy.deepcopy(_CRI_DEFAULTS[cri_type])
    if hbins is not None:
        cri_type['rg_pars']['nhbins'] = hbins
    if start_hue is not None:
        cri_type['rg_pars']['start_hue'] = start_hue
    if scalef is not None:
        cri_type['rg_pars']['normalized_chroma_ref'] = scalef
    Rf, Rg, cct, duv, Rfi, jabt, jabr, Rfhi, Rcshi, Rhshi, cri_type = spd_to_cri(
        SPD, cri_type=cri_type, out=out)
    rg_pars = cri_type['rg_pars']

    #Calculate Metameric uncertainty and base color shifts:
    dataVF = VF_colorshift_model(SPD,
                                 cri_type=cri_type,
                                 model_type=vf_model_type,
                                 cspace=cri_type['cspace'],
                                 sampleset=eval(cri_type['sampleset']),
                                 pool=False,
                                 pcolorshift=vf_pcolorshift,
                                 vfcolor=0)
    Rf_ = np.array([dataVF[i]['metrics']['Rf'] for i in range(len(dataVF))]).T
    Rt = np.array([dataVF[i]['metrics']['Rt'] for i in range(len(dataVF))]).T
    Rti = np.array([dataVF[i]['metrics']['Rti']
                    for i in range(len(dataVF))][0])

    # Get normalized and sliced sample data for plotting:
    rg_pars = cri_type['rg_pars']
    nhbins, normalize_gamut, normalized_chroma_ref, start_hue = [
        rg_pars[x] for x in sorted(rg_pars.keys())
    ]
    normalized_chroma_ref = scalef
    # np.sqrt((jabr[...,1]**2 + jabr[...,2]**2)).mean(axis = 0).mean()

    if scale_vf_chroma_to_sample_chroma == True:
        normalize_gamut = False
        bjabt, bjabr = gamut_slicer(
            jabt,
            jabr,
            out='jabt,jabr',
            nhbins=nhbins,
            start_hue=start_hue,
            normalize_gamut=normalize_gamut,
            normalized_chroma_ref=normalized_chroma_ref,
            close_gamut=True)
        Cr_s = (np.sqrt(bjabr[:-1, ..., 1]**2 + bjabr[:-1, ..., 2]**2)).mean(
            axis=0)  # for rescaling vector field average reference chroma

    normalize_gamut = True  #(for plotting)
    bjabt, bjabr, binnrs, jabti, jabri = gamut_slicer(
        jabt,
        jabr,
        out='jabt,jabr,binnr,jabti,jabri',
        nhbins=nhbins,
        start_hue=start_hue,
        normalize_gamut=normalize_gamut,
        normalized_chroma_ref=normalized_chroma_ref,
        close_gamut=True)

    Rfhi_vf = np.empty(Rfhi.shape)
    Rcshi_vf = np.empty(Rcshi.shape)
    Rhshi_vf = np.empty(Rhshi.shape)
    for i in range(cct.shape[0]):

        # Get normalized and sliced VF data for hue specific metrics:
        vfjabt = np.hstack(
            (np.ones(dataVF[i]['fielddata']['vectorfield']['axt'].shape),
             dataVF[i]['fielddata']['vectorfield']['axt'],
             dataVF[i]['fielddata']['vectorfield']['bxt']))
        vfjabr = np.hstack(
            (np.ones(dataVF[i]['fielddata']['vectorfield']['axr'].shape),
             dataVF[i]['fielddata']['vectorfield']['axr'],
             dataVF[i]['fielddata']['vectorfield']['bxr']))
        nhbins, normalize_gamut, normalized_chroma_ref, start_hue = [
            rg_pars[x] for x in sorted(rg_pars.keys())
        ]
        vfbjabt, vfbjabr, vfbDEi = gamut_slicer(
            vfjabt,
            vfjabr,
            out='jabt,jabr,DEi',
            nhbins=nhbins,
            start_hue=start_hue,
            normalize_gamut=normalize_gamut,
            normalized_chroma_ref=normalized_chroma_ref,
            close_gamut=False)

        if scale_vf_chroma_to_sample_chroma == True:
            #rescale vfbjabt and vfbjabr to same chroma level as bjabr.
            Cr_vfb = np.sqrt(vfbjabr[..., 1]**2 + vfbjabr[..., 2]**2)
            Cr_vf = np.sqrt(vfjabr[..., 1]**2 + vfjabr[..., 2]**2)
            hr_vf = np.arctan2(vfjabr[..., 2], vfjabr[..., 1])
            Ct_vf = np.sqrt(vfjabt[..., 1]**2 + vfjabt[..., 2]**2)
            ht_vf = np.arctan2(vfjabt[..., 2], vfjabt[..., 1])
            fC = Cr_s.mean() / Cr_vfb.mean()
            vfjabr[..., 1] = fC * Cr_vf * np.cos(hr_vf)
            vfjabr[..., 2] = fC * Cr_vf * np.sin(hr_vf)
            vfjabt[..., 1] = fC * Ct_vf * np.cos(ht_vf)
            vfjabt[..., 2] = fC * Ct_vf * np.sin(ht_vf)
            vfbjabt, vfbjabr, vfbDEi = gamut_slicer(
                vfjabt,
                vfjabr,
                out='jabt,jabr,DEi',
                nhbins=nhbins,
                start_hue=start_hue,
                normalize_gamut=normalize_gamut,
                normalized_chroma_ref=normalized_chroma_ref,
                close_gamut=False)

        scale_factor = cri_type['scale']['cfactor']
        scale_fcn = cri_type['scale']['fcn']
        vfRfhi, vfRcshi, vfRhshi = jab_to_rhi(
            jabt=vfbjabt,
            jabr=vfbjabr,
            DEi=vfbDEi,
            cri_type=cri_type,
            scale_factor=scale_factor,
            scale_fcn=scale_fcn,
            use_bin_avg_DEi=True
        )  # [:-1,...] removes last row from jab as this was added to close the gamut.

        Rfhi_vf[:, i:i + 1] = vfRfhi
        Rhshi_vf[:, i:i + 1] = vfRhshi
        Rcshi_vf[:, i:i + 1] = vfRcshi

    # Create dict with CRI info:
    data = {'SPD' : SPD, 'cct' : cct, 'duv' : duv, 'bjabt' : bjabt, 'bjabr' : bjabr,\
            'jabti':jabti, 'jabri':jabri, 'hbinnr':binnrs,\
           'Rf' : Rf, 'Rg' : Rg, 'Rfi': Rfi, 'Rfhi' : Rfhi, 'Rcshi' : Rcshi, 'Rhshi' : Rhshi, \
           'Rt' : Rt, 'Rti' : Rti,  'Rfhi_vf' : Rfhi_vf, 'Rfcshi_vf' : Rcshi_vf, 'Rfhshi_vf' : Rhshi_vf, \
           'dataVF' : dataVF,'cri_type' : cri_type,
           # 'jabt_':jabt_,'jabr_':jabr_
           }
    return data
Exemplo n.º 19
0
def spd_to_ies_tm30_metrics(St, cri_type = None, \
                            hbins = 16, start_hue = 0.0,\
                            scalef = 100, \
                            vf_model_type = _VF_MODEL_TYPE, \
                            vf_pcolorshift = _VF_PCOLORSHIFT,\
                            scale_vf_chroma_to_sample_chroma = False):
    """
    Calculates IES TM30 metrics from spectral data.      
      
      Args:
        :St:
            | numpy.ndarray with spectral data 
        :cri_type:
            | None, optional
            | If None: defaults to cri_type = 'iesrf'.
            | Not none values of :hbins:, :start_hue: and :scalef: overwrite 
            | input in cri_type['rg_pars'] 
        :hbins:
            | None or numpy.ndarray with sorted hue bin centers (°), optional
        :start_hue: 
            | None, optional
        :scalef:
            | None, optional
            | Scale factor for reference circle.
        :vf_pcolorshift:
            | _VF_PCOLORSHIFT or user defined dict, optional
            | The polynomial models of degree 5 and 6 can be fully specified or 
            | summarized by the model parameters themselved OR by calculating the
            | dCoverC and dH at resp. 5 and 6 hues. :VF_pcolorshift: specifies 
            | these hues and chroma level.
        :scale_vf_chroma_to_sample_chroma: 
            | False, optional
            | Scale chroma of reference and test vf fields such that average of 
            | binned reference chroma equals that of the binned sample chroma
            | before calculating hue bin metrics.
            
    Returns:
        :data: 
            | Dictionary with color rendering data:
            | 
            | - 'St, Sr'  : ndarray of test SPDs and corresponding ref. illuminants.
            | - 'xyz_cct': xyz of white point calculate with cieobs defined for cct calculations in cri_type['cieobs']
            | - 'cct, duv': CCT and Duv obtained with cieobs in cri_type['cieobs']['cct']
            | - 'xyzti, xyzri': ndarray tristimulus values of test and ref. samples (obtained with with cieobs in cri_type['cieobs']['xyz'])
            | - 'xyztw, xyzrw': ndarray tristimulus values of test and ref. white points (obtained with with cieobs in cri_type['cieobs']['xyz'])
            | - 'DEi, DEa': ndarray with individual sample color differences DEi and average DEa between test and ref.       
            | - 'Rf'  : ndarray with general color fidelity index values
            | - 'Rg'  : ndarray with color gamut area index values
            | - 'Rfi'  : ndarray with specific (sample) color fidelity indices
            | - 'Rfhj' : ndarray with local (hue binned) fidelity indices
            | - 'DEhj' : ndarray with local (hue binned) color differences
            | - 'Rcshj': ndarray with local chroma shifts indices
            | - 'Rhshj': ndarray with local hue shifts indices
            | - 'hue_bin_data': dict with output from _get_hue_bin_data() [see its help for more info]
            | - 'cri_type': same as input (for reference purposes)
            | - 'vf' : dictionary with vector field measures and data.
            |         Keys:
            |           - 'Rt'  : ndarray with general metameric uncertainty index Rt
            |           - 'Rti' : ndarray with specific metameric uncertainty indices Rti
            |           - 'Rfhj' : ndarray with local (hue binned) fidelity indices 
            |                            obtained from VF model predictions at color space
            |                            pixel coordinates
            |           - 'DEhj' : ndarray with local (hue binned) color differences
            |                           (same as above)
            |           - 'Rcshj': ndarray with local chroma shifts indices for vectorfield coordinates
            |                           (same as above)
            |           - 'Rhshj': ndarray with local hue shifts indicesfor vectorfield coordinates
            |                           (same as above)
            |           - 'Rfi': ndarray with sample fidelity indices for vectorfield coordinates
            |                           (same as above)
            |           - 'DEi': ndarray with sample color differences for vectorfield coordinates
            |                           (same as above)
            |           - 'hue_bin_data': dict with output from _get_hue_bin_data() for vectorfield coordinates
            |           - 'dataVF': dictionary with output of cri.VFPX.VF_colorshift_model()
    """
    if cri_type is None:
        cri_type = 'iesrf'

    if isinstance(cri_type,str): # get dict 
        cri_type = copy.deepcopy(_CRI_DEFAULTS[cri_type])
    if hbins is not None:
        cri_type['rg_pars']['nhbins'] = hbins 
    if start_hue is not None:
        cri_type['rg_pars']['start_hue'] = start_hue
    if scalef is not None:
        cri_type['rg_pars']['normalized_chroma_ref'] = scalef
    
    #Calculate color rendering measures for SPDs in St:      
    data,_ = spd_to_cri(St, cri_type = cri_type, out = 'data,hue_bin_data', 
                        fit_gamut_ellipse = True)
    hdata = data['hue_bin_data']
    Rfhj, Rcshj, Rhshj = data['Rfhj'], data['Rcshj'], data['Rhshj']
    cct = data['cct']
    
    #Calculate Metameric uncertainty and base color shifts:
    dataVF = VF_colorshift_model(St, cri_type = cri_type, 
                                 model_type = vf_model_type, 
                                 cspace = cri_type['cspace'], 
                                 sampleset = eval(cri_type['sampleset']), 
                                 pool = False, 
                                 pcolorshift = vf_pcolorshift, 
                                 vfcolor = 0)
    Rf_ = np.array([dataVF[i]['metrics']['Rf'] for i in range(len(dataVF))]).T
    Rt = np.array([dataVF[i]['metrics']['Rt'] for i in range(len(dataVF))]).T
    Rti = np.array([dataVF[i]['metrics']['Rti'] for i in range(len(dataVF))][0])
    _data_vf = {'Rt' : Rt, 'Rti' : Rti, 'Rf_' : Rf_} # add to dict for output

    # Get normalized and sliced hue-bin _hj data for plotting:
    rg_pars = cri_type['rg_pars']
    nhbins, normalize_gamut, normalized_chroma_ref, start_hue = [rg_pars[x] for x in sorted(rg_pars.keys())]
    
    # Get chroma of samples:    
    if scale_vf_chroma_to_sample_chroma == True:
        jabt_hj_closed, jabr_hj_closed = hdata['jabt_hj_closed'], hdata['jabr_hj_closed']
        Cr_hj_s = (np.sqrt(jabr_hj_closed[:-1,...,1]**2 + jabr_hj_closed[:-1,...,2]**2)).mean(axis=0) # for rescaling vector field average reference chroma

    #jabtn_hj_closed, jabrn_hj_closed = hdata['jabtn_hj_closed'], hdata['jabrn_hj_closed']
    
    # get vector field data for each source (must be on 2nd dim)
    jabt_vf = np.transpose(np.array([np.hstack((np.ones(dataVF[i]['fielddata']['vectorfield']['axt'].shape),dataVF[i]['fielddata']['vectorfield']['axt'],dataVF[i]['fielddata']['vectorfield']['bxt'])) for i in range(cct.shape[0])]),(1,0,2))
    jabr_vf = np.transpose(np.array([np.hstack((np.ones(dataVF[i]['fielddata']['vectorfield']['axr'].shape),dataVF[i]['fielddata']['vectorfield']['axr'],dataVF[i]['fielddata']['vectorfield']['bxr'])) for i in range(cct.shape[0])]),(1,0,2))
    
    # Get hue bin data for vector field data:
    hue_bin_data_vf = _get_hue_bin_data(jabt_vf, jabr_vf, 
                                        start_hue = start_hue, nhbins = nhbins,
                                        normalized_chroma_ref = normalized_chroma_ref )
    
    # Rescale chroma of vector field such that it is on average equal to that of the binned samples:
    if scale_vf_chroma_to_sample_chroma == True:
        Cr_vf_hj, Cr_vf, Ct_vf = hue_bin_data_vf['Cr_hj'], hue_bin_data_vf['Cr'], hue_bin_data_vf['Ct']
        hr_vf, ht_vf = hue_bin_data_vf['hr'], hue_bin_data_vf['ht']
        fC = np.nanmean(Cr_hj_s)/np.nanmean(Cr_vf_hj)
        jabr_vf[...,1], jabr_vf[...,2] = fC * Cr_vf*np.cos(hr_vf), fC * Cr_vf*np.sin(hr_vf)
        jabt_vf[...,1], jabt_vf[...,2] = fC * Ct_vf*np.cos(ht_vf), fC * Ct_vf*np.sin(ht_vf)
        
        # Get new hue bin data for rescaled vector field data:
        hue_bin_data_vf = _get_hue_bin_data(jabt_vf, jabr_vf, 
                                            start_hue = start_hue, nhbins = nhbins,
                                            normalized_chroma_ref = normalized_chroma_ref )
    
    # Get scale factor and scaling function for Rfx:
    scale_factor = cri_type['scale']['cfactor']
    scale_fcn = cri_type['scale']['fcn']

    # Calculate Local color fidelity, chroma and hue shifts for vector field data:
    (Rcshj_vf, Rhshj_vf,
     Rfhj_vf, DEhj_vf) = _hue_bin_data_to_rxhj(hue_bin_data_vf, 
                                               cri_type = cri_type,
                                               scale_factor = scale_factor,
                                               scale_fcn = scale_fcn) 
                                               
    # Get sample color fidelity for vector field data:
    (Rfi_vf, DEi_vf) = _hue_bin_data_to_rfi(hue_bin_data_vf, 
                                            cri_type = cri_type,
                                            scale_factor = scale_factor,
                                            scale_fcn = scale_fcn)
    # Store in dict:
    _data_vf.update({'Rfi' : Rfi_vf, 'DEi' : DEi_vf,
                     'Rcshj' : Rcshj_vf, 'Rhshj' : Rhshj_vf,
                     'Rfhj' : Rfhj_vf, 'DEhj': DEhj_vf,
                     'dataVF' : dataVF, 'hue_bin_data' : hue_bin_data_vf})
    
    # Add to main dictionary:
    data['vf'] = _data_vf
    return data
Exemplo n.º 20
0
def PX_colorshift_model(Jabt,
                        Jabr,
                        jab_ranges=None,
                        jab_deltas=None,
                        limit_grid_radius=0):
    """
    Pixelates the color space and calculates the color shifts in each pixel.
    
    Args:
        :Jabt: 
            | ndarray with color coordinates under the (single) test SPD.
        :Jabr: 
            | ndarray with color coordinates under the (single) reference SPD.  
        :jab_ranges:
            | None or ndarray, optional
            | Specifies the pixelization of color space.
            | (ndarray.shape = (3,3), with  first axis: J,a,b, and second 
            | axis: min, max, delta)
        :jab_deltas:
            | float or ndarray, optional
            | Specifies the sampling range. 
            | A float uses jab_deltas as the maximum Euclidean distance to select
            | samples around each pixel center. A ndarray of 3 deltas, uses
            | a city block sampling around each pixel center.
        :limit_grid_radius:
            | 0, optional
            | A value of zeros keeps grid as specified by axr,bxr.
            | A value > 0 only keeps (a,b) coordinates within :limit_grid_radius:
            
    Returns:
        :returns: 
            | dict with the following keys:
            |   - 'Jab': dict with with ndarrays for:
            |        Jabt, Jabr, DEi, DEi_ab (only ab-coordinates), DEa (mean) 
            |         and DEa_ab
            |   - 'vshifts': dict with:
            |      * 'vectorshift': ndarray with vector shifts between average
            |                       Jabt and Jabr for each pixel
            |      * 'vectorshift_ab': ndarray with vector shifts averaged 
            |                          over J for each pixel
            |      * 'vectorshift_ab_J0': ndarray with vector shifts averaged 
            |                             over J for each pixel of J=0 plane.
            |      * 'vectorshift_len': length of 'vectorshift'
            |      * 'vectorshift_ab_len': length of 'vectorshift_ab'
            |      * 'vectorshift_ab_J0_len': length of 'vectorshift_ab_J0'
            |      * 'vectorshift_len_DEnormed': length of 'vectorshift' 
            |                                    normalized to 'DEa'
            |      * 'vectorshift_ab_len_DEnormed': length of 'vectorshift_ab' 
            |                                       normalized to 'DEa_ab'
            |      * 'vectorshift_ab_J0_len_DEnormed': length of 'vectorshift_ab_J0' 
            |                                          normalized to 'DEa_ab'
            |   - 'pixeldata': dict with pixel info:
            |      * 'grid' ndarray with coordinates of all pixel centers.
            |      * 'idx': list[int] with pixel index for each non-empty pixel
            |      * 'Jab': ndarray with center coordinates of non-empty pixels
            |      * 'samplenrs': list[list[int]] with sample numbers belong to 
            |                     each non-empty pixel
            |      * 'IDs: summarizing list, 
            |              with column order: 'idxp, jabp, samplenrs'
            |  - 'fielddata' : dict with dicts containing data on the calculated 
            |                  vector-field and circle-fields 
            |      * 'vectorfield': dict with ndarrays for the ab-coordinates 
            |         under the ref. (axr, bxr) and test (axt, bxt) illuminants,
            |         centered at the pixel centers corresponding to the 
                      ab-coordinates of the reference illuminant.
     """

    # get pixelIDs of all samples under ref. conditions:
    gridp, idxp, jabp, pixelsamplenrs, pixelIDs = get_pixel_coordinates(
        Jabr,
        jab_ranges=jab_ranges,
        jab_deltas=jab_deltas,
        limit_grid_radius=limit_grid_radius)

    # get average Jab coordinates for each pixel:
    Npixels = len(idxp)  # number of non-empty pixels
    Jabr_avg = np.zeros((gridp.shape[0], 3))
    Jabr_avg.fill(np.nan)
    Jabt_avg = Jabr_avg.copy()
    for i in range(Npixels):
        Jabr_avg[idxp[i], :] = Jabr[pixelsamplenrs[i], :].mean(axis=0)
        Jabt_avg[idxp[i], :] = Jabt[pixelsamplenrs[i], :].mean(axis=0)
        #jabtemp = Jabr[pixelsamplenrs[i],:]
        #jabtempm = Jabr_avg[idxp[i],:]

    # calculate Jab vector shift:
    vectorshift = Jabt_avg - Jabr_avg

    # calculate ab vector shift:
    uabs = gridp[gridp[:, 0] == 0, 1:3]  #np.unique(gridp[:,1:3],axis=0)
    vectorshift_ab_J0 = np.zeros((uabs.shape[0], 2))
    vectorshift_ab_J0.fill(np.nan)
    vectorshift_ab = np.zeros((vectorshift.shape[0], 2))
    vectorshift_ab.fill(np.nan)
    for i in range(uabs.shape[0]):
        cond = (gridp[:, 1:3] == uabs[i, :]).all(axis=1)
        if cond.any() & np.logical_not(
                np.isnan(vectorshift[cond, 1:3]).all()
        ):  #last condition is to avoid warning of taking nanmean of empty slice when all are NaNs
            vectorshift_ab_J0[i, :] = np.nanmean(vectorshift[cond, 1:3],
                                                 axis=0)
            vectorshift_ab[cond, :] = np.nanmean(vectorshift[cond, 1:3],
                                                 axis=0)

    # Calculate length of shift vectors:
    vectorshift_len = np.sqrt((vectorshift**2).sum(axis=vectorshift.ndim - 1))
    vectorshift_ab_len = np.sqrt(
        (vectorshift_ab**2).sum(axis=vectorshift_ab.ndim - 1))
    vectorshift_ab_J0_len = np.sqrt(
        (vectorshift_ab_J0**2).sum(axis=vectorshift_ab_J0.ndim - 1))

    # Calculate average DE for normalization of vectorshifts
    DEi_Jab_avg = np.sqrt(((Jabt - Jabr)**2).sum(axis=Jabr.ndim - 1))
    DE_Jab_avg = DEi_Jab_avg.mean(axis=0)
    DEi_ab_avg = np.sqrt(
        ((Jabt[..., 1:3] - Jabr[..., 1:3])**2).sum(axis=Jabr[..., 1:3].ndim -
                                                   1))
    DE_ab_avg = DEi_ab_avg.mean(axis=0)

    # calculate vectorfield:
    axr = uabs[:, 0, None]
    bxr = uabs[:, 1, None]
    axt = axr + vectorshift_ab_J0[:, 0, None]
    bxt = bxr + vectorshift_ab_J0[:, 1, None]

    data = {
        'Jab': {
            'Jabr': Jabr_avg,
            'Jabt': Jabt_avg,
            'DEi': DEi_Jab_avg,
            'DEi_ab': DEi_ab_avg,
            'DEa': DE_Jab_avg,
            'DEa_ab': DE_ab_avg
        },
        'vshifts': {
            'vectorshift': vectorshift,
            'vectorshift_ab': vectorshift_ab,
            'vectorshift_ab_J0': vectorshift_ab_J0,
            'vectorshift_len': vectorshift_len,
            'vectorshift_ab_len': vectorshift_ab_len,
            'vectorshift_ab_J0_len': vectorshift_ab_J0_len,
            'vectorshift_len_DEnormed': vectorshift_len / DE_Jab_avg,
            'vectorshift_ab_len_DEnormed': vectorshift_ab_len / DE_ab_avg,
            'vectorshift_ab_J0_len_DEnormed': vectorshift_ab_J0_len / DE_ab_avg
        },
        'pixeldata': {
            'grid': gridp,
            'idx': idxp,
            'Jab': jabp,
            'samplenrs': pixelsamplenrs,
            'IDs': pixelIDs
        },
        'fielddata': {
            'vectorfield': {
                'axr': axr,
                'bxr': bxr,
                'axt': axt,
                'bxt': bxt
            }
        }
    }
    return data
Exemplo n.º 21
0
def plot_shift_data(data, fieldtype = 'vectorfield', scalef = _VF_MAXR, color = 'k', \
                    axtype = 'polar', ax = None, \
                    hbins = 10,  start_hue = 0.0, bin_labels = '#', plot_center_lines = True,  \
                    plot_axis_labels = False, plot_edge_lines = False, plot_bin_colors = True, \
                    force_CVG_layout = True):
    """
    Plots vector or circle fields generated by VFcolorshiftmodel() 
    or PXcolorshiftmodel().
     
    Args:
        :data: 
            | dict generated by VFcolorshiftmodel() or PXcolorshiftmodel()
            | Must contain 'fielddata'- key, which is a dict with possible keys:
            |     - key: 'vectorfield': ndarray with vector field data
            |     - key: 'circlefield': ndarray with circle field data
        :color: 
            | 'k', optional
            | Color for plotting the vector-fields.
        :axtype:
            | 'polar' or 'cart', optional
            | Make polar or Cartesian plot.
        :ax: 
            | None or 'new' or 'same', optional
            |   - None or 'new' creates new plot
            |   - 'same': continue plot on same axes.
            |   - axes handle: plot on specified axes.
        :hbins:
            | 16 or ndarray with sorted hue bin centers (°), optional
        :start_hue:
            | _VF_MAXR, optional
            | Scale factor for graphic.
        :plot_axis_labels:
            | False, optional
            | Turns axis ticks on/off (True/False).
        :bin_labels:
            | None or list[str] or '#', optional
            | Plots labels at the bin center hues.
            |   - None: don't plot.
            |   - list[str]: list with str for each bin. 
            |                (len(:bin_labels:) = :nhbins:)
            |   - '#': plots number.
        :plot_edge_lines:
            | True or False, optional
            | Plot grey bin edge lines with '--'.
        :plot_center_lines:
            | False or True, optional
            | Plot colored lines at 'center' of hue bin.
        :plot_bin_colors:
            | True, optional
            | Colorize hue-bins.
        :force_CVG_layout: 
            | False or True, optional
            | True: Force plot of basis of CVG.
    
    Returns:
        :returns:
            | figCVG, hax, cmap
          
            |   :figCVG: handle to CVG figure
            |   :hax: handle to CVG axes
            |   :cmap: list with rgb colors for hue bins 
            |          (for use in other plotting fcns)
   
    """

    # Plot basis of CVG:
    figCVG, hax, cmap = plot_hue_bins(hbins=hbins,
                                      axtype=axtype,
                                      ax=ax,
                                      plot_center_lines=plot_center_lines,
                                      plot_edge_lines=plot_edge_lines,
                                      plot_bin_colors=plot_bin_colors,
                                      scalef=scalef,
                                      force_CVG_layout=force_CVG_layout,
                                      bin_labels=bin_labels)

    # plot vector field:
    if data is not None:
        if fieldtype is not None:
            vf = data['fielddata'][fieldtype]
            if axtype == 'polar':
                if fieldtype == 'vectorfield':
                    vfrtheta = math.positive_arctan(vf['axr'],
                                                    vf['bxr'],
                                                    htype='rad')
                    vfrr = np.sqrt(vf['axr']**2 + vf['bxr']**2)
                    hax.quiver(vfrtheta,
                               vfrr,
                               vf['axt'] - vf['axr'],
                               vf['bxt'] - vf['bxr'],
                               headlength=3,
                               color=color,
                               angles='uv',
                               scale_units='y',
                               scale=2,
                               linewidth=0.5)
                else:
                    vfttheta = math.positive_arctan(vf['axt'],
                                                    vf['bxt'],
                                                    htype='rad')
                    vfrtheta = math.positive_arctan(vf['axr'],
                                                    vf['bxr'],
                                                    htype='rad')
                    vftr = np.sqrt(vf['axt']**2 + vf['bxt']**2)
                    dh = (math.angle_v1v2(np.hstack((vf['axt'], vf['bxt'])),
                                          np.hstack((vf['axr'], vf['bxr'])),
                                          htype='deg')[:, None])  #hue shift
                    dh = dh / np.nanmax(dh)
                    plt.set_cmap('jet')
                    hax.scatter(vfttheta,
                                vftr,
                                s=100 * dh,
                                c=dh,
                                linestyle='None',
                                marker='o',
                                norm=None)
                hax.set_ylim([0, 1.1 * scalef])
            else:
                if fieldtype == 'vectorfield':
                    hax.quiver(vf['axr'],
                               vf['bxr'],
                               vf['axt'] - vf['axr'],
                               vf['bxt'] - vf['bxr'],
                               headlength=1,
                               color=color,
                               angles='uv',
                               scale_units='xy',
                               scale=1,
                               linewidth=0.5)
                else:
                    hax.plot(vf['axr'],
                             vf['bxr'],
                             color=color,
                             marker='.',
                             linestyle='None')

    return figCVG, hax, cmap
Exemplo n.º 22
0
def VF_colorshift_model(S, cri_type = _VF_CRI_DEFAULT, model_type = _VF_MODEL_TYPE, \
                        cspace = _VF_CSPACE, sampleset = None, pool = False, \
                        pcolorshift = {'href': np.arange(np.pi/10,2*np.pi,2*np.pi/10),'Cref' : _VF_MAXR, 'sig' : _VF_SIG}, \
                        vfcolor = 'k',verbosity = 0):
    """
    Applies full vector field model calculations to spectral data.
    
    Args:
        :S: 
            | nump.ndarray with spectral data.
        :cri_type:
            | _VF_CRI_DEFAULT or str or dict, optional
            | Specifies type of color fidelity model to use. 
            | Controls choice of ref. ill., sample set, averaging, scaling, etc.
            | See luxpy.cri.spd_to_cri for more info.
        :modeltype:
            | _VF_MODEL_TYPE or 'M6' or 'M5', optional
            | Specifies degree 5 or degree 6 polynomial model in ab-coordinates.
        :cspace:
            | _VF_CSPACE or dict, optional
            | Specifies color space. See _VF_CSPACE_EXAMPLE for example structure.
        :sampleset:
            | None or str or ndarray, optional
            | Sampleset to be used when calculating vector field model.
        :pool: 
            | False, optional
            | If :S: contains multiple spectra, True pools all jab data before 
            | modeling the vector field, while False models a different field 
            | for each spectrum.
        :pcolorshift: 
            | default dict (see below) or user defined dict, optional
            | Dict containing the specification input 
            | for apply_poly_model_at_hue_x().
            | Default dict = {'href': np.arange(np.pi/10,2*np.pi,2*np.pi/10),
            |                 'Cref' : _VF_MAXR, 
            |                 'sig' : _VF_SIG, 
            |                 'labels' : '#'} 
            | The polynomial models of degree 5 and 6 can be fully specified or 
            | summarized by the model parameters themselved OR by calculating the
            | dCoverC and dH at resp. 5 and 6 hues.
        :vfcolor:
            | 'k', optional
            | For plotting the vector fields.
        :verbosity: 
            | 0, optional
            | Report warnings or not.
            
    Returns:
        :returns: 
            | list[dict] (each list element refers to a different test SPD)
            | with the following keys:
            |   - 'Source': dict with ndarrays of the S, cct and duv of source spd.
            |   - 'metrics': dict with ndarrays for:
            |         * Rf (color fidelity: base + metameric shift)
            |         * Rt (metameric uncertainty index) 
            |         * Rfi (specific color fidelity indices)
            |         * Rti (specific metameric uncertainty indices)
            |         * cri_type (str with cri_type)
            |   - 'Jab': dict with with ndarrays for Jabt, Jabr, DEi
            |   - 'dC/C_dH_x_sig' : 
            |           np.vstack((dCoverC_x,dCoverC_x_sig,dH_x,dH_x_sig)).T
            |           See get_poly_model() for more info.
            |   - 'fielddata': dict with dicts containing data on the calculated 
            |      vector-field and circle-fields: 
            |        * 'vectorfield' : {'axt': vfaxt, 'bxt' : vfbxt, 
            |                           'axr' : vfaxr, 'bxr' : vfbxr},
            |        * 'circlefield' : {'axt': cfaxt, 'bxt' : cfbxt, 
            |                           'axr' : cfaxr, 'bxr' : cfbxr}},
            |   - 'modeldata' : dict with model info:
            |                {'pmodel': pmodel, 
            |                'pcolorshift' : pcolorshift, 
            |                  'dab_model' : dab_model, 
            |                  'dab_res' : dab_res,
            |                  'dab_std' : dab_std,
            |                  'modeltype' : modeltype, 
            |                  'fmodel' : poly_model,
            |                  'Jabtm' : Jabtm, 
            |                  'Jabrm' : Jabrm, 
            |                  'DEim' : DEim},
            |   - 'vshifts' :dict with various vector shifts:
            |        * 'Jabshiftvector_r_to_t' : ndarray with difference vectors
            |                                    between jabt and jabr.
            |        * 'vshift_ab_s' : vshift_ab_s: ab-shift vectors of samples 
            |        * 'vshift_ab_s_vf' : vshift_ab_s_vf: ab-shift vectors of 
            |                             VF model predictions of samples.
            |        * 'vshift_ab_vf' : vshift_ab_vf: ab-shift vectors of VF 
            |                            model predictions of vector field grid.
    """

    if type(cri_type) == str:
        cri_type_str = cri_type
    else:
        cri_type_str = None

    # Calculate Rf, Rfi and Jabr, Jabt:
    Rf, Rfi, Jabt, Jabr, cct, duv, cri_type = spd_to_cri(
        S,
        cri_type=cri_type,
        out='Rf,Rfi,jabt,jabr,cct,duv,cri_type',
        sampleset=sampleset)

    # In case of multiple source SPDs, pool:
    if (len(Jabr.shape) == 3) & (Jabr.shape[1] > 1) & (pool == True):
        #Nsamples = Jabr.shape[0]
        Jabr = np.transpose(Jabr, (1, 0, 2))  # set lamps on first dimension
        Jabt = np.transpose(Jabt, (1, 0, 2))
        Jabr = Jabr.reshape(Jabr.shape[0] * Jabr.shape[1],
                            3)  # put all lamp data one after the other
        Jabt = Jabt.reshape(Jabt.shape[0] * Jabt.shape[1], 3)
        Jabt = Jabt[:, None, :]  # add dim = 1
        Jabr = Jabr[:, None, :]

    out = [{} for _ in range(Jabr.shape[1])]  #initialize empty list of dicts
    if pool == False:
        N = Jabr.shape[1]
    else:
        N = 1
    for i in range(N):

        Jabr_i = Jabr[:, i, :].copy()
        Jabr_i = Jabr_i[:, None, :]
        Jabt_i = Jabt[:, i, :].copy()
        Jabt_i = Jabt_i[:, None, :]

        DEi = np.sqrt((Jabr_i[..., 0] - Jabt_i[..., 0])**2 +
                      (Jabr_i[..., 1] - Jabt_i[..., 1])**2 +
                      (Jabr_i[..., 2] - Jabt_i[..., 2])**2)

        # Determine polynomial model:
        poly_model, pmodel, dab_model, dab_res, dCHoverC_res, dab_std, dCHoverC_std = get_poly_model(
            Jabt_i, Jabr_i, modeltype=_VF_MODEL_TYPE)

        # Apply model at fixed hues:
        href = pcolorshift['href']
        Cref = pcolorshift['Cref']
        sig = pcolorshift['sig']
        dCoverC_x, dCoverC_x_sig, dH_x, dH_x_sig = apply_poly_model_at_hue_x(
            poly_model, pmodel, dCHoverC_res, hx=href, Cxr=Cref, sig=sig)

        # Calculate deshifted a,b values on original samples:
        Jt = Jabt_i[..., 0].copy()
        at = Jabt_i[..., 1].copy()
        bt = Jabt_i[..., 2].copy()
        Jr = Jabr_i[..., 0].copy()
        ar = Jabr_i[..., 1].copy()
        br = Jabr_i[..., 2].copy()
        ar = ar + dab_model[:, 0:1]  # deshift reference to model prediction
        br = br + dab_model[:, 1:2]  # deshift reference to model prediction

        Jabtm = np.hstack((Jt, at, bt))
        Jabrm = np.hstack((Jr, ar, br))

        # calculate color differences between test and deshifted ref:
        #        DEim = np.sqrt((Jr - Jt)**2 + (at - ar)**2 + (bt - br)**2)
        DEim = np.sqrt(0 * (Jr - Jt)**2 + (at - ar)**2 +
                       (bt - br)**2)  # J is not used

        # Apply scaling function to convert DEim to Rti:
        scale_factor = cri_type['scale']['cfactor']
        scale_fcn = cri_type['scale']['fcn']
        avg = cri_type['avg']
        Rfi_deshifted = scale_fcn(DEim, scale_factor)
        Rf_deshifted = scale_fcn(avg(DEim, axis=0), scale_factor)

        rms = lambda x: np.sqrt(np.sum(x**2, axis=0) / x.shape[0])
        Rf_deshifted_rms = scale_fcn(rms(DEim), scale_factor)

        # Generate vector field:
        vfaxt, vfbxt, vfaxr, vfbxr = generate_vector_field(
            poly_model,
            pmodel,
            axr=np.arange(-_VF_MAXR, _VF_MAXR + _VF_DELTAR, _VF_DELTAR),
            bxr=np.arange(-_VF_MAXR, _VF_MAXR + _VF_DELTAR, _VF_DELTAR),
            limit_grid_radius=_VF_MAXR,
            color=0)
        vfaxt, vfbxt, vfaxr, vfbxr = generate_vector_field(
            poly_model,
            pmodel,
            axr=np.arange(-_VF_MAXR, _VF_MAXR + _VF_DELTAR, _VF_DELTAR),
            bxr=np.arange(-_VF_MAXR, _VF_MAXR + _VF_DELTAR, _VF_DELTAR),
            limit_grid_radius=_VF_MAXR,
            color=0)

        # Calculate ab-shift vectors of samples and VF model predictions:
        vshift_ab_s = calculate_shiftvectors(Jabt_i,
                                             Jabr_i,
                                             average=False,
                                             vtype='ab')[:, 0, 0:3]
        vshift_ab_s_vf = calculate_shiftvectors(Jabtm,
                                                Jabrm,
                                                average=False,
                                                vtype='ab')

        # Calculate ab-shift vectors using vector field model:
        Jabt_vf = np.hstack((np.zeros((vfaxt.shape[0], 1)), vfaxt, vfbxt))
        Jabr_vf = np.hstack((np.zeros((vfaxr.shape[0], 1)), vfaxr, vfbxr))
        vshift_ab_vf = calculate_shiftvectors(Jabt_vf,
                                              Jabr_vf,
                                              average=False,
                                              vtype='ab')

        # Generate circle field:
        x, y = plotcircle(radii=np.arange(0, _VF_MAXR + _VF_DELTAR, 10),
                          angles=np.arange(0, 359, 1),
                          out='x,y')
        cfaxt, cfbxt, cfaxr, cfbxr = generate_vector_field(
            poly_model,
            pmodel,
            make_grid=False,
            axr=x[:, None],
            bxr=y[:, None],
            limit_grid_radius=_VF_MAXR,
            color=0)

        out[i] = {
            'Source': {
                'S': S,
                'cct': cct[i],
                'duv': duv[i]
            },
            'metrics': {
                'Rf': Rf[:, i],
                'Rt': Rf_deshifted,
                'Rt_rms': Rf_deshifted_rms,
                'Rfi': Rfi[:, i],
                'Rti': Rfi_deshifted,
                'cri_type': cri_type_str
            },
            'Jab': {
                'Jabt': Jabt_i,
                'Jabr': Jabr_i,
                'DEi': DEi
            },
            'dC/C_dH_x_sig':
            np.vstack((dCoverC_x, dCoverC_x_sig, dH_x, dH_x_sig)).T,
            'fielddata': {
                'vectorfield': {
                    'axt': vfaxt,
                    'bxt': vfbxt,
                    'axr': vfaxr,
                    'bxr': vfbxr
                },
                'circlefield': {
                    'axt': cfaxt,
                    'bxt': cfbxt,
                    'axr': cfaxr,
                    'bxr': cfbxr
                }
            },
            'modeldata': {
                'pmodel': pmodel,
                'pcolorshift': pcolorshift,
                'dab_model': dab_model,
                'dab_res': dab_res,
                'dab_std': dab_std,
                'model_type': model_type,
                'fmodel': poly_model,
                'Jabtm': Jabtm,
                'Jabrm': Jabrm,
                'DEim': DEim
            },
            'vshifts': {
                'Jabshiftvector_r_to_t': np.hstack(
                    (Jt - Jr, at - ar, bt - br)),
                'vshift_ab_s': vshift_ab_s,
                'vshift_ab_s_vf': vshift_ab_s_vf,
                'vshift_ab_vf': vshift_ab_vf
            }
        }

    return out
Exemplo n.º 23
0
def apply_poly_model_at_hue_x(poly_model, pmodel, dCHoverC_res, \
                              hx = None, Cxr = 40, sig = _VF_SIG):
    """
    Applies base color shift model at (hue,chroma) coordinates
    
    Args:
        :poly_model: 
            | function handle to model
        :pmodel:
            | ndarray with model parameters.
        :dCHoverC_res:
            | ndarray with residuals between 'dCoverC,dH' of samples 
            | and 'dCoverC,dH' predicted by the model.
            | Note: dCoverC = (Ct - Cr)/Cr and dH = ht - hr 
            |      (predicted from model, see notes luxpy.cri.get_poly_model())
        :hx:
            | None or ndarray, optional
            | None defaults to np.arange(np.pi/10.0,2*np.pi,2*np.pi/10.0)
        :Cxr:
            | 40, optional
        :sig: 
            | _VF_SIG or float, optional
            | Determines smooth transition between hue-bin-boundaries (no hard 
            | cutoff at hue bin boundary).
        
    Returns:
        :returns: 
            | ndarrays with dCoverC_x, dCoverC_x_sig, dH_x, dH_x_sig
            | Note '_sig' denotes the uncertainty: 
            |     e.g.  dH_x_sig is the uncertainty of dH at input (hue/chroma).
    """

    if hx is None:
        dh = 2 * np.pi / 10.0
        hx = np.arange(
            dh / 2, 2 * np.pi, dh
        )  #hue angles at which to apply model, i.e. calculate 'average' measures

    # A calculate reference coordinates:
    axr = Cxr * np.cos(hx)
    bxr = Cxr * np.sin(hx)

    # B apply model at reference coordinates to obtain test coordinates:
    axt, bxt, Cxt, hxt, axr, bxr, Cxr, hxr = apply_poly_model_at_x(
        poly_model, pmodel, axr, bxr)

    # C Calculate dC/C, dH for test and ref at fixed hues:
    dCoverC_x = (Cxt - Cxr) / (np.hstack((Cxr + Cxt)).max())
    dH_x = (180 / np.pi) * (hxt - hxr)
    #    dCoverC_x = np.round(dCoverC_x,decimals = 2)
    #    dH_x = np.round(dH_x,decimals = 0)

    # D calculate 'average' noise measures using sig-value:
    href = dCHoverC_res[:, 0:1]
    dCoverC_res = dCHoverC_res[:, 1:2]
    dHoverC_res = dCHoverC_res[:, 2:3]
    dHsigi = np.exp((np.dstack(
        (np.abs(hx - href), np.abs((hx - href - 2 * np.pi)),
         np.abs(hx - href - 2 * np.pi))).min(axis=2)**2) / (-2) / sig)
    dH_x_sig = (180 / np.pi) * (np.sqrt(
        (dHsigi * (dHoverC_res**2)).sum(axis=0, keepdims=True) /
        dHsigi.sum(axis=0, keepdims=True)))
    #dH_x_sig_avg = np.sqrt(np.sum(dH_x_sig**2,axis=1)/hx.shape[0])
    dCoverC_x_sig = (np.sqrt(
        (dHsigi * (dCoverC_res**2)).sum(axis=0, keepdims=True) /
        dHsigi.sum(axis=0, keepdims=True)))
    #dCoverC_x_sig_avg = np.sqrt(np.sum(dCoverC_x_sig**2,axis=1)/hx.shape[0])

    return dCoverC_x, dCoverC_x_sig, dH_x, dH_x_sig
Exemplo n.º 24
0
 def __abs__(self):
     return np.sqrt(self * self)
Exemplo n.º 25
0
def get_pixel_coordinates(jab,
                          jab_ranges=None,
                          jab_deltas=None,
                          limit_grid_radius=0):
    """
    Get pixel coordinates corresponding to array of jab color coordinates.
    
    Args:
        :jab: 
            | ndarray of color coordinates
        :jab_ranges:
            | None or ndarray, optional
            | Specifies the pixelization of color space.
            |    (ndarray.shape = (3,3), with  first axis: J,a,b, and second 
                 axis: min, max, delta)
        :jab_deltas:
            | float or ndarray, optional
            | Specifies the sampling range. 
            | A float uses jab_deltas as the maximum Euclidean distance to select
            | samples around each pixel center. A ndarray of 3 deltas, uses
            | a city block sampling around each pixel center.
        :limit_grid_radius: 
            | 0, optional
            | A value of zeros keeps grid as specified by axr,bxr.
            | A value > 0 only keeps (a,b) coordinates within :limit_grid_radius: 
    
    Returns:
        :returns:
            | gridp, idxp, jabp, samplenrs, samplesIDs
            |   - :gridp: ndarray with coordinates of all pixel centers.
            |   - :idxp: list[int] with pixel index for each non-empty pixel
            |   - :jabp: ndarray with center color coordinates of non-empty pixels
            |   - :samplenrs: list[list[int]] with sample numbers belong to each 
            |                 non-empty pixel
            |   - :sampleIDs: summarizing list, 
            |                 with column order: 'idxp, jabp, samplenrs'
    """
    if jab_deltas is None:
        jab_deltas = np.array([_VF_DELTAR, _VF_DELTAR, _VF_DELTAR])
    if jab_ranges is None:
        jab_ranges = np.vstack(
            ([0, 100, jab_deltas[0]
              ], [-_VF_MAXR, _VF_MAXR + jab_deltas[1], jab_deltas[1]],
             [-_VF_MAXR, _VF_MAXR + jab_deltas[2], jab_deltas[2]]))

    # Get pixel grid:
    gridp = generate_grid(jab_ranges=jab_ranges,
                          limit_grid_radius=limit_grid_radius)

    # determine pixel coordinates of each sample in jab:
    samplesIDs = []
    for idx in range(gridp.shape[0]):

        # get pixel coordinates:
        jp = gridp[idx, 0]
        ap = gridp[idx, 1]
        bp = gridp[idx, 2]
        #Cp = np.sqrt(ap**2+bp**2)

        if type(jab_deltas) == np.ndarray:
            sampleID = np.where(
                ((np.abs(jab[..., 0] - jp) <= jab_deltas[0] / 2) &
                 (np.abs(jab[..., 1] - ap) <= jab_deltas[1] / 2) &
                 (np.abs(jab[..., 2] - bp) <= jab_deltas[2] / 2)))
        else:
            sampleID = np.where(
                (np.sqrt((jab[..., 0] - jp)**2 + (jab[..., 1] - ap)**2 +
                         (jab[..., 2] - bp)**2) <= jab_deltas / 2))

        if (sampleID[0].shape[0] > 0):
            samplesIDs.append(
                np.hstack((idx, np.array([jp, ap, bp]), sampleID[0])))

    idxp = [np.int(samplesIDs[i][0]) for i in range(len(samplesIDs))]
    jabp = np.vstack([samplesIDs[i][1:4] for i in range(len(samplesIDs))])
    samplenrs = [
        np.array(samplesIDs[i][4:], dtype=int).tolist()
        for i in range(len(samplesIDs))
    ]

    return gridp, idxp, jabp, samplenrs, samplesIDs
Exemplo n.º 26
0
def minimizebnd(fun, x0, args=(), method = 'Nelder-Mead', use_bnd = True, \
                bounds = (None,None) , options = None, \
                x0_vsize = None, x0_keys = None, **kwargs):
    """
    | Minimization function that allows for bounds on any type of method in 
    | SciPy's minimize function by transforming the parameters values 
    | (see Matlab's fminsearchbnd). 
    | Starting values, and lower and upper bounds can also be provided as a dict.
    
    Args:
        :x0: 
            | parameter starting values
            | If x0_keys is None then :x0: is vector else, :x0: is dict and
            | x0_size should be provided with length/size of values for each of 
            |  the keys in :x0: to convert it to a vector.                
        :use_bnd:
            | True, optional
            | False: omits bounds and defaults to regular minimize function.
        :bounds:
            | (lower, upper), optional
            | Tuple of lists or dicts (x0_keys is None) of lower and upper bounds 
            | for each of the parameters values.
        :kwargs: 
            | allows input for other type of arguments (e.g. in OutputFcn)
         
    Note:
        For other input arguments, see ?scipy.optimize.minimize()
         
    Returns:
        :res: 
            | dict with minimize() output. 
            | Additionally, function value, fval, of solution is also in :res:,
            | as well as a vector or dict (if x0 was dict) 
            | with final solutions (res['x'])
    """
    # Convert dict to vec:
    if isinstance(x0, dict):
        x0, vsize = vec_to_dict(dic=x0, vsize=x0_vsize, keys=x0_keys)

    if use_bnd == False:
        res = sp.optimize.minimize(fun,
                                   x0,
                                   args=args,
                                   method=method,
                                   options=options,
                                   **kwargs)
        res['fval'] = fun(res['x'], *args)
        if x0_keys is None:
            res['x_final'] = res['x']
        else:
            res['x_final'] = vec_to_dict(vec=res['x'],
                                         vsize=x0_vsize,
                                         keys=x0_keys)
        return res
    else:

        LB, UB = bounds

        # Convert dict to vec:
        if isinstance(LB, dict):
            LB, vsize = vec_to_dict(dic=LB, vsize=x0_vsize, keys=x0_keys)
        if isinstance(UB, dict):
            UB, vsize = vec_to_dict(dic=UB, vsize=x0_vsize, keys=x0_keys)

        #size checks
        xsize = x0.shape
        x0 = x0.flatten()
        n = x0.shape[0]

        if LB is None:
            LB = -np.inf * np.ones(n)
        else:
            LB = LB.flatten()

        if UB is None:
            UB = np.inf * np.ones(n)
        else:
            UB = UB.flatten()

        if (n != LB.shape[0]) | (n != UB.shape[0]):
            raise Exception(
                'minimizebnd(): x0 is incompatible in size with either LB or UB.'
            )

        #set default options if necessary
        if options is None:
            options = {}

        # stuff into a struct to pass around
        params = {}
        params['args'] = args
        params['LB'] = LB
        params['UB'] = UB
        params['fun'] = fun
        params['n'] = n
        params['OutputFcn'] = None

        #    % 0 --> unconstrained variable
        #    % 1 --> lower bound only
        #    % 2 --> upper bound only
        #    % 3 --> dual finite bounds
        #    % 4 --> fixed variable
        params['BoundClass'] = np.zeros(n)

        for i in range(n):
            k = np.isfinite(LB[i]) + 2 * np.isfinite(UB[i])
            params['BoundClass'][i] = k
            if (k == 3) & (LB[i] == UB[i]):
                params['BoundClass'][i] = 4

        # transform starting values into their unconstrained
        # surrogates. Check for infeasible starting guesses.
        x0u = x0
        k = 0
        for i in range(n):

            if params['BoundClass'][i] == 1:
                # lower bound only
                if x0[i] <= LB[i]:
                    # infeasible starting value. Use bound.
                    x0u[k] = 0
                else:
                    x0u[k] = np.sqrt(x0[i] - LB[i])

            elif params['BoundClass'][i] == 2:
                # upper bound only
                if x0[i] >= UB[i]:
                    # infeasible starting value. use bound.
                    x0u[k] = 0
                else:
                    x0u[k] = np.sqrt(UB[i] - x0[i])

            elif params['BoundClass'][i] == 3:
                # lower and upper bounds
                if x0[i] <= LB[i]:
                    # infeasible starting value
                    x0u[k] = -np.pi / 2
                elif x0[i] >= UB[i]:
                    # infeasible starting value
                    x0u[k] = np.pi / 2
                else:
                    x0u[k] = 2 * (x0[i] - LB[i]) / (UB[i] - LB[i]) - 1
                    # shift by 2*pi to avoid problems at zero in fminsearch
                    #otherwise, the initial simplex is vanishingly small
                    x0u[k] = 2 * np.pi + np.arcsin(
                        np.hstack((-1, np.hstack((1, x0u[k])).min())).max())

            elif params['BoundClass'][i] == 0:
                # unconstrained variable. x0u(i) is set.
                x0u[k] = x0[i]

            if params['BoundClass'][i] != 4:
                # increment k
                k += 1
            else:
                # fixed variable. drop it before fminsearch sees it.
                # k is not incremented for this variable.
                pass

        # if any of the unknowns were fixed, then we need to shorten x0u now.
        if k <= n:
            x0u = x0u[:k + 1]

        # were all the variables fixed?
        if x0u.shape[0] == 0:
            # All variables were fixed. quit immediately, setting the
            # appropriate parameters, then return.

            # undo the variable transformations into the original space
            x = xtransform(x0u, params)

            # final reshape
            x = x.reshape(xsize)

            # stuff fval with the final value
            fval = params['fun'](x, *params['args'])

            # minimize was not called
            output = {'success': False}

            output['x'] = x
            output['x_final'] = x
            output['iterations'] = 0
            output['funcount'] = 1
            output['algorithm'] = method
            output[
                'message'] = 'All variables were held fixed by the applied bounds'
            output['status'] = 0

            # return with no call at all to fminsearch
            return output

    # Check for an outputfcn. If there is any, then substitute my
    # own wrapper function.
    # Use a nested function as the OutputFcn wrapper
    def outfun_wrapper(x, **kwargs):
        # we need to transform x first
        xtrans = xtransform(x, params)

        # then call the user supplied OutputFcn
        stop = params['OutputFcn'](xtrans, **kwargs)

        return stop

    if 'OutputFcn' in options:
        if options['OutputFcn'] is not None:
            params['OutputFcn'] = options['OutputFcn']
            options['OutputFcn'] = outfun_wrapper

    # now we can call minimize, but with our own
    # intra-objective function.
    res = sp.optimize.minimize(intrafun,
                               x0u,
                               args=params,
                               method=method,
                               options=options)
    #[xu,fval,exitflag,output] = fminsearch(@intrafun,x0u,options,params);

    # get function value:
    fval = intrafun(res['x'], params)

    # undo the variable transformations into the original space
    x = xtransform(res['x'], params)

    # final reshape
    x = x.reshape(xsize)

    res['fval'] = fval
    res['x'] = x  #overwrite x in res to unconstrained format
    if x0_keys is None:
        res['x_final'] = res['x']
    else:
        res['x_final'] = vec_to_dict(vec=res['x'],
                                     vsize=x0_vsize,
                                     keys=x0_keys)

    return res