Exemplo n.º 1
0
def line_intersect(a1, a2, b1, b2):
    """
    Line intersections of series of two line segments a and b. 
        
    Args:
        :a1: 
            | ndarray (.shape  = (N,2)) specifying end-point 1 of line a
        :a2: 
            | ndarray (.shape  = (N,2)) specifying end-point 2 of line a
        :b1: 
            | ndarray (.shape  = (N,2)) specifying end-point 1 of line b
        :b2: 
            | ndarray (.shape  = (N,2)) specifying end-point 2 of line b
    
    Note: 
        N is the number of line segments a and b.
    
    Returns:
        :returns: 
            | ndarray with line-intersections (.shape = (N,2))
    
    References:
        1. https://stackoverflow.com/questions/3252194/numpy-and-line-intersections
    """
    T = np.array([[0.0, -1.0], [1.0, 0.0]])
    da = np.atleast_2d(a2 - a1)
    db = np.atleast_2d(b2 - b1)
    dp = np.atleast_2d(a1 - b1)
    dap = np.dot(da, T)
    denom = np.sum(dap * db, axis=1)
    num = np.sum(dap * dp, axis=1)
    return np.atleast_2d(num / denom).T * db + b1
Exemplo n.º 2
0
def np3dT(data):  # keep last axis the same
    """
    Make a tuple, list or numpy array at least a 3d numpy array and transposed 
    first 2 axes.
    
    Args:
        :data: 
            | tuple, list, ndarray
        
    Returns:
        :returns: 
            | ndarray with .ndim >= 3 and with first two axes 
              transposed (axis=3 is kept the same).
    """
    if isinstance(
            data, np.ndarray
    ):  # assume already atleast_3d when nd.array (user has to ensure input is an array)
        if (len(data.shape) >= 3):
            return data.transpose((1, 0, 2))
        else:
            return np.expand_dims(np.atleast_2d(data), axis=0).transpose(
                (1, 0, 2))
    else:
        return np.expand_dims(np.atleast_2d(np.aray(data)), axis=0).transpose(
            (1, 0, 2))
