Ejemplo n.º 1
0
def _get_daylightphase_Mi_values(xD,yD, Mcoeffs = None, cieobs = None, S012_daylightphase = None):
    """
    Get daylight phase coefficients M1, M2 following Judd et al. (1964)
    
    Args:
        :xD,yD:
            | ndarray of x-coordinates, ndarray of y-coordinates of daylight phase
        :Mcoeffs:
            | Coefficients in M1 & M2 weights for specific cieobs.
            | If None and cieobs is not None: they will be calculated.
        :cieobs:
            | CMF set to use when calculating coefficients in M1, M2 weights.
            | If None: Mcoeffs must be supplied.
        :S012_daylightphase: 
            | ndarray with CIE S0, S1, S2 daylight phase component functions
    
    Returns:
        :M1,M2:
            | daylight phase coefficients M1, M2
        
    Reference:
        1. `Judd et al.(1964). Spectral Distribution of Typical Daylight as a 
        Function of Correlated Color Temperature. 
        JOSA A, 54(8), pp. 1031-1040 (1964) <https://doi.org/10.1364/JOSA.54.001031>`_
    """
    if (Mcoeffs is None) & (cieobs is  None):
        raise Exception("_get_daylightphase_Mi_values(): Mcoeffs and cieobs can't both be None")
    if (Mcoeffs is None) & isinstance(cieobs,str): # use pre-calculated coeffs.
        Mcoeffs = _DAYLIGHT_M12_COEFFS[cieobs]
    if isinstance(Mcoeffs,str) & (cieobs is not None): # calculate coeffs.
        if (Mcoeffs == 'calc'):
            Mcoeffs = get_daylightphase_Mi_coeffs(cieobs = cieobs, S012_daylightphase = S012_daylightphase)
            Mcoeffs = Mcoeffs[cieobs] if isinstance(cieobs,str) else Mcoeffs['cmf_0']
        else:
            raise Exception("Mcoeffs is a string, but not 'calc': unknown option.")
    if Mcoeffs is not None:
        c = Mcoeffs

    # Calculate M1, M2 and round to 3 decimals (CIE recommendation):
    denom = c['i'] + c['j']*xD + c['k']*yD
    rr = 3 # rounding of Mi
    M1 = np.round((c['i1'] + c['j1']*xD + c['k1']*yD) / denom, rr)
    M2 = np.round((c['i2'] + c['j2']*xD + c['k2']*yD) / denom, rr)
    
    # denom = (xyz[2,0]*xyz[1,1] - xyz[1,0]*xyz[2,1]) + (xyz[2,1]*S[1] - xyz[1,1]*S[2])*xD + (xyz[1,0]*S[2] - xyz[2,0]*S[1])*yD
    # M1 = np.round(((xyz[0,0]*xyz[2,1] - xyz[2,0]*xyz[0,1]) + (xyz[0,1]*S[2] - xyz[2,1]*S[0])*xD + (xyz[2,0]*S[0] - xyz[0,0]*S[2])*yD) / denom, 3)
    # M2 = np.round(((xyz[1,0]*xyz[0,1] - xyz[0,0]*xyz[1,1]) + (xyz[1,1]*S[0] - xyz[0,1]*S[1])*xD + (xyz[0,0]*S[1] - xyz[1,0]*S[0])*yD) / denom, 3)
              
    return M1, M2, c 
Ejemplo n.º 2
0
def xyz_to_rgb(xyz,N,tr, xyz_black, tr_type = 'lut'): 
    """
    Convert xyz to input rgb. 
    
    Args:
        :xyz:
            | ndarray [Nx3] with XYZ tristimulus values 
        :N:
            | xyz to linear rgb conversion matrix
        :tr:
            | Tone Response function parameters or lut
        :xyz_black:
            | ndarray with XYZ tristimulus values of black
        :tr_type:
            | 'lut', optional
            | Type of Tone Response in tr input argument
            | options:
            |  - 'lut': Tone-Response as a look-up-table
            |  - 'gog': Tone-Response as a gain-offset-gamma function
            
    Returns:
        :rgb:
            | ndarray [Nx3] of display RGB values
    """
    rgblin = _clamp0(np.dot(N,(xyz - xyz_black).T).T) # calculate rgblin and clamp to zero (on separate line for speed)
    return np.round(_rgb_delinearizer(rgblin,tr, tr_type = tr_type)) # delinearize rgblin
