def positive_arctan(x,y, htype = 'deg'): """ Calculate positive angle (0°-360° or 0 - 2*pi rad.) from x and y. Args: :x: | ndarray of x-coordinates :y: | ndarray of y-coordinates :htype: | 'deg' or 'rad', optional | - 'deg': hue angle between 0° and 360° | - 'rad': hue angle between 0 and 2pi radians Returns: :returns: | ndarray of positive angles. """ if htype == 'deg': r2d = 180.0/np.pi h360 = 360.0 else: r2d = 1.0 h360 = 2.0*np.pi h = np.atleast_1d((np.arctan2(y,x)*r2d)) h[np.where(h<0)] = h[np.where(h<0)] + h360 return h
def positive_arctan(x, y, htype='deg'): if htype == 'deg': r2d = 180.0 / np.pi h360 = 360.0 else: r2d = 1.0 h360 = 2.0 * np.pi h = np.atleast_1d((np.arctan2(y, x) * r2d)) h[np.where(h < 0)] = h[np.where(h < 0)] + h360 return h
def __init__(self, *args, argtype='xyz', vtype='xyz', _TINY=1e-15): self._TINY = _TINY self.vtype = vtype if len(args) == 0: args = [0.0, 0.0, 0.0] args = [np.atleast_1d(args[i]) for i in range(len(args))] # make atleast_1d ndarray if vtype == 'xyz': self.x = args[0] self.y = args[1] self.z = args[2] elif vtype == 'tpr': if len(args) == 2: args.append(np.ones(args[0].shape)) self.set_tpr(*args) self.shape = self.x.shape
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 hue_quadrature(h, unique_hue_data=None): """ Get hue quadrature H from hue h. Args: :h: | float or ndarray [(N,) or (N,1)] with hue data in degrees (!). :unique_hue data: | None or dict, optional | - None: defaults to: | {'hues': 'red yellow green blue red'.split(), | 'i': np.arange(5.0), | 'hi':[20.14, 90.0, 164.25,237.53,380.14], | 'ei':[0.8,0.7,1.0,1.2,0.8], | 'Hi':[0.0,100.0,200.0,300.0,400.0]} | - dict: user specified unique hue data | (same structure as above) Returns: :H: | ndarray of Hue quadrature value(s). """ if unique_hue_data is None: unique_hue_data = { 'hues': 'red yellow green blue red'.split(), 'i': np.arange(5.0), 'hi': [20.14, 90.0, 164.25, 237.53, 380.14], 'ei': [0.8, 0.7, 1.0, 1.2, 0.8], 'Hi': [0.0, 100.0, 200.0, 300.0, 400.0] } changed_number_to_array = False if isinstance(h, float) | isinstance(h, int): h = np.atleast_1d(h) changed_number_to_array = True squeezed = False if h.ndim > 1: if (h.shape[0] == 1): h = np.squeeze(h, axis=0) squeezed = True hi = unique_hue_data['hi'] Hi = unique_hue_data['Hi'] ei = unique_hue_data['ei'] h[h < hi[0]] += 360.0 h_tmp = np.atleast_2d(h) if h_tmp.shape[0] == 1: h_tmp = h_tmp.T h_hi = np.repeat(h_tmp, repeats=len(hi), axis=1) hi_h = np.repeat(np.atleast_2d(hi), repeats=h.shape[0], axis=0) d = (h_hi - hi_h) d[d < 0] = 1000.0 p = d.argmin(axis=1) p[p == (len(hi) - 1)] = 0 # make sure last unique hue data is not selected H = np.array([ Hi[pi] + (100.0 * (h[i] - hi[pi]) / ei[pi]) / ((h[i] - hi[pi]) / ei[pi] + (hi[pi + 1] - h[i]) / ei[pi + 1]) for (i, pi) in enumerate(p) ]) if changed_number_to_array: H = H[0] if squeezed: H = np.expand_dims(H, axis=0) return H
def plot_color_data(x,y,z=None, axh=None, show = True, cieobs =_CIEOBS, \ cspace = _CSPACE, formatstr = 'k-', **kwargs): """ Plot color data from x,y [,z]. Args: :x: | float or ndarray with x-coordinate data :y: | float or ndarray with y-coordinate data :z: | None or float or ndarray with Z-coordinate data, optional | If None: make 2d plot. :axh: | None or axes handle, optional | Determines axes to plot data in. | None: make new figure. :show: | True or False, optional | Invoke matplotlib.pyplot.show() right after plotting :cieobs: | luxpy._CIEOBS or str, optional | Determines CMF set to calculate spectrum locus or other. :cspace: | luxpy._CSPACE or str, optional | Determines color space / chromaticity diagram to plot data in. | Note that data is expected to be in specified :cspace: :formatstr: | 'k-' or str, optional | Format str for plotting (see ?matplotlib.pyplot.plot) :kwargs: | additional keyword arguments for use with matplotlib.pyplot. Returns: :returns: | None (:show: == True) | or | handle to current axes (:show: == False) """ x = np.atleast_1d(x) y = np.atleast_1d(y) if 'grid' in kwargs.keys(): plt.grid(kwargs['grid']);kwargs.pop('grid') if z is not None: z = np.atleast_1d(z) if axh is None: fig = plt.figure() axh = plt.axes(projection='3d') axh.plot3D(x,y,z,formatstr, linewidth = 2,**kwargs) plt.zlabel(_CSPACE_AXES[cspace][0], kwargs) else: plt.plot(x,y,formatstr,linewidth = 2,**kwargs) plt.xlabel(_CSPACE_AXES[cspace][1], kwargs) plt.ylabel(_CSPACE_AXES[cspace][2], kwargs) if 'label' in kwargs.keys(): plt.legend() if show == True: plt.show() else: return plt.gca()