def ctf(self, **kwargs): """ Convert color space coordinates to XYZ tristimulus values. Args: :dtype: | 'xyz' | Convert to this color space. :**kwargs: | additional input arguments required for | color space transformation. | See specific luxpy function for more info | (e.g. ?luxpy.xyz_to_lab) Returns: :returns: | luxpy.XYZ with .value field that is a ndarray with tristimulus values """ db = put_args_in_db(self.cspace_par, locals().copy()) return XYZ(value=colortf(self.value, tf='{:s}>xyz'.format(self.dtype), bwtf=db), relative=self.relative, cieobs=self.cieobs, dtype='xyz')
def plotDL(ccts = None, cieobs =_CIEOBS, cspace = _CSPACE, axh = None, \ show = True, force_daylight_below4000K = False, cspace_pars = {}, \ formatstr = 'k-', **kwargs): """ Plot daylight locus. Args: :ccts: | None or list[float], optional | None defaults to [4000 K to 1e19 K] in 100 steps on a log10 scale. :force_daylight_below4000K: | False or True, optional | CIE daylight phases are not defined below 4000 K. | If True plot anyway. :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: ccts = 10**np.linspace(np.log10(4000.0),np.log10(10.0**19.0),100.0) xD,yD = daylightlocus(ccts, force_daylight_below4000K = force_daylight_below4000K) Y = 100*np.ones(xD.shape) DL = Yxy_to_xyz(np.vstack((Y, xD,yD)).T) DL = colortf(DL, tf = cspace, tfa0 = cspace_pars) Y,x,y = asplit(DL) axh = plot_color_data(x,y,axh = axh, cieobs = cieobs, cspace = cspace, show=show, formatstr=formatstr, **kwargs) if show == False: return axh
def optfcn(x, rgbcal, xyzcal, tr, xyz_black, cspace, p_grays, p_whites,out,verbosity): M = x.reshape((3,3)) xyzest = rgb_to_xyz(rgbcal, M, tr, xyz_black, tr_type) xyzw = xyzcal[p_whites,:].mean(axis=0) # get xyzw for input into xyz_to_lab() or colortf() labcal, labest = colortf(xyzcal,tf=cspace,xyzw=xyzw), colortf(xyzest,tf=cspace,xyzw=xyzw) # calculate lab coord. of cal. and est. DEs = ((labcal-labest)**2).sum(axis=1)**0.5 DEg = DEs[p_grays] DEw = DEs[p_whites] F = (avg(DEs)**2 + avg(DEg)**2 + avg(DEw**2))**0.5 if verbosity > 1: print('\nPerformance of TR + rgb-to-xyz conversion matrix M:') print('all: DE(jab): avg = {:1.4f}, std = {:1.4f}'.format(avg(DEs),np.std(DEs))) print('grays: DE(jab): avg = {:1.4f}, std = {:1.4f}'.format(avg(DEg),np.std(DEg))) print('whites(s) DE(jab): avg = {:1.4f}, std = {:1.4f}'.format(avg(DEw),np.std(DEw))) if out == 'F': return F else: return eval(out)
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) """ if isinstance(cieobs, str): cmf = _CMF[cieobs]['bar'] else: cmf = cieobs 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 to_xyz(self, **kwargs): """ Convert color space coordinates to XYZ tristimulus values. """ db = put_args_in_db(self.cspace_par, locals().copy()) return XYZ(value=colortf(self.value, tf='{:s}>xyz'.format(self.dtype), bwtf=db), relative=self.relative, cieobs=self.cieobs, dtype='xyz')
def xyz_to_rfl(xyz, CSF = None, rfl = None, out = 'rfl_est', \ refspd = None, D = None, cieobs = _CIEOBS, \ cspace = 'xyz', cspace_tf = {},\ interp_type = 'nd', k_neighbours = 4, verbosity = 0): """ Approximate spectral reflectance of xyz values based on nd-dimensional linear interpolation or k nearest neighbour interpolation of samples from a standard reflectance set. Args: :xyz: | ndarray with xyz values of target points. :CSF: | None, optional | RGB camera response functions. | If None: input :xyz: contains raw rgb (float) values. Override :cspace: | argument and perform estimation directly in raw rgb space!!! :rfl: | ndarray, optional | Reflectance set for color coordinate to rfl mapping. :out: | 'rfl_est' or str, optional :refspd: | None, optional | Refer ence spectrum for color coordinate to rfl mapping. | None defaults to D65. :cieobs: | _CIEOBS, optional | CMF set used for calculation of xyz from spectral data. :cspace: | 'xyz', optional | Color space for color coordinate to rfl mapping. | Tip: Use linear space (e.g. 'xyz', 'Yuv',...) for (interp_type == 'nd'), | and perceptually uniform space (e.g. 'ipt') for (interp_type == 'nearest') :cspace_tf: | {}, optional | Dict with parameters for xyz_to_cspace and cspace_to_xyz transform. :interp_type: | 'nd', optional | Options: | - 'nd': perform n-dimensional linear interpolation using Delaunay triangulation. | - 'nearest': perform nearest neighbour interpolation. :k_neighbours: | 4 or int, optional | Number of nearest neighbours for reflectance spectrum interpolation. | Neighbours are found using scipy.spatial.cKDTree :verbosity: | 0, optional | If > 0: make a plot of the color coordinates of original and | rendered image pixels. Returns: :returns: | :rfl_est: | ndarrays with estimated reflectance spectra. """ # get rfl set: if rfl is None: # use IESTM30['4880'] set rfl = _CRI_RFL['ies-tm30']['4880']['5nm'] wlr = rfl[0] # get Ref spd: if refspd is None: refspd = _CIE_ILLUMINANTS['D65'].copy() refspd = cie_interp( refspd, wlr, kind='linear') # force spd to same wavelength range as rfl # Calculate rgb values of standard rfl set under refspd: if CSF is None: # Calculate lab coordinates: xyz_rr, xyz_wr = spd_to_xyz(refspd, relative=True, rfl=rfl, cieobs=cieobs, out=2) cspace_tf_copy = cspace_tf.copy() cspace_tf_copy[ 'xyzw'] = xyz_wr # put correct white point in param. dict lab_rr = colortf(xyz_rr, tf=cspace, fwtf=cspace_tf_copy, bwtf=cspace_tf_copy)[:, 0, :] else: # Calculate rgb coordinates from camera sensitivity functions rgb_rr = rfl_to_rgb(rfl, spd=refspd, CSF=CSF, wl=None) lab_rr = rgb_rr xyz = xyz lab_rr = np.round(lab_rr, _ROUNDING) # speed up search # Convert xyz to lab-type values under refspd: if CSF is None: lab = colortf(xyz, tf=cspace, fwtf=cspace_tf_copy, bwtf=cspace_tf_copy) else: lab = xyz # xyz contained rgb values !!! rgb = xyz lab = np.round(lab, _ROUNDING) # speed up search if interp_type == 'nearest': # Find rfl (cfr. lab_rr) from rfl set that results in 'near' metameric # color coordinates for each value in lab_ur (i.e. smallest DE): # Construct cKDTree: tree = sp.spatial.cKDTree(lab_rr, copy_data=True) # Interpolate rfls using k nearest neightbours and inverse distance weigthing: d, inds = tree.query(lab, k=k_neighbours) if k_neighbours > 1: d += _EPS w = (1.0 / d**2)[:, :, None] # inverse distance weigthing rfl_est = np.sum(w * rfl[inds + 1, :], axis=1) / np.sum(w, axis=1) else: rfl_est = rfl[inds + 1, :].copy() elif interp_type == 'nd': rfl_est = math.ndinterp1_scipy(lab_rr, rfl[1:], lab) _isnan = np.isnan(rfl_est[:, 0]) if ( _isnan.any() ): #do nearest neigbour method for those that fail using Delaunay (i.e. ndinterp1_scipy) # Find rfl (cfr. lab_rr) from rfl set that results in 'near' metameric # color coordinates for each value in lab_ur (i.e. smallest DE): # Construct cKDTree: tree = sp.spatial.cKDTree(lab_rr, copy_data=True) # Interpolate rfls using k nearest neightbours and inverse distance weigthing: d, inds = tree.query(lab[_isnan, ...], k=k_neighbours) if k_neighbours > 1: d += _EPS w = (1.0 / d**2)[:, :, None] # inverse distance weigthing rfl_est_isnan = np.sum(w * rfl[inds + 1, :], axis=1) / np.sum( w, axis=1) else: rfl_est_isnan = rfl[inds + 1, :].copy() rfl_est[_isnan, :] = rfl_est_isnan else: raise Exception('xyz_to_rfl(): unsupported interp_type!') rfl_est[ rfl_est < 0] = 0 #can occur for points outside convexhull of standard rfl set. rfl_est = np.vstack((rfl[0], rfl_est)) if ((verbosity > 0) | ('xyz_est' in out.split(',')) | ('lab_est' in out.split(',')) | ('DEi_ab' in out.split(',')) | ('DEa_ab' in out.split(','))) & (CSF is None): xyz_est, _ = spd_to_xyz(refspd, rfl=rfl_est, relative=True, cieobs=cieobs, out=2) cspace_tf_copy = cspace_tf.copy() cspace_tf_copy[ 'xyzw'] = xyz_wr # put correct white point in param. dict lab_est = colortf(xyz_est, tf=cspace, fwtf=cspace_tf_copy)[:, 0, :] DEi_ab = np.sqrt(((lab_est[:, 1:3] - lab[:, 1:3])**2).sum(axis=1)) DEa_ab = DEi_ab.mean() elif ((verbosity > 0) | ('xyz_est' in out.split(',')) | ('rgb_est' in out.split(',')) | ('DEi_rgb' in out.split(',')) | ('DEa_rgb' in out.split(','))) & (CSF is not None): rgb_est = rfl_to_rgb(rfl_est[1:], spd=refspd, CSF=CSF, wl=wlr) xyz_est = rgb_est DEi_rgb = np.sqrt(((rgb_est - rgb)**2).sum(axis=1)) DEa_rgb = DEi_rgb.mean() if verbosity > 0: if CSF is None: ax = plot_color_data(lab[...,1], lab[...,2], z = lab[...,0], \ show = False, cieobs = cieobs, cspace = cspace, \ formatstr = 'ro', label = 'Original') plot_color_data(lab_est[...,1], lab_est[...,2], z = lab_est[...,0], \ show = True, axh = ax, cieobs = cieobs, cspace = cspace, \ formatstr = 'bd', label = 'Rendered') else: n = 100 #min(rfl.shape[0]-1,rfl_est.shape[0]-1) s = np.random.permutation(rfl.shape[0] - 1)[:min(n, rfl.shape[0] - 1)] st = np.random.permutation(rfl_est.shape[0] - 1)[:min(n, rfl_est.shape[0] - 1)] fig = plt.figure() ax = np.zeros((3, ), dtype=np.object) ax[0] = fig.add_subplot(131) ax[1] = fig.add_subplot(132) ax[2] = fig.add_subplot(133, projection='3d') ax[0].plot(rfl[0], rfl[1:][s].T, linestyle='-') ax[0].set_title('Original RFL set (random selection of all)') ax[0].set_ylim([0, 1]) ax[1].plot(rfl_est[0], rfl_est[1:][st].T, linestyle='--') ax[0].set_title('Estimated RFL set (random selection of targets)') ax[1].set_ylim([0, 1]) ax[2].plot(rgb[st, 0], rgb[st, 1], rgb[st, 2], 'ro', label='Original') ax[2].plot(rgb_est[st, 0], rgb_est[st, 1], rgb_est[st, 2], 'bd', label='Rendered') ax[2].legend() if out == 'rfl_est': return rfl_est elif out == 'rfl_est,xyz_est': return rfl_est, xyz_est else: return eval(out)
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 calibration_performance(rgb, xyztarget, M, N, tr, xyz_black, xyz_white, tr_type = 'lut', cspace='lab', avg = lambda x: ((x**2).mean()**0.5), rgb_is_xyz = False, is_verification_data = False, nbit = 8, verbosity = 1, sep = ',', header = None): """ Check calibration performance. Calculate DE for each stimulus. Args: :rgb: | ndarray [Nx3] or string with filename of RGB values | (or xyz values if argument rgb_to_xyz == True!) :xyztarget: | ndarray [Nx3] or string with filename of target XYZ values corresponding | to the RGB settings (or the measured XYZ values, if argument rgb_to_xyz == True). :M: | linear rgb to xyz conversion matrix :N: | xyz to linear rgb conversion matrix :tr: | Tone Response function parameters or lut :xyz_black: | ndarray with XYZ tristimulus values of black :xyz_white: | ndarray with tristimlus values of white :tr_type: | 'lut', optional | options: | - 'lut': Derive/specify Tone-Response as a look-up-table | - 'gog': Derive/specify Tone-Response as a gain-offset-gamma function :cspace: | color space or chromaticity diagram to calculate color differences in. :avg: | lambda x: ((x**2).mean()**0.5), optional | Function used to average the color differences of the individual RGB settings | in the optimization of the xyz_to_rgb and rgb_to_xyz conversion matrices. :rgb_is_xyz: | False, optional | If True: the data in argument rgb are actually measured XYZ tristimulus values | and are directly compared to the target xyz. :is_verification_data: | False, optional | If False: the data is assumed to be corresponding to RGB value settings used | in the calibration (i.e. containing whites, blacks, grays, pure and binary mixtures) | If True: no assumptions on content of rgb, so use this settings when | checking the performance for a set of measured and target xyz data | different than the ones used in the actual calibration measurements. :nbit: | 8, optional | RGB values in nbit format (e.g. 8, 16, ...) :verbosity: | 1, optional | > 0: print and plot optimization results :sep: | ',', optional | separator in files with rgbcal and xyzcal data :header: | None, optional | header specifier for files with rgbcal and xyzcal data | (see pandas.read_csv) Returns: :M: | linear rgb to xyz conversion matrix :N: | xyz to linear rgb conversion matrix :tr: | Tone Response function parameters or lut :xyz_black: | ndarray with XYZ tristimulus values of black :xyz_white: | ndarray with tristimlus values of white """ # process rgb, xyzcal inputs: rgb, xyz = _parse_rgbxyz_input(rgb, xyz = xyztarget, sep = sep, header=header) if rgb_is_xyz == False: # estimate xyz, otherwise assume rgb already contains xyzs xyzest = rgb_to_xyz(rgb,M,tr, xyz_black, tr_type = tr_type) # convert rgb of all samples to xyz else: xyzest = rgb lab, labest = colortf(xyz,tf=cspace,xyzw=xyz_white), colortf(xyzest,tf=cspace,xyzw=xyz_white) # calculate lab coord. of cal. and est. DElabi,DEli, DEabi = ((lab-labest)**2).sum(axis=1)**0.5, ((lab[:,:1]-labest[:,:1])**2).sum(axis=1)**0.5, ((lab[:,1:]-labest[:,1:])**2).sum(axis=1)**0.5 # calculate DE of all samples if verbosity > 0: print("\nCalibration performance (all colors): \n DE(l*a*b*): avg = {:1.2f}, std = {:1.2f}".format(avg(DElabi),DElabi.std())) # print mean DEl*a*b* print(" DE(l*) : avg = {:1.2f}, std = {:1.2f}".format(avg(DEli),DEli.std())) # print mean DEl* print(" DE(a*b*) : avg = {:1.2f}, std = {:1.2f}".format(avg(DEabi),DEabi.std())) # print mean DEa*b* if is_verification_data == False: _plot_DEs_vs_digital_values(DElabi, DEli, DEabi, rgb, nbit = nbit, avg = avg, verbosity = verbosity) _plot_target_vs_predicted_lab(lab, labest, cspace = cspace, verbosity = verbosity) return DElabi,DEli, DEabi
def xyz_to_rfl(xyz, rfl = None, out = 'rfl_est', \ refspd = None, D = None, cieobs = _CIEOBS, \ cspace = 'ipt', cspace_tf = {},\ k_neighbours = 4, verbosity = 0): """ Approximate spectral reflectance of xyz based on k nearest neighbour interpolation of samples from a standard reflectance set. Args: :xyz: | ndarray with tristimulus values of target points. :rfl: | ndarray, optional | Reflectance set for color coordinate to rfl mapping. :out: | 'rfl_est' or str, optional :refspd: | None, optional | Refer ence spectrum for color coordinate to rfl mapping. | None defaults to D65. :cieobs: | _CIEOBS, optional | CMF set used for calculation of xyz from spectral data. :cspace: | 'ipt', optional | Color space for color coordinate to rfl mapping. :cspace_tf: | {}, optional | Dict with parameters for xyz_to_... and ..._to_xyz transform. :k_neighbours: | 4 or int, optional | Number of nearest neighbours for reflectance spectrum interpolation. | Neighbours are found using scipy.cKDTree :verbosity: | 0, optional | If > 0: make a plot of the color coordinates of original and rendered image pixels. Returns: :returns: | :rfl_est: | ndarrays with estimated reflectance spectra. """ # get rfl set: if rfl is None: # use IESTM30['4880'] set rfl = _CRI_RFL['ies-tm30']['4880']['5nm'] # get Ref spd: if refspd is None: refspd = _CIE_ILLUMINANTS['D65'].copy() # Calculate lab-type coordinates of standard rfl set under refspd: xyz_rr, xyz_wr = spd_to_xyz(refspd, relative=True, rfl=rfl, cieobs=cieobs, out=2) cspace_tf_copy = cspace_tf.copy() cspace_tf_copy['xyzw'] = xyz_wr # put correct white point in param. dict lab_rr = colortf(xyz_rr, tf=cspace, fwtf=cspace_tf_copy, bwtf=cspace_tf_copy)[:, 0, :] # Convert xyz to lab-type values under refspd: lab = colortf(xyz, tf=cspace, fwtf=cspace_tf_copy, bwtf=cspace_tf_copy) # Find rfl (cfr. lab_rr) from rfl set that results in 'near' metameric # color coordinates for each value in lab_ur (i.e. smallest DE): # Construct cKDTree: tree = cKDTree(lab_rr, copy_data=True) # Interpolate rfls using k nearest neightbours and inverse distance weigthing: d, inds = tree.query(lab, k=k_neighbours) if k_neighbours > 1: w = (1.0 / d**2)[:, :, None] # inverse distance weigthing rfl_est = np.sum(w * rfl[inds + 1, :], axis=1) / np.sum(w, axis=1) else: rfl_est = rfl[inds + 1, :].copy() rfl_est = np.vstack((rfl[0], rfl_est)) if (verbosity > 0) | ('xyz_est' in out.split(',')) | ( 'lab_est' in out.split(',')) | ('DEi_ab' in out.split(',')) | ( 'DEa_ab' in out.split(',')): xyz_est, _ = spd_to_xyz(refspd, rfl=rfl_est, relative=True, cieobs=cieobs, out=2) cspace_tf_copy = cspace_tf.copy() cspace_tf_copy[ 'xyzw'] = xyz_wr # put correct white point in param. dict lab_est = colortf(xyz_est, tf=cspace, fwtf=cspace_tf_copy)[:, 0, :] DEi_ab = np.sqrt(((lab_est[:, 1:3] - lab[:, 1:3])**2).sum(axis=1)) DEa_ab = DEi_ab.mean() if verbosity > 0: ax = plot_color_data(lab[...,1], lab[...,2], z = lab[...,0], \ show = False, cieobs = cieobs, cspace = cspace, \ formatstr = 'ro', label = 'Original') plot_color_data(lab_est[...,1], lab_est[...,2], z = lab_est[...,0], \ show = True, axh = ax, cieobs = cieobs, cspace = cspace, \ formatstr = 'bd', label = 'Rendered') if out == 'rfl_est': return rfl_est elif out == 'rfl_est,xyz_est': return rfl_est, xyz_est else: return eval(out)
def xyz_to_rfl(xyz, rfl = None, out = 'rfl_est', \ refspd = None, D = None, cieobs = _CIEOBS, \ cspace = 'xyz', cspace_tf = {},\ interp_type = 'nd', k_neighbours = 4, verbosity = 0): """ Approximate spectral reflectance of xyz based on nd-dimensional linear interpolation or k nearest neighbour interpolation of samples from a standard reflectance set. Args: :xyz: | ndarray with tristimulus values of target points. :rfl: | ndarray, optional | Reflectance set for color coordinate to rfl mapping. :out: | 'rfl_est' or str, optional :refspd: | None, optional | Refer ence spectrum for color coordinate to rfl mapping. | None defaults to D65. :cieobs: | _CIEOBS, optional | CMF set used for calculation of xyz from spectral data. :cspace: | 'xyz', optional | Color space for color coordinate to rfl mapping. | Tip: Use linear space (e.g. 'xyz', 'Yuv',...) for (interp_type == 'nd'), | and perceptually uniform space (e.g. 'ipt') for (interp_type == 'nearest') :cspace_tf: | {}, optional | Dict with parameters for xyz_to_cspace and cspace_to_xyz transform. :interp_type: | 'nd', optional | Options: | - 'nd': perform n-dimensional linear interpolation using Delaunay triangulation. | - 'nearest': perform nearest neighbour interpolation. :k_neighbours: | 4 or int, optional | Number of nearest neighbours for reflectance spectrum interpolation. | Neighbours are found using scipy.spatial.cKDTree :verbosity: | 0, optional | If > 0: make a plot of the color coordinates of original and | rendered image pixels. Returns: :returns: | :rfl_est: | ndarrays with estimated reflectance spectra. """ # get rfl set: if rfl is None: # use IESTM30['4880'] set rfl = _CRI_RFL['ies-tm30']['4880']['5nm'] # get Ref spd: if refspd is None: refspd = _CIE_ILLUMINANTS['D65'].copy() # Calculate lab-type coordinates of standard rfl set under refspd: xyz_rr, xyz_wr = spd_to_xyz(refspd, relative=True, rfl=rfl, cieobs=cieobs, out=2) cspace_tf_copy = cspace_tf.copy() cspace_tf_copy['xyzw'] = xyz_wr # put correct white point in param. dict lab_rr = colortf(xyz_rr, tf=cspace, fwtf=cspace_tf_copy, bwtf=cspace_tf_copy)[:, 0, :] # Convert xyz to lab-type values under refspd: lab = colortf(xyz, tf=cspace, fwtf=cspace_tf_copy, bwtf=cspace_tf_copy) if interp_type == 'nearest': # Find rfl (cfr. lab_rr) from rfl set that results in 'near' metameric # color coordinates for each value in lab_ur (i.e. smallest DE): # Construct cKDTree: tree = sp.spatial.cKDTree(lab_rr, copy_data=True) # Interpolate rfls using k nearest neightbours and inverse distance weigthing: d, inds = tree.query(lab, k=k_neighbours) if k_neighbours > 1: d += _EPS w = (1.0 / d**2)[:, :, None] # inverse distance weigthing rfl_est = np.sum(w * rfl[inds + 1, :], axis=1) / np.sum(w, axis=1) else: rfl_est = rfl[inds + 1, :].copy() elif interp_type == 'nd': rfl_est = math.ndinterp1_scipy(lab_rr, rfl[1:], lab) _isnan = np.isnan(rfl_est[:, 0]) if ( _isnan.any() ): #do nearest neigbour method for those that fail using Delaunay (i.e. ndinterp1_scipy) # Find rfl (cfr. lab_rr) from rfl set that results in 'near' metameric # color coordinates for each value in lab_ur (i.e. smallest DE): # Construct cKDTree: tree = sp.spatial.cKDTree(lab_rr, copy_data=True) # Interpolate rfls using k nearest neightbours and inverse distance weigthing: d, inds = tree.query(lab[_isnan, ...], k=k_neighbours) if k_neighbours > 1: d += _EPS w = (1.0 / d**2)[:, :, None] # inverse distance weigthing rfl_est_isnan = np.sum(w * rfl[inds + 1, :], axis=1) / np.sum( w, axis=1) else: rfl_est_isnan = rfl[inds + 1, :].copy() rfl_est[_isnan, :] = rfl_est_isnan else: raise Exception('xyz_to_rfl(): unsupported interp_type!') rfl_est[ rfl_est < 0] = 0 #can occur for points outside convexhull of standard rfl set. rfl_est = np.vstack((rfl[0], rfl_est)) if (verbosity > 0) | ('xyz_est' in out.split(',')) | ( 'lab_est' in out.split(',')) | ('DEi_ab' in out.split(',')) | ( 'DEa_ab' in out.split(',')): xyz_est, _ = spd_to_xyz(refspd, rfl=rfl_est, relative=True, cieobs=cieobs, out=2) cspace_tf_copy = cspace_tf.copy() cspace_tf_copy[ 'xyzw'] = xyz_wr # put correct white point in param. dict lab_est = colortf(xyz_est, tf=cspace, fwtf=cspace_tf_copy)[:, 0, :] DEi_ab = np.sqrt(((lab_est[:, 1:3] - lab[:, 1:3])**2).sum(axis=1)) DEa_ab = DEi_ab.mean() if verbosity > 0: ax = plot_color_data(lab[...,1], lab[...,2], z = lab[...,0], \ show = False, cieobs = cieobs, cspace = cspace, \ formatstr = 'ro', label = 'Original') plot_color_data(lab_est[...,1], lab_est[...,2], z = lab_est[...,0], \ show = True, axh = ax, cieobs = cieobs, cspace = cspace, \ formatstr = 'bd', label = 'Rendered') if out == 'rfl_est': return rfl_est elif out == 'rfl_est,xyz_est': return rfl_est, xyz_est else: return eval(out)
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 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'] if isinstance(cieobs, str): cmf = _CMF[cieobs]['bar'] else: cmf = cieobs 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
def plotSL(cieobs =_CIEOBS, cspace = _CSPACE, DL = False, BBL = True, D65 = False,\ EEW = False, cctlabels = False, axh = None, show = True,\ cspace_pars = {}, formatstr = 'k-',\ diagram_colors = False, diagram_samples = 100, diagram_opacity = 1.0,\ diagram_lightness = 0.25,\ **kwargs): """ Plot spectrum locus for cieobs in cspace. Args: :DL: | True or False, optional | True plots Daylight Locus as well. :BBL: | True or False, optional | True plots BlackBody Locus as well. :D65: | False or True, optional | True plots D65 chromaticity as well. :EEW: | False or True, optional | True plots Equi-Energy-White chromaticity as well. :cctlabels: | False or True, 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()) :diagram_colors: | False, optional | True: plot colored chromaticity diagram. :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 :kwargs: | additional keyword arguments for use with matplotlib.pyplot. Returns: :returns: | None (:show: == True) | or | handle to current axes (:show: == False) """ 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) showcopy = show if np.any([DL, BBL, D65, EEW]): show = False if diagram_colors == True: axh_ = plot_chromaticity_diagram_colors(axh = axh, show = diagram_colors, cspace = cspace, cieobs = cieobs,\ cspace_pars = cspace_pars,\ diagram_samples = diagram_samples,\ diagram_opacity = diagram_opacity,\ diagram_lightness = diagram_lightness,\ label_fontname = None, label_fontsize = None) else: axh_ = axh axh_ = plot_color_data(x, y, axh=axh_, cieobs=cieobs, cspace=cspace, show=show, formatstr=formatstr, **kwargs) if DL == True: if 'label' in kwargs.keys(): # avoid label also being used for DL kwargs.pop('label') plotDL(ccts=None, cieobs=cieobs, cspace=cspace, axh=axh_, show=show, cspace_pars=cspace_pars, formatstr='k:', **kwargs) if BBL == True: if 'label' in kwargs.keys(): # avoid label also being used for BB kwargs.pop('label') plotBB(ccts=None, cieobs=cieobs, cspace=cspace, axh=axh_, show=show, cspace_pars=cspace_pars, cctlabels=cctlabels, formatstr='k-.', **kwargs) if D65 == True: YxyD65 = colortf(spd_to_xyz(_CIE_ILLUMINANTS['D65'], cieobs=cieobs), tf=cspace, tfa0=cspace_pars) axh.plot(YxyD65[..., 1], YxyD65[..., 2], 'bo') if EEW == True: YxyEEW = colortf(spd_to_xyz(_CIE_ILLUMINANTS['E'], cieobs=cieobs), tf=cspace, tfa0=cspace_pars) axh.plot(YxyEEW[..., 1], YxyEEW[..., 2], 'ko') if showcopy == False: return axh_ else: plt.show()
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
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 = True, 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: | True, 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: """ offset = _EPS ii, jj = np.meshgrid(np.linspace(offset, 1 + offset, diagram_samples), np.linspace(1+offset, offset, diagram_samples)) ij = np.dstack((ii, jj)) SL = _CMF[cieobs]['bar'][1:4].T SL = np.vstack((SL,SL[0])) SL = 100.0*SL/SL[:,1,None] SL = colortf(SL, tf = cspace, tfa0 = cspace_pars) Y,x,y = asplit(SL) SL = np.vstack((x,y)).T 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 = (0.0, 1, -0.05, 1), clip_path=None, alpha=diagram_opacity) image.set_clip_path(polygon) plt.plot(x,y, color = 'darkgray') if cspace == 'Yxy': plt.xlim([0,1]) plt.ylim([0,1]) elif cspace == 'Yuv': plt.xlim([0,0.6]) plt.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): plt.xlabel(xlabel, fontname = label_fontname, fontsize = label_fontsize) plt.ylabel(ylabel, fontname = label_fontname, fontsize = label_fontsize) if show_grid == True: plt.grid() #plt.show() return axh else: return None
def subsample_RFL_set(rfl, rflpath = '', samplefcn = 'rand', S = _CIE_ILLUMINANTS['E'], \ jab_ranges = None, jab_deltas = None, cieobs = _VF_CIEOBS, cspace = _VF_CSPACE, \ ax = np.arange(-_VF_MAXR,_VF_MAXR+_VF_DELTAR,_VF_DELTAR), \ bx = np.arange(-_VF_MAXR,_VF_MAXR+_VF_DELTAR,_VF_DELTAR), \ jx = None, limit_grid_radius = 0): """ Sub-samples a spectral reflectance set by pixelization of color space. Args: :rfl: | ndarray or str | Array with of str referring to a set of spectral reflectance functions to be subsampled. | If str to file: file must contain data as columns, with first column the wavelengths. :rflpath: | '' or str, optional | Path to folder with rfl-set specified in a str :rfl: filename. :samplefcn: | 'rand' or 'mean', optional | -'rand': selects a random sample from the samples within each pixel | -'mean': returns the mean spectral reflectance in each pixel. :S: | _CIE_ILLUMINANTS['E'], optional | Illuminant used to calculate the color coordinates of the spectral reflectance samples. :jab_ranges: | None or ndarray, optional | Specifies the pixelization of color space. (ndarray.shape = (3,3), with first axis: J,a,b, and second axis: min, max, delta) :jab_deltas: | float or ndarray, optional | Specifies the sampling range. | A float uses jab_deltas as the maximum Euclidean distance to select samples around each pixel center. A ndarray of 3 deltas, uses a city block sampling around each pixel center. :cspace: | _VF_CSPACE or dict, optional | Specifies color space. See _VF_CSPACE_EXAMPLE for example structure. :cieobs: | _VF_CIEOBS or str, optional | Specifies CMF set used to calculate color coordinates. :ax: | default ndarray or user defined ndarray, optional | default = np.arange(-_VF_MAXR,_VF_MAXR+_VF_DELTAR,_VF_DELTAR) :bx: | default ndarray or user defined ndarray, optional | default = np.arange(-_VF_MAXR,_VF_MAXR+_VF_DELTAR,_VF_DELTAR) :jx: | None, optional | Note that not-None :jab_ranges: override :ax:, :bx: and :jx input. :limit_grid_radius: | 0, optional | A value of zeros keeps grid as specified by axr,bxr. | A value > 0 only keeps (a,b) coordinates within :limit_grid_radius: Returns: :returns: | rflsampled, jabp | ndarrays with resp. the subsampled set of spectral reflectance functions and the pixel coordinate centers. """ # Testing effects of sample set, pixel size and gamut size: if type(rfl) == str: rfl = pd.read_csv(os.path.join(rflpath,rfl),header = None).get_values().T # Calculate Jab coordinates of samples: xyz,xyzw = spd_to_xyz(S, cieobs = cieobs, rfl = rfl.copy(), out = 2) cspace_pars = cspace.copy() cspace_pars.pop('type') cspace_pars['xyzw'] = xyzw jab = colortf(xyz,tf = cspace['type'],fwtf = cspace_pars) # Generate grid and get samples in each grid: gridp,idxp, jabp, pixelsamplenrs, pixelIDs = get_pixel_coordinates(jab, jab_ranges = jab_ranges, jab_deltas = jab_deltas, limit_grid_radius = limit_grid_radius) # Get rfls from set using sampling function (mean or rand): W = rfl[:1] R = rfl[1:] rflsampled = np.nan*np.ones((len(idxp),R.shape[1])) for i in range(len(idxp)): if samplefcn == 'mean': rfl_i = np.nanmean(rfl[pixelsamplenrs[i],:],axis = 0) else: samplenr_i = np.random.randint(len(pixelsamplenrs[i])) rfl_i = rfl[pixelsamplenrs[i][samplenr_i],:] rflsampled[i,:] = rfl_i rflsampled = np.vstack((W,rflsampled)) return rflsampled, jabp
def DE_cspace(xyzt, xyzr, dtype = 'xyz', tf = _CSPACE, DEtype = 'jab', avg = None, avg_axis = 0, out = 'DEi', xyzwt = None, xyzwr = None, fwtft = {}, fwtfr = {}, KLCH = None,\ camtype = cam._CAM_DEFAULT_TYPE, ucstype = 'ucs'): """ Calculate color difference DE in specific color space. Args: :xyzt: | ndarray with tristimulus values of test data. :xyzr: | ndarray with tristimulus values of reference data. :dtype: | 'xyz' or 'jab', optional | Specifies data type in :xyzt: and :xyzr:. :xyzwt: | None or ndarray, optional | White point tristimulus values of test data | None defaults to the one set in :fwtft: | or else to the default of cspace. :xyzwr: | None or ndarray, optional | Whitepoint tristimulus values of reference data | None defaults to the one set in non-empty :fwtfr: | or else to default of cspace. :tf: | _CSPACE, optional | Color space to use for color difference calculation. :fwtft: | {}, optional | Dict with parameters for forward transform from xyz to cspace for test data. :fwtfr: | {}, optional | Dict with parameters for forward transform | from xyz to cspace for reference data. :KLCH: | None, optional | Weigths for L, C, H | None: default to [1,1,1] | KLCH is not used when tf == 'camucs'. :DEtype: | 'jab' or str, optional | Options: | - 'jab' : calculates full color difference over all 3 dimensions. | - 'ab' : calculates chromaticity difference. | - 'j' : calculates lightness or brightness difference | (depending on :outin:). | - 'j,ab': calculates both 'j' and 'ab' options | and returns them as a tuple. :avg: | None, optional | None: don't calculate average DE, | otherwise use function handle in :avg:. :avg_axis: | axis to calculate average over, optional :out: | 'DEi' or str, optional | Requested output. :camtype: | luxpy.cam._CAM_DEFAULT_TYPE, optional | Str specifier for CAM type to use, options: 'ciecam02' or 'ciecam16'. | Only when DEtype == 'camucs'. :ucstype: | 'ucs' or 'lcd' or 'scd', optional | Str specifier for which type of color attribute compression | parameters to use: | -'ucs': uniform color space, | -'lcd', large color differences, | -'scd': small color differences | Only when DEtype == 'camucs'. Note: For the other input arguments, see specific color space used. Returns: :returns: | ndarray with DEi [, DEa] or other as specified by :out: """ # Get xyzw from dict if xyzw is None & dict is Not None if xyzwr is not None: fwtfr['xyzw'] = xyzwr else: if bool(fwtfr): xyzwr = fwtfr['xyzw'] if xyzwt is not None: fwtft['xyzw'] = xyzwt else: if bool(fwtft): xyzwt = fwtft['xyzw'] if tf == 'camucs': if dtype == 'xyz': if fwtfr['xyzw'] is None: fwtfr['xyzw'] = cam._CAM_DEFAULT_WHITE_POINT if fwtft['xyzw'] is None: fwtft['xyzw'] = cam._CAM_DEFAULT_WHITE_POINT jabt = cam.camXucs(xyzt, camtype=camtype, ucstype=ucstype, **fwtft) jabr = cam.camXucs(xyzr, camtype=camtype, ucstype=ucstype, **fwtfr) else: jabt = xyzt jabr = xyzr KL, c1, c2 = [ cam._CAM_UCS_PARAMETERS[camtype][ucstype][x] for x in sorted(cam._CAM_UCS_PARAMETERS[camtype][ucstype].keys()) ] # Calculate color difference and take account of KL: DEi = ((((jabt[...,0:1]-jabr[...,0:1])/KL)**2).sum(axis = jabt[...,0:1].ndim - 1, keepdims = True),\ ((jabt[...,1:3]-jabr[...,1:3])**2).sum(axis = jabt[...,1:3].ndim - 1, keepdims = True)) elif (tf == 'DE2000') | (tf == 'DE00'): return DE2000(xyzt, xyzr, dtype = 'xyz', DEtype = DEtype, avg = avg,\ avg_axis = avg_axis, out = out, xyzwt = xyzwt, xyzwr = xyzwr, KLCH = KLCH) else: if dtype == 'xyz': # Use colortf: jabt = colortf(xyzt, tf=tf, fwtf=fwtft) jabr = colortf(xyzr, tf=tf, fwtf=fwtfr) else: jabt = xyzt jabr = xyzr if (KLCH == None) | (KLCH == [1, 1, 1]): # Calculate color difference and take account of KL: DEi = (((jabt[...,0:1]-jabr[...,0:1])**2).sum(axis = jabt[...,0:1].ndim - 1, keepdims = True),\ ((jabt[...,1:3]-jabr[...,1:3])**2).sum(axis = jabt[...,1:3].ndim - 1, keepdims = True)) else: #using LCH specification for use with KLCH weights: Jt = jabt[..., 0:1] at = jabt[..., 1:2] bt = jabt[..., 2:3] Ct = np.sqrt(at**2 + bt**2) ht = cam.hue_angle(at, bt, htype='rad') Jr = jabr[..., 0:1] ar = jabr[..., 1:2] br = jabr[..., 2:3] Cr = np.sqrt(ar**2 + br**2) hr = cam.hue_angle(at, bt, htype='rad') dJ = Jt - Jr dC = Ct - Cr dH = ht - hr DEab2 = ((at - ar)**2 + (bt - br)**2) dH = np.sqrt(DEab2 - dC**2) DEi = ((dJ / KLCH[0])**2, (dC / KLCH[1])**2 + (dH / KLCH[2])**2) return _process_DEi(DEi, DEtype=DEtype, avg=avg, avg_axis=avg_axis, out=out)
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 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 plotSL(cieobs=_CIEOBS, cspace=_CSPACE, DL=True, BBL=True, D65=False, EEW=False, cctlabels=False, axh=None, show=True, cspace_pars={}, formatstr='k-', **kwargs): """ Plot spectrum locus for cieobs in cspace. Args: :DL: | True or False, optional | True plots Daylight Locus as well. :BBL: | True or False, optional | True plots BlackBody Locus as well. :D65: | False or True, optional | True plots D65 chromaticity as well. :EEW: | False or True, optional | True plots Equi-Energy-White chromaticity as well. :cctlabels: | False or True, 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) """ SL = _CMF[cieobs]['bar'][1:4].T SL = np.vstack((SL, SL[0])) SL = 100.0 * SL / SL[:, 1, None] SL = colortf(SL, tf=cspace, tfa0=cspace_pars) Y, x, y = asplit(SL) showcopy = show if np.any([DL, BBL, D65, EEW]): show = False axh_ = plot_color_data(x, y, axh=axh, cieobs=cieobs, cspace=cspace, show=show, formatstr=formatstr, **kwargs) if DL == True: if 'label' in kwargs.keys(): # avoid label also being used for DL kwargs.pop('label') plotDL(ccts=None, cieobs=cieobs, cspace=cspace, axh=axh, show=show, cspace_pars=cspace_pars, formatstr='k:', **kwargs) if BBL == True: if 'label' in kwargs.keys(): # avoid label also being used for BB kwargs.pop('label') plotBB(ccts=None, cieobs=cieobs, cspace=cspace, axh=axh, show=show, cspace_pars=cspace_pars, cctlabels=cctlabels, formatstr='k-.', **kwargs) if D65 == True: YxyD65 = colortf(spd_to_xyz(_CIE_ILLUMINANTS['D65']), tf=cspace, tfa0=cspace_pars) plt.plot(YxyD65[..., 1], YxyD65[..., 2], 'bo') if EEW == True: YxyEEW = colortf(spd_to_xyz(_CIE_ILLUMINANTS['E']), tf=cspace, tfa0=cspace_pars) plt.plot(YxyEEW[..., 1], YxyEEW[..., 2], 'ko') if showcopy == False: return axh_ else: plt.show()