Ejemplo n.º 3
0
def _get_daylightlocus_parameters(ccts, spds, cieobs):
    """
    Get daylight locus parameters for a single cieobs from daylight phase spectra
    determined based on parameters for '1931_2' as reported in CIE15-20xx.
    """
    
    #------------------------------------------------
    # Get locus parameters:
    #======================
    # get xy coordinates for new cieobs:
    xyz_ = spd_to_xyz(spds, cieobs = cieobs)
    xy = xyz_[...,:2]/xyz_.sum(axis=-1,keepdims=True)
    
    # Fit 3e order polynomal xD(1/T) [4000 K < T <= 7000 K]:
    l7 = ccts<7000
    pxT_l7 = np.polyfit((1000/ccts[l7]), xy[l7,0],3)  
    
    # Fit 3e order polynomal xD(1/T) [T > 7000 K]:
    L7 = ccts>=7000
    pxT_L7 = np.polyfit((1000/ccts[L7]), xy[L7,0],3)  
    
    # Fit 2nd order polynomal yD(xD):
    pxy = np.round(np.polyfit(xy[:,0],xy[:,1],2),3)
    #pxy = np.hstack((0,pxy)) # make also 3e order for easy stacking
        
    return (xy, pxy, pxT_l7, pxT_L7, l7, L7)
def getUSCensusAgeDist():
    """
    Get US Census Age Distribution
    """
    t_num = _INDVCMF_DATA['USCensus2010population']

    list_AgeCensus = t_num[0]
    freq_AgeCensus = np.round(
        t_num[1] / 1000
    )  # Reduce # of populations to manageable number, this doesn't change probability

    # Remove age < 10 and 70 < age:
    freq_AgeCensus[:10] = 0
    freq_AgeCensus[71:] = 0

    list_Age = []
    for k in range(len(list_AgeCensus)):
        list_Age = np.hstack(
            (list_Age, np.repeat(list_AgeCensus[k], freq_AgeCensus[k])))

    return list_Age
Ejemplo n.º 5
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)
Ejemplo n.º 6
0
    #--------------------------------------------------------------------------
    # read calibration data:
    #--------------------------------------------------------------------------
    xyzcal = pd.read_csv(_PATH_DATA + 'XYZcal.csv',sep=',', header=None).values # read measured xyz data 
    rgbcal = pd.read_csv(_PATH_DATA + 'RGBcal.csv',sep=',', header=None).values # read rgb data

   
    #--------------------------------------------------------------------------
    # Apply functions as an example:
    #--------------------------------------------------------------------------
    print('\nFunctional example:')
    
    # Get calibration gog-parameters/lut and conversion matrices M, N as well as xyz-black:
    M, N, tr, xyz_black, xyz_white = calibrate(rgbcal, xyzcal, L_type = L_type, tr_type = tr_type, avg = avg, cspace = cspace) # get calibration parameters
    if tr_type == 'gog':
        print("Calibration parameters :\nTR(gamma,offset,gain)=\n", np.round(tr,5),'\nM=\n',np.round(M,5),'\nN=\n',np.round(N,5))
    
    # Check calibration performance:
    DElabi,DEli, DEabi = calibration_performance(rgbcal, xyzcal, M, N, tr, xyz_black, xyz_white, 
                                                 cspace=cspace, tr_type = tr_type, avg = avg, 
                                                 verbosity = 1, is_verification_data = False) # calculate calibration performance in cspace='lab'
    
    
    # define a test xyz for converion to rgb:
    xyz_test = np.array([[100.0,100.0,100.0]])*0.5 
    print('\nTest calibration for user defined xyz:\n    xyz_test_est:', xyz_test) # print chosen test xyz 
    
    # for a test xyz calculate the estimated rgb:
    rgb_test_est = xyz_to_rgb(xyz_test, N, tr, xyz_black, tr_type = tr_type) 
    print('    rgb_test_est:', rgb_test_est) # print estimated rgb 
    
