Example #1
0
def _plot_tm30_report_top(axh, source = '', manufacturer = '', date = '', model = ''):
    """
    Print source name, source model, manufacturer and date in an empty axes.

    Args:
        :axh: 
            | Plot on specified axes. 
        :source:
            | string with source name.
        :manufacturer:
            | string with source manufacturer.
        :model:
            | string with source model.
        :date:
            | string with source measurement date.

    Returns:
        :axh:
            | handle to figure axes. 
    """
    axh.set_xticks(np.arange(10))
    axh.set_xticklabels(['' for i in np.arange(10)])
    axh.set_yticks(np.arange(2))
    axh.set_yticklabels(['' for i in np.arange(4)])
    axh.set_axis_off()
    axh.set_xlabel([])
    
    axh.text(0,1, 'Source: ' + source, fontsize = 10, horizontalalignment='left',verticalalignment='center',color = 'k')
    axh.text(0,0, '   Date: ' + date, fontsize = 10, horizontalalignment='left',verticalalignment='center',color = 'k')
    axh.text(5,1, 'Manufacturer: ' + manufacturer, fontsize = 10, horizontalalignment='left',verticalalignment='center',color = 'k')
    axh.text(5,0, 'Model: ' + model, fontsize = 10, horizontalalignment='left',verticalalignment='center',color = 'k')
    return axh
Example #2
0
 def get_subset(self, idx_R=None, idx_S=None):
     """
     Get spectral data related to specific light source and reflectance data
     | (cfr. axis = 1 and axis = 0 in xyz ndarrays).
     
     Args:
         :idx_S: 
             | None, optional
             | Index of light source related spectral data.
             | None: selects all.
         :idx_R:
             | None, optional
             | Index of reflectance sample related spectral data.
             | None selects all.
     Returns:
         :returns:
             | luxpy.CDATA instance with only selected spectral data.
         
     Note: 
         If ndim < 3: selection is based on :idx_R:
     """
     if idx_S is None:
         idx_S = np.arange(self.value.shape[1])
     if idx_R is None:
         idx_R = np.arange(self.value.shape[0])
     if self.value.ndim == 3:
         self.value = self.value[idx_R, idx_S, :]
     else:
         self.value = self.value[idx_R, ...]
     self.shape = self.value.shape
     return self
Example #3
0
def 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), \
                          make_grid = True, limit_grid_radius = 0,color = 'k'):
    """
    Generates a field of vectors using the base color shift model.
    
    | Has the option to plot vector field.
    
    Args:
        :poly_model: 
            | function handle to model
        :pmodel:
            | ndarray with model parameters.
        :axr: 
            | np.arange(-_VF_MAXR,_VF_MAXR+_VF_DELTAR,_VF_DELTAR), optional
            | Ndarray specifying the a-coordinates at which to apply the model.
        :bxr:
            | np.arange(-_VF_MAXR,_VF_MAXR+_VF_DELTAR,_VF_DELTAR), optional
            | Ndarray specifying the b-coordinates at which to apply the model.
        :make_grid:
            | True, optional
            | True: generate a 2d-grid from :axr:, :bxr:.
        :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:
        :color:
            | 'k', optional
            | For plotting the vector field.
            | If :color: == 0, no plot will be generated.
    
    Returns:
        :returns: 
            | If :color: == 0: ndarray of axt,bxt,axr,bxr
            | Else: handle to axes used for plotting.
    """

    # Generate grid from axr, bxr:
    if make_grid == True:
        axr, bxr = generate_grid(ax=axr,
                                 bx=bxr,
                                 out='ax,bx',
                                 limit_grid_radius=limit_grid_radius)

    # Apply model at ref. coordinates:
    axt, bxt, Cxt, hxt, axr, bxr, Cxr, hxr = apply_poly_model_at_x(
        poly_model, pmodel, axr, bxr)

    # Plot vectorfield:
    if color is not 0:
        #plt.plot(axr, bxr,'ro',markersize=2)
        plt.quiver(axr, bxr, axt - axr, bxt - bxr, headlength=1, color=color)
        plt.xlabel("a'")
        plt.ylabel("b'")
        return plt.gca()  #plt.show(plot1)
    else:
        return axt, bxt, axr, bxr
