def test_Yuv(self, getvars): S, spds, rfls, xyz, xyzw = getvars labw = lx.xyz_to_Yuv(xyzw) lab = lx.xyz_to_Yuv(xyz) xyzw_ = lx.Yuv_to_xyz(labw) xyz_ = lx.Yuv_to_xyz(lab) assert np.isclose(xyz, xyz_).all() assert np.isclose(xyzw, xyzw_).all()
def xyz_to_space(input_type='RGB', R=None, G=None, B=None, W=None, file='GW_2019-09-13.txt', space='uv'): """ [ADD THIS] Parameters ---------- input_type : TYPE, optional DESCRIPTION. The default is 'RGB'. R : TYPE, optional DESCRIPTION. The default is None. G : TYPE, optional DESCRIPTION. The default is None. B : TYPE, optional DESCRIPTION. The default is None. W : TYPE, optional DESCRIPTION. The default is None. file : TYPE, optional DESCRIPTION. The default is 'GW_2019-09-13.txt'. space : TYPE, optional DESCRIPTION. The default is 'uv'. Returns ------- TYPE DESCRIPTION. """ if input_type == 'RGB': R_Yuv = lx.xyz_to_Yuv(R) G_Yuv = lx.xyz_to_Yuv(G) B_Yuv = lx.xyz_to_Yuv(B) return R_Yuv, G_Yuv, B_Yuv elif input_type == 'RGBW': R_Yuv = lx.xyz_to_Yuv(R) G_Yuv = lx.xyz_to_Yuv(G) B_Yuv = lx.xyz_to_Yuv(B) W_Yuv = lx.xyz_to_Yuv(W) return R_Yuv, G_Yuv, B_Yuv, W_Yuv elif input_type == 'file': XYZs = np.loadtxt(file, delimiter='\t') if space == 'uv': output = lx.xyz_to_Yuv(XYZs) else: output = lx.xyz_to_Yxy(XYZs) for count, _ in enumerate(output): count = count + 1 return XYZs, output, count
def _plot_tm30_report_bottom(axh, spd, notes = '', max_len_notes_line = 40): """ Print some notes, the CIE x, y, u',v' and Ra, R9 values of the source in some empty axes. Args: :axh: | None, optional | Plot on specified axes. :spd: | ndarray or dict | If ndarray: single spectral power distribution. :notes: | string to be split :max_len_notes_line: | 40, optional | Maximum length of a single line when splitting the string. Returns: :axh: | handle to figure axes. """ ciera = spd_to_cri(spd, cri_type = 'ciera') cierai = spd_to_cri(spd, cri_type = 'ciera-14', out = 'Rfi') xyzw = spd_to_xyz(spd, cieobs = '1931_2', relative = True) Yxyw = xyz_to_Yxy(xyzw) Yuvw = xyz_to_Yuv(xyzw) notes_ = _split_notes(notes, max_len_notes_line = max_len_notes_line) axh.set_xticks(np.arange(10)) axh.set_xticklabels(['' for i in np.arange(10)]) axh.set_yticks(np.arange(4)) axh.set_yticklabels(['' for i in np.arange(4)]) axh.set_axis_off() axh.set_xlabel([]) axh.text(0,2.8, 'Notes: ', fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(0.75,2.8, notes_, fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(6,2.8, "x {:1.4f}".format(Yxyw[0,1]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(6,2.2, "y {:1.4f}".format(Yxyw[0,2]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(6,1.6, "u' {:1.4f}".format(Yuvw[0,1]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(6,1.0, "v' {:1.4f}".format(Yuvw[0,2]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(7.5,2.8, "CIE 13.3-1995", fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(7.5,2.2, " (CRI) ", fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(7.5,1.6, " $R_a$ {:1.0f}".format(ciera[0,0]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') axh.text(7.5,1.0, " $R_9$ {:1.0f}".format(cierai[9,0]), fontsize = 9, horizontalalignment='left',verticalalignment='top',color = 'k') # Create a Rectangle patch rect = patches.Rectangle((7.2,0.5),1.7,2.5,linewidth=1,edgecolor='k',facecolor='none') # Add the patch to the Axes axh.add_patch(rect) return axh
def to_Yuv(self, **kwargs): """ Convert XYZ tristimulus values CIE 1976 Yu'v' chromaticity values. Returns: :Yuv: | luxpy.LAB with .value field that is a ndarray | with CIE 1976 Yu'v' chromaticity values. | (Y value refers to luminance or luminance factor) """ return LAB(value=xyz_to_Yuv(self.value), relative=self.relative, cieobs=self.cieobs, dtype='Yuv')
def xyz_to_neutrality_smet2018(xyz10, nlocitype='uw', uw_model='Linvar'): """ Calculate degree of neutrality using the unique white model in Smet et al. (2014) or the normalized (max = 1) degree of chromatic adaptation model from Smet et al. (2017). Args: :xyz10: | ndarray with CIE 1964 10° xyz tristimulus values. :nlocitype: | 'uw', optional | 'uw': use unique white models published in Smet et al. (2014). | 'ca': use degree of chromatic adaptation model from Smet et al. (2017). :uw_model: | 'Linvar', optional | Use Luminance invariant unique white model from Smet et al. (2014). | Other options: 'L200' (200 cd/m²), 'L1000' (1000 cd/m²) and 'L2000' (2000 cd/m²). Returns: :N: | ndarray with calculated neutrality References: 1. `Smet, K., Deconinck, G., & Hanselaer, P., (2014), Chromaticity of unique white in object mode. Optics Express, 22(21), 25830–25841. <https://www.osapublishing.org/oe/abstract.cfm?uri=oe-22-21-25830>`_ 2. `Smet, K.A.G., Zhai, Q., Luo, M.R., Hanselaer, P., (2017), Study of chromatic adaptation using memory color matches, Part II: colored illuminants, Opt. Express, 25(7), pp. 8350-8365. <https://www.osapublishing.org/oe/abstract.cfm?uri=oe-25-7-8350&origin=search)>`_ """ if nlocitype == 'uw': uv = xyz_to_Yuv(xyz10)[..., 1:] G0 = lambda up, vp, a: np.exp(-0.5 * (a[0] * (up - a[2])**2 + a[1] * (vp - a[3])**2 + 2 * a[4] * (up - a[2]) * (vp - a[3]))) return G0(uv[..., 0:1], uv[..., 1:2], _UW_NEUTRALITY_PARAMETERS_SMET2014[uw_model]) elif nlocitype == 'ca': return cat.smet2017_D(xyz10, Dmax=1).T else: raise Exception('Unrecognized nlocitype')
def xyz_to_yuv(xyzs): """ [ADD THIS] Parameters ---------- xyzs : TYPE DESCRIPTION. Returns ------- u_v_ : TYPE DESCRIPTION. """ yuv = lx.xyz_to_Yuv(xyzs) return yuv
def calculate_lut(ccts=None, cieobs=None, add_to_lut=True): """ Function that calculates LUT for the ccts stored in ./data/cctluts/cct_lut_cctlist.dat or given as input argument. Calculation is performed for CMF set specified in cieobs. Adds a new (temprorary) field to the _CCT_LUT dict. Args: :ccts: | ndarray or str, optional | list of ccts for which to (re-)calculate the LUTs. | If str, ccts contains path/filename.dat to list. :cieobs: | None or str, optional | str specifying cmf set. Returns: :returns: | ndarray with cct and duv. Note: Function changes the global variable: _CCT_LUT! """ if ccts is None: ccts = getdata('{}cct_lut_cctlist.dat'.format(_CCT_LUT_PATH)) elif isinstance(ccts, str): ccts = getdata(ccts) Yuv = np.ones((ccts.shape[0], 2)) * np.nan for i, cct in enumerate(ccts): Yuv[i, :] = xyz_to_Yuv( spd_to_xyz(blackbody(cct, wl3=[360, 830, 1]), cieobs=cieobs))[:, 1:3] u = Yuv[:, 0, None] # get CIE 1960 u v = (2.0 / 3.0) * Yuv[:, 1, None] # get CIE 1960 v cctuv = np.hstack((ccts, u, v)) if add_to_lut == True: _CCT_LUT[cieobs] = cctuv return cctuv
def xyz_to_cct_ohno2011(xyz): """ Calculate cct and Duv from CIE 1931 2° xyz following Ohno (2011). Args: :xyz: | ndarray with CIE 1931 2° X,Y,Z tristimulus values Returns: :cct, duv: | ndarrays with correlated color temperatures and distance to blackbody locus in CIE 1960 uv References: 1. Ohno, Y. (2011). Calculation of CCT and Duv and Practical Conversion Formulae. CORM 2011 Conference, Gaithersburg, MD, May 3-5, 2011 """ uvp = xyz_to_Yuv(xyz)[..., 1:] uv = uvp * np.array([[1, 2 / 3]]) Lfp = ((uv[..., 0] - 0.292)**2 + (uv[..., 1] - 0.24)**2)**0.5 a = np.arctan((uv[..., 1] - 0.24) / (uv[..., 0] - 0.292)) a[a < 0] = a[a < 0] + np.pi Lbb = np.polyval(_KIJ[0, :], a) Duv = Lfp - Lbb T1 = 1 / np.polyval(_KIJ[1, :], a) T1[a >= 2.54] = 1 / np.polyval(_KIJ[2, :], a[a >= 2.54]) dTc1 = np.polyval(_KIJ[3, :], a) * (Lbb + 0.01) / Lfp * Duv / 0.01 dTc1[a >= 2.54] = 1 / np.polyval(_KIJ[4, :], a[a >= 2.54]) * ( Lbb[a >= 2.54] + 0.01) / Lfp[a >= 2.54] * Duv[a >= 2.54] / 0.01 T2 = T1 - dTc1 c = np.log10(T2) c[T2 == 0] = -np.inf dTc2 = np.polyval(_KIJ[5, :], c) dTc2[Duv < 0] = np.polyval(_KIJ[6, :], c[Duv < 0]) * np.abs( Duv[Duv < 0] / 0.03)**2 Tfinal = T2 - dTc2 return Tfinal, Duv
def xyz_to_u_v_(xyz): """ Conver XYZ to u', v'.| --------------- Parameters: :xyz: array (shape: (1, 3)) Returns: :u_v_mean: array (shape: (1, 2)) """ y_uv = lx.xyz_to_Yuv(xyz) y_uv_mean = np.array( [[y_uv[:, 0].mean(), y_uv[:, 1].mean(), y_uv[:, 2].mean()]]) y_uv_mean = np.around(y_uv_mean, decimals=3) u_mean = y_uv_mean[:, 1] u_mean = str(u_mean)[1:-1] v_mean = y_uv_mean[:, 2] v_mean = str(v_mean)[1:-1] u_v_mean = np.array([[u_mean], [v_mean]]) u_v_mean = u_v_mean.T return u_v_mean
def cct_to_xyz(ccts, duv=None, cieobs=_CIEOBS, wl=None, mode='lut', out=None, accuracy=0.1, force_out_of_lut=True, upper_cct_max=10.0 * 20, approx_cct_temp=True): """ Convert correlated color temperature (CCT) and Duv (distance above (>0) or below (<0) the Planckian locus) to XYZ tristimulus values. | Finds xyzw_estimated by minimization of: | | F = numpy.sqrt(((100.0*(cct_min - cct)/(cct))**2.0) | + (((duv_min - duv)/(duv))**2.0)) | | with cct,duv the input values and cct_min, duv_min calculated using | luxpy.xyz_to_cct(xyzw_estimated,...). Args: :ccts: | ndarray of cct values :duv: | None or ndarray of duv values, optional | Note that duv can be supplied together with cct values in :ccts: as ndarray with shape (N,2) :cieobs: | luxpy._CIEOBS, optional | CMF set used to calculated xyzw. :mode: | 'lut' or 'search', optional | Determines what method to use. :out: | None (or 1), optional | If not None or 1: output a ndarray that contains estimated xyz and minimization results: | (cct_min, duv_min, F_min (objective fcn value)) :wl: | None, optional | Wavelengths used when calculating Planckian radiators. :accuracy: | float, optional | Stop brute-force search when cct :accuracy: is reached. :upper_cct_max: | 10.0**20, optional | Limit brute-force search to this cct. :approx_cct_temp: | True, optional | If True: use xyz_to_cct_HA() to get a first estimate of cct to speed up search. :force_out_of_lut: | True, optional | If True and cct is out of range of the LUT, then switch to brute-force search method, else return numpy.nan values. Returns: :returns: | ndarray with estimated XYZ tristimulus values Note: If duv is not supplied (:ccts:.shape is (N,1) and :duv: is None), source is assumed to be on the Planckian locus. """ # make ccts a min. 2d np.array: if isinstance(ccts, list): ccts = np2dT(np.array(ccts)) else: ccts = np2d(ccts) if len(ccts.shape) > 2: raise Exception('cct_to_xyz(): Input ccts.shape must be <= 2 !') # get cct and duv arrays from :ccts: cct = np2d(ccts[:, 0, None]) if (duv is None) & (ccts.shape[1] == 2): duv = np2d(ccts[:, 1, None]) elif duv is not None: duv = np2d(duv) #get estimates of approximate xyz values in case duv = None: BB = cri_ref(ccts=cct, wl3=wl, ref_type=['BB']) xyz_est = spd_to_xyz(data=BB, cieobs=cieobs, out=1) results = np.ones([ccts.shape[0], 3]) * np.nan if duv is not None: # optimization/minimization setup: def objfcn(uv_offset, uv0, cct, duv, out=1): #, cieobs = cieobs, wl = wl, mode = mode): uv0 = np2d(uv0 + uv_offset) Yuv0 = np.concatenate((np2d([100.0]), uv0), axis=1) cct_min, duv_min = xyz_to_cct(Yuv_to_xyz(Yuv0), cieobs=cieobs, out='cct,duv', wl=wl, mode=mode, accuracy=accuracy, force_out_of_lut=force_out_of_lut, upper_cct_max=upper_cct_max, approx_cct_temp=approx_cct_temp) F = np.sqrt(((100.0 * (cct_min[0] - cct[0]) / (cct[0]))**2.0) + (((duv_min[0] - duv[0]) / (duv[0]))**2.0)) if out == 'F': return F else: return np.concatenate((cct_min, duv_min, np2d(F)), axis=1) # loop through each xyz_est: for i in range(xyz_est.shape[0]): xyz0 = xyz_est[i] cct_i = cct[i] duv_i = duv[i] cct_min, duv_min = xyz_to_cct(xyz0, cieobs=cieobs, out='cct,duv', wl=wl, mode=mode, accuracy=accuracy, force_out_of_lut=force_out_of_lut, upper_cct_max=upper_cct_max, approx_cct_temp=approx_cct_temp) if np.abs(duv[i]) > _EPS: # find xyz: Yuv0 = xyz_to_Yuv(xyz0) uv0 = Yuv0[0][1:3] OptimizeResult = minimize(fun=objfcn, x0=np.zeros((1, 2)), args=(uv0, cct_i, duv_i, 'F'), method='Nelder-Mead', options={ "maxiter": np.inf, "maxfev": np.inf, 'xatol': 0.000001, 'fatol': 0.000001 }) betas = OptimizeResult['x'] #betas = np.zeros(uv0.shape) if out is not None: results[i] = objfcn(betas, uv0, cct_i, duv_i, out=3) uv0 = np2d(uv0 + betas) Yuv0 = np.concatenate((np2d([100.0]), uv0), axis=1) xyz_est[i] = Yuv_to_xyz(Yuv0) else: xyz_est[i] = xyz0 if (out is None) | (out == 1): return xyz_est else: # Also output results of minimization: return np.concatenate((xyz_est, results), axis=1)
def xyz_to_cct_ohno(xyzw, cieobs=_CIEOBS, out='cct', wl=None, accuracy=0.1, force_out_of_lut=True, upper_cct_max=10.0**20, approx_cct_temp=True): """ Convert XYZ tristimulus values to correlated color temperature (CCT) and Duv (distance above (>0) or below (<0) the Planckian locus) using Ohno's method. Args: :xyzw: | ndarray of tristimulus values :cieobs: | luxpy._CIEOBS, optional | CMF set used to calculated xyzw. :out: | 'cct' (or 1), optional | Determines what to return. | Other options: 'duv' (or -1), 'cct,duv'(or 2), "[cct,duv]" (or -2) :wl: | None, optional | Wavelengths used when calculating Planckian radiators. :accuracy: | float, optional | Stop brute-force search when cct :accuracy: is reached. :upper_cct_max: | 10.0**20, optional | Limit brute-force search to this cct. :approx_cct_temp: | True, optional | If True: use xyz_to_cct_HA() to get a first estimate of cct to speed up search. :force_out_of_lut: | True, optional | If True and cct is out of range of the LUT, then switch to brute-force search method, else return numpy.nan values. Returns: :returns: | ndarray with: | cct: out == 'cct' (or 1) | duv: out == 'duv' (or -1) | cct, duv: out == 'cct,duv' (or 2) | [cct,duv]: out == "[cct,duv]" (or -2) Note: LUTs are stored in ./data/cctluts/ Reference: 1. `Ohno Y. Practical use and calculation of CCT and Duv. Leukos. 2014 Jan 2;10(1):47-55. <http://www.tandfonline.com/doi/abs/10.1080/15502724.2014.839020>`_ """ xyzw = np2d(xyzw) if len(xyzw.shape) > 2: raise Exception('xyz_to_cct_ohno(): Input xyzwa.ndim must be <= 2 !') # get 1960 u,v of test source: Yuv = xyz_to_Yuv( xyzw) # remove possible 1-dim + convert xyzw to CIE 1976 u',v' axis_of_v3 = len(Yuv.shape) - 1 # axis containing color components u = Yuv[:, 1, None] # get CIE 1960 u v = (2.0 / 3.0) * Yuv[:, 2, None] # get CIE 1960 v uv = np2d(np.concatenate((u, v), axis=axis_of_v3)) # load cct & uv from LUT: if cieobs not in _CCT_LUT: _CCT_LUT[cieobs] = calculate_lut(ccts=None, cieobs=cieobs, add_to_lut=False) cct_LUT = _CCT_LUT[cieobs][:, 0, None] uv_LUT = _CCT_LUT[cieobs][:, 1:3] # calculate CCT of each uv: CCT = np.ones(uv.shape[0]) * np.nan # initialize with NaN's Duv = CCT.copy() # initialize with NaN's idx_m = 0 idx_M = uv_LUT.shape[0] - 1 for i in range(uv.shape[0]): out_of_lut = False delta_uv = (((uv_LUT - uv[i])**2.0).sum( axis=1))**0.5 # calculate distance of uv with uv_LUT idx_min = delta_uv.argmin() # find index of minimum distance # find Tm, delta_uv and u,v for 2 points surrounding uv corresponding to idx_min: if idx_min == idx_m: idx_min_m1 = idx_min out_of_lut = True else: idx_min_m1 = idx_min - 1 if idx_min == idx_M: idx_min_p1 = idx_min out_of_lut = True else: idx_min_p1 = idx_min + 1 if (out_of_lut == True) & (force_out_of_lut == True): # calculate using search-function cct_i, Duv_i = xyz_to_cct_search(xyzw[i], cieobs=cieobs, wl=wl, accuracy=accuracy, out='cct,duv', upper_cct_max=upper_cct_max, approx_cct_temp=approx_cct_temp) CCT[i] = cct_i Duv[i] = Duv_i continue elif (out_of_lut == True) & (force_out_of_lut == False): CCT[i] = np.nan Duv[i] = np.nan cct_m1 = cct_LUT[idx_min_m1] # - 2*_EPS delta_uv_m1 = delta_uv[idx_min_m1] uv_m1 = uv_LUT[idx_min_m1] cct_p1 = cct_LUT[idx_min_p1] delta_uv_p1 = delta_uv[idx_min_p1] uv_p1 = uv_LUT[idx_min_p1] cct_0 = cct_LUT[idx_min] delta_uv_0 = delta_uv[idx_min] # calculate uv distance between Tm_m1 & Tm_p1: delta_uv_p1m1 = ((uv_p1[0] - uv_m1[0])**2.0 + (uv_p1[1] - uv_m1[1])**2.0)**0.5 # Triangular solution: x = ((delta_uv_m1**2) - (delta_uv_p1**2) + (delta_uv_p1m1**2)) / (2 * delta_uv_p1m1) Tx = cct_m1 + ((cct_p1 - cct_m1) * (x / delta_uv_p1m1)) #uBB = uv_m1[0] + (uv_p1[0] - uv_m1[0]) * (x / delta_uv_p1m1) vBB = uv_m1[1] + (uv_p1[1] - uv_m1[1]) * (x / delta_uv_p1m1) Tx_corrected_triangular = Tx * 0.99991 signDuv = np.sign(uv[i][1] - vBB) Duv_triangular = signDuv * np.atleast_1d( ((delta_uv_m1**2.0) - (x**2.0))**0.5) # Parabolic solution: a = delta_uv_m1 / (cct_m1 - cct_0 + _EPS) / (cct_m1 - cct_p1 + _EPS) b = delta_uv_0 / (cct_0 - cct_m1 + _EPS) / (cct_0 - cct_p1 + _EPS) c = delta_uv_p1 / (cct_p1 - cct_0 + _EPS) / (cct_p1 - cct_m1 + _EPS) A = a + b + c B = -(a * (cct_p1 + cct_0) + b * (cct_p1 + cct_m1) + c * (cct_0 + cct_m1)) C = (a * cct_p1 * cct_0) + (b * cct_p1 * cct_m1) + (c * cct_0 * cct_m1) Tx = -B / (2 * A + _EPS) Tx_corrected_parabolic = Tx * 0.99991 Duv_parabolic = signDuv * (A * np.power(Tx_corrected_parabolic, 2) + B * Tx_corrected_parabolic + C) Threshold = 0.002 if Duv_triangular < Threshold: CCT[i] = Tx_corrected_triangular Duv[i] = Duv_triangular else: CCT[i] = Tx_corrected_parabolic Duv[i] = Duv_parabolic # Regulate output: if (out == 'cct') | (out == 1): return np2dT(CCT) elif (out == 'duv') | (out == -1): return np2dT(Duv) elif (out == 'cct,duv') | (out == 2): return np2dT(CCT), np2dT(Duv) elif (out == "[cct,duv]") | (out == -2): return np.vstack((CCT, Duv)).T
def xyz_to_cct_search(xyzw, cieobs=_CIEOBS, out='cct', wl=None, accuracy=0.1, upper_cct_max=10.0**20, approx_cct_temp=True): """ Convert XYZ tristimulus values to correlated color temperature (CCT) and Duv(distance above (> 0) or below ( < 0) the Planckian locus) by a brute-force search. | The algorithm uses an approximate cct_temp (HA approx., see xyz_to_cct_HA) as starting point or uses the middle of the allowed cct-range (1e2 K - 1e20 K, higher causes overflow) on a log-scale, then constructs a 4-step section of the blackbody (Planckian) locus on which to find the minimum distance to the 1960 uv chromaticity of the test source. Args: :xyzw: | ndarray of tristimulus values :cieobs: | luxpy._CIEOBS, optional | CMF set used to calculated xyzw. :out: | 'cct' (or 1), optional | Determines what to return. | Other options: 'duv' (or -1), 'cct,duv'(or 2), "[cct,duv]" (or -2) :wl: | None, optional | Wavelengths used when calculating Planckian radiators. :accuracy: | float, optional | Stop brute-force search when cct :accuracy: is reached. :upper_cct_max: | 10.0**20, optional | Limit brute-force search to this cct. :approx_cct_temp: | True, optional | If True: use xyz_to_cct_HA() to get a first estimate of cct to speed up search. Returns: :returns: | ndarray with: | cct: out == 'cct' (or 1) | duv: out == 'duv' (or -1) | cct, duv: out == 'cct,duv' (or 2) | [cct,duv]: out == "[cct,duv]" (or -2) Notes: This program is more accurate, but slower than xyz_to_cct_ohno! Note that cct must be between 1e3 K - 1e20 K (very large cct take a long time!!!) """ xyzw = np2d(xyzw) if len(xyzw.shape) > 2: raise Exception('xyz_to_cct_search(): Input xyzw.shape must be <= 2 !') # get 1960 u,v of test source: Yuvt = xyz_to_Yuv(np.squeeze( xyzw)) # remove possible 1-dim + convert xyzw to CIE 1976 u',v' #axis_of_v3t = len(Yuvt.shape)-1 # axis containing color components ut = Yuvt[:, 1, None] #.take([1],axis = axis_of_v3t) # get CIE 1960 u vt = (2 / 3) * Yuvt[:, 2, None] #.take([2],axis = axis_of_v3t) # get CIE 1960 v # Initialize arrays: ccts = np.ones((xyzw.shape[0], 1)) * np.nan duvs = ccts.copy() #calculate preliminary solution(s): if (approx_cct_temp == True): ccts_est = xyz_to_cct_HA(xyzw) procent_estimates = np.array([[3000.0, 100000.0, 0.05], [100000.0, 200000.0, 0.1], [200000.0, 300000.0, 0.25], [300000.0, 400000.0, 0.4], [400000.0, 600000.0, 0.4], [600000.0, 800000.0, 0.4], [800000.0, np.inf, 0.25]]) else: upper_cct = np.array(upper_cct_max) lower_cct = np.array(10.0**2) cct_scale_fun = lambda x: np.log10(x) cct_scale_ifun = lambda x: np.power(10.0, x) dT = (cct_scale_fun(upper_cct) - cct_scale_fun(lower_cct)) / 2 ccttemp = np.array([cct_scale_ifun(cct_scale_fun(lower_cct) + dT)]) ccts_est = np2d(ccttemp * np.ones((xyzw.shape[0], 1))) dT_approx_cct_False = dT.copy() # Loop through all ccts: for i in range(xyzw.shape[0]): #initialize CCT search parameters: cct = np.nan duv = np.nan ccttemp = ccts_est[i].copy() # Take care of (-1, NaN)'s from xyz_to_cct_HA signifying (CCT < lower, CCT > upper) bounds: approx_cct_temp_temp = approx_cct_temp if (approx_cct_temp == True): cct_scale_fun = lambda x: x cct_scale_ifun = lambda x: x if (ccttemp != -1) & ( np.isnan(ccttemp) == False ): # within validity range of CCT estimator-function for ii in range(procent_estimates.shape[0]): if (ccttemp >= (1.0 - 0.05 * (ii == 0)) * procent_estimates[ii, 0]) & ( ccttemp < (1.0 + 0.05 * (ii == 0)) * procent_estimates[ii, 1]): procent_estimate = procent_estimates[ii, 2] break dT = np.multiply( ccttemp, procent_estimate ) # determines range around CCTtemp (25% around estimate) or 100 K elif (ccttemp == -1) & (np.isnan(ccttemp) == False): ccttemp = np.array([procent_estimates[0, 0] / 2]) procent_estimate = 1 # cover 0 K to min_CCT of estimator dT = np.multiply(ccttemp, procent_estimate) elif (np.isnan(ccttemp) == True): upper_cct = np.array(upper_cct_max) lower_cct = np.array(10.0**2) cct_scale_fun = lambda x: np.log10(x) cct_scale_ifun = lambda x: np.power(10.0, x) dT = (cct_scale_fun(upper_cct) - cct_scale_fun(lower_cct)) / 2 ccttemp = np.array( [cct_scale_ifun(cct_scale_fun(lower_cct) + dT)]) approx_cct_temp = False else: dT = dT_approx_cct_False nsteps = 3 signduv = 1.0 ccttemp = ccttemp[0] delta_cct = dT while ((delta_cct > accuracy)): # keep converging on CCT #generate range of ccts: ccts_i = cct_scale_ifun( np.linspace( cct_scale_fun(ccttemp) - dT, cct_scale_fun(ccttemp) + dT, nsteps + 1)) ccts_i[ccts_i < 100.0] = 100.0 # avoid nan's in calculation # Generate BB: BB = cri_ref(ccts_i, wl3=wl, ref_type=['BB'], cieobs=cieobs) # Calculate xyz: xyz = spd_to_xyz(BB, cieobs=cieobs) # Convert to CIE 1960 u,v: Yuv = xyz_to_Yuv(np.squeeze( xyz)) # remove possible 1-dim + convert xyz to CIE 1976 u',v' #axis_of_v3 = len(Yuv.shape)-1 # axis containing color components u = Yuv[:, 1, None] # get CIE 1960 u v = (2.0 / 3.0) * Yuv[:, 2, None] # get CIE 1960 v # Calculate distance between list of uv's and uv of test source: dc = ((ut[i] - u)**2 + (vt[i] - v)**2)**0.5 if np.isnan(dc.min()) == False: #eps = _EPS q = dc.argmin() if np.size( q ) > 1: #to minimize calculation time: only calculate median when necessary cct = np.median(ccts[q]) duv = np.median(dc[q]) q = np.median(q) q = int(q) #must be able to serve as index else: cct = ccts_i[q] duv = dc[q] if (q == 0): ccttemp = cct_scale_ifun( np.array(cct_scale_fun([cct])) + 2 * dT / nsteps) #dT = 2.0*dT/nsteps continue # look in higher section of planckian locus if (q == np.size(ccts_i)): ccttemp = cct_scale_ifun( np.array(cct_scale_fun([cct])) - 2 * dT / nsteps) #dT = 2.0*dT/nsteps continue # look in lower section of planckian locus if (q > 0) & (q < np.size(ccts_i) - 1): dT = 2 * dT / nsteps # get Duv sign: d_p1m1 = ((u[q + 1] - u[q - 1])**2.0 + (v[q + 1] - v[q - 1])**2.0)**0.5 x = (dc[q - 1]**2.0 - dc[q + 1]**2.0 + d_p1m1**2.0) / 2.0 * d_p1m1 vBB = v[q - 1] + ((v[q + 1] - v[q - 1]) * (x / d_p1m1)) signduv = np.sign(vt[i] - vBB) #calculate difference with previous intermediate solution: delta_cct = abs(cct - ccttemp) ccttemp = np.array(cct) #%set new intermediate CCT approx_cct_temp = approx_cct_temp_temp else: ccttemp = np.nan cct = np.nan duv = np.nan duvs[i] = signduv * abs(duv) ccts[i] = cct # Regulate output: if (out == 'cct') | (out == 1): return np2d(ccts) elif (out == 'duv') | (out == -1): return np2d(duvs) elif (out == 'cct,duv') | (out == 2): return np2d(ccts), np2d(duvs) elif (out == "[cct,duv]") | (out == -2): return np.vstack((ccts, duvs)).T
.. codeauthor:: Kevin A.G. Smet (ksmet1977 at gmail.com) """ import luxpy as lx # package for color science calculations import matplotlib.pyplot as plt # package for plotting import numpy as np # fundamental package for scientific computing import timeit # package for timing functions cieobs = '1964_10' # set CIE observer, i.e. cmf set ccts = [3000, 4000, 4500, 6000] # define M = 4 CCTs ref_types = ['BB', 'DL', 'cierf', 'DL'] # define reference illuminant types # calculate reference illuminants: REF = lx.cri_ref(ccts, ref_type=ref_types, norm_type='lambda', norm_f=600) TCS8 = lx._CRI_RFL['cie-13.3-1995']['8'] # 8 TCS from CIE 13.3-1995 xyz_TCS8_REF = lx.spd_to_xyz(REF, cieobs=cieobs, rfl=TCS8, relative=True) xyz_TCS8_REF_2, xyz_REF_2 = lx.spd_to_xyz(REF, cieobs=cieobs, rfl=TCS8, relative=True, out=2) Yuv_REF_2 = lx.xyz_to_Yuv(xyz_REF_2) axh = lx.plotSL(cspace = 'Yuv', cieobs = cieobs, show = False,\ BBL = True, DL = True, diagram_colors = True) # Step 2: Y, u, v = np.squeeze(lx.asplit(Yuv_REF_2)) # splits array along last axis # Step 3: lx.plot_color_data(u, v, formatstr='go', label='Yuv_REF_2')
# 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, :]) xyz_ = lx.lab_to_xyz(lab, xyzw=xyzw[:1, :]) labw = lx.xyz_to_luv(xyzw, xyzw=xyzw) lab = lx.xyz_to_luv(xyz, xyzw=xyzw) xyzw_ = lx.luv_to_xyz(labw, xyzw=xyzw) xyz_ = lx.luv_to_xyz(lab, xyzw=xyzw) labw = lx.xyz_to_ipt(xyzw)