Esempio n. 1
0
 def mean(self):
     """
     Take mean of all spectra in SPD instance.
     """
     self.value = np.nanmean(self.value, axis=0, keepdims=True)
     self.shape = self.value.shape
     self.N = self.shape[0]
     return self
Esempio n. 2
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
Esempio n. 3
0
def subsample_RFL_set(rfl, rflpath = '', samplefcn = 'rand', S = _CIE_ILLUMINANTS['E'], \
                      jab_ranges = None, jab_deltas = None, cieobs = _VF_CIEOBS, cspace = _VF_CSPACE, \
                      ax = np.arange(-_VF_MAXR,_VF_MAXR+_VF_DELTAR,_VF_DELTAR), \
                      bx = np.arange(-_VF_MAXR,_VF_MAXR+_VF_DELTAR,_VF_DELTAR), \
                      jx = None, limit_grid_radius = 0):
    """
    Sub-samples a spectral reflectance set by pixelization of color space.
    
    Args:
        :rfl: 
            | ndarray or str
            | Array with of str referring to a set of spectral reflectance 
            |  functions to be subsampled.
            | If str to file: file must contain data as columns, with first 
            |  column the wavelengths.
        :rflpath:
            | '' or str, optional
            | Path to folder with rfl-set specified in a str :rfl: filename.
        :samplefcn:
            | 'rand' or 'mean', optional
            |   -'rand': selects a random sample from the samples within each pixel
            |   -'mean': returns the mean spectral reflectance in each pixel.
        :S: 
            | _CIE_ILLUMINANTS['E'], optional
            | Illuminant used to calculate the color coordinates of the spectral 
            |  reflectance samples.
        :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.
        :cspace:
            | _VF_CSPACE or dict, optional
            | Specifies color space. See _VF_CSPACE_EXAMPLE for example structure.
        :cieobs:
            | _VF_CIEOBS or str, optional
            | Specifies CMF set used to calculate color coordinates.
        :ax: 
            | default ndarray or user defined ndarray, optional
            | default = np.arange(-_VF_MAXR,_VF_MAXR+_VF_DELTAR,_VF_DELTAR) 
        :bx: 
            | default ndarray or user defined ndarray, optional
            | default = np.arange(-_VF_MAXR,_VF_MAXR+_VF_DELTAR,_VF_DELTAR) 
        :jx: 
            | None, optional
            | Note that not-None :jab_ranges: override :ax:, :bx: and :jx input.
        :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:
            | rflsampled, jabp
            | ndarrays with resp. the subsampled set of spectral reflectance 
            | functions and the pixel coordinate centers.
    """
    # Testing effects of sample set, pixel size and gamut size:
    if type(rfl) == str:
        rfl = pd.read_csv(os.path.join(rflpath, rfl),
                          header=None).get_values().T

    # Calculate Jab coordinates of samples:
    xyz, xyzw = spd_to_xyz(S, cieobs=cieobs, rfl=rfl.copy(), out=2)
    cspace_pars = cspace.copy()
    cspace_pars.pop('type')
    cspace_pars['xyzw'] = xyzw
    jab = colortf(xyz, tf=cspace['type'], fwtf=cspace_pars)

    # Generate grid and get samples in each grid:
    gridp, idxp, jabp, pixelsamplenrs, pixelIDs = get_pixel_coordinates(
        jab,
        jab_ranges=jab_ranges,
        jab_deltas=jab_deltas,
        limit_grid_radius=limit_grid_radius)

    # Get rfls from set using sampling function (mean or rand):
    W = rfl[:1]
    R = rfl[1:]
    rflsampled = np.zeros((len(idxp), R.shape[1]))
    rflsampled.fill(np.nan)
    for i in range(len(idxp)):
        if samplefcn == 'mean':
            rfl_i = np.nanmean(rfl[pixelsamplenrs[i], :], axis=0)
        else:
            samplenr_i = np.random.randint(len(pixelsamplenrs[i]))
            rfl_i = rfl[pixelsamplenrs[i][samplenr_i], :]
        rflsampled[i, :] = rfl_i
    rflsampled = np.vstack((W, rflsampled))
    return rflsampled, jabp
Esempio n. 4
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
Esempio n. 5
0
def spd_to_COI_ASNZS1680(S=None,
                         tf=_COI_CSPACE,
                         cieobs=_COI_CIEOBS,
                         out='COI,cct',
                         extrapolate_rfl=False):
    """
    Calculate the Cyanosis Observation Index (COI) [ASNZS 1680.2.5-1995].
    
    Args:
        :S:
            | ndarray with light source spectrum (first column are wavelengths).
        :tf:
            | _COI_CSPACE, optional
            | Color space in which to calculate the COI.
            | Default is CIELAB.
        :cieobs: 
            | _COI_CIEOBS, optional
            | CMF set to use. 
            | Default is '1931_2'.
        :out: 
            | 'COI,cct' or str, optional
            | Determines output.
        :extrapolate_rfl:
            | False, optional
            | If False: 
            |  limit the wavelength range of the source to that of the standard
            |  reflectance spectra for the 50% and 100% oxygenated blood.
            
    Returns:
        :COI:
            | ndarray with cyanosis indices for input sources.
        :cct:
            | ndarray with correlated color temperatures.
            
    Note:
        Clause 7.2 of the ASNZS 1680.2.5-1995. standard mentions the properties
        demanded of the light source used in region where visual conditions 
        suitable to the detection of cyanosis should be provided:
        
            1. The correlated color temperature (CCT) of the source should be from 
            3300 to 5300 K.
                
            2. The cyanosis observation index should not exceed 3.3

    """

    if S is None:  #use default
        S = _CIE_ILLUMINANTS['F4']

    if extrapolate_rfl == False:  # _COI_RFL do not cover the full 360-830nm range.
        wl_min = _COI_RFL_BLOOD[0].min()
        wl_max = _COI_RFL_BLOOD[0].max()
        S = S[:, np.where((S[0] >= wl_min) & (S[0] <= wl_max))[0]]

    # Calculate reference spd:
    Sr = blackbody(4000, wl3=S[0])  # same wavelength range

    # Calculate xyz of blood under test source and ref. source:
    xyzt, xyzwt = spd_to_xyz(S,
                             rfl=_COI_RFL_BLOOD,
                             relative=True,
                             cieobs=cieobs,
                             out=2)
    xyzr, xyzwr = spd_to_xyz(Sr,
                             rfl=_COI_RFL_BLOOD,
                             relative=True,
                             cieobs=cieobs,
                             out=2)

    # Calculate color difference between blood under test and ref.
    DEi = deltaE.DE_cspace(xyzt, xyzr, xyzwt=xyzwt, xyzwr=xyzwr, tf=tf)

    # Calculate Cyanosis Observation Index:
    COI = np.nanmean(DEi, axis=0)[:, None]

    # Calculate cct, if requested:
    if 'cct' in out.split(','):
        cct, duv = xyz_to_cct(xyzwt, cieobs=cieobs, out=2)

    # manage output:
    if out == 'COI':
        return COI
    elif out == 'COI,cct':
        return COI, cct
    else:
        return eval(out)
Esempio n. 6
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