def genMonteCarloObs(n_obs=1,
                     fieldsize=10,
                     list_Age=[32],
                     out='LMS',
                     wl=None,
                     allow_negative_values=False):
    """
    Monte-Carlo generation of individual observer cone fundamentals.
    
    Args: 
        :n_obs: 
            | 1, optional
            | Number of observer CMFs to generate.
        :list_Age:
            | list of observer ages or str, optional
            | Defaults to 32 (cfr. CIE2006 CMFs)
            | If 'us_census': use US population census of 2010 
              to generate list_Age.
        :fieldsize: 
            | fieldsize in degrees (between 2° and 10°), optional
            | Defaults to 10°.
        :out: 
            | 'LMS' or str, optional
            | Determines output.
        :wl: 
            | None, optional
            | Interpolation/extraplation of :LMS: output to specified wavelengths.
            | None: output original _WL = np.array([390,780,5])
        :allow_negative_values: 
            | False, optional
            | Cone fundamentals or color matching functions 
            |   should not have negative values.
            |     If False: X[X<0] = 0.
    
    Returns:
        :returns: 
            | LMS [,var_age, vAll] 
            |   - LMS: ndarray with population LMS functions.
            |   - var_age: ndarray with population observer ages.
            |   - vAll: dict with population physiological factors (see .keys()) 
            
    References:
         1. `Asano Y, Fairchild MD, and Blondé L (2016). 
         Individual Colorimetric Observer Model. 
         PLoS One 11, 1–19. 
         <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0145671>`_
         
         2. `Asano Y, Fairchild MD, Blondé L, and Morvan P (2016). 
         Color matching experiment for highlighting interobserver variability. 
         Color Res. Appl. 41, 530–539. 
         <https://onlinelibrary.wiley.com/doi/abs/10.1002/col.21975>`_
         
         3. `CIE, and CIE (2006). 
         Fundamental Chromaticity Diagram with Physiological Axes - Part I 
         (Vienna: CIE). 
         <http://www.cie.co.at/publications/fundamental-chromaticity-diagram-physiological-axes-part-1>`_ 
         
         4. `Asano's Individual Colorimetric Observer Model 
         <https://www.rit.edu/cos/colorscience/re_AsanoObserverFunctions.php>`_
    """

    # Scale down StdDev by scalars optimized using Asano's 75 observers
    # collected in Germany:
    stdDevAllParam = _INDVCMF_STD_DEV_ALL_PARAM.copy()
    scale_factors = [0.98, 0.98, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5]
    scale_factors = dict(zip(list(stdDevAllParam.keys()), scale_factors))
    stdDevAllParam = {
        k: v * scale_factors[k]
        for (k, v) in stdDevAllParam.items()
    }

    # Get Normally-distributed Physiological Factors:
    vAll = getMonteCarloParam(n_obs=n_obs)

    if list_Age is 'us_census':
        list_Age = getUSCensusAgeDist()

    # Generate Random Ages with the same probability density distribution
    # as color matching experiment:
    sz_interval = 1
    list_AgeRound = np.round(np.array(list_Age) / sz_interval) * sz_interval
    h = math.histogram(list_AgeRound,
                       bins=np.unique(list_AgeRound),
                       bin_center=True)[0]
    p = h / h.sum()  # probability density distribution

    var_age = np.random.choice(np.unique(list_AgeRound), \
                               size = n_obs, replace = True,\
                               p = p)

    # Set requested wavelength range:
    if wl is not None:
        wl = getwlr(wl3=wl)
    else:
        wl = _WL

    LMS_All = np.zeros((3 + 1, wl.shape[0], n_obs))
    LMS_All.fill(np.nan)
    for k in range(n_obs):
        t_LMS, t_trans_lens, t_trans_macula, t_sens_photopig = cie2006cmfsEx(age = var_age[k], fieldsize = fieldsize, wl = wl,\
                                                                          var_od_lens = vAll['od_lens'][k], var_od_macula = vAll['od_macula'][k], \
                                                                          var_od_L = vAll['od_L'][k], var_od_M = vAll['od_M'][k], var_od_S = vAll['od_S'][k],\
                                                                          var_shft_L = vAll['shft_L'][k], var_shft_M = vAll['shft_M'][k], var_shft_S = vAll['shft_S'][k],\
                                                                          out = 'LMS,trans_lens,trans_macula,sens_photopig')
        LMS_All[:, :, k] = t_LMS


