def histogram(a, bins=10, bin_center = False, range=None, normed=False, weights=None, density=None): """ Histogram function that can take as bins either the center (cfr. matlab hist) or bin-edges. Args: :bin_center: | False, optional | False: if :bins: int, str or sequence of scalars: | default to numpy.histogram (uses bin edges). | True: if :bins: is a sequence of scalars: | bins (containing centers) are transformed to edges | and nump.histogram is run. | Mimicks matlab hist (uses bin centers). Note: For other armuments and output, see ?numpy.histogram Returns: :returns: | ndarray with histogram """ if (isinstance(bins, list) | isinstance(bins, np.ndarray)) & (bin_center == True): if len(bins) == 1: edges = np.hstack((bins[0],np.inf)) else: centers = bins d = np.diff(centers)/2 edges = np.hstack((centers[0]-d[0], centers[:-1] + d, centers[-1] + d[-1])) edges[1:] = edges[1:] + np.finfo(float).eps return np.histogram(a, bins=edges, range=range, normed=normed, weights=weights, density=density) else: return np.histogram(a, bins=bins, range=range, normed=normed, weights=weights, density=density)
def v_to_cik(v, inverse = False): """ Calculate 2x2 '(covariance matrix)^-1' elements cik Args: :v: | (Nx5) np.ndarray | ellipse parameters [Rmax,Rmin,xc,yc,theta] :inverse: | If True: return inverse of cik. Returns: :cik: | 'Nx2x2' (covariance matrix)^-1 Notes: | cik is not actually a covariance matrix, | only for a Gaussian or normal distribution! """ v = np.atleast_2d(v) g11 = (1/v[:,0]*np.cos(v[:,4]))**2 + (1/v[:,1]*np.sin(v[:,4]))**2 g22 = (1/v[:,0]*np.sin(v[:,4]))**2 + (1/v[:,1]*np.cos(v[:,4]))**2 g12 = (1/v[:,0]**2 - 1/v[:,1]**2)*np.sin(v[:,4])*np.cos(v[:,4]) cik = np.zeros((g11.shape[0],2,2)) for i in range(g11.shape[0]): cik[i,:,:] = np.vstack((np.hstack((g11[i],g12[i])), np.hstack((g12[i],g22[i])))) if inverse == True: cik[i,:,:] = np.linalg.inv(cik[i,:,:]) return cik
def _plot_DEs_vs_digital_values(DEslab, DEsl, DEsab, rgbcal, avg = lambda x: ((x**2).mean()**0.5), nbit = 8, verbosity = 1): """ Make a plot of the lab, l and ab color differences for the different calibration stimulus types. """ if verbosity > 0: p_pure = [(rgbcal[:,1]==0) & (rgbcal[:,2]==0), (rgbcal[:,0]==0) & (rgbcal[:,2]==0), (rgbcal[:,0]==0) & (rgbcal[:,1]==0)] 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)) p_cyans = (rgbcal[:,0]==0) & (rgbcal[:,1]!=0) & (rgbcal[:,2]!=0) p_yellows = (rgbcal[:,0]!=0) & (rgbcal[:,1]!=0) & (rgbcal[:,2]==0) p_magentas = (rgbcal[:,0]!=0) & (rgbcal[:,1]==0) & (rgbcal[:,2]==0) fig,(ax0,ax1,ax2) = plt.subplots(nrows=1,ncols=3, figsize = (15,4)) rgb_colors='rgb' rgb_labels=['red','green','blue'] marker ='o' markersize = 10 if p_whites.any(): ax0.plot(rgbcal[p_whites,0], DEslab[p_whites],'ks',markersize = markersize, label='white') ax1.plot(rgbcal[p_whites,0], DEsl[p_whites],'ks',markersize = markersize,label='white') ax2.plot(rgbcal[p_whites,0], DEsab[p_whites],'ks',markersize = markersize,label='white') if p_grays.any(): ax0.plot(rgbcal[p_grays,0], DEslab[p_grays], color = 'gray', marker = marker,linestyle='none',label='gray') ax1.plot(rgbcal[p_grays,0], DEsl[p_grays], color = 'gray', marker = marker,linestyle='none',label='gray') ax2.plot(rgbcal[p_grays,0], DEsab[p_grays], color = 'gray', marker = marker,linestyle='none',label='gray') for i in range(3): if p_pure[i].any(): ax0.plot(rgbcal[p_pure[i],i], DEslab[p_pure[i]],rgb_colors[i]+marker,label=rgb_labels[i]) ax1.plot(rgbcal[p_pure[i],i], DEsl[p_pure[i]],rgb_colors[i]+marker,label=rgb_labels[i]) ax2.plot(rgbcal[p_pure[i],i], DEsab[p_pure[i]],rgb_colors[i]+marker,label=rgb_labels[i]) if p_cyans.any(): ax0.plot(rgbcal[p_cyans,1], DEslab[p_cyans],'c'+marker,label='cyan') ax1.plot(rgbcal[p_cyans,1], DEsl[p_cyans],'c'+marker,label='cyan') ax2.plot(rgbcal[p_cyans,1], DEsab[p_cyans],'c'+marker,label='cyan') if p_yellows.any(): ax0.plot(rgbcal[p_yellows,0], DEslab[p_yellows],'y'+marker,label='yellow') ax1.plot(rgbcal[p_yellows,0], DEsl[p_yellows],'y'+marker,label='yellow') ax2.plot(rgbcal[p_yellows,0], DEsab[p_yellows],'y'+marker,label='yellow') if p_magentas.any(): ax0.plot(rgbcal[p_magentas,0], DEslab[p_magentas],'m'+marker,label='magenta') ax1.plot(rgbcal[p_magentas,0], DEsl[p_magentas],'m'+marker,label='magenta') ax2.plot(rgbcal[p_magentas,0], DEsab[p_magentas],'m'+marker,label='magenta') ax0.plot(np.array([0,(2**nbit-1)*1.05]),np.hstack((avg(DEslab),avg(DEslab))),color = 'r',linewidth=2,linestyle='--') ax0.set_xlabel('digital values') ax0.set_ylabel('Color difference DElab') ax0.axis([0,(2**nbit-1)*1.05,0,max(DEslab)*1.1]) ax0.set_title('DElab') ax1.plot(np.array([0,(2**nbit-1)*1.05]),np.hstack((avg(DEsl),avg(DEsl))),color = 'r',linewidth=2,linestyle='--') ax1.set_xlabel('digital values') ax1.set_ylabel('Color difference DEl') ax1.axis([0,(2**nbit-1)*1.05,0,max(DEslab)*1.1]) ax1.set_title('DEl') ax2.plot(np.array([0,(2**nbit-1)*1.05]),np.hstack((avg(DEsab),avg(DEsab))),color = 'r',linewidth=2,linestyle='--') ax2.set_xlabel('digital values') ax2.set_ylabel('Color difference DEab') ax2.set_title('DEab') ax2.axis([0,(2**nbit-1)*1.05,0,max(DEslab)*1.1]) ax2.legend(loc='upper left')
def plot(v, origin=None, ax=None, color='k', marker='.', linestyle='-', **kwargs): """ Plot a vector from origin. Args: :v: | vec3 vector. :origin: | vec3 vector with same size attributes as in :v:. :ax: | None, optional | axes handle. | If None, create new figure with axes ax. :color: | 'k', optional | color specifier. :marker: | '.', optional | marker specifier. :linestyle: | '-', optional | linestyle specifier :**kwargs: | other keyword specifiers for plot. Returns: :ax: | handle to figure axes. """ if ax is None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') if origin is None: origin = vec3(np.zeros(v.x.shape), np.zeros(v.x.shape), np.zeros(v.x.shape)) ax.plot(np.hstack([origin.x, v.x]), np.hstack([origin.y, v.y]), np.hstack([origin.z, v.z]), color=color, marker=marker, **kwargs) ax.set_xlabel('x') ax.set_ylabel('y') ax.set_zlabel('z') return ax
def plot_rgb_color_patches(rgb, patch_shape=(100, 100), patch_layout=None, ax=None, show=True): """ Create (and plot) an image with patches with specified rgb values. Args: :rgb: | ndarray with rgb values for each of the patches :patch_shape: | (100,100), optional | shape of each of the patches in the image :patch_layout: | None, optional | If None: layout is calculated automatically to give a 'good' aspect ratio :ax: | None, optional | Axes to plot the image in. If None: a new axes is created. :show: | True, optional | If True: plot image in axes and return axes handle; else: return ndarray with image. Return: :ax: or :imagae: | Axes is returned if show == True, else: ndarray with rgb image is returned. """ if ax is None: fig, ax = plt.subplots(1, 1) if patch_layout is None: patch_layout = get_subplot_layout(rgb.shape[0]) image = np.zeros( np.hstack((np.array(patch_shape) * np.array(patch_layout), 3))) for i in range(rgb.shape[0]): r, c = np.unravel_index(i, patch_layout) R = int(r * patch_shape[0]) C = int(c * patch_shape[1]) image[R:R + patch_shape[0], C:C + patch_shape[1], :] = np.ones(np.hstack( (patch_shape, 3))) * rgb[i, None, :] if show == False: return image else: ax.imshow(image.astype('uint8')) ax.axis('off') return ax
def join(self, data): """ Join data along last axis and return instance. """ if data[0].ndim == 2: #faster implementation self.value = np.transpose( np.concatenate(data, axis=0).reshape((np.hstack( (len(data), data[0].shape)))), (1, 2, 0)) elif data[0].ndim == 1: self.value = np.concatenate(data, axis=0).reshape((np.hstack( (len(data), data[0].shape)))).T else: self.value = np.hstack(data)[0] return self
def getwld(wl): """ Get wavelength spacing. Args: :wl: | ndarray with wavelengths Returns: :returns: | - float: for equal wavelength spacings | - ndarray (.shape = (n,)): for unequal wavelength spacings """ d = np.diff(wl) dl = (np.hstack((d[0], d[0:-1] / 2.0, d[-1])) + np.hstack( (0.0, d[1:] / 2.0, 0.0))) if np.array_equal(dl, dl.mean() * np.ones(dl.shape)): dl = dl[0] return dl
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
def _complete_ies_lid(IES, lamp_h_type='TYPE90'): """ Convert IES LID map with lamp_h_type symmetry to a 'full' map with phi: [0,360] and theta: [0,180]. """ # Create full theta (0-180) and phi (0-360) sets IES['theta'] = IES['v_angs'] if IES['lamp_h_type'] == 'TYPE90': IES['values'] = np.matlib.repmat(IES['candela_2d'], 4, 1) IES['phi'] = np.hstack((IES['h_angs'], IES['h_angs'] + 90, IES['h_angs'] + 180, IES['h_angs'] + 270)) elif IES['lamp_h_type'] == 'TYPE180': IES['values'] = np.matlib.repmat(IES['candela_2d'], 2, 1) IES['phi'] = np.hstack((IES['h_angs'], IES['h_angs'] + 180)) else: IES['values'] = IES['candela_2d'] IES['phi'] = IES['h_angs'] IES['map']['thetas'] = IES['theta'] IES['map']['phis'] = IES['phi'] IES['map']['values'] = IES['values'] return IES
def xtransform(x, params): """ Converts unconstrained variables into their original domains. """ xtrans = np.zeros((params['n'])) # k allows some variables to be fixed, thus dropped from the optimization. k = 0 for i in range(params['n']): if params['BoundClass'][i] == 1: # lower bound only xtrans[i] = params['LB'][i] + x[k]**2 elif params['BoundClass'][i] == 2: # upper bound only xtrans[i] = params['UB'][i] - x[k]**2 elif params['BoundClass'][i] == 3: # lower and upper bounds xtrans[i] = (np.sin(x[k]) + 1) / 2 xtrans[i] = xtrans[i] * (params['UB'][i] - params['LB'][i]) + params['LB'][i] # just in case of any floating point problems xtrans[i] = np.hstack( (params['LB'][i], np.hstack( (params['UB'][i], xtrans[i])).min())).max() elif params['BoundClass'][i] == 4: # fixed variable, bounds are equal, set it at either bound xtrans[i] = params['LB'][i] elif params['BoundClass'][i] == 0: # unconstrained variable. xtrans[i] = x[k] if params['BoundClass'][i] != 4: k += 1 return xtrans
def get_discrimination_ellipse(Yxy = np.array([[100,1/3,1/3]]), etype = 'fmc2', nsteps = 10, k_neighbours = 3, average_cik = True, Y = None): """ Get discrimination ellipse(s) in v-format (R,r, xc, yc, theta) for Yxy using an interpolation of the MacAdam ellipses or using FMC-1 or FMC-2. Args: :Yxy: | 2D ndarray with [Y,]x,y coordinate centers. | If Yxy.shape[-1]==2: Y is added using the value from the Y-input argument. :etype: | 'fmc2', optional | Type color discrimination ellipse estimation to use. | options: 'macadam', 'fmc1', 'fmc2' | - 'macadam': interpolate covariance matrices of closest MacAdam ellipses (see: get_macadam_ellipse?). | - 'fmc1': use FMC-1 from ref 2 (see get_fmc_discrimination_ellipse?). | - 'fmc2': use FMC-1 from ref 3 (see get_fmc_discrimination_ellipse?). :nsteps: | 10, optional | Set multiplication factor for ellipses | (nsteps=1 corresponds to approximately 1 MacAdam step, | for FMC-2, Y also has to be 10.69, see note below). :k_neighbours: | 3, optional | Only for option 'macadam'. | Number of nearest ellipses to use to calculate ellipse at xy :average_cik: | True, optional | Only for option 'macadam'. | If True: take distance weighted average of inverse | 'covariance ellipse' elements cik. | If False: average major & minor axis lengths and | ellipse orientation angles directly. :Y: | None, optional | Only for option 'fmc2'(see note below). | If not None: Y = 10.69 and overrides values in Yxy. Note: 1. FMC-2 is almost identical to FMC-1 is Y = 10.69!; see [3] References: 1. MacAdam DL. Visual Sensitivities to Color Differences in Daylight*. J Opt Soc Am. 1942;32(5):247-274. 2. Chickering, K.D. (1967), Optimization of the MacAdam-Modified 1965 Friele Color-Difference Formula, 57(4):537-541 3. Chickering, K.D. (1971), FMC Color-Difference Formulas: Clarification Concerning Usage, 61(1):118-122 """ if Yxy.shape[-1] == 2: Yxy = np.hstack((100*np.ones((Yxy.shape[0],1)),Yxy)) if Y is not None: Yxy[...,0] = Y if etype == 'macadam': return get_macadam_ellipse(xy = Yxy[...,1:], k_neighbours = k_neighbours, nsteps = nsteps, average_cik = average_cik) else: return get_fmc_discrimination_ellipse(Yxy = Yxy, etype = etype, nsteps = nsteps, Y = Y)
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
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
def get_gij_fmc(Yxy, etype = 'fmc2', ellipsoid = True, Y = None, cspace = 'Yxy'): """ Get gij matrices describing the discrimination ellipses/ellipsoids for Yxy or xyz using FMC-1 or FMC-2. Args: :Yxy: | 2D ndarray with [Y,]x,y coordinate centers. | If Yxy.shape[-1]==2: Y is added using the value from the Y-input argument. :etype: | 'fmc2', optional | Type of FMC color discrimination equations to use (see references below). | options: 'fmc1', fmc2' :Y: | None, optional | Only affects FMC-2 (see note below). | If not None: Y = 10.69 and overrides values in Yxy. :ellipsoid: | True, optional | If True: return ellipsoids, else return ellipses (only if cspace == 'Yxy')! :cspace: | 'Yxy', optional | Return coefficients for Yxy-ellipses/ellipsoids ('Yxy') or XYZ ellipsoids ('xyz') Note: 1. FMC-2 is almost identical to FMC-1 is Y = 10.69!; see [2] References: 1. Chickering, K.D. (1967), Optimization of the MacAdam-Modified 1965 Friele Color-Difference Formula, 57(4), p.537-541 2. Chickering, K.D. (1971), FMC Color-Difference Formulas: Clarification Concerning Usage, 61(1), p.118-122 """ if Yxy.shape[-1] == 2: Yxy = np.hstack((100*np.ones((Yxy.shape[0],1)),Yxy)) if Y is not None: Yxy[...,0] = Y xyz = Yxy_to_xyz(Yxy) if etype == 'fmc2': gij = _get_gij_fmc_2(xyz, cspace = cspace) else: gij = _get_gij_fmc_1(xyz, cspace = cspace) if ellipsoid == True: return gij else: if cspace.lower()=='xyz': return gij else: return gij[:,1:,1:]
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
def getUSCensusAgeDist(): """ Get US Census Age Distribution """ t_num = _INDVCMF_DATA['USCensus2010population'] list_AgeCensus = t_num[0] freq_AgeCensus = np.round( t_num[1] / 1000 ) # Reduce # of populations to manageable number, this doesn't change probability # Remove age < 10 and 70 < age: freq_AgeCensus[:10] = 0 freq_AgeCensus[71:] = 0 list_Age = [] for k in range(len(list_AgeCensus)): list_Age = np.hstack( (list_Age, np.repeat(list_AgeCensus[k], freq_AgeCensus[k]))) return list_Age
def dtlz_range_(fname, M): """ Returns the decision range of a DTLZ function | The range is simply [0,1] for all variables. What varies is the number | of decision variables in each problem. The equation for that is | n = (M-1) + k | wherein k = 5 for DTLZ1, 10 for DTLZ2-6, and 20 for DTLZ7. Args: :fname: | a string with the name of the function ('dtlz1', 'dtlz2' etc.) :M: | a scalar with the number of objectives Returns: :lim: | a n x 2 matrix wherein the first column is the lower limit |(0), and the second column, the upper limit of search (1) """ #Checks if the string has or not the prefix 'dtlz', or if the number later #is greater than 7: fname = fname.lower() if (len(fname) < 5) or (fname[:4] != 'dtlz') or (float(fname[4]) > 7) : raise Exception('Sorry, the function {:s} is not implemented.'.format(fname)) # If the name is o.k., defines the value of k if fname == 'dtlz1': k = 5 elif fname == 'dtlz7': k = 20 else: #any other function k = 10; n = (M-1) + k #number of decision variables lim = np.hstack((np.zeros((n,1)), np.ones((n,1)))) return lim
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
def fit_ellipse(xy, center_on_mean_xy = False): """ Fit an ellipse to supplied data points. Args: :xy: | coordinates of points to fit (Nx2 array) :center_on_mean_xy: | False, optional | Center ellipse on mean of xy | (otherwise it might be offset due to solving | the contrained minization problem: aT*S*a, see ref below.) Returns: :v: | vector with ellipse parameters [Rmax,Rmin, xc,yc, theta (rad.)] Reference: 1. Fitzgibbon, A.W., Pilu, M., and Fischer R.B., Direct least squares fitting of ellipsees, Proc. of the 13th Internation Conference on Pattern Recognition, pp 253–257, Vienna, 1996. """ # remove centroid: # center = xy.mean(axis=0) # xy = xy - center # Fit ellipse: x, y = xy[:,0:1], xy[:,1:2] D = np.hstack((x * x, x * y, y * y, x, y, np.ones_like(x))) S, C = np.dot(D.T, D), np.zeros([6, 6]) C[0, 2], C[2, 0], C[1, 1] = 2, 2, -1 U, s, V = np.linalg.svd(np.dot(np.linalg.inv(S), C)) e = U[:, 0] # E, V = np.linalg.eig(np.dot(np.linalg.inv(S), C)) # n = np.argmax(np.abs(E)) # e = V[:,n] # get ellipse axis lengths, center and orientation: b, c, d, f, g, a = e[1] / 2, e[2], e[3] / 2, e[4] / 2, e[5], e[0] # get ellipse center: num = b * b - a * c if num == 0: xc = 0 yc = 0 else: xc = ((c * d - b * f) / num) yc = ((a * f - b * d) / num) # get ellipse orientation: theta = np.arctan2(np.array(2 * b), np.array((a - c))) / 2 # if b == 0: # if a > c: # theta = 0 # else: # theta = np.pi/2 # else: # if a > c: # theta = np.arctan2(2*b,(a-c))/2 # else: # theta = np.arctan2(2*b,(a-c))/2 + np.pi/2 # axis lengths: up = 2 * (a * f * f + c * d * d + g * b * b - 2 * b * d * f - a * c * g) down1 = (b * b - a * c) * ((c - a) * np.sqrt(1 + 4 * b * b / ((a - c) * (a - c))) - (c + a)) down2 = (b * b - a * c) * ((a - c) * np.sqrt(1 + 4 * b * b / ((a - c) * (a - c))) - (c + a)) a, b = np.sqrt((up / down1)), np.sqrt((up / down2)) # assert that a is the major axis (otherwise swap and correct angle) if(b > a): b, a = a, b # ensure the angle is betwen 0 and 2*pi theta = fmod(theta, 2.0 * np.pi) if center_on_mean_xy == True: xc,yc = xy.mean(axis=0) return np.hstack((a, b, xc, yc, theta))
def plot_chromaticity_diagram_colors(diagram_samples = 256, diagram_opacity = 1.0, diagram_lightness = 0.25,\ cieobs = _CIEOBS, cspace = 'Yxy', cspace_pars = {},\ show = True, axh = None,\ show_grid = False, label_fontname = 'Times New Roman', label_fontsize = 12,\ **kwargs): """ Plot the chromaticity diagram colors. Args: :diagram_samples: | 256, optional | Sampling resolution of color space. :diagram_opacity: | 1.0, optional | Sets opacity of chromaticity diagram :diagram_lightness: | 0.25, optional | Sets lightness of chromaticity diagram :axh: | None or axes handle, optional | Determines axes to plot data in. | None: make new figure. :show: | True or False, optional | Invoke matplotlib.pyplot.show() right after plotting :cieobs: | luxpy._CIEOBS or str, optional | Determines CMF set to calculate spectrum locus or other. :cspace: | luxpy._CSPACE or str, optional | Determines color space / chromaticity diagram to plot data in. | Note that data is expected to be in specified :cspace: :cspace_pars: | {} or dict, optional | Dict with parameters required by color space specified in :cspace: | (for use with luxpy.colortf()) :show_grid: | False, optional | Show grid (True) or not (False) :label_fontname: | 'Times New Roman', optional | Sets font type of axis labels. :label_fontsize: | 12, optional | Sets font size of axis labels. :kwargs: | additional keyword arguments for use with matplotlib.pyplot. Returns: """ if isinstance(cieobs, str): SL = _CMF[cieobs]['bar'][1:4].T else: SL = cieobs[1:4].T SL = 100.0 * SL / (SL[:, 1, None] + _EPS) SL = SL[SL.sum(axis=1) > 0, :] # avoid div by zero in xyz-to-Yxy conversion SL = colortf(SL, tf=cspace, tfa0=cspace_pars) plambdamax = SL[:, 1].argmax() SL = np.vstack( (SL[:(plambdamax + 1), :], SL[0]) ) # add lowest wavelength data and go to max of gamut in x (there is a reversal for some cmf set wavelengths >~700 nm!) Y, x, y = asplit(SL) SL = np.vstack((x, y)).T # create grid for conversion to srgb offset = _EPS min_x = min(offset, x.min()) max_x = max(1, x.max()) min_y = min(offset, y.min()) max_y = max(1, y.max()) ii, jj = np.meshgrid( np.linspace(min_x - offset, max_x + offset, int(diagram_samples)), np.linspace(max_y + offset, min_y - offset, int(diagram_samples))) ij = np.dstack((ii, jj)) ij[ij == 0] = offset ij2D = ij.reshape((diagram_samples**2, 2)) ij2D = np.hstack((diagram_lightness * 100 * np.ones( (ij2D.shape[0], 1)), ij2D)) xyz = colortf(ij2D, tf=cspace + '>xyz', tfa0=cspace_pars) xyz[xyz < 0] = 0 xyz[np.isinf(xyz.sum(axis=1)), :] = np.nan xyz[np.isnan(xyz.sum(axis=1)), :] = offset srgb = xyz_to_srgb(xyz) srgb = srgb / srgb.max() srgb = srgb.reshape((diagram_samples, diagram_samples, 3)) if show == True: if axh is None: fig = plt.figure() axh = fig.add_subplot(111) polygon = Polygon(SL, facecolor='none', edgecolor='none') axh.add_patch(polygon) image = axh.imshow(srgb, interpolation='bilinear', extent=(min_x, max_x, min_y - 0.05, max_y), clip_path=None, alpha=diagram_opacity) image.set_clip_path(polygon) axh.plot(x, y, color='darkgray') if (cspace == 'Yxy') & (isinstance(cieobs, str)): axh.set_xlim([0, 1]) axh.set_ylim([0, 1]) elif (cspace == 'Yuv') & (isinstance(cieobs, str)): axh.set_xlim([0, 0.6]) axh.set_ylim([0, 0.6]) if (cspace is not None): xlabel = _CSPACE_AXES[cspace][1] ylabel = _CSPACE_AXES[cspace][2] if (label_fontname is not None) & (label_fontsize is not None): axh.set_xlabel(xlabel, fontname=label_fontname, fontsize=label_fontsize) axh.set_ylabel(ylabel, fontname=label_fontname, fontsize=label_fontsize) if show_grid == True: axh.grid(True) #plt.show() return axh else: return None
def plotellipse(v, cspace_in = 'Yxy', cspace_out = None, nsamples = 100, \ show = True, axh = None, \ line_color = 'darkgray', line_style = ':', line_width = 1, line_marker = '', line_markersize = 4,\ plot_center = False, center_marker = 'o', center_color = 'darkgray', center_markersize = 4,\ show_grid = False, llabel = '', label_fontname = 'Times New Roman', label_fontsize = 12,\ out = None): """ Plot ellipse(s) given in v-format [Rmax,Rmin,xc,yc,theta]. Args: :v: | (Nx5) ndarray | ellipse parameters [Rmax,Rmin,xc,yc,theta] :cspace_in: | 'Yxy', optional | Color space of v. | If None: no color space assumed. Axis labels assumed ('x','y'). :cspace_out: | None, optional | Color space to plot ellipse(s) in. | If None: plot in cspace_in. :nsamples: | 100 or int, optional | Number of points (samples) in ellipse boundary :show: | True or boolean, optional | Plot ellipse(s) (True) or not (False) :axh: | None, optional | Ax-handle to plot ellipse(s) in. | If None: create new figure with axes. :line_color: | 'darkgray', optional | Color to plot ellipse(s) in. :line_style: | ':', optional | Linestyle of ellipse(s). :line_width': | 1, optional | Width of ellipse boundary line. :line_marker: | 'none', optional | Marker for ellipse boundary. :line_markersize: | 4, optional | Size of markers in ellipse boundary. :plot_center: | False, optional | Plot center of ellipse: yes (True) or no (False) :center_color: | 'darkgray', optional | Color to plot ellipse center in. :center_marker: | 'o', optional | Marker for ellipse center. :center_markersize: | 4, optional | Size of marker of ellipse center. :show_grid: | False, optional | Show grid (True) or not (False) :llabel: | None,optional | Legend label for ellipse boundary. :label_fontname: | 'Times New Roman', optional | Sets font type of axis labels. :label_fontsize: | 12, optional | Sets font size of axis labels. :out: | None, optional | Output of function | If None: returns None. Can be used to output axh of newly created | figure axes or to return Yxys an ndarray with coordinates of | ellipse boundaries in cspace_out (shape = (nsamples,3,N)) Returns: :returns: None, or whatever set by :out:. """ Yxys = np.zeros((nsamples, 3, v.shape[0])) ellipse_vs = np.zeros((v.shape[0], 5)) for i, vi in enumerate(v): # Set sample density of ellipse boundary: t = np.linspace(0, 2 * np.pi, int(nsamples)) a = vi[0] # major axis b = vi[1] # minor axis xyc = vi[2:4, None] # center theta = vi[-1] # rotation angle # define rotation matrix: R = np.hstack((np.vstack((np.cos(theta), np.sin(theta))), np.vstack((-np.sin(theta), np.cos(theta))))) # Calculate ellipses: Yxyc = np.vstack((1, xyc)).T Yxy = np.vstack( (np.ones((1, nsamples)), xyc + np.dot(R, np.vstack((a * np.cos(t), b * np.sin(t)))))).T Yxys[:, :, i] = Yxy # Convert to requested color space: if (cspace_out is not None) & (cspace_in is not None): Yxy = colortf(Yxy, cspace_in + '>' + cspace_out) Yxyc = colortf(Yxyc, cspace_in + '>' + cspace_out) Yxys[:, :, i] = Yxy # get ellipse parameters in requested color space: ellipse_vs[i, :] = math.fit_ellipse(Yxy[:, 1:]) #de = np.sqrt((Yxy[:,1]-Yxyc[:,1])**2 + (Yxy[:,2]-Yxyc[:,2])**2) #ellipse_vs[i,:] = np.hstack((de.max(),de.min(),Yxyc[:,1],Yxyc[:,2],np.nan)) # nan because orientation is xy, but request is some other color space. Change later to actual angle when fitellipse() has been implemented # plot ellipses: if show == True: if (axh is None) & (i == 0): fig = plt.figure() axh = fig.add_subplot(111) if (cspace_in is None): xlabel = 'x' ylabel = 'y' else: xlabel = _CSPACE_AXES[cspace_in][1] ylabel = _CSPACE_AXES[cspace_in][2] if (cspace_out is not None): xlabel = _CSPACE_AXES[cspace_out][1] ylabel = _CSPACE_AXES[cspace_out][2] if plot_center == True: axh.plot(Yxyc[:, 1], Yxyc[:, 2], color=center_color, linestyle='none', marker=center_marker, markersize=center_markersize) if llabel is None: axh.plot(Yxy[:, 1], Yxy[:, 2], color=line_color, linestyle=line_style, linewidth=line_width, marker=line_marker, markersize=line_markersize) else: axh.plot(Yxy[:, 1], Yxy[:, 2], color=line_color, linestyle=line_style, linewidth=line_width, marker=line_marker, markersize=line_markersize, label=llabel) axh.set_xlabel(xlabel, fontname=label_fontname, fontsize=label_fontsize) axh.set_ylabel(ylabel, fontname=label_fontname, fontsize=label_fontsize) if show_grid == True: plt.grid(True) #plt.show() Yxys = np.transpose(Yxys, axes=(0, 2, 1)) if out is not None: return eval(out) else: return None
def plotBB(ccts=None, cieobs=_CIEOBS, cspace=_CSPACE, axh=None, cctlabels=True, show=True, cspace_pars={}, formatstr='k-', **kwargs): """ Plot blackbody locus. Args: :ccts: | None or list[float], optional | None defaults to [1000 to 1e19 K]. | Range: | [1000,1500,2000,2500,3000,3500,4000,5000,6000,8000,10000] | + [15000 K to 1e19 K] in 100 steps on a log10 scale :cctlabels: | True or False, optional | Add cct text labels at various points along the blackbody locus. :axh: | None or axes handle, optional | Determines axes to plot data in. | None: make new figure. :show: | True or False, optional | Invoke matplotlib.pyplot.show() right after plotting :cieobs: | luxpy._CIEOBS or str, optional | Determines CMF set to calculate spectrum locus or other. :cspace: | luxpy._CSPACE or str, optional | Determines color space / chromaticity diagram to plot data in. | Note that data is expected to be in specified :cspace: :formatstr: | 'k-' or str, optional | Format str for plotting (see ?matplotlib.pyplot.plot) :cspace_pars: | {} or dict, optional | Dict with parameters required by color space specified in :cspace: | (for use with luxpy.colortf()) :kwargs: | additional keyword arguments for use with matplotlib.pyplot. Returns: :returns: | None (:show: == True) | or | handle to current axes (:show: == False) """ if ccts is None: ccts1 = np.array([ 1000.0, 1500.0, 2000.0, 2500.0, 3000.0, 3500.0, 4000.0, 5000.0, 6000.0, 8000.0, 10000.0 ]) ccts2 = 10**np.linspace(np.log10(15000.0), np.log10(10.0**19.0), 100) ccts = np.hstack((ccts1, ccts2)) else: ccts1 = None BB = cri_ref(ccts, ref_type='BB') xyz = spd_to_xyz(BB, cieobs=cieobs) Yxy = colortf(xyz, tf=cspace, tfa0=cspace_pars) Y, x, y = asplit(Yxy) axh = plot_color_data(x, y, axh=axh, cieobs=cieobs, cspace=cspace, show=show, formatstr=formatstr, **kwargs) if (cctlabels == True) & (ccts1 is not None): for i in range(ccts1.shape[0]): if ccts1[i] >= 3000.0: if i % 2 == 0.0: axh.plot(x[i], y[i], 'k+', color='0.5') axh.text(x[i] * 1.05, y[i] * 0.95, '{:1.0f}K'.format(ccts1[i]), color='0.5') axh.plot(x[-1], y[-1], 'k+', color='0.5') axh.text(x[-1] * 1.05, y[-1] * 0.95, '{:1.0e}K'.format(ccts[-1]), color='0.5') if show == False: return axh
axs[1].axis('off') px_rmse = ((hrhsi_est - hrhsi)**2).sum(axis=-1)**0.5 # rmse per pixel axs[2].set_title( 'RMSE(ground-truth, estimated) HR-HSI\nRMSE = {:1.4f}, max = {:1.4f}'. format((px_rmse**2).mean()**0.5, px_rmse.max())) im = axs[2].imshow(px_rmse, cmap='jet', aspect='auto') # rmse per pixel cbar = axs[2].figure.colorbar(im, ax=axs[2]) cbar.ax.set_ylabel('RMSE', rotation=-90, va="bottom") psorted = np.unravel_index( np.argsort(px_rmse, axis=None), px_rmse.shape) # index of pixels sorted by px_rmse np.random.seed(1) pxs = np.random.permutation(min(hrhsi.shape[:2]))[:12].reshape(2, 3, 2) iis = np.hstack((pxs[..., 0].ravel(), psorted[0][-3:])) jjs = np.hstack((pxs[..., 1].ravel(), psorted[1][-3:])) colors = np.array(['m', 'b', 'c', 'g', 'y', 'r', 'k', 'lightgrey', 'grey']) for t in range(len(iis)): ii, jj = iis[t], jjs[t] axs[1].plot(jj, ii, color=colors[t], marker='o', mec='w') axs[2].plot(jj, ii, color=colors[t], marker='o', mec='w') axs[3].plot(wlr, hrhsi[ii, jj, :], color=colors[t], linestyle='-', label='ground-truth (r{:1.0f},c{:1.0f})'.format(ii, jj)) axs[3].plot(wlr, hrhsi_est[ii, jj, :], color=colors[t], linestyle='--',
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
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
def get_poly_model(jabt, jabr, modeltype=_VF_MODEL_TYPE): """ Setup base color shift model (delta_a, delta_b), determine model parameters and accuracy. | Calculates a base color shift (delta) from the ref. chromaticity ar, br. Args: :jabt: | ndarray with jab color coordinates under the test SPD. :jabr: | ndarray with jab color coordinates under the reference SPD. :modeltype: | _VF_MODEL_TYPE or 'M6' or 'M5', optional | Specifies degree 5 or degree 6 polynomial model in ab-coordinates. | (see notes below) Returns: :returns: | (poly_model, | pmodel, | dab_model, | dab_res, | dCHoverC_res, | dab_std, | dCHoverC_std) | | :poly_model: function handle to model | :pmodel: ndarray with model parameters | :dab_model: ndarray with ab model predictions from ar, br. | :dab_res: ndarray with residuals between 'da,db' of samples and | 'da,db' predicted by the model. | :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 below) | :dab_std: ndarray with std of :dab_res: | :dCHoverC_std: ndarray with std of :dCHoverC_res: Notes: 1. Model types: | poly5_model = lambda a,b,p: p[0]*a + p[1]*b + p[2]*(a**2) + p[3]*a*b + p[4]*(b**2) | poly6_model = lambda a,b,p: p[0] + p[1]*a + p[2]*b + p[3]*(a**2) + p[4]*a*b + p[5]*(b**2) 2. Calculation of dCoverC and dH: | dCoverC = (np.cos(hr)*da + np.sin(hr)*db)/Cr | dHoverC = (np.cos(hr)*db - np.sin(hr)*da)/Cr """ at = jabt[..., 1] bt = jabt[..., 2] ar = jabr[..., 1] br = jabr[..., 2] # A. Calculate da, db: da = at - ar db = bt - br # B.1 Calculate model matrix: # 5-parameter model: M5 = np.array([[ np.sum(ar * ar), np.sum(ar * br), np.sum(ar * ar**2), np.sum(ar * ar * br), np.sum(ar * br**2) ], [ np.sum(br * ar), np.sum(br * br), np.sum(br * ar**2), np.sum(br * ar * br), np.sum(br * br**2) ], [ np.sum((ar**2) * ar), np.sum((ar**2) * br), np.sum((ar**2) * ar**2), np.sum((ar**2) * ar * br), np.sum((ar**2) * br**2) ], [ np.sum(ar * br * ar), np.sum(ar * br * br), np.sum(ar * br * ar**2), np.sum(ar * br * ar * br), np.sum(ar * br * br**2) ], [ np.sum((br**2) * ar), np.sum((br**2) * br), np.sum((br**2) * ar**2), np.sum((br**2) * ar * br), np.sum((br**2) * br**2) ]]) #6-parameters model M6 = np.array([[ ar.size, np.sum(1.0 * ar), np.sum(1.0 * br), np.sum(1.0 * ar**2), np.sum(1.0 * ar * br), np.sum(1.0 * br**2) ], [ np.sum(ar * 1.0), np.sum(ar * ar), np.sum(ar * br), np.sum(ar * ar**2), np.sum(ar * ar * br), np.sum(ar * br**2) ], [ np.sum(br * 1.0), np.sum(br * ar), np.sum(br * br), np.sum(br * ar**2), np.sum(br * ar * br), np.sum(br * br**2) ], [ np.sum((ar**2) * 1.0), np.sum((ar**2) * ar), np.sum((ar**2) * br), np.sum((ar**2) * ar**2), np.sum((ar**2) * ar * br), np.sum((ar**2) * br**2) ], [ np.sum(ar * br * 1.0), np.sum(ar * br * ar), np.sum(ar * br * br), np.sum(ar * br * ar**2), np.sum(ar * br * ar * br), np.sum(ar * br * br**2) ], [ np.sum((br**2) * 1.0), np.sum((br**2) * ar), np.sum((br**2) * br), np.sum((br**2) * ar**2), np.sum((br**2) * ar * br), np.sum((br**2) * br**2) ]]) # B.2 Define model function: poly5_model = lambda a, b, p: p[0] * a + p[1] * b + p[2] * (a**2) + p[ 3] * a * b + p[4] * (b**2) poly6_model = lambda a, b, p: p[0] + p[1] * a + p[2] * b + p[3] * ( a**2) + p[4] * a * b + p[5] * (b**2) if modeltype == 'M5': M = M5 poly_model = poly5_model else: M = M6 poly_model = poly6_model M = np.linalg.inv(M) # C.1 Data a,b analysis output: if modeltype == 'M5': da_model_parameters = np.dot( M, np.array([ np.sum(da * ar), np.sum(da * br), np.sum(da * ar**2), np.sum(da * ar * br), np.sum(da * br**2) ])) db_model_parameters = np.dot( M, np.array([ np.sum(db * ar), np.sum(db * br), np.sum(db * ar**2), np.sum(db * ar * br), np.sum(db * br**2) ])) else: da_model_parameters = np.dot( M, np.array([ np.sum(da * 1.0), np.sum(da * ar), np.sum(da * br), np.sum(da * ar**2), np.sum(da * ar * br), np.sum(da * br**2) ])) db_model_parameters = np.dot( M, np.array([ np.sum(db * 1.0), np.sum(db * ar), np.sum(db * br), np.sum(db * ar**2), np.sum(db * ar * br), np.sum(db * br**2) ])) pmodel = np.vstack((da_model_parameters, db_model_parameters)) # D.1 Calculate model da, db: da_model = poly_model(ar, br, pmodel[0]) db_model = poly_model(ar, br, pmodel[1]) dab_model = np.hstack((da_model, db_model)) # D.2 Calculate residuals for da & db: da_res = da - da_model db_res = db - db_model dab_res = np.hstack((da_res, db_res)) dab_std = np.vstack((np.std(da_res, axis=0), np.std(db_res, axis=0))) # E Calculate href, Cref: href = np.arctan2(br, ar) Cref = (ar**2 + br**2)**0.5 # F Calculate dC/C, dH/C for data and model and calculate residuals: dCoverC = (np.cos(href) * da + np.sin(href) * db) / Cref dHoverC = (np.cos(href) * db - np.sin(href) * da) / Cref dCoverC_model = (np.cos(href) * da_model + np.sin(href) * db_model) / Cref dHoverC_model = (np.cos(href) * db_model - np.sin(href) * da_model) / Cref dCoverC_res = dCoverC - dCoverC_model dHoverC_res = dHoverC - dHoverC_model dCHoverC_std = np.vstack((np.std(dCoverC_res, axis=0), np.std(dHoverC_res, axis=0))) dCHoverC_res = np.hstack((href, dCoverC_res, dHoverC_res)) return poly_model, pmodel, dab_model, dab_res, dCHoverC_res, dab_std, dCHoverC_std
def render_image(img = None, spd = None, rfl = None, out = 'img_hyp', \ refspd = None, D = None, cieobs = _CIEOBS, \ cspace = 'xyz', cspace_tf = {}, CSF = None,\ interp_type = 'nd', k_neighbours = 4, show = True, verbosity = 0, show_ref_img = True,\ stack_test_ref = 12,\ write_to_file = None): """ Render image under specified light source spd. Args: :img: | None or str or ndarray with float (max = 1) rgb image. | None load a default image. :spd: | ndarray, optional | Light source spectrum for rendering | If None: use CIE illuminant F4 :rfl: | ndarray, optional | Reflectance set for color coordinate to rfl mapping. :out: | 'img_hyp' or str, optional | (other option: 'img_ren': rendered image under :spd:) :refspd: | None, optional | Reference spectrum for color coordinate to rfl mapping. | None defaults to D65 (srgb has a D65 white point) :D: | None, optional | Degree of (von Kries) adaptation from spd to refspd. :cieobs: | _CIEOBS, optional | CMF set for calculation of xyz from spectral data. :cspace: | 'xyz', optional | Color space for color coordinate to rfl mapping. | Tip: Use linear space (e.g. 'xyz', 'Yuv',...) for (interp_type == 'nd'), | and perceptually uniform space (e.g. 'ipt') for (interp_type == 'nearest') :cspace_tf: | {}, optional | Dict with parameters for xyz_to_cspace and cspace_to_xyz transform. :CSF: | None, optional | RGB camera response functions. | If None: input :xyz: contains raw rgb values. Override :cspace: | argument and perform estimation directly in raw rgb space!!! :interp_type: | 'nd', optional | Options: | - 'nd': perform n-dimensional linear interpolation using Delaunay triangulation. | - 'nearest': perform nearest neighbour interpolation. :k_neighbours: | 4 or int, optional | Number of nearest neighbours for reflectance spectrum interpolation. | Neighbours are found using scipy.spatial.cKDTree :show: | True, optional | Show images. :verbosity: | 0, optional | If > 0: make a plot of the color coordinates of original and rendered image pixels. :show_ref_img: | True, optional | True: shows rendered image under reference spd. False: shows | original image. :write_to_file: | None, optional | None: do nothing, else: write to filename(+path) in :write_to_file: :stack_test_ref: | 12, optional | - 12: left (test), right (ref) format for show and imwrite | - 21: top (test), bottom (ref) | - 1: only show/write test | - 2: only show/write ref | - 0: show both, write test Returns: :returns: | img_hyp, img_ren, | ndarrays with float hyperspectral image and rendered images """ # Get image: #imread = lambda x: plt.imread(x) #matplotlib.pyplot if img is not None: if isinstance(img, str): img = plt.imread(img) # use matplotlib.pyplot's imread else: img = plt.imread(_HYPSPCIM_DEFAULT_IMAGE) if isinstance(img, np.uint8): img = img / 255 elif isinstance(img, np.uint16): img = img / (2**16 - 1) # Convert to 2D format: rgb = img.reshape(img.shape[0] * img.shape[1], 3) # *1.0: make float rgb[rgb == 0] = _EPS # avoid division by zero for pure blacks. # Get unique rgb values and positions: rgb_u, rgb_indices = np.unique(rgb, return_inverse=True, axis=0) # get rfl set: if rfl is None: # use IESTM30['4880'] set rfl = _CRI_RFL['ies-tm30']['4880']['5nm'] wlr = rfl[ 0] # spectral reflectance set determines wavelength range for estimation (xyz_to_rfl()) # get Ref spd: if refspd is None: refspd = _CIE_ILLUMINANTS['D65'].copy() refspd = cie_interp( refspd, wlr, kind='linear') # force spd to same wavelength range as rfl # Convert rgb_u to xyz and lab-type values under assumed refspd: if CSF is None: xyz_wr = spd_to_xyz(refspd, cieobs=cieobs, relative=True) xyz_ur = colortf(rgb_u * 255, tf='srgb>xyz') else: xyz_ur = rgb_u # for input in xyz_to_rfl (when CSF is not None: this functions assumes input is indeed rgb !!!) # Estimate rfl's for xyz_ur: rfl_est, xyzri = xyz_to_rfl(xyz_ur, rfl = rfl, out = 'rfl_est,xyz_est', \ refspd = refspd, D = D, cieobs = cieobs, \ cspace = cspace, cspace_tf = cspace_tf, CSF = CSF,\ interp_type = interp_type, k_neighbours = k_neighbours, verbosity = verbosity) # Get default test spd if none supplied: if spd is None: spd = _CIE_ILLUMINANTS['F4'] if CSF is None: # calculate xyz values under test spd: xyzti, xyztw = spd_to_xyz(spd, rfl=rfl_est, cieobs=cieobs, out=2) # Chromatic adaptation from test spd to refspd: if D is not None: xyzti = cat.apply(xyzti, xyzw1=xyztw, xyzw2=xyz_wr, D=D) # Convert xyzti under test spd to srgb: rgbti = colortf(xyzti, tf='srgb') / 255 else: # Calculate rgb coordinates from camera sensitivity functions under spd: rgbti = rfl_to_rgb(rfl_est, spd=spd, CSF=CSF, wl=None) # Chromatic adaptation from test spd to refspd: if D is not None: white = np.ones_like(spd) white[0] = spd[0] rgbwr = rfl_to_rgb(white, spd=refspd, CSF=CSF, wl=None) rgbwt = rfl_to_rgb(white, spd=spd, CSF=CSF, wl=None) rgbti = cat.apply_vonkries2(rgbti, rgbwt, rgbwr, xyzw0=np.array([[1.0, 1.0, 1.0]]), in_='rgb', out_='rgb', D=1) # Reconstruct original locations for rendered image rgbs: img_ren = rgbti[rgb_indices] img_ren.shape = img.shape # reshape back to 3D size of original img_ren = img_ren # For output: if show_ref_img == True: rgb_ref = colortf(xyzri, tf='srgb') / 255 if ( CSF is None ) else xyzri # if CSF not None: xyzri contains rgbri !!! img_ref = rgb_ref[rgb_indices] img_ref.shape = img.shape # reshape back to 3D size of original img_str = 'Rendered (under ref. spd)' img = img_ref else: img_str = 'Original' img = img if (stack_test_ref > 0) | show == True: if stack_test_ref == 21: img_original_rendered = np.vstack( (img_ren, np.ones((4, img.shape[1], 3)), img)) img_original_rendered_str = 'Rendered (under test spd)\n ' + img_str elif stack_test_ref == 12: img_original_rendered = np.hstack( (img_ren, np.ones((img.shape[0], 4, 3)), img)) img_original_rendered_str = 'Rendered (under test spd) | ' + img_str elif stack_test_ref == 1: img_original_rendered = img_ren img_original_rendered_str = 'Rendered (under test spd)' elif stack_test_ref == 2: img_original_rendered = img img_original_rendered_str = img_str elif stack_test_ref == 0: img_original_rendered = img_ren img_original_rendered_str = 'Rendered (under test spd)' if write_to_file is not None: # Convert from RGB to BGR formatand write: #print('Writing rendering results to image file: {}'.format(write_to_file)) with warnings.catch_warnings(): warnings.simplefilter("ignore") imsave(write_to_file, img_original_rendered) if show == True: # show images using pyplot.show(): plt.figure() plt.imshow(img_original_rendered) plt.title(img_original_rendered_str) plt.gca().get_xaxis().set_ticklabels([]) plt.gca().get_yaxis().set_ticklabels([]) if stack_test_ref == 0: plt.figure() plt.imshow(img) plt.title(img_str) plt.axis('off') if 'img_hyp' in out.split(','): # Create hyper_spectral image: rfl_image_2D = rfl_est[ rgb_indices + 1, :] # create array with all rfls required for each pixel img_hyp = rfl_image_2D.reshape(img.shape[0], img.shape[1], rfl_image_2D.shape[1]) # Setup output: if out == 'img_hyp': return img_hyp elif out == 'img_ren': return img_ren else: return eval(out)
def plot_shift_data(data, fieldtype = 'vectorfield', scalef = _VF_MAXR, color = 'k', \ axtype = 'polar', ax = None, \ hbins = 10, start_hue = 0.0, bin_labels = '#', plot_center_lines = True, \ plot_axis_labels = False, plot_edge_lines = False, plot_bin_colors = True, \ force_CVG_layout = True): """ Plots vector or circle fields generated by VFcolorshiftmodel() or PXcolorshiftmodel(). Args: :data: | dict generated by VFcolorshiftmodel() or PXcolorshiftmodel() | Must contain 'fielddata'- key, which is a dict with possible keys: | - key: 'vectorfield': ndarray with vector field data | - key: 'circlefield': ndarray with circle field data :color: | 'k', optional | Color for plotting the vector-fields. :axtype: | 'polar' or 'cart', optional | Make polar or Cartesian plot. :ax: | None or 'new' or 'same', optional | - None or 'new' creates new plot | - 'same': continue plot on same axes. | - axes handle: plot on specified axes. :hbins: | 16 or ndarray with sorted hue bin centers (°), optional :start_hue: | _VF_MAXR, optional | Scale factor for graphic. :plot_axis_labels: | False, optional | Turns axis ticks on/off (True/False). :bin_labels: | None or list[str] or '#', optional | Plots labels at the bin center hues. | - None: don't plot. | - list[str]: list with str for each bin. | (len(:bin_labels:) = :nhbins:) | - '#': plots number. :plot_edge_lines: | True or False, optional | Plot grey bin edge lines with '--'. :plot_center_lines: | False or True, optional | Plot colored lines at 'center' of hue bin. :plot_bin_colors: | True, optional | Colorize hue-bins. :force_CVG_layout: | False or True, optional | True: Force plot of basis of CVG. Returns: :returns: | figCVG, hax, cmap | :figCVG: handle to CVG figure | :hax: handle to CVG axes | :cmap: list with rgb colors for hue bins | (for use in other plotting fcns) """ # Plot basis of CVG: figCVG, hax, cmap = plot_hue_bins(hbins=hbins, axtype=axtype, ax=ax, plot_center_lines=plot_center_lines, plot_edge_lines=plot_edge_lines, plot_bin_colors=plot_bin_colors, scalef=scalef, force_CVG_layout=force_CVG_layout, bin_labels=bin_labels) # plot vector field: if data is not None: if fieldtype is not None: vf = data['fielddata'][fieldtype] if axtype == 'polar': if fieldtype == 'vectorfield': vfrtheta = math.positive_arctan(vf['axr'], vf['bxr'], htype='rad') vfrr = np.sqrt(vf['axr']**2 + vf['bxr']**2) hax.quiver(vfrtheta, vfrr, vf['axt'] - vf['axr'], vf['bxt'] - vf['bxr'], headlength=3, color=color, angles='uv', scale_units='y', scale=2, linewidth=0.5) else: vfttheta = math.positive_arctan(vf['axt'], vf['bxt'], htype='rad') vfrtheta = math.positive_arctan(vf['axr'], vf['bxr'], htype='rad') vftr = np.sqrt(vf['axt']**2 + vf['bxt']**2) dh = (math.angle_v1v2(np.hstack((vf['axt'], vf['bxt'])), np.hstack((vf['axr'], vf['bxr'])), htype='deg')[:, None]) #hue shift dh = dh / np.nanmax(dh) plt.set_cmap('jet') hax.scatter(vfttheta, vftr, s=100 * dh, c=dh, linestyle='None', marker='o', norm=None) hax.set_ylim([0, 1.1 * scalef]) else: if fieldtype == 'vectorfield': hax.quiver(vf['axr'], vf['bxr'], vf['axt'] - vf['axr'], vf['bxt'] - vf['bxr'], headlength=1, color=color, angles='uv', scale_units='xy', scale=1, linewidth=0.5) else: hax.plot(vf['axr'], vf['bxr'], color=color, marker='.', linestyle='None') return figCVG, hax, cmap
def get_pixel_coordinates(jab, jab_ranges=None, jab_deltas=None, limit_grid_radius=0): """ Get pixel coordinates corresponding to array of jab color coordinates. Args: :jab: | ndarray of color coordinates :jab_ranges: | None or ndarray, optional | Specifies the pixelization of color space. | (ndarray.shape = (3,3), with first axis: J,a,b, and second axis: min, max, delta) :jab_deltas: | float or ndarray, optional | Specifies the sampling range. | A float uses jab_deltas as the maximum Euclidean distance to select | samples around each pixel center. A ndarray of 3 deltas, uses | a city block sampling around each pixel center. :limit_grid_radius: | 0, optional | A value of zeros keeps grid as specified by axr,bxr. | A value > 0 only keeps (a,b) coordinates within :limit_grid_radius: Returns: :returns: | gridp, idxp, jabp, samplenrs, samplesIDs | - :gridp: ndarray with coordinates of all pixel centers. | - :idxp: list[int] with pixel index for each non-empty pixel | - :jabp: ndarray with center color coordinates of non-empty pixels | - :samplenrs: list[list[int]] with sample numbers belong to each | non-empty pixel | - :sampleIDs: summarizing list, | with column order: 'idxp, jabp, samplenrs' """ if jab_deltas is None: jab_deltas = np.array([_VF_DELTAR, _VF_DELTAR, _VF_DELTAR]) if jab_ranges is None: jab_ranges = np.vstack( ([0, 100, jab_deltas[0] ], [-_VF_MAXR, _VF_MAXR + jab_deltas[1], jab_deltas[1]], [-_VF_MAXR, _VF_MAXR + jab_deltas[2], jab_deltas[2]])) # Get pixel grid: gridp = generate_grid(jab_ranges=jab_ranges, limit_grid_radius=limit_grid_radius) # determine pixel coordinates of each sample in jab: samplesIDs = [] for idx in range(gridp.shape[0]): # get pixel coordinates: jp = gridp[idx, 0] ap = gridp[idx, 1] bp = gridp[idx, 2] #Cp = np.sqrt(ap**2+bp**2) if type(jab_deltas) == np.ndarray: sampleID = np.where( ((np.abs(jab[..., 0] - jp) <= jab_deltas[0] / 2) & (np.abs(jab[..., 1] - ap) <= jab_deltas[1] / 2) & (np.abs(jab[..., 2] - bp) <= jab_deltas[2] / 2))) else: sampleID = np.where( (np.sqrt((jab[..., 0] - jp)**2 + (jab[..., 1] - ap)**2 + (jab[..., 2] - bp)**2) <= jab_deltas / 2)) if (sampleID[0].shape[0] > 0): samplesIDs.append( np.hstack((idx, np.array([jp, ap, bp]), sampleID[0]))) idxp = [np.int(samplesIDs[i][0]) for i in range(len(samplesIDs))] jabp = np.vstack([samplesIDs[i][1:4] for i in range(len(samplesIDs))]) samplenrs = [ np.array(samplesIDs[i][4:], dtype=int).tolist() for i in range(len(samplesIDs)) ] return gridp, idxp, jabp, samplenrs, samplesIDs
def cie2006cmfsEx(age = 32,fieldsize = 10, wl = None,\ var_od_lens = 0, var_od_macula = 0, \ var_od_L = 0, var_od_M = 0, var_od_S = 0,\ var_shft_L = 0, var_shft_M = 0, var_shft_S = 0,\ out = 'LMS', allow_negative_values = False): """ Generate Individual Observer CMFs (cone fundamentals) based on CIE2006 cone fundamentals and published literature on observer variability in color matching and in physiological parameters. Args: :age: | 32 or float or int, optional | Observer age :fieldsize: | 10, optional | Field size of stimulus in degrees (between 2° and 10°). :wl: | None, optional | Interpolation/extraplation of :LMS: output to specified wavelengths. | None: output original _WL = np.array([390,780,5]) :var_od_lens: | 0, optional | Std Dev. in peak optical density [%] of lens. :var_od_macula: | 0, optional | Std Dev. in peak optical density [%] of macula. :var_od_L: | 0, optional | Std Dev. in peak optical density [%] of L-cone. :var_od_M: | 0, optional | Std Dev. in peak optical density [%] of M-cone. :var_od_S: | 0, optional | Std Dev. in peak optical density [%] of S-cone. :var_shft_L: | 0, optional | Std Dev. in peak wavelength shift [nm] of L-cone. :var_shft_L: | 0, optional | Std Dev. in peak wavelength shift [nm] of M-cone. :var_shft_S: | 0, optional | Std Dev. in peak wavelength shift [nm] of S-cone. :out: | 'LMS' or , optional | Determines output. :allow_negative_values: | False, optional | Cone fundamentals or color matching functions should not have negative values. | If False: X[X<0] = 0. Returns: :returns: | - 'LMS' : ndarray with individual observer area-normalized | cone fundamentals. Wavelength have been added. | [- 'trans_lens': ndarray with lens transmission | (no wavelengths added, no interpolation) | - 'trans_macula': ndarray with macula transmission | (no wavelengths added, no interpolation) | - 'sens_photopig' : ndarray with photopigment sens. | (no wavelengths added, no interpolation)] References: 1. `Asano Y, Fairchild MD, and Blondé L (2016). Individual Colorimetric Observer Model. PLoS One 11, 1–19. <http://journals.plos.org/plosone/article?id=10.1371/journal.pone.0145671>`_ 2. `Asano Y, Fairchild MD, Blondé L, and Morvan P (2016). Color matching experiment for highlighting interobserver variability. Color Res. Appl. 41, 530–539. <https://onlinelibrary.wiley.com/doi/abs/10.1002/col.21975>`_ 3. `CIE, and CIE (2006). Fundamental Chromaticity Diagram with Physiological Axes - Part I (Vienna: CIE). <http://www.cie.co.at/publications/fundamental-chromaticity-diagram-physiological-axes-part-1>`_ 4. `Asano's Individual Colorimetric Observer Model <https://www.rit.edu/cos/colorscience/re_AsanoObserverFunctions.php>`_ """ fs = fieldsize rmd = _INDVCMF_DATA['rmd'].copy() LMSa = _INDVCMF_DATA['LMSa'].copy() docul = _INDVCMF_DATA['docul'].copy() # field size corrected macular density: pkOd_Macula = 0.485 * np.exp(-fs / 6.132) * ( 1 + var_od_macula / 100) # varied peak optical density of macula corrected_rmd = rmd * pkOd_Macula # age corrected lens/ocular media density: if (age <= 60): correct_lomd = docul[:1] * (1 + 0.02 * (age - 32)) + docul[1:2] else: correct_lomd = docul[:1] * (1.56 + 0.0667 * (age - 60)) + docul[1:2] correct_lomd = correct_lomd * (1 + var_od_lens / 100 ) # varied overall optical density of lens # Peak Wavelength Shift: wl_shifted = np.empty(LMSa.shape) wl_shifted[0] = _WL + var_shft_L wl_shifted[1] = _WL + var_shft_M wl_shifted[2] = _WL + var_shft_S LMSa_shft = np.empty(LMSa.shape) kind = 'cubic' LMSa_shft[0] = sp.interpolate.interp1d(wl_shifted[0], LMSa[0], kind=kind, bounds_error=False, fill_value="extrapolate")(_WL) LMSa_shft[1] = sp.interpolate.interp1d(wl_shifted[1], LMSa[1], kind=kind, bounds_error=False, fill_value="extrapolate")(_WL) LMSa_shft[2] = sp.interpolate.interp1d(wl_shifted[2], LMSa[2], kind=kind, bounds_error=False, fill_value="extrapolate")(_WL) # LMSa[2,np.where(_WL >= _WL_CRIT)] = 0 #np.nan # Not defined above 620nm # LMSa_shft[2,np.where(_WL >= _WL_CRIT)] = 0 ssw = np.hstack( (0, np.sign(np.diff(LMSa_shft[2, :])) )) #detect poor interpolation (sign switch due to instability) LMSa_shft[2, np.where((ssw >= 0) & (_WL > 560))] = np.nan # corrected LMS (no age correction): pkOd_L = (0.38 + 0.54 * np.exp(-fs / 1.333)) * ( 1 + var_od_L / 100) # varied peak optical density of L-cone pkOd_M = (0.38 + 0.54 * np.exp(-fs / 1.333)) * ( 1 + var_od_M / 100) # varied peak optical density of M-cone pkOd_S = (0.30 + 0.45 * np.exp(-fs / 1.333)) * ( 1 + var_od_S / 100) # varied peak optical density of S-cone alpha_lms = 0. * LMSa_shft alpha_lms[0] = 1 - 10**(-pkOd_L * (10**LMSa_shft[0])) alpha_lms[1] = 1 - 10**(-pkOd_M * (10**LMSa_shft[1])) alpha_lms[2] = 1 - 10**(-pkOd_S * (10**LMSa_shft[2])) # this fix is required because the above math fails for alpha_lms[2,:]==0 alpha_lms[2, np.where(_WL >= _WL_CRIT)] = 0 # Corrected to Corneal Incidence: lms_barq = alpha_lms * (10**(-corrected_rmd - correct_lomd)) * np.ones( alpha_lms.shape) # Corrected to Energy Terms: lms_bar = lms_barq * _WL # Set NaN values to zero: lms_bar[np.isnan(lms_bar)] = 0 # normalized: LMS = 100 * lms_bar / np.nansum(lms_bar, axis=1, keepdims=True) # Output extra: trans_lens = 10**(-correct_lomd) trans_macula = 10**(-corrected_rmd) sens_photopig = alpha_lms * _WL # Add wavelengths: LMS = np.vstack((_WL, LMS)) if ('xyz' in out.lower().split(',')): LMS = lmsb_to_xyzb(LMS, fieldsize, out='xyz', allow_negative_values=allow_negative_values) out = out.replace('xyz', 'LMS').replace('XYZ', 'LMS') if ('lms' in out.lower().split(',')): out = out.replace('lms', 'LMS') # Interpolate/extrapolate: if wl is None: interpolation = None else: interpolation = 'cubic' LMS = spd(LMS, wl=wl, interpolation=interpolation, norm_type='area') if (out == 'LMS'): return LMS elif (out == 'LMS,trans_lens,trans_macula,sens_photopig'): return LMS, trans_lens, trans_macula, sens_photopig elif (out == 'LMS,trans_lens,trans_macula,sens_photopig,LMSa'): return LMS, trans_lens, trans_macula, sens_photopig, LMSa else: return eval(out)