def spd_to_tm30(St): # calculate CIE 1931 2° white point xyz: xyzw_cct, _ = spd_to_xyz(St, cieobs = '1931_2', relative = True, out = 2) # calculate cct, duv: cct, duv = xyz_to_cct(xyzw_cct, cieobs = '1931_2', out = 'cct,duv') # calculate ref illuminant: Sr = _cri_ref(cct, mix_range = [4000, 5000], cieobs = '1931_2', wl3 = St[0]) # calculate CIE 1964 10° sample and white point xyz under test and ref. illuminants: xyz, xyzw = spd_to_xyz(np.vstack((St,Sr[1:])), cieobs = '1964_10', rfl = _TM30_SAMPLE_SET, relative = True, out = 2) N = St.shape[0]-1 xyzt, xyzr = xyz[:,:N,:], xyz[:,N:,:] xyzwt, xyzwr = xyzw[:N,:], xyzw[N:,:] # calculate CAM02-UCS coordinates # (standard conditions = {'La':100.0,'Yb':20.0,'surround':'avg','D':1.0): jabt = _xyz_to_jab_cam02ucs(xyzt, xyzw = xyzwt) jabr = _xyz_to_jab_cam02ucs(xyzr, xyzw = xyzwr) # calculate DEi, Rfi: DEi = (((jabt-jabr)**2).sum(axis=-1,keepdims=True)**0.5)[...,0] Rfi = log_scale(DEi, scale_factor = [6.73]) # calculate Rf DEa = DEi.mean(axis = 0,keepdims = True) Rf = log_scale(DEa, scale_factor = [6.73]) # calculate hue-bin data: hue_bin_data = _get_hue_bin_data(jabt, jabr, start_hue = 0, nhbins = 16) # calculate Rg: Rg = _hue_bin_data_to_rg(hue_bin_data) # calculate local color fidelity values, Rfhj, # local hue shift, Rhshj and local chroma shifts, Rcshj: Rcshj, Rhshj, Rfhj, DEhj = _hue_bin_data_to_Rxhj(hue_bin_data, scale_factor = [6.73]) # Fit ellipse to gamut shape of samples under test source: gamut_ellipse_fit = _hue_bin_data_to_ellipsefit(hue_bin_data) hue_bin_data['gamut_ellipse_fit'] = gamut_ellipse_fit # return output dict: return {'St' : St, 'Sr' : Sr, 'xyzw_cct' : xyzw_cct, 'xyzwt' : xyzwt, 'xyzwr' : xyzwr, 'xyzt' : xyzt, 'xyzr' : xyzr, 'cct': cct.T, 'duv': duv.T, 'jabt' : jabt, 'jabr' : jabr, 'DEi' : DEi, 'DEa' : DEa, 'Rfi' : Rfi, 'Rf' : Rf, 'hue_bin_data' : hue_bin_data, 'Rg' : Rg, 'DEhj' : DEhj, 'Rfhj' : Rfhj, 'Rcshj': Rcshj,'Rhshj':Rhshj, 'hue_bin_data' : hue_bin_data}
def spd_to_mcri(SPD, D=0.9, E=None, Yb=20.0, out='Rm', wl=None): """ Calculates the MCRI or Memory Color Rendition Index, Rm Args: :SPD: | ndarray with spectral data (can be multiple SPDs, first axis are the wavelengths) :D: | 0.9, optional | Degree of adaptation. :E: | None, optional | Illuminance in lux | (used to calculate La = (Yb/100)*(E/pi) to then calculate D | following the 'cat02' model). | If None: the degree is determined by :D: | If (:E: is not None) & (:Yb: is None): :E: is assumed to contain the adapting field luminance La (cd/m²). :Yb: | 20.0, optional | Luminance factor of background. (used when calculating La from E) | If None, E contains La (cd/m²). :out: | 'Rm' or str, optional | Specifies requested output (e.g. 'Rm,Rmi,cct,duv') :wl: | None, optional | Wavelengths (or [start, end, spacing]) to interpolate the SPDs to. | None: default to no interpolation Returns: :returns: | float or ndarray with MCRI Rm for :out: 'Rm' | Other output is also possible by changing the :out: str value. References: 1. `K.A.G. Smet, W.R. Ryckaert, M.R. Pointer, G. Deconinck, P. Hanselaer,(2012) “A memory colour quality metric for white light sources,” Energy Build., vol. 49, no. C, pp. 216–225. <http://www.sciencedirect.com/science/article/pii/S0378778812000837>`_ """ SPD = np2d(SPD) if wl is not None: SPD = spd(data=SPD, interpolation=_S_INTERP_TYPE, kind='np', wl=wl) # unpack metric default values: avg, catf, cieobs, cri_specific_pars, cspace, ref_type, rg_pars, sampleset, scale = [ _MCRI_DEFAULTS[x] for x in sorted(_MCRI_DEFAULTS.keys()) ] similarity_ai = cri_specific_pars['similarity_ai'] Mxyz2lms = cspace['Mxyz2lms'] scale_fcn = scale['fcn'] scale_factor = scale['cfactor'] sampleset = eval(sampleset) # A. calculate xyz: xyzti, xyztw = spd_to_xyz(SPD, cieobs=cieobs['xyz'], rfl=sampleset, out=2) if 'cct' in out.split(','): cct, duv = xyz_to_cct(xyztw, cieobs=cieobs['cct'], out='cct,duv', mode='lut') # B. perform chromatic adaptation to adopted whitepoint of ipt color space, i.e. D65: if catf is not None: Dtype_cat, F, Yb_cat, catmode_cat, cattype_cat, mcat_cat, xyzw_cat = [ catf[x] for x in sorted(catf.keys()) ] # calculate degree of adaptationn D: if E is not None: if Yb is not None: La = (Yb / 100.0) * (E / np.pi) else: La = E D = cat.get_degree_of_adaptation(Dtype=Dtype_cat, F=F, La=La) else: Dtype_cat = None # direct input of D if (E is None) and (D is None): D = 1.0 # set degree of adaptation to 1 ! if D > 1.0: D = 1.0 if D < 0.6: D = 0.6 # put a limit on the lowest D # apply cat: xyzti = cat.apply(xyzti, cattype=cattype_cat, catmode=catmode_cat, xyzw1=xyztw, xyzw0=None, xyzw2=xyzw_cat, D=D, mcat=[mcat_cat], Dtype=Dtype_cat) xyztw = cat.apply(xyztw, cattype=cattype_cat, catmode=catmode_cat, xyzw1=xyztw, xyzw0=None, xyzw2=xyzw_cat, D=D, mcat=[mcat_cat], Dtype=Dtype_cat) # C. convert xyz to ipt and split: ipt = xyz_to_ipt( xyzti, cieobs=cieobs['xyz'], M=Mxyz2lms ) #input matrix as published in Smet et al. 2012, Energy and Buildings I, P, T = asplit(ipt) # D. calculate specific (hue dependent) similarity indicators, Si: if len(xyzti.shape) == 3: ai = np.expand_dims(similarity_ai, axis=1) else: ai = similarity_ai a1, a2, a3, a4, a5 = asplit(ai) mahalanobis_d2 = (a3 * np.power((P - a1), 2.0) + a4 * np.power( (T - a2), 2.0) + 2.0 * a5 * (P - a1) * (T - a2)) if (len(mahalanobis_d2.shape) == 3) & (mahalanobis_d2.shape[-1] == 1): mahalanobis_d2 = mahalanobis_d2[:, :, 0].T Si = np.exp(-0.5 * mahalanobis_d2) # E. calculate general similarity indicator, Sa: Sa = avg(Si, axis=0, keepdims=True) # F. rescale similarity indicators (Si, Sa) with a 0-1 scale to memory color rendition indices (Rmi, Rm) with a 0 - 100 scale: Rmi = scale_fcn(np.log(Si), scale_factor=scale_factor) Rm = np2d(scale_fcn(np.log(Sa), scale_factor=scale_factor)) # G. calculate Rg (polyarea of test / polyarea of memory colours): if 'Rg' in out.split(','): I = I[ ..., None] #broadcast_shape(I, target_shape = None,expand_2d_to_3d = 0) a1 = a1[:, None] * np.ones( I.shape ) #broadcast_shape(a1, target_shape = None,expand_2d_to_3d = 0) a2 = a2[:, None] * np.ones( I.shape ) #broadcast_shape(a2, target_shape = None,expand_2d_to_3d = 0) a12 = np.concatenate( (a1, a2), axis=2 ) #broadcast_shape(np.hstack((a1,a2)), target_shape = ipt.shape,expand_2d_to_3d = 0) ipt_mc = np.concatenate((I, a12), axis=2) nhbins, normalize_gamut, normalized_chroma_ref, start_hue = [ rg_pars[x] for x in sorted(rg_pars.keys()) ] Rg = jab_to_rg(ipt, ipt_mc, ordered_and_sliced=False, nhbins=nhbins, start_hue=start_hue, normalize_gamut=normalize_gamut) if (out != 'Rm'): return eval(out) else: return Rm
rfls_np_cp = rfls_np.copy() rfls_norm = lx.spd_normalize(rfls_np_cp, norm_type='area', norm_f=2) rfls_norm = lx.spd_normalize(rfls_np_cp, norm_type='lambda', norm_f=560) rfls_norm = lx.spd_normalize(rfls_np_cp, norm_type='max', norm_f=1) rfls_norm = lx.spd_normalize(rfls_np_cp, norm_type='pu', norm_f=1) rfls_norm = lx.spd_normalize(rfls_np_cp, norm_type='ru', norm_f=1) rfls_norm = lx.spd_normalize(rfls_np_cp, norm_type='qu', norm_f=1) # check spd_to_xyz: xyz = lx.spd_to_xyz(spds) xyz = lx.spd_to_xyz(spds, relative=False) xyz = lx.spd_to_xyz(spds, rfl=rfls) xyz, xyzw = lx.spd_to_xyz(spds, rfl=rfls, cieobs='1931_2', out=2) # check xyz_to_cct(): cct, duv = lx.xyz_to_cct(xyzw, cieobs='1931_2', out=2) # check xyz_to_..., ..._to_xyz: labw = lx.xyz_to_Yxy(xyzw) lab = lx.xyz_to_Yxy(xyz) xyzw_ = lx.Yxy_to_xyz(labw) xyz_ = lx.Yxy_to_xyz(lab) labw = lx.xyz_to_Yuv(xyzw) lab = lx.xyz_to_Yuv(xyz) xyzw_ = lx.Yuv_to_xyz(labw) xyz_ = lx.Yuv_to_xyz(lab) labw = lx.xyz_to_lab(xyzw, xyzw=xyzw[:1, :]) lab = lx.xyz_to_lab(xyz, xyzw=xyzw[:1, :]) xyzw_ = lx.lab_to_xyz(labw, xyzw=xyzw[:1, :])
def test_xyz_to_cct(self, getvars): # check xyz_to_cct(): S, spds, rfls, xyz, xyzw = getvars cct, duv = lx.xyz_to_cct(xyzw, cieobs='1931_2', out=2)
-0.679 * np.log(cct / 2194)**2) - 0.0172 D = np.exp(-(3912 * ((1 / cct) - (1 / 6795)))**2) # degree of adaptation else: raise Exception('Unrecognized nlocitype') if out == 'duv,D': return duv, D elif out == 'duv': return duv elif out == 'D': return D else: raise Exception('smet_white_loci(): Requested output unrecognized.') if __name__ == '__main__': ccts = np.array([6605, 6410, 6800]) BBs = cri_ref(ccts, ref_type=['BB', 'BB', 'BB']) xyz10 = spd_to_xyz(BBs, cieobs='1964_10') ccts_calc = xyz_to_cct(xyz10, cieobs='1964_10') Dn_uw = xyz_to_neutrality_smet2018(xyz10, nlocitype='uw') Dn_ca = xyz_to_neutrality_smet2018(xyz10, nlocitype='ca') Duv10_uw, Dn_uw2 = cct_to_neutral_loci_smet2018(ccts, nlocitype='uw', out='duv,D') Duv10_ca, Dn_ca2 = cct_to_neutral_loci_smet2018(ccts, nlocitype='ca', out='duv,D')
N_components = 4 #if not None, spd model parameters (peakwl, fwhm, ...) are optimized component_spds = None #component_spds= {}; # if empty dict, then generate using initialize_spd_model_pars and overwrite with function args: peakwl and fwhm. N_components must match length of either peakwl or fwhm allow_nongaussianbased_mono_spds = False S3, _ = spd_optimizer(target, tar_type = tar_type, cspace_bwtf = {'cieobs' : cieobs, 'mode' : 'search'},\ optimizer_type = '3mixer', N_components = N_components,component_spds = component_spds,\ allow_nongaussianbased_mono_spds = allow_nongaussianbased_mono_spds,\ peakwl = peakwl, fwhm = fwhm, obj_fcn = obj_fcn, obj_tar_vals = obj_tar_vals,\ obj_fcn_weights = obj_fcn_weights, decimals = decimals,\ use_piecewise_fcn=False, verbosity = 1) #bw_order=[-2],bw_order_min=-2,bw_order_max=-1.5) # to test use of pure lorentzian mono spds. # Check output agrees with target: xyz = spd_to_xyz(S3, relative=False, cieobs=cieobs) cct = xyz_to_cct(xyz, cieobs=cieobs, mode='lut') Rf = obj_fcn1(S3) Rg = obj_fcn2(S3) Ra = obj_fcn3(S3) print('\nS3: Optimization results:') print("S3: Optim / target cct: {:1.1f} K / {:1.1f} K".format( cct[0, 0], target)) print("S3: Optim / target Rf: {:1.3f} / {:1.3f}".format( Rf[0, 0], obj_tar_vals[0])) print("S3: Optim / target Rg: {:1.3f} / {:1.3f}".format( Rg[0, 0], obj_tar_vals[1])) print("S3: Optim / target Ra: {:1.3f} / {:1.3f}".format( Ra[0, 0], obj_tar_vals[2])) #plot spd: plt.figure()
def spd_to_COI_ASNZS1680(S=None, tf=_COI_CSPACE, cieobs=_COI_CIEOBS, out='COI,cct', extrapolate_rfl=False): """ Calculate the Cyanosis Observation Index (COI) [ASNZS 1680.2.5-1995]. Args: :S: | ndarray with light source spectrum (first column are wavelengths). :tf: | _COI_CSPACE, optional | Color space in which to calculate the COI. | Default is CIELAB. :cieobs: | _COI_CIEOBS, optional | CMF set to use. | Default is '1931_2'. :out: | 'COI,cct' or str, optional | Determines output. :extrapolate_rfl: | False, optional | If False: | limit the wavelength range of the source to that of the standard | reflectance spectra for the 50% and 100% oxygenated blood. Returns: :COI: | ndarray with cyanosis indices for input sources. :cct: | ndarray with correlated color temperatures. Note: Clause 7.2 of the ASNZS 1680.2.5-1995. standard mentions the properties demanded of the light source used in region where visual conditions suitable to the detection of cyanosis should be provided: 1. The correlated color temperature (CCT) of the source should be from 3300 to 5300 K. 2. The cyanosis observation index should not exceed 3.3 """ if S is None: #use default S = _CIE_ILLUMINANTS['F4'] if extrapolate_rfl == False: # _COI_RFL do not cover the full 360-830nm range. wl_min = _COI_RFL_BLOOD[0].min() wl_max = _COI_RFL_BLOOD[0].max() S = S[:, np.where((S[0] >= wl_min) & (S[0] <= wl_max))[0]] # Calculate reference spd: Sr = blackbody(4000, wl3=S[0]) # same wavelength range # Calculate xyz of blood under test source and ref. source: xyzt, xyzwt = spd_to_xyz(S, rfl=_COI_RFL_BLOOD, relative=True, cieobs=cieobs, out=2) xyzr, xyzwr = spd_to_xyz(Sr, rfl=_COI_RFL_BLOOD, relative=True, cieobs=cieobs, out=2) # Calculate color difference between blood under test and ref. DEi = deltaE.DE_cspace(xyzt, xyzr, xyzwt=xyzwt, xyzwr=xyzwr, tf=tf) # Calculate Cyanosis Observation Index: COI = np.nanmean(DEi, axis=0)[:, None] # Calculate cct, if requested: if 'cct' in out.split(','): cct, duv = xyz_to_cct(xyzwt, cieobs=cieobs, out=2) # manage output: if out == 'COI': return COI elif out == 'COI,cct': return COI, cct else: return eval(out)