#        listout = out.split(',')
#        if ('trans_lens' in listout) | ('trans_macula' in listout) | ('trans_photopig' in listout):
#            trans_lens[:,k] = t_trans_lens
#            trans_macula[:,k] = t_trans_macula
#            sens_photopig[:,:,k] = t_sens_photopig

    if n_obs == 1:
        LMS_All = np.squeeze(LMS_All, axis=2)

    if ('xyz' in out.lower().split(',')):
        LMS_All = lmsb_to_xyzb(LMS_All,
                               fieldsize,
                               out='xyz',
                               allow_negative_values=allow_negative_values)
        out = out.replace('xyz', 'LMS').replace('XYZ', 'LMS')
    if ('lms' in out.lower().split(',')):
        out = out.replace('lms', 'LMS')

    if (out == 'LMS'):
        return LMS_All
    elif (out == 'LMS,var_age,vAll'):
        return LMS_All, var_age, vAll
    else:
        return eval(out)
Ejemplo n.º 8
0
def get_daylightphase_Mi_coeffs(cieobs = None, wl3 = None, S012_daylightphase = None):
    """
    Get coefficients of Mi weights of daylight phase for specific cieobs
    
    Args:
        :cieobs:
            | None or str or ndarray or list of str or list of ndarrays, optional
            | CMF set to get coefficients for.
            | If None: get coeffs for all CMFs in _CMF
        :wl3:
            | None, optional
            | Wavelength range to interpolate S012_daylightphase to.
        :S012_daylightphase:
            | None, optional
            | Daylight phase component functions.
            | If None: use _S012_DAYLIGHTPHASE
    
    Returns:
        :Mcoeffs:
            | Dictionary with i,j,k,i1,j1,k1,i2,j2,k2 for each cieobs in :cieobs:
            | If cieobs contains ndarrays, then keys in dict will be 
            | labeled 'cmf_0', 'cmf_1', ...
    """
    #-------------------------------------------------
    # Get Mi coefficients:
    #=====================
    # Get tristimulus values of daylight phase component functions:
    if S012_daylightphase is None:
        S012_daylightphase = _S012_DAYLIGHTPHASE
    if wl3 is not None:
        S012_daylightphase = cie_interp(S012_daylightphase,wl_new = wl3, kind='linear',negative_values_allowed = True)
    
    if cieobs is None: cieobs = _CMF['types']
    if not isinstance(cieobs, list):
        cieobs = [cieobs]
    Mcoeffs = {}
    i = 0
    for cieobs_ in cieobs:
        if isinstance(cieobs_,str):
            if 'scotopic' in cieobs_:
                continue
            if 'std_dev_obs' in cieobs_:
                continue
            key = cieobs_
        else:
            key = 'cmf_{:1.0f}'.format(i)
        
        xyz = spd_to_xyz(S012_daylightphase, cieobs = cieobs_, relative = False, K = 1)
        S = xyz.sum(axis=-1)
    
        # Get coefficients in Mi:
        f = 1000/S[0]**2
        r = 4 # rounding of i,j,k,i1,j1,k1,i2,j2,k2
        c = {'i': np.round(f*(xyz[2,0]*xyz[1,1] - xyz[1,0]*xyz[2,1]),r)}
        c['j'] = np.round(f*(xyz[2,1]*S[1] - xyz[1,1]*S[2]),r)
        c['k'] = np.round(f*(xyz[1,0]*S[2] - xyz[2,0]*S[1]),r)
        c['i1'] = np.round(f*(xyz[0,0]*xyz[2,1] - xyz[2,0]*xyz[0,1]),r)
        c['j1'] = np.round(f*(xyz[0,1]*S[2] - xyz[2,1]*S[0]),r)
        c['k1'] = np.round(f*(xyz[2,0]*S[0] - xyz[0,0]*S[2]),r)
        c['i2'] = np.round(f*(xyz[1,0]*xyz[0,1] - xyz[0,0]*xyz[1,1]),r)
        c['j2'] = np.round(f*(xyz[1,1]*S[0] - xyz[0,1]*S[1]),r)
        c['k2']= np.round(f*(xyz[0,0]*S[1] - xyz[1,0]*S[0]),r)
        Mcoeffs[key] = c
        i+=1
    return Mcoeffs