Example #4
0
def _plot_tm30_report_bottom(axh, spd, notes = '', max_len_notes_line = 40):
    """
    Print some notes, the CIE x, y, u',v' and Ra, R9 values of the source in some empty axes.
    
    Args:
        :axh: 
            | None, optional
            | Plot on specified axes. 
        :spd:
            | ndarray or dict
            | If ndarray: single spectral power distribution.
        :notes:
            | string to be split
        :max_len_notes_line:
            | 40, optional
            | Maximum length of a single line when splitting the string.
        
    Returns:
        :axh:
            | handle to figure axes.    
    """
    ciera = spd_to_cri(spd, cri_type = 'ciera')
    cierai = spd_to_cri(spd, cri_type = 'ciera-14', out = 'Rfi')
    xyzw = spd_to_xyz(spd, cieobs = '1931_2', relative = True)
    Yxyw = xyz_to_Yxy(xyzw)
    Yuvw = xyz_to_Yuv(xyzw)
    
    notes_ = _split_notes(notes, max_len_notes_line = max_len_notes_line)

    axh.set_xticks(np.arange(10))
    axh.set_xticklabels(['' for i in np.arange(10)])
    axh.set_yticks(np.arange(4))
    axh.set_yticklabels(['' for i in np.arange(4)])
    axh.set_axis_off()
    axh.set_xlabel([])
    
    axh.text(0,2.8, 'Notes: ', fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k')
    axh.text(0.75,2.8,  notes_, fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k')
    axh.text(6,2.8, "x   {:1.4f}".format(Yxyw[0,1]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k')
    axh.text(6,2.2, "y   {:1.4f}".format(Yxyw[0,2]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k')
    axh.text(6,1.6, "u'  {:1.4f}".format(Yuvw[0,1]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k')
    axh.text(6,1.0, "v'  {:1.4f}".format(Yuvw[0,2]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k')
    axh.text(7.5,2.8, "CIE 13.3-1995", fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k')
    axh.text(7.5,2.2, "     (CRI)    ", fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k')
    axh.text(7.5,1.6, "    $R_a$  {:1.0f}".format(ciera[0,0]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k')
    axh.text(7.5,1.0, "    $R_9$  {:1.0f}".format(cierai[9,0]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k')

    # Create a Rectangle patch
    rect = patches.Rectangle((7.2,0.5),1.7,2.5,linewidth=1,edgecolor='k',facecolor='none')
    
    # Add the patch to the Axes
    axh.add_patch(rect)

    return axh
Example #5
0
def _create_subjects_index_arr(subjects=None, grouping=None):
    """ Create subjects indexing array"""
    if subjects is None:
        if grouping is None:
            raise Exception('Grouping must be supplied!')
        groups = np.unique(grouping)
        for i, group in enumerate(groups):
            if i == 0:
                subjects = np.arange(((grouping == group) * 1).sum())
            else:
                subjects = np.hstack(
                    (subjects, np.arange(((grouping == group) * 1).sum())))
    return subjects
Example #6
0
def _hue_bin_data_to_Rxhj(hue_bin_data, scale_factor):

    nhbins = hue_bin_data['nhbins']
    start_hue = hue_bin_data['start_hue']

    # A. Local color fidelity, Rfhj:
    #DEhj = ((hue_bin_data['jabt_hj']-hue_bin_data['jabr_hj'])**2).sum(axis=-1)**0.5
    DEhj = hue_bin_data[
        'DE_hj']  #TM30 specifies average of DEi per hue bin, not DE of average jabt, jabr
    Rfhj = log_scale(DEhj, scale_factor=scale_factor)

    # B.Local chroma shift and hue shift, [Rcshi, Rhshi]:
    # B.1 relative paths:
    dab = (hue_bin_data['jabt_hj'] - hue_bin_data['jabr_hj'])[..., 1:] / (
        hue_bin_data['Cr_hj'][..., None])

    # B.2 Reference unit circle:
    hbincenters = np.arange(start_hue + np.pi / nhbins, 2 * np.pi,
                            2 * np.pi / nhbins)[..., None]
    arc = np.cos(hbincenters)
    brc = np.sin(hbincenters)

    # B.3 calculate local chroma shift, Rcshi:
    Rcshi = dab[..., 0] * arc + dab[..., 1] * brc

    # B.4 calculate local hue shift, Rcshi:
    Rhshi = dab[..., 1] * arc - dab[..., 0] * brc

    return Rcshi, Rhshi, Rfhj, DEhj
Example #7
0
def mutation(Xp, options):
    """
    Performs mutation in the individuals.
    
    | The mutation is one of the operators responsible for random changes in
    | the individuals. Each parent x will have a new individual, called trial
    | vector u, after the mutation.
    | To do that, pick up two random individuals from the population, x2 and
    | x3, and creates a difference vector v = x2 - x3. Then, chooses another
    | point, called base vector, xb, and creates the trial vector by
    |
    |   u = xb + F*v = xb + F*(x2 - x3)
    |
    | wherein F is an internal parameter, called scale factor.

    Args:
       :Xp: 
           | a n x mu ndarray with mu "parents" and of dimension n
       :options: 
           | the dict with the internal parameters

    Returns:
       :Xo: 
           | a n x mu ndarray with the mu mutated individuals (of dimension n)
    """
    # Creates a mu x mu matrix of 1:n elements on each row
    A = np.arange(options['mu']).repeat(options['mu']).reshape(options['mu'],options['mu']).T   
    # Now, one removes the diagonal of A, because it contains indexes that repeat 
    # the current i-th individual
    A = np.reshape(A[(np.eye(A.shape[0]))==False],(options['mu'],options['mu']-1))

    # Now, creates a matrix that permutes the elements of A randomly
    J = np.argsort(np.random.rand(*A.shape), axis = 1)
#    J = getdata('J.txt')-1

    Ilin = J*options['mu'] + np.arange(options['mu'])[:,None]
    A = A.T.flatten()[Ilin].reshape(A.shape)
    
    # Chooses three random points (for each row)
    xbase = Xp[:, A[:,0]] #base vectors
    v = Xp[:, A[:,1]] - Xp[:, A[:,2]] #difference vector
    
    # Performs the mutation
    Xo = xbase + options['F']*v

    return Xo
Example #8
0
def plotcircle(center=np.array([[0., 0.]]),
               radii=np.arange(0, 60, 10),
               angles=np.arange(0, 350, 10),
               color='k',
               linestyle='--',
               out=None,
               axh=None,
               **kwargs):
    """
    Plot one or more concentric circles.
    
    Args:
        :center: 
            | np.array([[0.,0.]]) or ndarray with center coordinates, optional
        :radii:
            | np.arange(0,60,10) or ndarray with radii of circle(s), optional
        :angles:
            | np.arange(0,350,10) or ndarray with angles (°), optional
        :color:
            | 'k', optional
            | Color for plotting.
        :linestyle:
            | '--', optional
            | Linestyle of circles.
        :out: 
            | None, optional
            | If None: plot circles, return (x,y) otherwise.
    """
    xs = np.array([0])
    ys = xs.copy()
    if ((out != 'x,y') & (axh is None)):
        fig, axh = plt.subplots(rows=1, ncols=1)
    for ri in radii:
        x = center[:, 0] + ri * np.cos(angles * np.pi / 180)
        y = center[:, 1] + ri * np.sin(angles * np.pi / 180)
        xs = np.hstack((xs, x))
        ys = np.hstack((ys, y))
        if (out != 'x,y'):
            axh.plot(x, y, color=color, linestyle=linestyle, **kwargs)
    if out == 'x,y':
        return xs, ys
    elif out == 'axh':
        return axh
Example #9
0
def _rgb_delinearizer(rgblin, tr, tr_type = 'lut'):
    """ De-linearize linear rgblin using tr tone response function or lut """
    if tr_type == 'gog':
        return np.array([TRi(rgblin[:,i],*tr[i]) for i in range(3)]).T
    elif tr_type == 'lut':
        maxv = (tr.shape[0] - 1)
        bins = np.vstack((tr-np.diff(tr,axis=0,prepend=0)/2,tr[-1,:]+0.01)) # create bins
        idxs = np.array([(np.digitize(rgblin[:,i],bins[:,i]) - 1)  for i in range(3)]).T # find bin indices
        idxs[idxs>maxv] = maxv 
        rgb = np.arange(tr.shape[0])[idxs]
        return rgb
Example #10
0
def _permutate_grouping(grouping, subjects, paired=False):
    """ permutate grouping and subjects indexing arrays"""
    if paired == False:
        perm_idx = np.arange(grouping.shape[0], dtype=int)
        perm_idx = np.random.permutation(perm_idx)
        perm_grouping = grouping[perm_idx]
        perm_subjects = subjects[perm_idx]
    else:
        groups = np.unique(grouping)
        o = 10**((grouping.reshape(len(groups),
                                   len(grouping) // len(groups))) + 1)
        if subjects is not None:
            s = subjects.reshape(len(groups), len(grouping) // len(groups))
        for i in range(o.shape[-1]):
            perm_idx = np.arange(o.shape[0], dtype=int)
            perm_idx = np.random.permutation(perm_idx)
            o[:, i] = o[perm_idx, i]
            s[:, i] = s[perm_idx, i]
        o = np.log10(o) - 1
        perm_grouping = o.flatten()
        if subjects is not None:
            perm_subjects = s.flatten()
    return perm_grouping, perm_subjects
Example #11
0
def plotcircle(radii = np.arange(0,60,10), \
               angles = np.arange(0,350,10),\
               color = 'k',linestyle = '--', out = None):
    """
    Plot one or more concentric circles around (0,0).
    
    Args:
        :radii:
            | np.arange(0,60,10) or ndarray with radii of circle(s), optional
        :angles:
            | np.arange(0,350,10) or ndarray with angles (°), optional
        :color: 
            | 'k', optional
            | Color for plotting.
        :linestyle:
            | '--', optional
            | Linestyle of circles.
        :out: 
            | None, optional
            | If None: plot circles, return (x,y) otherwise.
               
     Returns:
          :x,y:
               | ndarrays with circle coordinates (only returned if out is 'x,y')
    """
    x = np.array([0])
    y = x.copy()
    for ri in radii:
        xi = ri * np.cos(angles * np.pi / 180)
        yi = ri * np.sin(angles * np.pi / 180)
        x = np.hstack((x, xi))
        y = np.hstack((y, yi))
        if out != 'x,y':
            plt.plot(xi, yi, color=color, linestyle=linestyle)
    if out == 'x,y':
        return x, y
Example #12
0
def crowdingdistance(F):
    """
    Computes the crowding distance of a nondominated front.
    
    | The crowding distance gives a measure of how close the individuals are
    | with regard to its neighbors. The higher this value, the greater the
    | spacing. This is used to promote better diversity in the population.

    Args:
       :F: 
           | an m x mu ndarray with mu individuals and m objectives

    Returns:
       :cdist: 
           | a m-length column vector
    """
    m, mu = F.shape #gets the size of F
    
    if mu == 2:
       cdist = np.vstack((np.inf, np.inf))
       return cdist

    
    #[Fs, Is] = sort(F,2); #sorts the objectives by individuals
    Is = F.argsort(axis = 1)
    Fs = np.sort(F,axis=1)
    
    # Creates the numerator
    C = Fs[:,2:] - Fs[:,:-2]
    C = np.hstack((np.inf*np.ones((m,1)), C, np.inf*np.ones((m,1)))) #complements with inf in the extremes
    
    # Indexing to permute the C matrix in the right ordering
    Aux = np.arange(m).repeat(mu).reshape(m,mu)   
    ind = np.ravel_multi_index((Aux.flatten(),Is.flatten()),(m, mu)) #converts to lin. indexes # ind = sub2ind([m, mu], Aux(:), Is(:));
    C2 = C.flatten().copy()
    C2[ind] = C2.flatten()
    C = C2.reshape((m, mu))

    # Constructs the denominator
    den = np.repeat((Fs[:,-1] - Fs[:,0])[:,None], mu, axis = 1)
    
    # Calculates the crowding distance
    cdist = (C/den).sum(axis=0)
    cdist = cdist.flatten() #assures a column vector
    return cdist
Example #13
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
Example #14
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
Example #15
0
import warnings
from imageio import imsave

__all__ = [
    '_HYPSPCIM_PATH', '_HYPSPCIM_DEFAULT_IMAGE', 'render_image', 'xyz_to_rfl',
    'get_superresolution_hsi', 'hsi_to_rgb', 'rfl_to_rgb'
]

_HYPSPCIM_PATH = _PKG_PATH + _SEP + 'hypspcim' + _SEP
_HYPSPCIM_DEFAULT_IMAGE = _PKG_PATH + _SEP + 'toolboxes' + _SEP + 'hypspcim' + _SEP + 'data' + _SEP + 'testimage1.jpg'

_ROUNDING = 6  # to speed up xyz_to_rfl search algorithm

# Nikon D700 camera sensitivity functions:
_CSF_NIKON_D700 = np.vstack(
    (np.arange(400, 710, 10),
     np.array([[
         0.005, 0.007, 0.012, 0.015, 0.023, 0.025, 0.030, 0.026, 0.024, 0.019,
         0.010, 0.004, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
         0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
         0.000
     ],
               [
                   0.000, 0.000, 0.000, 0.000, 0.000, 0.001, 0.002, 0.003,
                   0.005, 0.007, 0.012, 0.013, 0.015, 0.016, 0.017, 0.020,
                   0.013, 0.011, 0.009, 0.005, 0.001, 0.001, 0.001, 0.001,
                   0.001, 0.001, 0.001, 0.001, 0.002, 0.002, 0.003
               ],
               [
                   0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
                   0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000, 0.000,
Example #16
0
def calibrate(rgbcal, xyzcal, L_type = 'lms', tr_type = 'lut', cieobs = '1931_2', 
              nbit = 8, cspace = 'lab', avg = lambda x: ((x**2).mean()**0.5), ensure_increasing_lut_at_low_rgb = 0.2,
              verbosity = 1, sep=',',header=None): 
    """
    Calculate TR parameters/lut and conversion matrices.
    
    Args:
        :rgbcal:
            | ndarray [Nx3] or string with filename of RGB values 
            | rgcal must contain at least the following type of settings:
            | - pure R,G,B: e.g. for pure R: (R != 0) & (G==0) & (B == 0)
            | - white(s): R = G = B = 2**nbit-1
            | - gray(s): R = G = B
            | - black(s): R = G = B = 0
            | - binary colors: cyan (G = B, R = 0), yellow (G = R, B = 0), magenta (R = B, G = 0)
        :xyzcal:
            | ndarray [Nx3] or string with filename of measured XYZ values for 
            | the RGB settings in rgbcal.
        :L_type:
            | 'lms', optional
            | Type of response to use in the derivation of the Tone-Response curves.
            | options:
            |  - 'lms': use cone fundamental responses: L vs R, M vs G and S vs B 
            |           (reduces noise and generally leads to more accurate characterization) 
            |  - 'Y': use the luminance signal: Y vs R, Y vs G, Y vs B
        :tr_type:
            | 'lut', optional
            | options:
            |  - 'lut': Derive/specify Tone-Response as a look-up-table
            |  - 'gog': Derive/specify Tone-Response as a gain-offset-gamma function
        :cieobs:
            | '1931_2', optional
            | CIE CMF set used to determine the XYZ tristimulus values
            | (needed when L_type == 'lms': determines the conversion matrix to
            | convert xyz to lms values)
        :nbit:
            | 8, optional
            | RGB values in nbit format (e.g. 8, 16, ...)
        :cspace:
            | color space or chromaticity diagram to calculate color differences in
            | when optimizing the xyz_to_rgb and rgb_to_xyz conversion matrices.
        :avg:
            | lambda x: ((x**2).mean()**0.5), optional
            | Function used to average the color differences of the individual RGB settings
            | in the optimization of the xyz_to_rgb and rgb_to_xyz conversion matrices.
        :ensure_increasing_lut_at_low_rgb:
            | 0.2 or float (max = 1.0) or None, optional
            | Ensure an increasing lut by setting all values below the RGB with the maximum
            | zero-crossing of np.diff(lut) and RGB/RGB.max() values of :ensure_increasing_lut_at_low_rgb:
            | (values of 0.2 are a good rule of thumb value)
            | Non-strictly increasing lut values can be caused at low RGB values due
            | to noise and low measurement signal. 
            | If None: don't force lut, but keep as is.
        :verbosity:
            | 1, optional
            | > 0: print and plot optimization results
        :sep:
            | ',', optional
            | separator in files with rgbcal and xyzcal data
        :header:
            | None, optional
            | header specifier for files with rgbcal and xyzcal data 
            | (see pandas.read_csv)
            
    Returns:
        :M:
            | linear rgb to xyz conversion matrix
        :N:
            | xyz to linear rgb conversion matrix
        :tr:
            | Tone Response function parameters or lut
        :xyz_black:
            | ndarray with XYZ tristimulus values of black
        :xyz_white:
            | ndarray with tristimlus values of white
    """
    
    # process rgb, xyzcal inputs:
    rgbcal, xyzcal = _parse_rgbxyz_input(rgbcal, xyz = xyzcal, sep = sep, header=header)
    
    # get black-positions and average black xyz (flare):
    p_blacks = (rgbcal[:,0]==0) & (rgbcal[:,1]==0) & (rgbcal[:,2]==0)
    xyz_black = xyzcal[p_blacks,:].mean(axis=0,keepdims=True)
    
    # Calculate flare corrected xyz:
    xyz_fc = xyzcal - xyz_black
    
    # get positions of pure r, g, b values:
    p_pure = [(rgbcal[:,1]==0) & (rgbcal[:,2]==0), 
              (rgbcal[:,0]==0) & (rgbcal[:,2]==0), 
              (rgbcal[:,0]==0) & (rgbcal[:,1]==0)] 
    
    # set type of L-response to use: Y for R,G,B or L,M,S for R,G,B:
    if L_type == 'Y':
        L = np.array([xyz_fc[:,1] for i in range(3)]).T
    elif L_type == 'lms':
        lms = (math.normalize_3x3_matrix(_CMF[cieobs]['M'].copy()) @ xyz_fc.T).T
        L = np.array([lms[:,i] for i in range(3)]).T
        
    # Get rgb linearizer parameters or lut and apply to all rgb's:
    if tr_type == 'gog':
        par = np.array([sp.optimize.curve_fit(TR, rgbcal[p_pure[i],i], L[p_pure[i],i]/L[p_pure[i],i].max(), p0=[1,0,1])[0] for i in range(3)]) # calculate parameters of each TR
        tr = par
    elif tr_type == 'lut':
        dac = np.arange(2**nbit)
        # lut = np.array([cie_interp(np.vstack((rgbcal[p_pure[i],i],L[p_pure[i],i]/L[p_pure[i],i].max())), dac, kind ='cubic')[1,:] for i in range(3)]).T
        lut = np.array([sp.interpolate.PchipInterpolator(rgbcal[p_pure[i],i],L[p_pure[i],i]/L[p_pure[i],i].max())(dac) for i in range(3)]).T # use this one to avoid potential overshoot with cubic spline interpolation (but slightly worse performance)
        lut[lut<0] = 0
          
        # ensure monotonically increasing lut values for low signal:
        if ensure_increasing_lut_at_low_rgb is not None:
            #ensure_increasing_lut_at_low_rgb = 0.2 # anything below that has a zero-crossing for diff(lut) will be set to zero
            for i in range(3):
                p0 = np.where((np.diff(lut[dac/dac.max() < ensure_increasing_lut_at_low_rgb,i])<=0))[0]
                if p0.any():
                    p0 = range(0,p0[-1])
                    lut[p0,i] = 0
        tr = lut

    
    # plot:
    if verbosity > 0:
        colors = 'rgb'
        linestyles = ['-','--',':']
        rgball = np.repeat(np.arange(2**8)[:,None],3,axis=1)
        Lall = _rgb_linearizer(rgball, tr, tr_type = tr_type)
        plt.figure()
        for i in range(3):
            plt.plot(rgbcal[p_pure[i],i],L[p_pure[i],i]/L[p_pure[i],i].max(),colors[i]+'o')
            plt.plot(rgball[:,i],Lall[:,i],colors[i]+linestyles[i],label=colors[i])
        plt.xlabel('Display RGB')
        plt.ylabel('Linear RGB')
        plt.legend()
        plt.title('Tone response curves')
    
    # linearize all rgb values and clamp to 0
    rgblin = _rgb_linearizer(rgbcal, tr, tr_type = tr_type) 
 
    # get rgblin to xyz_fc matrix:
    M = np.linalg.lstsq(rgblin, xyz_fc, rcond=None)[0].T 
    
    # get xyz_fc to rgblin matrix:
    N = np.linalg.inv(M)
    
    # get better approximation for conversion matrices:
    p_grays = (rgbcal[:,0] == rgbcal[:,1]) & (rgbcal[:,0] == rgbcal[:,2])
    p_whites = (rgbcal[:,0] == (2**nbit-1)) & (rgbcal[:,1] == (2**nbit-1)) & (rgbcal[:,2] == (2**nbit-1))
    xyz_white = xyzcal[p_whites,:].mean(axis=0,keepdims=True) # get xyzw for input into xyz_to_lab() or colortf()
    def optfcn(x, rgbcal, xyzcal, tr, xyz_black, cspace, p_grays, p_whites,out,verbosity):
        M = x.reshape((3,3))
        xyzest = rgb_to_xyz(rgbcal, M, tr, xyz_black, tr_type)
        xyzw = xyzcal[p_whites,:].mean(axis=0) # get xyzw for input into xyz_to_lab() or colortf()
        labcal, labest = colortf(xyzcal,tf=cspace,xyzw=xyzw), colortf(xyzest,tf=cspace,xyzw=xyzw) # calculate lab coord. of cal. and est.
        DEs = ((labcal-labest)**2).sum(axis=1)**0.5
        DEg = DEs[p_grays]
        DEw = DEs[p_whites]
        F = (avg(DEs)**2 + avg(DEg)**2 + avg(DEw**2))**0.5
        if verbosity > 1:
            print('\nPerformance of TR + rgb-to-xyz conversion matrix M:')
            print('all: DE(jab): avg = {:1.4f}, std = {:1.4f}'.format(avg(DEs),np.std(DEs)))
            print('grays: DE(jab): avg = {:1.4f}, std = {:1.4f}'.format(avg(DEg),np.std(DEg)))
            print('whites(s) DE(jab): avg = {:1.4f}, std = {:1.4f}'.format(avg(DEw),np.std(DEw)))
        if out == 'F':
            return F
        else:
            return eval(out)
    x0 = M.ravel()
    res = math.minimizebnd(optfcn, x0, args =(rgbcal, xyzcal, tr, xyz_black, cspace, p_grays, p_whites,'F',0), use_bnd=False)
    xf = res['x_final']
    M = optfcn(xf, rgbcal, xyzcal, tr, xyz_black, cspace, p_grays, p_whites,'M',verbosity)
    N = np.linalg.inv(M)
    return M, N, tr, xyz_black, xyz_white
Example #17
0
def get_daylightloci_parameters(ccts=None,
                                cieobs=None,
                                wl3=[300, 830, 10],
                                verbosity=0):
    """
    Get parameters for the daylight loci functions xD(1000/CCT) and yD(xD).
    
    Args:
        :ccts:
            | None, optional
            | ndarray with CCTs, if None: ccts = np.arange(4000,25000,250)
        :cieobs:
            | None or list, optional
            | CMF sets to determine parameters for.
            | If None: get for all CMFs sets in _CMF (except scoptopic and deviate observer)
        :wl3:
            | [300,830,10], optional
            | Wavelength range and spacing of daylight phases to be determined
            | from '1931_2'. The default setting results in parameters very close
            | to that in CIE15-2004/2018.
        :verbosity:
            | 0, optional
            | print parameters and make plots.
            
    Returns:
        :dayloci:
            | dict with parameters for each cieobs
    """
    if ccts is None:
        ccts = np.arange(4000, 25000, 250)

    # Get daylight phase spds using cieobs '1931_2':
    # wl3 = [300,830,10] # results in Judd's (1964) coefficients for the function yD(xD)x; other show slight deviations
    for i, cct in enumerate(ccts):
        spd = daylightphase(cct, wl3=wl3, force_daylight_below4000K=False)
        if i == 0:
            spds = spd
        else:
            spds = np.vstack((spds, spd[1:]))

    if verbosity > 0:
        fig, axs = plt.subplots(
            nrows=2, ncols=len(_CMF['types']) -
            2)  # -2: don't include scoptopic and dev observers

    dayloci = {}
    i = 0
    for cieobs in _CMF['types']:
        if 'scotopic' in cieobs:
            continue
        if 'std_dev_obs' in cieobs:
            continue

        # get parameters for cieobs:
        xy, pxy, pxT_l7, pxT_L7, l7, L7 = _get_daylightlocus_parameters(
            ccts, spds, cieobs)
        dayloci[cieobs] = {'pxT_l7k': pxT_l7, 'pxT_L7k': pxT_L7, 'pxy': pxy}

        if verbosity > 0:
            print('\n cieobs:', cieobs)
            print('pxT_l7 (Tcp<=7000K):', pxT_l7)
            print('pxT_L7 (Tcp>7000K):', pxT_L7)
            print('p:xy', pxy)

            axs[0, i].plot(ccts, xy[:, 0], 'r-', label='Data')
            axs[0, i].plot(ccts[l7],
                           np.polyval(pxT_l7, 1000 / ccts[l7]),
                           'b--',
                           label='Fit (Tcp<=7000K)')
            axs[0, i].plot(ccts[L7],
                           np.polyval(pxT_L7, 1000 / ccts[L7]),
                           'c--',
                           label='Fit (Tcp>7000K)')
            axs[0, i].set_title(cieobs)
            axs[0, i].set_xlabel('Tcp (K)')
            axs[0, i].set_ylabel('xD')
            axs[0, i].legend()

            #plotSL(cieobs = cieobs, cspace = 'Yxy', DL = False, axh = axs[1,i])
            axs[1, i].plot(xy[:, 0], xy[:, 1], 'r-', label='Data')
            axs[1, i].plot(xy[:, 0],
                           np.polyval(pxy, xy[:, 0]),
                           'b--',
                           label='Fit')
            # axs[1,i].plot(xy[:,0],np.polyval(pxy_31,xy[:,0]),'g:')
            axs[1, i].set_xlabel('xD')
            axs[1, i].set_ylabel('yD')
            axs[1, i].legend()

        i += 1

    return dayloci
Example #18
0
def _get_hue_map(hbins = 16, start_hue = 0.0, 
                 hbinnrs = None, xyzri = None, xyzrw = None, cri_type = None):
    """
    Generate color map for hue bins.
    
    Args:
        :hbins:
            | 16 or ndarray with sorted hue bin centers (°), optional
        :start_hue:
            | 0.0, optional
        :hbinnrs: 
            | None, optional
            | ndarray with hue bin number of each sample.
            | If hbinnrs, xyzri, xyzrw and cri_type are all not-None: 
            |    use these to calculate color map, otherwise just use number of
            |    hue bins :hbins: and :start_hue:
        :xyzri:
            | None, optional
            | relative xyz tristimulus values of samples under ref. illuminant.
            | see :hbinnrs: for more info when this is used.
        :xyzrw:
            | None, optional
            | relative xyz tristimulus values of ref. illuminant white point.
            | see :hbinnrs: for more info when this is used.
        :cri_type:
            | None, optional
            | Specifies dict with default cri model parameters 
            | (needed to get correct :cieobs:) 
            | see :hbinnrs: for more info when this is used.
    
    Returns:
        :cmap:
            | list with rgb values (one for each hue bin) for plotting.
    """
    # Setup hbincenters and hsv_hues:
    if isinstance(hbins,float) | isinstance(hbins,int):
        nhbins = hbins
        dhbins = 360/(nhbins) # hue bin width
        hbincenters = np.arange(start_hue + dhbins/2, 360, dhbins)
        hbincenters = np.sort(hbincenters)
    else:
        hbincenters = hbins
        idx = np.argsort(hbincenters)
        hbincenters = hbincenters[idx]
        nhbins = hbincenters.shape[0]
    
    cmap = []
    if (hbinnrs is not None) & (xyzri is not None) & (xyzrw is not None) & (cri_type is not None):
        xyzw = spd_to_xyz(_CIE_D65, relative = True, cieobs = cri_type['cieobs']['xyz'])
        xyzri = cat.apply(xyzri[:,0,:],xyzw1 = xyzrw, xyzw2 = xyzw)
                
        # Create color from xyz average:
        for i in range(nhbins):
            xyzrhi = xyzri[hbinnrs[:,0] == i,:].mean(axis=0,keepdims=True)
            rgbrhi = xyz_to_srgb(xyzrhi)/255
            cmap.append(rgbrhi)
    else:
        # Create color from hue angle:
            
        # Setup color for plotting hue bins:
        hbincenters = hbincenters*np.pi/180
        hsv_hues = hbincenters - 30*np.pi/180
        hsv_hues = hsv_hues/hsv_hues.max()
            
        for i in range(nhbins):   
            #c = np.abs(np.array(colorsys.hsv_to_rgb(hsv_hues[i], 0.75, 0.85)))
            c = np.abs(np.array(colorsys.hls_to_rgb(hsv_hues[i], 0.45, 0.5)))
            cmap.append(c)
    
    return cmap
Example #19
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
Example #20
0
def generate_grid(jab_ranges = None, out = 'grid', \
                  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):
    """
    Generate a grid of color coordinates.
    
    Args:
        :out:
            | 'grid' or 'vectors', optional
            |   - 'grid': outputs a single 2d numpy.nd-vector with the grid coordinates
            |   - 'vector': outputs each dimension seperately.
        :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)
        :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: 
            | single ndarray with ax,bx [,jx] 
            |  or
            | seperate ndarrays for each dimension specified.
    """
    # generate grid from jab_ranges array input, otherwise use ax, bx, jx input:
    if jab_ranges is not None:
        if jab_ranges.shape[0] == 3:
            jx = np.arange(jab_ranges[0][0], jab_ranges[0][1],
                           jab_ranges[0][2])
            ax = np.arange(jab_ranges[1][0], jab_ranges[1][1],
                           jab_ranges[1][2])
            bx = np.arange(jab_ranges[2][0], jab_ranges[2][1],
                           jab_ranges[2][2])
        else:
            jx = None
            ax = np.arange(jab_ranges[0][0], jab_ranges[0][1],
                           jab_ranges[0][2])
            bx = np.arange(jab_ranges[1][0], jab_ranges[1][1],
                           jab_ranges[1][2])

    # Generate grid from (jx), ax, bx:
    Ax, Bx = np.meshgrid(ax, bx)
    grid = np.dstack((Ax, Bx))
    grid = np.reshape(grid, (np.array(grid.shape[:-1]).prod(), grid.ndim - 1))
    if jx is not None:
        for i, v in enumerate(jx):
            gridi = np.hstack((np.ones((grid.shape[0], 1)) * v, grid))
            if i == 0:
                gridwithJ = gridi
            else:
                gridwithJ = np.vstack((gridwithJ, gridi))
        grid = gridwithJ

    if jx is None:
        ax = grid[:, 0:1]
        bx = grid[:, 1:2]
    else:
        jx = grid[:, 0:1]
        ax = grid[:, 1:2]
        bx = grid[:, 2:3]

    if limit_grid_radius > 0:  # limit radius of grid:
        Cr = (ax**2 + bx**2)**0.5
        ax = ax[Cr <= limit_grid_radius, None]
        bx = bx[Cr <= limit_grid_radius, None]
        if jx is not None:
            jx = jx[Cr <= limit_grid_radius, None]

    # create output:
    if out == 'grid':
        if jx is None:
            return np.hstack((ax, bx))
        else:
            return np.hstack((jx, ax, bx))
    else:
        if jx is None:
            return ax, bx
        else:
            return jx, ax, bx
Example #21
0
def plot_tm30_Rfi(spd, cri_type = 'ies-tm30', axh = None, 
                  font_size = _TM30_FONT_SIZE, **kwargs):
    """
    Plot Sample Color Fidelity values (Rfi).
    
    Args:
        :spd:
            | ndarray or dict
            | If ndarray: single spectral power distribution.
            | If dict: dictionary with pre-computed parameters (using _tm30_process_spd()).
            |  required keys:
            |   'Rf','Rg','cct','duv','Sr','cri_type','xyzri','xyzrw',
            |   'hbinnrs','Rfi','Rfhi','Rcshi','Rhshi',
            |   'jabt_binned','jabr_binned',
            |   'nhbins','start_hue','normalize_gamut','normalized_chroma_ref'
            | see cri.spd_to_cri() for more info on parameters.
        :cri_type:
            | _CRI_TYPE_DEFAULT or str or dict, optional
            |   -'str: specifies dict with default cri model parameters 
            |     (for supported types, see luxpy.cri._CRI_DEFAULTS['cri_types'])
            |   - dict: user defined model parameters 
            |     (see e.g. luxpy.cri._CRI_DEFAULTS['cierf'] 
            |     for required structure)
            | Note that any non-None input arguments (in kwargs) 
            | to the function will override default values in cri_type dict.
        :axh: 
            | None, optional
            | If None: create new figure with single axes, else plot on specified axes.    
        :font_size:
            | _TM30_FONT_SIZE, optional
            | Font size of text, axis labels and axis values.
        :kwargs:
            | Additional optional keyword arguments, 
            | the same as in cri.spd_to_cri()
            
    Returns:
        :axh: 
            | handle to figure axes.
        :data:
            | dictionary with required parameters for plotting functions.     
    """
    data = _tm30_process_spd(spd, cri_type = 'ies-tm30',**kwargs)
    Rfi = data['Rfi']
    
    # get rgb values representing each sample:
    N = data['xyzri'].shape[0]
    xyzw = spd_to_xyz(_CIE_D65, relative = True, cieobs = data['cri_type']['cieobs']['xyz'])
    xyzri = cat.apply(data['xyzri'][:,0,:],xyzw1 = data['xyzrw'], xyzw2 = xyzw)
    rgbri = xyz_to_srgb(xyzri)/255
    
    # Create color map:
    cmap = []
    for i in range(N):
        cmap.append(rgbri[i,...])

    # Plot sample color fidelity, Rfi:
    if axh is None:
        fig, axh = plt.subplots(nrows = 1, ncols = 1)
    for j in range(N):
        axh.bar(j,Rfi[j,0], color = cmap[j], width = 1,edgecolor = None, alpha = 0.9)
        #axh.text(j,Rfi[j,0]*1.1, '{:1.0f}'.format(Rfi[j,0]) ,fontsize = 9,horizontalalignment='center',verticalalignment='center',color = np.array([1,1,1])*0.3)
    xticks = np.arange(0,N,step=2)
    xtickslabels = ['CES{:1.0f}'.format(ii+1) for ii in range(0,N,2)]
    axh.set_xticks(xticks)
    axh.set_xticklabels(xtickslabels, fontsize = font_size, rotation = 90)
    axh.set_ylabel(r'Color Sample Fidelity $(R_{f,CESi})$', fontsize = font_size)
    axh.set_ylim([0,100])
    axh.set_xlim([-0.5,N-0.5])
    
    return axh, data
Example #22
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
Example #23
0
              Opt. Express, vol. 23, no. 9, pp. 12045–12064, 2015.
              <https://www.osapublishing.org/oe/abstract.cfm?uri=oe-23-9-12045&origin=search>`_
            | `M. Withouck, K. A. G. Smet, and P. Hanselaer, (2015), 
            “Brightness prediction of different sized unrelated self-luminous stimuli,” 
            Opt. Express, vol. 23, no. 10, pp. 13455–13466. 
            <https://www.osapublishing.org/oe/abstract.cfm?uri=oe-23-10-13455&origin=search>`_
"""
from luxpy import _CMF, _CIE_ILLUMINANTS, _MUNSELL, spd_to_xyz, cie_interp
from luxpy.utils import np, np2d, asplit, ajoin
from luxpy.color.cam.colorappearancemodels import hue_angle, hue_quadrature

_CAM15U_AXES = {'qabW_cam15u': ["Q (cam15u)", "aW (cam15u)", "bW (cam15u)"]}

_CAM15U_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]
}

_CAM15U_PARAMETERS = {
    'k': [666.7, 782.3, 1444.6],
    'cp':
    1.0 / 3,
    'cA':
    3.22,
    'cAlms': [2.0, 1.0, 1 / 20],
    'ca':
    1.0,
    'calms': [1.0, -12 / 11, 1 / 11],
Example #24
0
    def __init__(self, spd = None, wl = None, ax0iswl = True, dtype = 'S', \
                 wl_new = None, interp_method = 'auto', negative_values_allowed = False, extrap_values = None,\
                 norm_type = None, norm_f = 1,\
                 header = None, sep = ','):
        """
        Initialize instance of SPD.
        
        Args:
            :spd: 
                | None or ndarray or str, optional
                | If None: self.value is initialized with zeros.
                | If str: spd contains filename.
                | If ndarray: ((wavelength, spectra)) or (spectra). 
                |     If latter, :wl: should contain the wavelengths.
            :wl: 
                | None or ndarray, optional
                | Wavelengths.
                | Either specified as a 3-vector ([start, stop, spacing]) 
                | or as full wavelength array.
            :a0iswl:
                | True, optional
                | Signals that first axis of :spd: contains wavelengths.
            :dtype:
                | 'S', optional
                | Type of spectral object (e.g. 'S' for source spectrum, 'R' for
                  reflectance spectra, etc.)
                | See SPD._INTERP_TYPES for more options. 
                | This is used to automatically determine the correct kind of
                  interpolation method according to CIE15-2018.
            :wl_new: 
                | None or ndarray with wavelength range, optional
                | If None: don't interpolate, else perform interpolation.
            :interp_method:
                | - 'auto', optional
                | If 'auto': method is determined based on :dtype:
            :negative-values_allowed:
                | False, optional (for cie_interp())
                | Spectral data can not be negative. Values < 0 are therefore 
                  clipped when set to False.
            :extrap_values:
                | None, optional
                | float or list or ndarray with values to extrapolate
                | If None: use CIE recommended 'closest value' approach.
            :norm_type:
                | None or str, optional
                | - 'lambda': make lambda in norm_f equal to 1
                | - 'area': area-normalization times norm_f
                | - 'max': max-normalization times norm_f
            :norm_f:
                | 1, optional
                | Normalization factor determines size of normalization 
                | for 'max' and 'area' 
                | or which wavelength is normalized to 1 for 'lambda' option.
        """
        if spd is not None:
            if isinstance(spd, str):
                spd = SPD.read_csv_(self, file=spd, header=header, sep=sep)
            if ax0iswl == True:
                self.wl = spd[0]
                self.value = spd[1:]
            else:
                self.wl = wl
                if (self.wl.size == 3):
                    self.wl = np.arange(self.wl[0], self.wl[1] + 1, self.wl[2])
                self.value = spd
            if self.value.shape[1] != self.wl.shape[0]:
                raise Exception(
                    'SPD.__init__(): Dimensions of wl and spd do not match.')
        else:
            if (wl is None):
                self.wl = SPD._WL3
            else:
                self.wl = wl
            if (self.wl.size == 3):
                self.wl = np.arange(self.wl[0], self.wl[1] + 1, self.wl[2])
            self.value = np.zeros((1, self.wl.size))

        self.wl = self.wl
        self.dtype = dtype
        self.shape = self.value.shape
        self.N = self.shape[0]

        if wl_new is not None:
            if interp_method == 'auto':
                interp_method = dtype
            self.cie_interp(wl_new,
                            kind=interp_method,
                            negative_values_allowed=negative_values_allowed,
                            extrap_values=extrap_values)
        if norm_type is not None:
            self.normalize(norm_type=norm_type, norm_f=norm_f)
Example #25
0
def plot_hue_bins(hbins = 16, start_hue = 0.0, scalef = 100, \
        plot_axis_labels = False, bin_labels = '#', plot_edge_lines = True, \
        plot_center_lines = False, plot_bin_colors = True, \
        plot_10_20_circles = False,\
        axtype = 'polar', ax = None, force_CVG_layout = False):
    """
    Makes basis plot for Color Vector Graphic (CVG).
    
    Args:
        :hbins:
            | 16 or ndarray with sorted hue bin centers (°), optional
        :start_hue:
            | 0.0, optional
        :scalef:
            | 100, 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.
        :plot_10_20_circles:
            | False, optional
            | If True and :axtype: == 'cart': Plot white circles at 
            | 80%, 90%, 100%, 110% and 120% of :scalef: 
        :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.
        :force_CVG_layout:
            | False or True, optional
            | True: Force plot of basis of CVG on first encounter.
            
    Returns:
        :returns: 
            | gcf(), gca(), list with rgb colors for hue bins (for use in 
              other plotting fcns)
        
    """

    # Setup hbincenters and hsv_hues:
    if isinstance(hbins, float) | isinstance(hbins, int):
        nhbins = hbins
        dhbins = 360 / (nhbins)  # hue bin width
        hbincenters = np.arange(start_hue + dhbins / 2, 360, dhbins)
        hbincenters = np.sort(hbincenters)

    else:
        hbincenters = hbins
        idx = np.argsort(hbincenters)
        if isinstance(bin_labels, list) | isinstance(bin_labels, np.ndarray):
            bin_labels = bin_labels[idx]
        hbincenters = hbincenters[idx]
        nhbins = hbincenters.shape[0]
    hbincenters = hbincenters * np.pi / 180

    # Setup hbin labels:
    if bin_labels is '#':
        bin_labels = ['#{:1.0f}'.format(i + 1) for i in range(nhbins)]
    elif isinstance(bin_labels, str):
        bin_labels = [
            bin_labels + '{:1.0f}'.format(i + 1) for i in range(nhbins)
        ]

    # initializing the figure
    cmap = None
    if (ax is None) or (ax == 'new'):
        fig = plt.figure()
        newfig = True
    else:
        fig = plt.gcf()
        newfig = False
    rect = [0.1, 0.1, 0.8,
            0.8]  # setting the axis limits in [left, bottom, width, height]

    if axtype == 'polar':
        # the polar axis:
        if newfig == True:
            ax = fig.add_axes(rect, polar=True, frameon=False)
    else:
        #cartesian axis:
        if newfig == True:
            ax = fig.add_axes(rect)

    if (newfig == True) | (force_CVG_layout == True):

        # Calculate hue-bin boundaries:
        r = np.vstack((np.zeros(hbincenters.shape),
                       1. * scalef * np.ones(hbincenters.shape)))
        theta = np.vstack((np.zeros(hbincenters.shape), hbincenters))
        #t = hbincenters.copy()
        dU = np.roll(hbincenters.copy(), -1)
        dL = np.roll(hbincenters.copy(), 1)
        dtU = dU - hbincenters
        dtL = hbincenters - dL
        dtU[dtU < 0] = dtU[dtU < 0] + 2 * np.pi
        dtL[dtL < 0] = dtL[dtL < 0] + 2 * np.pi
        dL = hbincenters - dtL / 2
        dU = hbincenters + dtU / 2
        dt = (dU - dL)
        dM = dL + dt / 2

        # Setup color for plotting hue bins:
        hsv_hues = hbincenters - 30 * np.pi / 180
        hsv_hues = hsv_hues / hsv_hues.max()

        edges = np.vstack(
            (np.zeros(hbincenters.shape), dL))  # setup hue bin edges array

        if axtype == 'cart':
            if plot_center_lines == True:
                hx = r * np.cos(theta) * 1.2
                hy = r * np.sin(theta) * 1.2
            if bin_labels is not None:
                hxv = np.vstack((np.zeros(hbincenters.shape),
                                 1.4 * scalef * np.cos(hbincenters)))
                hyv = np.vstack((np.zeros(hbincenters.shape),
                                 1.4 * scalef * np.sin(hbincenters)))
            if plot_edge_lines == True:
                #hxe = np.vstack((np.zeros(hbincenters.shape),1.2*scalef*np.cos(dL)))
                #hye = np.vstack((np.zeros(hbincenters.shape),1.2*scalef*np.sin(dL)))
                hxe = np.vstack(
                    (0.1 * scalef * np.cos(dL), 1.5 * scalef * np.cos(dL)))
                hye = np.vstack(
                    (0.1 * scalef * np.sin(dL), 1.5 * scalef * np.sin(dL)))

        # Plot hue-bins:
        for i in range(nhbins):

            # Create color from hue angle:
            #c = np.abs(np.array(colorsys.hsv_to_rgb(hsv_hues[i], 0.75, 0.85)))
            c = np.abs(np.array(colorsys.hls_to_rgb(hsv_hues[i], 0.45, 0.5)))
            if i == 0:
                cmap = [c]
            else:
                cmap.append(c)

            if axtype == 'polar':
                if plot_edge_lines == True:
                    ax.plot(edges[:, i],
                            r[:, i] * 1.,
                            color='grey',
                            marker='None',
                            linestyle='--',
                            linewidth=1,
                            markersize=2)
                if plot_center_lines == True:
                    if np.mod(i, 2) == 1:
                        ax.plot(theta[:, i],
                                r[:, i],
                                color=c,
                                marker=None,
                                linestyle='--',
                                linewidth=1)
                    else:
                        ax.plot(theta[:, i],
                                r[:, i],
                                color=c,
                                marker=None,
                                linestyle='--',
                                linewidth=1,
                                markersize=10)
                if plot_bin_colors == True:
                    bar = ax.bar(dM[i],
                                 r[1, i],
                                 width=dt[i],
                                 color=c,
                                 alpha=0.25)
                if bin_labels is not None:
                    ax.text(hbincenters[i],
                            1.3 * scalef,
                            bin_labels[i],
                            fontsize=10,
                            horizontalalignment='center',
                            verticalalignment='center',
                            color=np.array([1, 1, 1]) * 0.45)
                if plot_axis_labels == False:
                    ax.set_xticklabels([])
                    ax.set_yticklabels([])
            else:
                axis_ = 1. * np.array(
                    [-scalef * 1.5, scalef * 1.5, -scalef * 1.5, scalef * 1.5])
                if plot_edge_lines == True:
                    ax.plot(hxe[:, i],
                            hye[:, i],
                            color='grey',
                            marker='None',
                            linestyle='--',
                            linewidth=1,
                            markersize=2)

                if plot_center_lines == True:
                    if np.mod(i, 2) == 1:
                        ax.plot(hx[:, i],
                                hy[:, i],
                                color=c,
                                marker=None,
                                linestyle='--',
                                linewidth=1)
                    else:
                        ax.plot(hx[:, i],
                                hy[:, i],
                                color=c,
                                marker=None,
                                linestyle='--',
                                linewidth=1,
                                markersize=10)
                if bin_labels is not None:
                    ax.text(hxv[1, i],
                            hyv[1, i],
                            bin_labels[i],
                            fontsize=10,
                            horizontalalignment='center',
                            verticalalignment='center',
                            color=np.array([1, 1, 1]) * 0.45)
                ax.axis(axis_)

        if plot_axis_labels == False:
            ax.set_xticklabels([])
            ax.set_yticklabels([])
        else:
            ax.set_xlabel("a'")
            ax.set_ylabel("b'")

        ax.plot(0, 0, color='grey', marker='+', linestyle=None, markersize=6)

        if (axtype != 'polar') & (plot_10_20_circles == True):
            r = np.array([
                0.8, 0.9, 1.1, 1.2
            ]) * scalef  # plot circles at 80, 90, 100, 110, 120 % of scale f
            plotcircle(radii=r,
                       angles=np.arange(0, 365, 5),
                       color='w',
                       linestyle='-',
                       axh=ax,
                       linewidth=0.5)
            plotcircle(radii=[scalef],
                       angles=np.arange(0, 365, 5),
                       color='k',
                       linestyle='-',
                       axh=ax,
                       linewidth=1)
            ax.text(0,
                    -0.75 * scalef,
                    '-20%',
                    fontsize=8,
                    horizontalalignment='center',
                    verticalalignment='center',
                    color='w')
            ax.text(0,
                    -1.25 * scalef,
                    '+20%',
                    fontsize=8,
                    horizontalalignment='center',
                    verticalalignment='center',
                    color='w')

        if (axtype != 'polar') & (plot_bin_colors == True) & (_CVG_BG
                                                              is not None):
            ax.imshow(_CVG_BG, origin='upper', extent=axis_)

    return fig, ax, cmap