def plot(self, ylabel='Spectrum', wavelength_bar=True, *args, **kwargs): """ Make a plot of the spectral data in SPD instance. Returns: :returns: | handle to current axes. """ plt.plot(self.wl, self.value.T, *args, **kwargs) if wavelength_bar == True: Smax = np.nanmax(self.value) axh = plot_spectrum_colors(spd=None, spdmax=Smax, axh=plt.gca(), wavelength_height=-0.05) plt.xlabel('Wavelength (nm)') plt.ylabel(ylabel) return plt.gca()
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
def plotceruleanline(cieobs=_CIEOBS, cspace=_CSPACE, axh=None, formatstr='ko-', cspace_pars={}): """ Plot cerulean (yellow (577 nm) - blue (472 nm)) line | Kuehni, CRA, 2014: | Table II: spectral lights. Args: :axh: | None or axes handle, optional | Determines axes to plot data in. | None: make new figure. :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: | handle to cerulean line References: 1. `Kuehni, R. G. (2014). Unique hues and their stimuli—state of the art. Color Research & Application, 39(3), 279–287. <https://doi.org/10.1002/col.21793>`_ (see Table II, IV) """ cmf = _CMF[cieobs]['bar'] p_y = cmf[0] == 577.0 #Kuehni, CRA 2013 (mean, table IV) p_b = cmf[0] == 472.0 #Kuehni, CRA 2013 (mean, table IV) xyz_y = cmf[1:, p_y].T xyz_b = cmf[1:, p_b].T lab = colortf(np.vstack((xyz_b, xyz_y)), tf=cspace, tfa0=cspace_pars) if axh is None: axh = plt.gca() hcerline = axh.plot(lab[:, 1], lab[:, 2], formatstr, label='Cerulean line') return hcerline
def plot(self, ylabel='Spectrum', *args, **kwargs): """ Make a plot of the spectral data in SPD instance. Returns: :returns: | handle to current axes. """ plt.plot(self.wl, self.value.T, *args, **kwargs) plt.xlabel('Wavelength (nm)') plt.ylabel(ylabel) return plt.gca()
def plot(self, plt_type='3d', ax=None, title=None, **kwargs): """ Plot color coordinates. Args: :plt_type: | '3d' or 3 or '2d or 2, optional | -'3d' or 3: plot all 3 dimensions (lightness and chromaticity) | -'2d' or 2: plot only chromaticity dimensions. :ax: | None or axes handles, optional | None: create new figure axes, else use :ax: for plotting. :title: | None or str, optional | Give plot a title. :**kwargs: | additional arguments for use with matplotlib.pyplot.scatter Returns: :gca: | handle to current axes. """ L, a, b = self.split_() if ax is None: fig = plt.figure() if (plt_type == '2d') | (plt_type == 2): if ax is None: ax = fig.add_subplot(111) ax.scatter(a, b, **kwargs) else: if ax is None: ax = fig.add_subplot(111, projection='3d') ax.scatter(a, b, L, **kwargs) ax.set_zlabel(_CSPACE_AXES[self.dtype][0]) ax.set_xlabel(_CSPACE_AXES[self.dtype][1]) ax.set_ylabel(_CSPACE_AXES[self.dtype][2]) if title is not None: ax.set_title(title) return plt.gca()
def plot(self, ax=None, title=None, **kwargs): """ Plot tristimulus or cone fundamental values. Args: :ax: | None or axes handles, optional | None: create new figure axes, else use :ax: for plotting. :title: | None or str, optional | Give plot a title. :**kwargs: | additional arguments for use with matplotlib.pyplot.scatter Returns: :gca: | handle to current axes. """ X, Y, Z = self.split_() if ax is None: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') if self.dtype == 'xyz': ax.scatter(X, Z, Y, **kwargs) ax.set_xlabel(_CSPACE_AXES[self.dtype][0]) ax.set_ylabel(_CSPACE_AXES[self.dtype][2]) ax.set_zlabel(_CSPACE_AXES[self.dtype][1]) elif self.dtype == 'lms': ax.scatter(X, Y, Z, **kwargs) ax.set_xlabel(_CSPACE_AXES[self.dtype][0]) ax.set_ylabel(_CSPACE_AXES[self.dtype][1]) ax.set_zlabel(_CSPACE_AXES[self.dtype][2]) if title is not None: ax.set_title(title) return plt.gca()
def render_image(img = None, spd = None, rfl = None, out = 'img_hyp', \ refspd = None, D = None, cieobs = _CIEOBS, \ cspace = 'ipt', cspace_tf = {},\ 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 uint8 rgb image. | None load a default image. :spd: | ndarray, optional | Light source spectrum for rendering :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: | 'ipt', optional | Color space for color coordinate to rfl mapping. :cspace_tf: | {}, optional | Dict with parameters for xyz_to_cspace and cspace_to_xyz transform. :k_neighbours: | 4 or int, optional | Number of nearest neighbours for reflectance spectrum interpolation. | Neighbours are found using scipy.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 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) # Convert to 2D format: rgb = img.reshape(img.shape[0] * img.shape[1], 3) * 1.0 # *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 Ref spd: if refspd is None: refspd = _CIE_ILLUMINANTS['D65'].copy() # Convert rgb_u to xyz and lab-type values under assumed refspd: xyz_wr = spd_to_xyz(refspd, cieobs=cieobs, relative=True) xyz_ur = colortf(rgb_u, tf='srgb>xyz') # 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,\ k_neighbours = k_neighbours, verbosity = verbosity) # Get default test spd if none supplied: if spd is None: spd = _CIE_ILLUMINANTS['F4'] # 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 # Reconstruct original locations for rendered image rgbs: img_ren = rgbti[rgb_indices] img_ren.shape = img.shape # reshape back to 3D size of original # For output: if show_ref_img == True: rgb_ref = colortf(xyzri, tf='srgb') / 255 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 / 255 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_str) 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_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, \ 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. :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)] # initializing the figure cmap = None if (ax == None) or (ax == 'new'): fig = plt.figure() newfig = True else: 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), 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) hy = r * np.sin(theta) if bin_labels is not None: hxv = np.vstack((np.zeros(hbincenters.shape), 1.3 * scalef * np.cos(hbincenters))) hyv = np.vstack((np.zeros(hbincenters.shape), 1.3 * 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))) # 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.84, 0.9))) #c = [abs(c[0]),abs(c[1]),abs(c[2])] # ensure all positive elements if i == 0: cmap = [c] else: cmap.append(c) if axtype == 'polar': if plot_edge_lines == True: ax.plot(edges[:, i], r[:, i] * 1.2, color='grey', marker='None', linestyle=':', linewidth=3, 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=2) else: ax.plot(theta[:, i], r[:, i], color=c, marker='o', linestyle='-', linewidth=3, markersize=10) if plot_bin_colors == True: bar = ax.bar(dM[i], r[1, i], width=dt[i], color=c, alpha=0.15) if bin_labels is not None: ax.text(hbincenters[i], 1.3 * scalef, bin_labels[i], fontsize=12, horizontalalignment='center', verticalalignment='center', color=np.array([1, 1, 1]) * 0.3) if plot_axis_labels == False: ax.set_xticklabels([]) ax.set_yticklabels([]) else: if plot_edge_lines == True: ax.plot(hxe[:, i], hye[:, i], color='grey', marker='None', linestyle=':', linewidth=3, 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=2) else: ax.plot(hx[:, i], hy[:, i], color=c, marker='o', linestyle='-', linewidth=3, markersize=10) if bin_labels is not None: ax.text(hxv[1, i], hyv[1, i], bin_labels[i], fontsize=12, horizontalalignment='center', verticalalignment='center', color=np.array([1, 1, 1]) * 0.3) ax.axis(1.1 * np.array( [hxv.min(), hxv.max(), hyv.min(), hyv.max()])) if plot_axis_labels == False: ax.set_xticklabels([]) ax.set_yticklabels([]) else: plt.xlabel("a'") plt.ylabel("b'") plt.plot(0, 0, color='k', marker='o', linestyle=None) return plt.gcf(), plt.gca(), cmap
def plot_ColorVectorGraphic(jabt, jabr, hbins = 16, start_hue = 0.0, scalef = 100, \ plot_axis_labels = False, bin_labels = None, \ plot_edge_lines = True, plot_center_lines = False, \ plot_bin_colors = True, axtype = 'polar', ax = None,\ force_CVG_layout = False): """ Plot Color Vector Graphic (CVG). Args: :jabt: | ndarray with jab data under test SPD :jabr: | ndarray with jab data under reference SPD :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. :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. Returns: :returns: | gcf(), gca(), list with rgb colors for hue bins (for use in other plotting fcns) """ # Plot basis of CVG: figCVG, ax, cmap = plot_hue_bins(hbins=hbins, start_hue=start_hue, scalef=scalef, axtype=axtype, ax=ax, plot_center_lines=plot_center_lines, plot_edge_lines=plot_edge_lines, force_CVG_layout=force_CVG_layout, bin_labels=bin_labels, plot_bin_colors=plot_bin_colors) if cmap == []: cmap = ['k' for i in range(hbins)] if axtype == 'polar': jabr_theta, jabr_r = math.cart2pol(jabr[..., 1:3], htype='rad') jabt_theta, jabt_r = math.cart2pol(jabt[..., 1:3], htype='rad') #ax.quiver(jabrtheta,jabr_r,jabt[...,1]-jabr[...,1], jabt[...,2]-jabr_binned[...,2], color = 'k', headlength=3, angles='uv', scale_units='y', scale = 2,linewidth = 0.5) ax.plot(jabt_theta, jabt_r, color='grey', linewidth=2) for j in range(hbins): c = cmap[j] ax.quiver(jabr_theta[j], jabr_r[j], jabt[j, 1] - jabr[j, 1], jabt[j, 2] - jabr[j, 2], edgecolor='k', facecolor=c, headlength=3, angles='uv', scale_units='y', scale=2, linewidth=0.5) else: #ax.quiver(jabr[...,1],jabr[...,2],jabt[...,1]-jabr[...,1], jabt[...,2]-jabr[...,2], color = 'k', headlength=3, angles='uv', scale_units='xy', scale = 1,linewidth = 0.5) ax.plot(jabt, jabt, color='grey', linewidth=2) for j in range(hbins): ax.quiver(jabr[j, 1], jabr[j, 2], jabt[j, 1] - jabr[j, 1], jabt[j, 2] - jabr[j, 2], color=cmap[j], headlength=3, angles='uv', scale_units='xy', scale=1, linewidth=0.5) if axtype == 'cart': plt.xlabel("a'") plt.ylabel("b'") return plt.gcf(), plt.gca(), cmap
def plot_color_data(x,y,z=None, axh=None, show = True, cieobs =_CIEOBS, \ cspace = _CSPACE, formatstr = 'k-', **kwargs): """ Plot color data from x,y [,z]. Args: :x: | float or ndarray with x-coordinate data :y: | float or ndarray with y-coordinate data :z: | None or float or ndarray with Z-coordinate data, optional | If None: make 2d plot. :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) :kwargs: | additional keyword arguments for use with matplotlib.pyplot. Returns: :returns: | None (:show: == True) | or | handle to current axes (:show: == False) """ x = np.atleast_1d(x) y = np.atleast_1d(y) if 'grid' in kwargs.keys(): plt.grid(kwargs['grid']);kwargs.pop('grid') if z is not None: z = np.atleast_1d(z) if axh is None: fig = plt.figure() axh = plt.axes(projection='3d') axh.plot3D(x,y,z,formatstr, linewidth = 2,**kwargs) plt.zlabel(_CSPACE_AXES[cspace][0], kwargs) else: plt.plot(x,y,formatstr,linewidth = 2,**kwargs) plt.xlabel(_CSPACE_AXES[cspace][1], kwargs) plt.ylabel(_CSPACE_AXES[cspace][2], kwargs) if 'label' in kwargs.keys(): plt.legend() if show == True: plt.show() else: return plt.gca()
def plotUH(xyz0 = None, uhues = [0,1,2,3], cieobs = _CIEOBS, cspace = _CSPACE, axh = None,formatstr = ['yo-.','bo-.','ro-.','go-.'], excludefromlegend = '',cspace_pars = {}): """ Plot unique hue lines from color space center point xyz0. | Kuehni, CRA, 2014: | uY,uB,uG: Table II: spectral lights; | uR: Table IV: Xiao data. Args: :xyz0: | None, optional | Center of color space (unique hue lines are expected to cross here) | None defaults to equi-energy-white. :uhues: | [0,1,2,3], optional | Unique hue lines to plot [0:'yellow',1:'blue',2:'red',3:'green'] :axh: | None or axes handle, optional | Determines axes to plot data in. | None: make new figure. :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: | ['yo-.','bo-.','ro-.','go-.'] or list[str], optional | Format str for plotting the different unique lines | (see also ?matplotlib.pyplot.plot) :excludefromlegend: | '' or str, optional | To exclude certain hues from axes legend. :cspace_pars: | {} or dict, optional | Dict with parameters required by color space specified in :cspace: | (for use with luxpy.colortf()) Returns: :returns: | list[handles] to unique hue lines References: 1. `Kuehni, R. G. (2014). Unique hues and their stimuli—state of the art. Color Research & Application, 39(3), 279–287. <https://doi.org/10.1002/col.21793>`_ (see Table II, IV) """ hues = ['yellow','blue','red','green'] cmf = _CMF[cieobs]['bar'] p_y = cmf[0] == 577.0 #unique yellow,#Kuehni, CRA 2013 (mean, table IV: spectral data) p_b = cmf[0] == 472.0 #unique blue,Kuehni, CRA 2013 (mean, table IV: spectral data) p_g = cmf[0] == 514.0 #unique green, Kuehni, CRA 2013 (mean, table II: spectral data) p_r = cmf[0] == 650.0 #unique red, Kuehni, CRA 2013 (Xiao data, table IV: display data) xyz_y = 100.0*cmf[1:,p_y].T xyz_b = 100.0*cmf[1:,p_b].T xyz_g = 100.0*cmf[1:,p_g].T xyz_r = 100.0*cmf[1:,p_r].T xyz_uh = np.vstack((xyz_y,xyz_b,xyz_r,xyz_g)) huniquehues = [] if xyz0 is None: xyz0 = np.array([100.0,100.0,100.0]) if axh is None: axh = plt.gca() for huenr in uhues: lab = colortf(np.vstack((xyz0,xyz_uh[huenr])),tf = cspace, tfa0 = cspace_pars) huh = axh.plot(lab[:,1],lab[:,2],formatstr[huenr],label = excludefromlegend + 'Unique '+ hues[huenr]) huniquehues = [huniquehues,huh] return huniquehues