Exemplo n.º 3
0
def v_to_cik(v, inverse=False):
    """
    Calculate 2x2 '(covariance matrix)^-1' elements cik 
    
    Args:
        :v: 
            | (Nx5) np.ndarray
            | ellipse parameters [Rmax,Rmin,xc,yc,theta]
        :inverse:
            | If True: return inverse of cik.
    
    Returns:
        :cik: 
            'Nx2x2' (covariance matrix)^-1
    
    Notes:
        | cik is not actually a covariance matrix,
        | only for a Gaussian or normal distribution!

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

    for i in range(g11.shape[0]):
        cik[i, :, :] = np.vstack((np.hstack(
            (g11[i], g12[i])), np.hstack((g12[i], g22[i]))))
        if inverse == True:
            cik[i, :, :] = np.linalg.inv(cik[i, :, :])
    return cik
Exemplo n.º 4
0
def np2dT(data):
    """
    Make a tuple, list or numpy array at least a 2D numpy array and transpose.
    
    Args:
        :data: 
            | tuple, list, ndarray
        
    Returns:
        :returns: 
            | ndarray with .ndim >= 2 and with transposed axes.
    """
    if isinstance(data, np.ndarray):# assume already atleast_2d when nd.array (user has to ensure input is an array)
        if (len(data.shape)>=2):
            return data.T   
        else:
            return np.atleast_2d(data).T
    else:
        return np.atleast_2d(np.array(data)).T
Exemplo n.º 5
0
def np3d(data):
    """
    Make a tuple, list or numpy array at least a 3d numpy array.
    
    Args:
        :data: 
            | tuple, list, ndarray
        
    Returns:
        :returns: 
            | ndarray with .ndim >= 3
    """
    if isinstance(data, np.ndarray):# assume already atleast_3d when nd.array (user has to ensure input is an array)
        if (len(data.shape)>=3):
            return data   
        else:
            return np.expand_dims(np.atleast_2d(data),axis=0)
    else:
        return np.expand_dims(np.atleast_2d(np.array(data)),axis=0)
Exemplo n.º 6
0
def rgb_to_spec_smits(rgb, intent='rfl', bitdepth=8, wlr=_WL3, rgb2spec=None):
    """
    Convert an array of RGB values to a spectrum using a Smits like conversion as implemented in Mitsuba.
    
    Args:
        :rgb: 
            | ndarray of list of rgb values
        :intent:
            | 'rfl' (or 'spd'), optional
            | type of requested spectrum conversion .
        :bitdepth:
            | 8, optional
            | bit depth of rgb values
        :wlr: 
            | _WL3, optional
            | desired wavelength (nm) range of spectrum.
        :rgb2spec:
            | None, optional
            | Dict with base spectra for white, cyan, magenta, yellow, blue, green and red for each intent.
            | If None: use _BASESPEC_SMITS.
        
    Returns:
        :spec: 
            | ndarray with spectrum or spectra (one for each rgb value, first row are the wavelengths)
    """
    if isinstance(rgb, list):
        rgb = np.atleast_2d(rgb)
    if rgb.max() > 1:
        rgb = rgb / (2**bitdepth - 1)
    if rgb2spec is None:
        rgb2spec = _BASESPEC_SMITS
    if not np.array_equal(rgb2spec['wlr'], getwlr(wlr)):
        rgb2spec = _convert_to_wlr(entries=copy.deepcopy(rgb2spec), wlr=wlr)
    spec = np.zeros((rgb.shape[0], rgb2spec['wlr'].shape[0]))
    for i in range(rgb.shape[0]):
        spec[i, :] = _fromLinearRGB(rgb[i, :],
                                    intent=intent,
                                    rgb2spec=rgb2spec,
                                    wlr=wlr)
    return np.vstack((rgb2spec['wlr'], spec))
Exemplo n.º 7
0
def Ydlep_to_xyz(Ydlep,
                 cieobs=_CIEOBS,
                 xyzw=_COLORTF_DEFAULT_WHITE_POINT,
                 flip_axes=False,
                 **kwargs):
    """
    Convert Y, dominant (complementary) wavelength and excitation purity to XYZ
    tristimulus values.

    Args:
        :Ydlep: 
            | ndarray with Y, dominant (complementary) wavelength
              and excitation purity
        :xyzw: 
            | None or narray with tristimulus values of a single (!) native white point, optional
            | None defaults to xyz of CIE D65 using the :cieobs: observer.
        :cieobs:
            | luxpy._CIEOBS, optional
            | CMF set to use when calculating spectrum locus coordinates.
        :flip_axes:
            | False, optional
            | If True: flip axis 0 and axis 1 in Ydelep to increase speed of loop in function.
            |          (single xyzw with is not flipped!)
    Returns:
        :xyz: 
            | ndarray with tristimulus values
    """

    Ydlep3 = np3d(Ydlep).copy().astype(np.float)

    # flip axis so that longest dim is on first axis  (save time in looping):
    if (Ydlep3.shape[0] < Ydlep3.shape[1]) & (flip_axes == True):
        axes12flipped = True
        Ydlep3 = Ydlep3.transpose((1, 0, 2))
    else:
        axes12flipped = False

    # convert xyzw to Yxyw:
    Yxyw = xyz_to_Yxy(xyzw)
    Yxywo = Yxyw.copy()

    # get spectrum locus Y,x,y and wavelengths:
    SL = _CMF[cieobs]['bar']
    wlsl = SL[0, None].T
    Yxysl = xyz_to_Yxy(SL[1:4].T)[:, None]

    # center on xyzw:
    Yxysl = Yxysl - Yxyw
    Yxyw = Yxyw - Yxyw

    #split:
    Y, dom, pur = asplit(Ydlep3)
    Yw, xw, yw = asplit(Yxyw)
    Ywo, xwo, ywo = asplit(Yxywo)
    Ysl, xsl, ysl = asplit(Yxysl)

    # loop over longest dim:
    x = np.empty(Y.shape)
    y = np.empty(Y.shape)
    for i in range(Ydlep3.shape[1]):

        # find closest wl's to dom:
        #wlslb,wlib = meshblock(wlsl,np.abs(dom[i,:])) #abs because dom<0--> complemtary wl
        wlib, wlslb = np.meshgrid(np.abs(dom[:, i]), wlsl)

        dwl = np.abs(wlslb - wlib)
        q1 = dwl.argmin(axis=0)  # index of closest wl
        dwl[q1] = 10000.0
        q2 = dwl.argmin(axis=0)  # index of second closest wl

        # calculate x,y of dom:
        x_dom_wl = xsl[q1, 0] + (xsl[q2, 0] - xsl[q1, 0]) * (
            np.abs(dom[:, i]) - wlsl[q1, 0]) / (wlsl[q2, 0] - wlsl[q1, 0]
                                                )  # calculate x of dom. wl
        y_dom_wl = ysl[q1, 0] + (ysl[q2, 0] - ysl[q1, 0]) * (
            np.abs(dom[:, i]) - wlsl[q1, 0]) / (wlsl[q2, 0] - wlsl[q1, 0]
                                                )  # calculate y of dom. wl

        # calculate x,y of test:
        d_wl = (x_dom_wl**2.0 +
                y_dom_wl**2.0)**0.5  # distance from white point to dom
        d = pur[:, i] * d_wl
        hdom = math.positive_arctan(x_dom_wl, y_dom_wl, htype='deg')
        x[:, i] = d * np.cos(hdom * np.pi / 180.0)
        y[:, i] = d * np.sin(hdom * np.pi / 180.0)

        # complementary:
        pc = np.where(dom[:, i] < 0.0)
        hdom[pc] = hdom[pc] - np.sign(dom[:, i][pc] -
                                      180.0) * 180.0  # get positive hue angle

        # calculate intersection of line through white point and test point and purple line:
        xy = np.vstack((x_dom_wl, y_dom_wl)).T
        xyw = np.vstack((xw, yw)).T
        xypl1 = np.vstack((xsl[0, None], ysl[0, None])).T
        xypl2 = np.vstack((xsl[-1, None], ysl[-1, None])).T
        da = (xy - xyw)
        db = (xypl2 - xypl1)
        dp = (xyw - xypl1)
        T = np.array([[0.0, -1.0], [1.0, 0.0]])
        dap = np.dot(da, T)
        denom = np.sum(dap * db, axis=1, keepdims=True)
        num = np.sum(dap * dp, axis=1, keepdims=True)
        xy_linecross = (num / denom) * db + xypl1
        d_linecross = np.atleast_2d(
            (xy_linecross[:, 0]**2.0 + xy_linecross[:, 1]**2.0)**0.5).T[:, 0]
        x[:, i][pc] = pur[:, i][pc] * d_linecross[pc] * np.cos(
            hdom[pc] * np.pi / 180)
        y[:, i][pc] = pur[:, i][pc] * d_linecross[pc] * np.sin(
            hdom[pc] * np.pi / 180)
    Yxy = np.dstack((Ydlep3[:, :, 0], x + xwo, y + ywo))
    if axes12flipped == True:
        Yxy = Yxy.transpose((1, 0, 2))
    else:
        Yxy = Yxy.transpose((0, 1, 2))
    return Yxy_to_xyz(Yxy).reshape(Ydlep.shape)
Exemplo n.º 8
0
def xyz_to_Ydlep(xyz,
                 cieobs=_CIEOBS,
                 xyzw=_COLORTF_DEFAULT_WHITE_POINT,
                 flip_axes=False,
                 **kwargs):
    """
    Convert XYZ tristimulus values to Y, dominant (complementary) wavelength
    and excitation purity.

    Args:
        :xyz:
            | ndarray with tristimulus values
        :xyzw:
            | None or ndarray with tristimulus values of a single (!) native white point, optional
            | None defaults to xyz of CIE D65 using the :cieobs: observer.
        :cieobs:
            | luxpy._CIEOBS, optional
            | CMF set to use when calculating spectrum locus coordinates.
        :flip_axes:
            | False, optional
            | If True: flip axis 0 and axis 1 in Ydelep to increase speed of loop in function.
            |          (single xyzw with is not flipped!)
    Returns:
        :Ydlep: 
            | ndarray with Y, dominant (complementary) wavelength
              and excitation purity
    """

    xyz3 = np3d(xyz).copy().astype(np.float)

    # flip axis so that shortest dim is on axis0 (save time in looping):
    if (xyz3.shape[0] < xyz3.shape[1]) & (flip_axes == True):
        axes12flipped = True
        xyz3 = xyz3.transpose((1, 0, 2))
    else:
        axes12flipped = False

    # convert xyz to Yxy:
    Yxy = xyz_to_Yxy(xyz3)
    Yxyw = xyz_to_Yxy(xyzw)

    # get spectrum locus Y,x,y and wavelengths:
    SL = _CMF[cieobs]['bar']

    wlsl = SL[0]
    Yxysl = xyz_to_Yxy(SL[1:4].T)[:, None]

    # center on xyzw:
    Yxy = Yxy - Yxyw
    Yxysl = Yxysl - Yxyw
    Yxyw = Yxyw - Yxyw

    #split:
    Y, x, y = asplit(Yxy)
    Yw, xw, yw = asplit(Yxyw)
    Ysl, xsl, ysl = asplit(Yxysl)

    # calculate hue:
    h = math.positive_arctan(x, y, htype='deg')

    hsl = math.positive_arctan(xsl, ysl, htype='deg')

    hsl_max = hsl[0]  # max hue angle at min wavelength
    hsl_min = hsl[-1]  # min hue angle at max wavelength

    dominantwavelength = np.empty(Y.shape)
    purity = np.empty(Y.shape)
    for i in range(xyz3.shape[1]):

        # find index of complementary wavelengths/hues:
        pc = np.where(
            (h[:, i] >= hsl_max) & (h[:, i] <= hsl_min + 360.0)
        )  # hue's requiring complementary wavelength (purple line)
        h[:, i][pc] = h[:, i][pc] - np.sign(
            h[:, i][pc] - 180.0
        ) * 180.0  # add/subtract 180° to get positive complementary wavelength

        # find 2 closest hues in sl:
        #hslb,hib = meshblock(hsl,h[:,i:i+1])
        hib, hslb = np.meshgrid(h[:, i:i + 1], hsl)
        dh = np.abs(hslb - hib)
        q1 = dh.argmin(axis=0)  # index of closest hue
        dh[q1] = 1000.0
        q2 = dh.argmin(axis=0)  # index of second closest hue

        dominantwavelength[:, i] = wlsl[q1] + np.divide(
            np.multiply((wlsl[q2] - wlsl[q1]),
                        (h[:, i] - hsl[q1, 0])), (hsl[q2, 0] - hsl[q1, 0])
        )  # calculate wl corresponding to h: y = y1 + (y2-y1)*(x-x1)/(x2-x1)
        dominantwavelength[:, i][pc] = -dominantwavelength[:, i][
            pc]  #complementary wavelengths are specified by '-' sign

        # calculate excitation purity:
        x_dom_wl = xsl[q1, 0] + (xsl[q2, 0] - xsl[q1, 0]) * (h[:, i] - hsl[
            q1, 0]) / (hsl[q2, 0] - hsl[q1, 0])  # calculate x of dom. wl
        y_dom_wl = ysl[q1, 0] + (ysl[q2, 0] - ysl[q1, 0]) * (h[:, i] - hsl[
            q1, 0]) / (hsl[q2, 0] - hsl[q1, 0])  # calculate y of dom. wl
        d_wl = (x_dom_wl**2.0 +
                y_dom_wl**2.0)**0.5  # distance from white point to sl
        d = (x[:, i]**2.0 +
             y[:, i]**2.0)**0.5  # distance from white point to test point
        purity[:, i] = d / d_wl

        # correct for those test points that have a complementary wavelength
        # calculate intersection of line through white point and test point and purple line:
        xy = np.vstack((x[:, i], y[:, i])).T
        xyw = np.hstack((xw, yw))
        xypl1 = np.hstack((xsl[0, None], ysl[0, None]))
        xypl2 = np.hstack((xsl[-1, None], ysl[-1, None]))
        da = (xy - xyw)
        db = (xypl2 - xypl1)
        dp = (xyw - xypl1)
        T = np.array([[0.0, -1.0], [1.0, 0.0]])
        dap = np.dot(da, T)
        denom = np.sum(dap * db, axis=1, keepdims=True)
        num = np.sum(dap * dp, axis=1, keepdims=True)
        xy_linecross = (num / denom) * db + xypl1
        d_linecross = np.atleast_2d(
            (xy_linecross[:, 0]**2.0 + xy_linecross[:, 1]**2.0)**0.5).T  #[0]
        purity[:, i][pc] = d[pc] / d_linecross[pc][:, 0]
    Ydlep = np.dstack((xyz3[:, :, 1], dominantwavelength, purity))

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

    # Interpolate efficiency functions to light source wl-range:
    if interpolate_sources is False:
        cs_cl_lrs = interpolate_efficiency_functions(El[0], cs_cl_lrs)
    else:
        El = cie_interp(El, cs_cl_lrs['WL'], kind='spd')

    # Get wavelength spacing:
    dl = getwld(El[0])

    # Separate wavelengths and data:
    wl = El[0]
    Elv = El[1:].copy()

    # define integral function:
    integral = lambda x: integrate.trapz(x, x=wl, axis=-1)
    #integral = lambda x: np.sum(x,  axis = -1)

    # Rescale El to E (if not None):
    if E is not None:

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

        # Rescale El to supplied E:
        Elv = (E / E_cv).T * Elv

    # Sum all sources in array if requested:
    if sum_sources == True:
        Elv = Elv.sum(axis=0, keepdims=True)

    # Calculate Circadian light using model param. and spectral data:
    CLa = fCLa(wl, Elv, integral, **cs_cl_lrs)

    # Calculate Circadian stimulus:
    CS = 0.7 * (1 - (1 / (1 + (CLa / 355.7)**1.1026)))

    return CS, CLa
Exemplo n.º 10
0
def box_m(*X, ni=None, verbosity=0):
    """
    Perform Box's M test (p>=2) to check equality of covariance matrices or Bartlett's test (p==1) for equality of variances.
    
    Args:
        :X: 
            | A number  (k groups) or list of 2d-ndarrays (rows: samples, cols: variables) with data.
            | or a number of 2d-ndarrays with covariance matrices (supply ni!)
        :ni:
            | None, optional
            | If None: X contains data, else, X contains covariance matrices.
        :verbosity: 
            | 0, optional
            | If 1: print results.
    
    Returns:
        :statistic:
            | F or chi2 value (see len(dfs))
        :pval:
            | p-value
        :df:
            | degrees of freedom.
            | if len(dfs) == 2: F-test was used.
            | if len(dfs) == 1: chi2 approx. was used.
    
    Notes:
        1. If p==1: Reduces to Bartlett's test for equal variances.
        2. If (ni>20).all() & (p<6) & (k<6): then a more appropriate chi2 test is used in a some cases.
    """

    k = len(X)  # groups
    p = np.atleast_2d(X[0]).shape[1]  # variables
    if p == 1:  # for p == 1: only variance!
        det = lambda x: np.array(x)
    else:
        det = lambda x: np.linalg.det(x)
    if ni is None:  # samples in each group
        ni = np.array([Xi.shape[0] for Xi in X])
        Si = np.array([np.cov(Xi.T) for Xi in X])
        if p == 1:
            Si = np.atleast_2d(Si).T
    else:
        Si = np.array([Xi for Xi in X])  # input are already cov matrices!
        ni = np.array(ni)
        if ni.shape[0] == 1:
            ni = ni * np.ones((k, ))

    N = ni.sum()
    S = np.array([(ni[i] - 1) * Si[i]
                  for i in range(len(ni))]).sum(axis=0) / (N - k)

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

    if (A2 - A1**2) > 0:
        v2 = (v1 + 2) / (A2 - A1**2)
        b = v1 / (1 - A1 - (v1 / v2))
        Fv1v2 = M / b
        statistic = Fv1v2
        pval = 1.0 - sp.stats.f.cdf(Fv1v2, v1, v2)
        dfs = [v1, v2]

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

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

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

    return statistic, pval, dfs
Exemplo n.º 11
0
def hue_quadrature(h, unique_hue_data=None):
    """
    Get hue quadrature H from hue h.
    
    Args:
        :h: 
            | float or ndarray [(N,) or (N,1)] with hue data in degrees (!).
        :unique_hue data:
            | None or dict, optional
            |   - None: defaults to:
            |         {'hues': 'red yellow green blue red'.split(), 
            |        'i': np.arange(5.0), 
            |        'hi':[20.14, 90.0, 164.25,237.53,380.14],
            |        'ei':[0.8,0.7,1.0,1.2,0.8],
            |        'Hi':[0.0,100.0,200.0,300.0,400.0]}
            |   - dict: user specified unique hue data  
            |           (same structure as above)
    
    Returns:
        :H: 
            | ndarray of Hue quadrature value(s).
    """
    if unique_hue_data is None:
        unique_hue_data = {
            'hues': 'red yellow green blue red'.split(),
            'i': np.arange(5.0),
            'hi': [20.14, 90.0, 164.25, 237.53, 380.14],
            'ei': [0.8, 0.7, 1.0, 1.2, 0.8],
            'Hi': [0.0, 100.0, 200.0, 300.0, 400.0]
        }

    changed_number_to_array = False
    if isinstance(h, float) | isinstance(h, int):
        h = np.atleast_1d(h)
        changed_number_to_array = True

    squeezed = False
    if h.ndim > 1:
        if (h.shape[0] == 1):
            h = np.squeeze(h, axis=0)
            squeezed = True

    hi = unique_hue_data['hi']
    Hi = unique_hue_data['Hi']
    ei = unique_hue_data['ei']
    h[h < hi[0]] += 360.0
    h_tmp = np.atleast_2d(h)
    if h_tmp.shape[0] == 1:
        h_tmp = h_tmp.T
    h_hi = np.repeat(h_tmp, repeats=len(hi), axis=1)
    hi_h = np.repeat(np.atleast_2d(hi), repeats=h.shape[0], axis=0)
    d = (h_hi - hi_h)
    d[d < 0] = 1000.0
    p = d.argmin(axis=1)
    p[p == (len(hi) - 1)] = 0  # make sure last unique hue data is not selected
    H = np.array([
        Hi[pi] + (100.0 * (h[i] - hi[pi]) / ei[pi]) /
        ((h[i] - hi[pi]) / ei[pi] + (hi[pi + 1] - h[i]) / ei[pi + 1])
        for (i, pi) in enumerate(p)
    ])
    if changed_number_to_array:
        H = H[0]
    if squeezed:
        H = np.expand_dims(H, axis=0)
    return H
Exemplo n.º 12
0
def get_macadam_ellipse(xy=None, k_neighbours=3, nsteps=10, average_cik=True):
    """
    Estimate n-step MacAdam ellipse at CIE x,y coordinates xy by calculating 
    average inverse covariance ellipse of the k_neighbours closest ellipses.
    
    Args:
        :xy:
            | None or ndarray, optional
            | If None: output Macadam ellipses, if not None: xy are the 
            | CIE xy coordinates for which ellipses will be estimated.
            
        :k_neighbours:
            | 3, optional
            | Number of nearest ellipses to use to calculate ellipse at xy
        :nsteps:
            | 10, optional
            | Set number of MacAdam steps of ellipse.
        :average_cik:
            | True, optional
            | If True: take distance weighted average of inverse 
            |   'covariance ellipse' elements cik. 
            | If False: average major & minor axis lengths and 
            |   ellipse orientation angles directly.
            
    Returns:
        :v_mac_est:
            | estimated MacAdam ellipse(s) in v-format [Rmax,Rmin,xc,yc,theta]
            
    """
    # list of MacAdam ellipses (x10)
    v_mac = np.atleast_2d([[0.16, 0.057, 0.0085, 0.0035, 62.5],
                           [0.187, 0.118, 0.022, 0.0055, 77],
                           [0.253, 0.125, 0.025, 0.005, 55.5],
                           [0.15, 0.68, 0.096, 0.023, 105],
                           [0.131, 0.521, 0.047, 0.02, 112.5],
                           [0.212, 0.55, 0.058, 0.023, 100],
                           [0.258, 0.45, 0.05, 0.02, 92],
                           [0.152, 0.365, 0.038, 0.019, 110],
                           [0.28, 0.385, 0.04, 0.015, 75.5],
                           [0.38, 0.498, 0.044, 0.012, 70],
                           [0.16, 0.2, 0.021, 0.0095, 104],
                           [0.228, 0.25, 0.031, 0.009, 72],
                           [0.305, 0.323, 0.023, 0.009, 58],
                           [0.385, 0.393, 0.038, 0.016, 65.5],
                           [0.472, 0.399, 0.032, 0.014, 51],
                           [0.527, 0.35, 0.026, 0.013, 20],
                           [0.475, 0.3, 0.029, 0.011, 28.5],
                           [0.51, 0.236, 0.024, 0.012, 29.5],
                           [0.596, 0.283, 0.026, 0.013, 13],
                           [0.344, 0.284, 0.023, 0.009, 60],
                           [0.39, 0.237, 0.025, 0.01, 47],
                           [0.441, 0.198, 0.028, 0.0095, 34.5],
                           [0.278, 0.223, 0.024, 0.0055, 57.5],
                           [0.3, 0.163, 0.029, 0.006, 54],
                           [0.365, 0.153, 0.036, 0.0095, 40]])

    # convert to v-format ([a,b, xc, yc, theta]):
    v_mac = v_mac[:, [2, 3, 0, 1, 4]]

    # convert last column to rad.:
    v_mac[:, -1] = v_mac[:, -1] * np.pi / 180

    # convert to desired number of MacAdam-steps:
    v_mac[:, 0:2] = v_mac[:, 0:2] / 10 * nsteps

    if xy is not None:
        #calculate inverse covariance matrices:
        cik = math.v_to_cik(v_mac, inverse=True)
        if average_cik == True:
            cik_long = np.hstack((cik[:, 0, :], cik[:, 1, :]))

        # Calculate k_neighbours closest ellipses to xy:
        tree = cKDTree(v_mac[:, 2:4], copy_data=True)
        d, inds = tree.query(xy, k=k_neighbours)

        if k_neighbours > 1:
            pd = 1
            w = (1.0 / np.abs(d)**pd)[:, :, None]  # inverse distance weigthing
            if average_cik == True:
                cik_long_est = np.sum(w * cik_long[inds, :], axis=1) / np.sum(
                    w, axis=1)
            else:
                v_mac_est = np.sum(w * v_mac[inds, :], axis=1) / np.sum(
                    w, axis=1)  # for average xyc

        else:
            v_mac_est = v_mac[inds, :].copy()

        # convert cik back to v:
        if (average_cik == True) & (k_neighbours > 1):
            cik_est = np.dstack((cik_long_est[:, 0:2], cik_long_est[:, 2:4]))
            v_mac_est = math.cik_to_v(cik_est, inverse=True)
        v_mac_est[:, 2:4] = xy
    else:
        v_mac_est = v_mac

    return v_mac_est
Exemplo n.º 13
0
def selection(P, O, options):
    """
    Selects the next population.
    Each parent is compared to its offspring. If the parent dominates its 
    child, then it goes to the next population. If the offspring dominates 
    the parent, that new member is added. However, if they are incomparable
    (there is no mutual domination), them both are sent to the next 
    population. After that, the new set of individuals must be truncated to 
    mu, wherein mu is the original number of points.
    This is accomplished by the use of "non-dominated sorting", that is,
    ranks the individual in fronts of non-domination, and within each
    front, measures them by using crowding distance. With regard to these
    two metrics, the best individuals are kept in the new population.

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

   Returns:
      :Pnew: 
          | the new population (a dict with x and f)
   """

    # ------ First part: checks dominance between parents and offspring
    # Verifies whether parent dominates offspring:
    aux1 = (P['f'] <= O['f']).all(axis=0)
    aux2 = (P['f'] < O['f']).any(axis=0)
    auxp = np.logical_and(aux1, aux2)  #P dominates O

    # Now, where offspring dominates parent:
    aux1 = (P['f'] >= O['f']).all(axis=0)
    aux2 = (P['f'] > O['f']).any(axis=0)
    auxo = np.logical_and(aux1, aux2)  #O dominates P
    auxpo = np.logical_and(~auxp, ~auxo)
    #P and O are incomparable

    # New population (where P dominates O, O dominates P and where they are
    # incomparable)
    R = {
        'f':
        np.hstack((P['f'][:, auxp].copy(), O['f'][:, auxo].copy(),
                   P['f'][:, auxpo].copy(), O['f'][:, auxpo].copy()))
    }
    R['x'] = np.hstack((P['x'][:, auxp].copy(), O['x'][:, auxo].copy(),
                        P['x'][:, auxpo].copy(), O['x'][:, auxpo].copy()))

    # ------- Second part: non-dominated sorting
    Pnew = {'x': np.atleast_2d([])}
    Pnew['f'] = np.atleast_2d([])  #prepares the new population
    while True:
        ispar = ndset(R['f'])  #gets the non-dominated front

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

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

    # ------- Third part: truncates using crowding distance
    # If the remaining front has the exact number of points to fill the original
    # size, then just include them. If it has too many, remove some according to
    # the crowding distance (notice it cannot have too few!)
    aux = (Pnew['f'].shape[1] +
           Frem.shape[1]) - options['mu']  #remaining points to fill
    if aux == 0:
        Pnew['x'] = np.hstack((Pnew['x'], Xrem.copy()))
        Pnew['f'] = np.hstack((Pnew['f'], Frem.copy()))
    elif aux > 0:
        for ii in range(aux):
            cdist = crowdingdistance(Frem)
            imin = cdist.argmin(
            )  #gets the point with smaller crowding distance
            Frem = np.delete(Frem, imin,
                             axis=1)  # Frem(:,imin) = []; #and remove it
            Xrem = np.delete(Xrem, imin, axis=1)  # Xrem(:,imin) = [];
        Pnew['x'] = np.hstack(
            (Pnew['x'], Xrem.copy())) if Pnew['x'].size else Xrem.copy()
        Pnew['f'] = np.hstack(
            (Pnew['f'], Frem.copy())) if Pnew['f'].size else Frem.copy()
    else:  #if there are too few points... well, we're doomed!
        raise Exception('Run to the hills! This is not supposed to happen!')

    return Pnew