def test_equality_input(): # get TC197 data: vd_197 = VisualData() in_197 = { 'LMSa': vd_197.absorbance.T.copy(), 'rmd': vd_197.macula_rel.T.copy(), 'docul2': vd_197.docul2.T.copy(), 'docul1_fine': vd_197.docul1_fine.T.copy(), 'docul2_fine': vd_197.docul2_fine.T.copy(), 'xyz1931': vd_197.XYZ31.T.copy(), 'xyz1964': vd_197.XYZ64.T.copy() } # Get luxpy data: data = ic.init(wl=None, dsrc_std='matlab', dsrc_lms_odens='cietc197', lms_to_xyz_method='cietc197', use_sign_figs=True, use_my_round=True, use_chop=True, out=1) in_lx = data['odata'] wls = in_lx['wls'] in_lx['docul1_fine'] = in_lx['docul'][1, :] in_lx['docul2_fine'] = in_lx['docul'][2, :] in_lx['xyz1931'] = lx.cie_interp(lx._CMF['1931_2']['bar'], wls, kind='cmf') in_lx['xyz1964'] = lx.cie_interp(lx._CMF['1964_10']['bar'], wls, kind='cmf') return in_197, in_lx
def hsi_to_rgb(hsi, spd=None, cieobs=_CIEOBS, srgb=False, linear_rgb=False, CSF=None, wl=[380, 780, 1]): """ Convert HyperSpectral Image to rgb. Args: :hsi: | ndarray with hyperspectral image [M,N,L] :spd: | None, optional | ndarray with illumination spectrum :cieobs: | _CIEOBS, optional | CMF set to convert spectral data to xyz tristimulus values. :srgb: | False, optional | If False: Use xyz_to_srgb(spd_to_xyz(...)) to convert to srgb values | If True: use camera sensitivity functions. :linear_rgb: | False, optional | If False: use gamma = 2.4 in xyz_to_srgb, if False: use gamma = 1. :CSF: | None, optional | ndarray with camera sensitivity functions | If None: use Nikon D700 :wl: | [380,780,1], optional | Wavelength range and spacing or ndarray with wavelengths of HSI image. Returns: :rgb: | ndarray with rgb image [M,N,3] """ if spd is None: spd = _CIE_E.copy() wlr = getwlr(wl) spd = cie_interp(spd, wl, kind='linear') hsi_2d = np.reshape(hsi, (hsi.shape[0] * hsi.shape[1], hsi.shape[2])) if srgb: xyz = spd_to_xyz(spd, cieobs=cieobs, relative=True, rfl=np.vstack((wlr, hsi_2d))) gamma = 1 if linear_rgb else 2.4 rgb = xyz_to_srgb(xyz, gamma=gamma) / 255 else: if CSF is None: CSF = _CSF_NIKON_D700 rgb = rfl_to_rgb(hsi_2d, spd=spd, CSF=CSF, wl=wl) return np.reshape(rgb, (hsi.shape[0], hsi.shape[1], 3))
def spdBB(CCT=5500, wl=[400, 700, 5], Lw=25000, cieobs='1964_10'): wl = getwlr(wl) dl = wl[1] - wl[0] spd = 2 * np.pi * 6.626068E-34 * (299792458**2) / ( (wl * 0.000000001)** 5) / (np.exp(6.626068E-34 * 299792458 / (wl * 0.000000001) / 1.3806503E-23 / CCT) - 1) spd = Lw * spd / (dl * 683 * (spd * cie_interp( _CMF[cieobs]['bar'].copy(), wl, kind='cmf')[2, :]).sum()) return np.vstack((wl, spd))
def _convert_to_wlr(entries=rgb2spec_entries, wlr=_WL3): wlr = getwlr(wlr) for entry in entries: if entry != 'wlr': for (k, v) in entries[entry].items(): if k != 'scalefactor': entries[entry][k] = cie_interp(_addwlr(entries[entry][k]), wlr, kind=entry)[1] entries['wlr'] = wlr return entries
def rfl_to_rgb(rfl, spd=None, CSF=None, wl=None, normalize_to_white=True): """ Convert spectral reflectance functions (illuminated by spd) to Camera Sensitivity Functions. Args: :rfl: | ndarray with spectral reflectance functions (1st row is wavelengths if wl is None). :spd: | None, optional | ndarray with illumination spectrum :CSF: | None, optional | ndarray with camera sensitivity functions | If None: use Nikon D700 :normalize_to_white: | True, optional | If True: white-balance output rgb to a perfect white diffuser. Returns: :rgb: | ndarray with rgb values for each spectral reflectance functions """ rfl_cp = rfl.copy() if (wl is None): wl = rfl_cp[0] rfl_cp = rfl_cp[1:] wlr = getwlr(wl) if spd is not None: spd = cie_interp(spd, wlr, kind='linear')[1:] else: spd = np.ones_like(wlr) if CSF is None: CSF = _CSF_NIKON_D700 CSF = cie_interp(CSF, wlr, kind='linear') CSF[1:] = CSF[1:] * spd rgb = rfl_cp @ CSF[1:].T if normalize_to_white: white = np.ones_like(spd) rgbw = white @ CSF[1:].T rgb = rgb / rgbw.max(axis=0, keepdims=True) return rgb
def interpolate_efficiency_functions(wl, cs_cl_lrs): """ Interpolate all spectral data in dict cs_cl_lrs to new wavelength range. """ for key in cs_cl_lrs: if key[-1] == 'l': #signifies l for spectral data temp = np.vstack((cs_cl_lrs['WL'],cs_cl_lrs[key])) # construct [wl,S] data cs_cl_lrs[key] = cie_interp(temp,wl, kind = 'cmf', negative_values_allowed=True)[1:] # interpolate and store in dict cs_cl_lrs['WL'] = wl # store new wavelength range return cs_cl_lrs
def cie_interp(self, wl_new, kind='auto', negative_values_allowed=False, extrap_values=None): """ Interpolate / extrapolate spectral data following standard CIE15-2018. | The interpolation type depends on the spectrum type defined in :kind:. | Extrapolation is always done by replicate the closest known values. Args: :wl_new: | ndarray with new wavelengths :kind: | 'auto', optional | If :kind: is None, return original data. | If :kind: is a spectrum type (see _INTERP_TYPES), the correct | interpolation type if automatically chosen. | If kind = 'auto': use self.dtype | Or :kind: can be any interpolation type supported by scipy.interpolate.interp1d :negative_values_allowed: | False, optional | If False: negative values are clipped to zero :extrap_values: | None, optional | float or list or ndarray with values to extrapolate | If None: use CIE recommended 'closest value' approach. Returns: :returns: | ndarray of interpolated spectral data. | (.shape = (number of spectra+1, number of wavelength in wl_new)) """ if (kind == 'auto') & (self.dtype is not None): kind = self.dtype spd = cie_interp(self.get_(), wl_new, kind=kind, negative_values_allowed=negative_values_allowed, extrap_values=extrap_values) self.wl = spd[0] self.value = spd[1:] return self
if __name__ == '__main__': #-------------------------------------------------------------------------- # Code test #-------------------------------------------------------------------------- import luxpy as lx from luxpy import np # Prepare some illuminant data: C = _CIE_ILLUMINANTS['C'].copy() Ill1 = C Ill2 = np.vstack( (C, lx.cie_interp(_CIE_ILLUMINANTS['D65'], C[0], kind='spd')[1:], C[1:, :] * 2, C[1:, :] * 3)) # Prepare some sample data: rflM = lx._MUNSELL['R'].copy() rflM = lx.cie_interp(rflM, C[0], kind='rfl') # Setup some model parameters: cieobs = '2006_10' Lw = 400 # Create Lw normalized data: # Normalize to Lw: def normalize_to_Lw(Ill, Lw, cieobs, rflM): xyzw = lx.spd_to_xyz(Ill, cieobs=cieobs, relative=False) for i in range(Ill.shape[0] - 1): Ill[i + 1] = Lw * Ill[i + 1] / xyzw[i, 1]
Yuv_t = Yuv_t[:-1, ...] Yuv_r = Yuv_r[:-1, ...] # Define preferred chromaticity shifts for 8 CIE CRI samples: # (*5, because Thorton uses full preferred shifts unlike Judd's Flattery Index) uv_shifts = np.array([[0.0020, 0.0008], [0.0000, 0.0000], [ -0.0020, 0.0008 ], [-0.0020, 0.0010], [-0.0020, -0.0004], [-0.0012, -0.0020], [0.0008, -0.0020], [0.0020, -0.0010]]) * 5 # Calculate chromaticity difference between test and shifted ref coordinates: DE = 800 * (( (Yuv_t[..., 1:] - (Yuv_r[..., 1:] + uv_shifts[:, None, :]))**2).sum(axis=-1))**0.5 # Calculate CPI: CPI = 156 - 7.317 * DE.mean( axis=0) # in Thornton 1974 we find 7.18, but then CPI(D65)!=100 return CPI if __name__ == '__main__': import luxpy as lx F4 = lx.cie_interp(lx._CIE_F4, wl_new=lx.getwlr([360, 830, 1]), kind='spd') D65 = lx.cie_interp(lx._CIE_D65, wl_new=lx.getwlr([360, 830, 1]), kind='spd') spds = np.vstack((F4, D65[1:, :])) cpi1 = spd_to_thornton_cpi(F4) cpi2 = spd_to_thornton_cpi(spds)
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)
else: f = lambda xyz, xyzw: cam.xyz_to_jabC_ciecam02( xyz, xyzw=xyzw, La=1000 * 20 / np.pi / 100, Yb=20, surround='avg') lab = f(xyz, xyzw) labd65 = np.repeat(f(_XYZ_D65_REF, _XYZW_D65_REF), lab.shape[1], axis=1) fci = 100 * (_polyarea3D(lab) / _polyarea3D(labd65))**1.5 return fci if __name__ == '__main__': import luxpy as lx F6 = lx.cie_interp(lx._CIE_ILLUMINANTS['F6'], wl_new=lx.getwlr([360, 830, 1]), kind='spd') F4 = lx.cie_interp(lx._CIE_F4, wl_new=lx.getwlr([360, 830, 1]), kind='spd') D65 = lx.cie_interp(lx._CIE_D65, wl_new=lx.getwlr([360, 830, 1]), kind='spd') spds = np.vstack((F6, F4[1:, :], D65[1:, :])) fci1a = spd_to_fci(F6, True) print(fci1a) fci1b = spd_to_fci(F6, False) print(fci1b) fci2 = spd_to_fci(spds) print(fci2)
Wrapper function for cam15u inverse mode with 'Q,aW,bW' input. | For help on parameter details: ?luxpy.cam.cam15u """ return cam15u(qab, fov=fov, direction='inverse', inputtype='xyz', outin='Q,aW,bW', parameters=parameters) #------------------------------------------------------------------------------ if __name__ == '__main__': C = _CIE_ILLUMINANTS['C'].copy() C = np.vstack((C, cie_interp(_CIE_ILLUMINANTS['D65'], C[0], kind='spd')[1:])) M = _MUNSELL.copy() rflM = M['R'] cieobs = '2006_10' # Normalize to Lw: Lw = 100 xyzw2 = spd_to_xyz(C, cieobs=cieobs, relative=False) for i in range(C.shape[0] - 1): C[i + 1] = Lw * C[i + 1] / xyzw2[i, 1] xyz, xyzw = spd_to_xyz(C, cieobs=cieobs, relative=True, rfl=rflM, out=2) qab = xyz_to_qabW_cam15u(xyzw, fov=10.0) qab2 = cam15u(C, fov=10.0, direction='forward',
def spd_to_CS_CLa_lrc(El = None, E = None, \ sum_sources = False, interpolate_sources = True): """ Calculate Circadian Stimulus (CS) and Circadian Light [LRC: Rea et al 2012]. Args: :El: | ndarray, optional | Defaults to D65 | light source spectral irradiance distribution :E: | None, float or ndarray, optional | Illuminance of light sources. | If None: El is used as is, otherwise El is renormalized to have an illuminance equal to E. :sum_sources: | False, optional | - False: calculate CS and CLa for all sources in El array. | - True: sum sources in El to a single source and perform calc. :interpolate_sources: | True, optional | - True: El is interpolated to wavelength range of efficiency | functions (as in LRC calculator). | - False: interpolate efficiency functions to source range. | Source interpolation is not recommended due to possible | errors for peaky spectra. | (see CIE15-2004, "Colorimetry"). Returns: :CS: | ndarray with Circadian stimulus values :CLa: | ndarray with Circadian Light values Notes: 1. The original 2012 (E.q. 1) had set the peak wavelength of the melanopsin at 480 nm. Rea et al. later published a corrigendum with updated model parameters for k, a_{b-y} and a_rod. The comparison table between showing values calculated for a number of sources with the old and updated parameters were very close (~1 unit voor CLa). 2. In that corrrection paper they did not mention a change in the factor (1622) that multiplies the (sum of) the integral(s) in Eq. 1. HOWEVER, the excel calculator released in 2017 and the online calculator show that factor to have a value of 1547.9. The change in values due to the new factor is much larger than their the updated mentioned in note 1! 3. For reasons of consistency the calculator uses the latest model parameters, as could be read from the excel calculator. They values adopted are: multiplier 1547.9, k = 0.2616, a_{b-y} = 0.7 and a_rod = 3.3. 4. The parameter values to convert CLa to CS were also taken from the 2017 excel calculator. References: 1. `LRC Online Circadian stimulus calculator <http://www.lrc.rpi.edu/cscalculator/>`_ 2. `LRC Excel based Circadian stimulus calculator. <http://www.lrc.rpi.edu/resources/CSCalculator_2017_10_03_Mac.xlsm>`_ 3. `Rea MS, Figueiro MG, Bierman A, and Hamner R (2012). Modelling the spectral sensitivity of the human circadian system. Light. Res. Technol. 44, 386–396. <https://doi.org/10.1177/1477153511430474>`_ 4. `Rea MS, Figueiro MG, Bierman A, and Hamner R (2012). Erratum: Modeling the spectral sensitivity of the human circadian system (Lighting Research and Technology (2012) 44:4 (386-396)). Light. Res. Technol. 44, 516. <https://doi.org/10.1177/1477153512467607>`_ """ # Create copy of dict with model parameters and spectral data: cs_cl_lrs = _LRC_CLA_CS_CONST['CLa'].copy() # Interpolate efficiency functions to light source wl-range: if interpolate_sources is False: cs_cl_lrs = interpolate_efficiency_functions(El[0], cs_cl_lrs) else: El = cie_interp(El, cs_cl_lrs['WL'], kind='spd') # Get wavelength spacing: dl = getwld(El[0]) # Separate wavelengths and data: wl = El[0] Elv = El[1:].copy() # define integral function: integral = lambda x: integrate.trapz(x, x=wl, axis=-1) #integral = lambda x: np.sum(x, axis = -1) # Rescale El to E (if not None): if E is not None: # Calculate current E value of El: E_cv = np.atleast_2d(683.002 * integral(cs_cl_lrs['Vphotl'] * Elv * dl)) # Rescale El to supplied E: Elv = (E / E_cv).T * Elv # Sum all sources in array if requested: if sum_sources == True: Elv = Elv.sum(axis=0, keepdims=True) # Calculate Circadian light using model param. and spectral data: CLa = fCLa(wl, Elv, integral, **cs_cl_lrs) # Calculate Circadian stimulus: CS = 0.7 * (1 - (1 / (1 + (CLa / 355.7)**1.1026))) return CS, CLa
relative=True, inputtype='xyz', direction='forward', parameters=parameters, cieobs='2006_10', match_to_conversionmatrix_to_cieobs=True) #------------------------------------------------------------------------------ if __name__ == '__main__0': test_model() if __name__ == '__main__': C = _CIE_ILLUMINANTS['C'].copy() C = np.vstack((C, cie_interp(_CIE_ILLUMINANTS['D65'], C[0], kind='spd')[1:], C[1:, :] * 2, C[1:, :] * 3)) M = _MUNSELL.copy() rflM = M['R'] rflM = cie_interp(rflM, C[0], kind='rfl') cieobs = '2006_10' Lw = 400 Yb = 20 # Normalize to Lw: xyzw2 = spd_to_xyz(C, cieobs=cieobs, relative=False) for i in range(C.shape[0] - 1): C[i + 1] = Lw * C[i + 1] / xyzw2[i, 1] CM = [] for i in range(C.shape[0] - 1): CM.append(np.vstack((C[0], C[i + 1] * rflM[1:, :]))) CM = np.transpose(np.array(CM), (1, 0, 2))
""" return cam18sl(xyz, datab = xyzb, Lb = Lb, fov = fov, direction = 'forward', inputtype = 'xyz', outin = 'Q,aS,bS', parameters = parameters) def qabS_cam18sl_to_xyz(qab, xyzb = None, Lb = [100], fov = 10.0, parameters = None, **kwargs): """ Wrapper function for cam18sl inverse mode with 'Q,aS,bS' input. | For help on parameter details: ?luxpy.cam.cam18sl """ return cam18sl(qab, datab = xyzb, Lb = Lb, fov = fov, direction = 'inverse', inputtype = 'xyz', outin = 'Q,aS,bS', parameters = parameters) #------------------------------------------------------------------------------ if __name__ == '__main__': C = _CIE_ILLUMINANTS['C'].copy() C = np.vstack((C,cie_interp(_CIE_ILLUMINANTS['D65'],C[0],kind='spd')[1:])) M = _MUNSELL.copy() rflM = M['R'] cieobs = '2006_10' # Normalize to Lw: Lw = 100 xyzw2 = spd_to_xyz(C, cieobs = cieobs, relative = False) for i in range(C.shape[0]-1): C[i+1] = Lw*C[i+1]/xyzw2[i,1] xyz, xyzw = spd_to_xyz(C, cieobs = cieobs, relative = True, rfl = rflM, out = 2) qab = xyz_to_qabW_cam18sl(xyzw, xyzb = None, Lb = [100], fov = 10.0) print('qab: ',qab) qab2 = cam18sl(C, datab = None, Lb = [100], fov = 10.0, direction = 'forward', inputtype = 'spd', outin = 'Q,aW,bW', parameters = None)
def test_model(): import pandas as pd import luxpy as lx # Read selected set of Munsell samples and LMS10(lambda): M = pd.read_csv('Munsell_LMS_nonlin_Nov18_2015_version.dat', header=None, sep='\t').values YLMS10_ = pd.read_csv('YLMS10_LMS_nonlin_Nov18_2015_version.dat', header=None, sep='\t').values Y10_ = YLMS10_[[0, 1], :].copy() LMS10_ = YLMS10_[[0, 2, 3, 4], :].copy() # Calculate lms: Y10 = cie_interp(_CMF['1964_10']['bar'].copy(), getwlr([400, 700, 5]), kind='cmf')[[0, 2], :] XYZ10_lx = _CMF['2006_10']['bar'].copy() XYZ10_lx = cie_interp(XYZ10_lx, getwlr([400, 700, 5]), kind='cmf') LMS10_lx = np.vstack( (XYZ10_lx[:1, :], np.dot( math.normalize_3x3_matrix(_CMF['2006_10']['M'], np.array([[1, 1, 1]])), XYZ10_lx[1:, :]))) LMS10 = cie_interp(LMS10_lx, getwlr([400, 700, 5]), kind='cmf') #LMS10 = np.vstack((XYZ10[:1,:],np.dot(lx.math.normalize_3x3_matrix(_CMF['2006_10']['M'],np.array([[1,1,1]])),XYZ10_lx[1:,:]))) #LMS10[1:,:] = LMS10[1:,:]/LMS10[1:,:].sum(axis=1,keepdims=True)*Y10[1:,:].sum() # test python model vs excel calculator: def spdBB(CCT=5500, wl=[400, 700, 5], Lw=25000, cieobs='1964_10'): wl = getwlr(wl) dl = wl[1] - wl[0] spd = 2 * np.pi * 6.626068E-34 * (299792458**2) / ( (wl * 0.000000001)** 5) / (np.exp(6.626068E-34 * 299792458 / (wl * 0.000000001) / 1.3806503E-23 / CCT) - 1) spd = Lw * spd / (dl * 683 * (spd * cie_interp( _CMF[cieobs]['bar'].copy(), wl, kind='cmf')[2, :]).sum()) return np.vstack((wl, spd)) # Create long term and applied spds: spd5500 = spdBB(5500, Lw=25000, wl=[400, 700, 5], cieobs='1964_10') spd6500 = spdBB(6500, Lw=400, wl=[400, 700, 5], cieobs='1964_10') # Calculate lms0 as a check: clms = np.array( [0.98446776, 0.98401909, 0.98571412] ) # correction factor for slight differences in _CMF and the cmfs from the excel calculator lms0 = 5 * 683 * (spd5500[1:] * LMS10[1:, :] * 0.2).sum(axis=1).T # Full excel parameters for testing: parameters = { 'cLMS': np.array([1, 1, 1]), 'lms0': np.array([4985.02802565, 5032.49518502, 4761.27272226]) * 1, 'Cc': 0.251617118325755, 'Cf': -0.4, 'clambda': [0.5, 0.5, 0.0], 'calpha': [1.0, -1.0, 0.0], 'cbeta': [0.5, 0.5, -1.0], 'cga1': [26.1047711317923, 33.9721745703298], 'cgb1': [6.76038379211498, 10.9220216677629], 'cga2': [0.587271269247578], 'cgb2': [-0.952412544980473], 'cl_int': [14.0035243121804, 1.0], 'cab_int': [4.99218965716342, 65.7869547646456], 'cab_out': [-0.1, -1.0], 'Ccwb': None, 'Mxyz2lms': [[0.21701045, 0.83573367, -0.0435106], [-0.42997951, 1.2038895, 0.08621089], [0., 0., 0.46579234]] } # Note cLMS is a relative scaling factor between CIE2006 10° and 1964 10°: # clms = np.array([1.00164919, 1.00119269, 1.0029173 ]) = (Y10[1:,:].sum(axis=1)/LMS10[1:,:].sum(axis=1))*(406.98099078/400) #parameters =_CAM_SWW16_PARAMETERS['JOSA'] # Calculate Munsell spectra multiplied with spd6500: spd6500xM = np.vstack((spd6500[:1, :], spd6500[1:, :] * M[1:, :])) # Test spectral input: print('SPD INPUT -----') jab = cam_sww16(spd6500xM, dataw=spd6500, Yb=20.0, Lw=400.0, Ccwb=1, relative=True, inputtype='spd', direction='forward', parameters=parameters, cieobs='2006_10', match_to_conversionmatrix_to_cieobs=True) # # Test xyz input: print('\nXYZ INPUT -----') xyz = lx.spd_to_xyz(spd6500xM, cieobs='2006_10', relative=False) xyzw = lx.spd_to_xyz(spd6500, cieobs='2006_10', relative=False) xyz2, xyzw2 = lx.spd_to_xyz(spd6500, cieobs='2006_10', relative=False, rfl=M, out=2) print(xyzw) jab = cam_sww16(xyz, dataw=xyzw, Yb=20.0, Lw=400, Ccwb=1, relative=True, inputtype='xyz', direction='forward', parameters=parameters, cieobs='2006_10', match_to_conversionmatrix_to_cieobs=True)
# For comparison _CRI_TYPE_TM30 = copy.deepcopy(lx.cri._CRI_DEFAULTS['iesrf-tm30-18']) _CRI_TYPE_TM30['sampleset'] = "_CRI_RFL['ies-tm30-18']['99']['1nm']" def _spd_to_tm30(spd, cieobs = '1931_2', mixer_type = '3mixer'): # Call function that calculates ref.illuminant and jabt & jabr only once to obtain Rf & Rg: data = lx.cri._tm30_process_spd(spd, cri_type = _CRI_TYPE_TM30) # use 1nm samples to avoid interpolation Rf, Rg = data['Rf'], data['Rg'] thetad = data['hue_bin_data']['gamut_ellipse_fit']['thetad'] ecc = data['hue_bin_data']['gamut_ellipse_fit']['a/b'] xyzw = lx.spd_to_xyz(spd, cieobs = cieobs, relative = False) # set K = 1 to avoid overflow when _FLOAT_TYPE = np.float16 data['xyzw'] = xyzw return data spds = lx._IESTM3018['S']['data'].copy() spds = lx.cie_interp(spds,wl_new = _WL,kind='spd') spds = spds[:202,:] data = spd_to_tm30(spds[[0,104],:]) # data = spd_to_tm30(lx._CIE_F4) jabr = data['hue_bin_data']['jabrn_hj_closed'] jabt = data['hue_bin_data']['jabtn_hj_closed'] plt.plot(jabt[...,1],jabt[...,2],'b+-') plt.plot(jabr[...,1],jabr[...,2],'rx-')
(Rfi_vf, DEi_vf) = _hue_bin_data_to_rfi(hue_bin_data_vf, cri_type = cri_type, scale_factor = scale_factor, scale_fcn = scale_fcn) # Store in dict: _data_vf.update({'Rfi' : Rfi_vf, 'DEi' : DEi_vf, 'Rcshj' : Rcshj_vf, 'Rhshj' : Rhshj_vf, 'Rfhj' : Rfhj_vf, 'DEhj': DEhj_vf, 'dataVF' : dataVF, 'hue_bin_data' : hue_bin_data_vf}) # Add to main dictionary: data['vf'] = _data_vf return data if __name__ == '__main__': # for testing: import luxpy as lx F4 = lx.cie_interp(lx._CIE_F4,wl_new=[360,830,1],kind='spd') D65 = lx.cie_interp(lx._CIE_D65,wl_new=[360,830,1],kind='spd') spds = lx._IESTM3018['S']['data'].copy() spds = lx.cie_interp(spds,wl_new = [360,830,1],kind='spd') spd = np.vstack((F4,D65[1:])) d = spd_to_ies_tm30_metrics(spd, cri_type = None, \ hbins = 16, start_hue = 0.0,\ scalef = 100, \ vf_model_type = _VF_MODEL_TYPE, \ vf_pcolorshift = _VF_PCOLORSHIFT,\ scale_vf_chroma_to_sample_chroma = False)