def check_dimensions(data, xyzw, caller='cat.apply()'): """ Check if dimensions of data and xyzw match. | Does nothing when they do, but raises error if dimensions don't match. Args: :data: | ndarray with color data. :xyzw: | ndarray with white point tristimulus values. :caller: | str with caller function for error handling, optional Returns: :returns: | ndarray with input color data, | Raises error if dimensions don't match. """ xyzw = np2d(xyzw) data = np2d(data) if ((xyzw.shape[0] > 1) & (data.shape[0] != xyzw.shape[0]) & (data.ndim == 2)): raise Exception( '{}: Cannot match dim of xyzw with data: xyzw.shape[0]>1 & != data.shape[0]' .format(caller))
def xyz_to_wuv(xyz, xyzw = _COLORTF_DEFAULT_WHITE_POINT, **kwargs): """ Convert XYZ tristimulus values CIE 1964 U*V*W* color space. Args: :xyz: | ndarray with tristimulus values :xyzw: | ndarray with tristimulus values of white point, optional (Defaults to luxpy._COLORTF_DEFAULT_WHITE_POINT) Returns: :wuv: | ndarray with W*U*V* values """ xyz = np2d(xyz) xyzw = np2d(xyzw) Yuv = xyz_to_Yuv(xyz) # convert to cie 1976 u'v' Yuvw = xyz_to_Yuv(xyzw) Y, u, v = asplit(Yuv) Yw, uw, vw = asplit(Yuvw) W = 25.0*(Y**(1/3)) - 17.0 U = 13.0*W*(u - uw) V = 13.0*W*(v - vw)*(2.0/3.0) # *(2/3) to convert to cie 1960 u, v return ajoin((W,U,V))
def wuv_to_xyz(wuv,xyzw = _COLORTF_DEFAULT_WHITE_POINT, **kwargs): """ Convert CIE 1964 U*V*W* color space coordinates to XYZ tristimulus values. Args: :wuv: | ndarray with W*U*V* values :xyzw: | ndarray with tristimulus values of white point, optional (Defaults to luxpy._COLORTF_DEFAULT_WHITE_POINT) Returns: :xyz: | ndarray with tristimulus values """ wuv = np2d(wuv) xyzw = np2d(xyzw) Yuvw = xyz_to_Yuv(xyzw) # convert to cie 1976 u'v' Yw, uw, vw = asplit(Yuvw) vw = (2.0/3.0)*vw # convert to cie 1960 u, v W,U,V = asplit(wuv) Y = ((W + 17.0) / 25.0)**3.0 u = uw + U/(13.0*W) v = (vw + V/(13.0*W)) * (3.0/2.0) Yuv = ajoin((Y,u,v)) # = 1976 u',v' return Yuv_to_xyz(Yuv)
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)
def normalize_3x3_matrix(M, xyz0=np.array([[1.0, 1.0, 1.0]])): """ Normalize 3x3 matrix M to xyz0 -- > [1,1,1] If M.shape == (1,9): M is reshaped to (3,3) Args: :M: | ndarray((3,3) or ndarray((1,9)) :xyz0: | 2darray, optional Returns: :returns: | normalized matrix such that M*xyz0 = [1,1,1] """ M = np2d(M) if M.shape[-1] == 9: M = M.reshape(3, 3) if xyz0.shape[0] == 1: return np.dot(np.diagflat(1 / (np.dot(M, xyz0.T))), M) else: return np.concatenate([ np.dot(np.diagflat(1 / (np.dot(M, xyz0[1].T))), M) for i in range(xyz0.shape[0]) ], axis=0).reshape(xyz0.shape[0], 3, 3)
def xyz_to_lms(xyz, cieobs=_CIEOBS, M=None, **kwargs): """ Convert XYZ tristimulus values to LMS cone fundamental responses. Args: :xyz: | ndarray with tristimulus values :cieobs: | _CIEOBS or str, optional :M: | None, optional | Conversion matrix for xyz to lms. | If None: use the one defined by :cieobs: Returns: :lms: | ndarray with LMS cone fundamental responses """ xyz = np2d(xyz) if M is None: M = _CMF[cieobs]['M'] # convert xyz to lms: if len(xyz.shape) == 3: lms = np.einsum('ij,klj->kli', M, xyz) else: lms = np.einsum('ij,lj->li', M, xyz) return lms
def lms_to_xyz(lms, cieobs=_CIEOBS, M=None, **kwargs): """ Convert LMS cone fundamental responses to XYZ tristimulus values. Args: :lms: | ndarray with LMS cone fundamental responses :cieobs: | _CIEOBS or str, optional :M: | None, optional | Conversion matrix for xyz to lms. | If None: use the one defined by :cieobs: Returns: :xyz: | ndarray with tristimulus values """ lms = np2d(lms) if M is None: M = _CMF[cieobs]['M'] # convert from lms to xyz: if len(lms.shape) == 3: xyz = np.einsum('ij,klj->kli', np.linalg.inv(M), lms) else: xyz = np.einsum('ij,lj->li', np.linalg.inv(M), lms) return xyz
def xyz_to_cct_mcamy(xyzw): """ Convert XYZ tristimulus values to correlated color temperature (CCT) using the mccamy approximation. | Only valid for approx. 3000 < T < 9000, if < 6500, error < 2 K. Args: :xyzw: | ndarray of tristimulus values Returns: :cct: | ndarray of correlated color temperatures estimates References: 1. `McCamy, Calvin S. (April 1992). "Correlated color temperature as an explicit function of chromaticity coordinates". Color Research & Application. 17 (2): 142–144. <http://onlinelibrary.wiley.com/doi/10.1002/col.5080170211/abstract>`_ """ Yxy = xyz_to_Yxy(xyzw) #axis_of_v3 = len(xyzw.shape)-1 n = (Yxy[:, 1] - 0.3320) / (Yxy[:, 2] - 0.1858) return np2d(-449.0 * (n**3) + 3525.0 * (n**2) - 6823.3 * n + 5520.33).T
def parse_x1x2_parameters(x, target_shape, catmode, expand_2d_to_3d=None, default=[1.0, 1.0]): """ Parse input parameters x and make them the target_shape for easy calculation. | Input in main function can now be a single value valid for all xyzw or an array with a different value for each xyzw. Args: :x: | list[float, float] or ndarray :target_shape: | tuple with shape information :catmode: | '1>0>2, optional | -'1>0>2': Two-step CAT | from illuminant 1 to baseline illuminant 0 to illuminant 2. | -'1>0': One-step CAT | from illuminant 1 to baseline illuminant 0. | -'0>2': One-step CAT | from baseline illuminant 0 to illuminant 2. :expand_2d_to_3d: | None, optional | [will be removed in future, serves no purpose] | Expand :x: from 2 to 3 dimensions. :default: | [1.0,1.0], optional | Default values for :x: Returns: :returns: | (ndarray, ndarray) for x10 and x20 """ if x is None: x10 = np.ones(target_shape) * default[0] if (catmode == '1>0>2') | (catmode == '1>2'): x20 = np.ones(target_shape) * default[1] else: x20 = np.ones(target_shape) * np.nan else: x = np2d(x) if (catmode == '1>0>2') | (catmode == '1>2'): if x.shape[-1] == 2: x10 = np.ones(target_shape) * x[..., 0] x20 = np.ones(target_shape) * x[..., 1] else: x10 = np.ones(target_shape) * x x20 = x10.copy() elif catmode == '1>0': x10 = np.ones(target_shape) * x[..., 0] x10 = np.ones(target_shape) * np.nan return x10, x20
def mahalanobis2(x, y=None, mu=None, sigmainv=None): """ Evaluate the squared mahalanobis distance with center mu and shape and orientation determined by sigmainv. Args: :x: | scalar or list or ndarray (.ndim = 1 or 2) with x(y)-coordinates at which to evaluate the mahalanobis distance squared. :y: | None or scalar or list or ndarray (.ndim = 1) with y-coordinates at which to evaluate the mahalanobis distance squared, optional. | If :y: is None, :x: should be a 2d array. :mu: | None or ndarray (.ndim = 2) with center coordinates of the mahalanobis ellipse, optional. | None defaults to ndarray([0,0]). :sigmainv: | None or ndarray with 'inverse covariance matrix', optional | Determines the shape and orientation of the PD. | None default to np.eye(2). Returns: :returns: | ndarray with magnitude of mahalanobis2(x,y) """ if mu is None: mu = np.zeros(2) if sigmainv is None: sigmainv = np.eye(2) x = np2d(x) if y is not None: x = x - mu[0] # center data on mu y = np2d(y) - mu[1] # center data on mu else: x = x - mu # center data on mu x, y = asplit(x) return (sigmainv[0, 0] * (x**2.0) + sigmainv[1, 1] * (y**2.0) + 2.0 * sigmainv[0, 1] * (x * y))
def Vrb_mb_to_xyz(Vrb, cieobs=_CIEOBS, scaling=[1, 1], M=None, Minverted=False, **kwargs): """ Convert V,r,b (Macleod-Boynton) color coordinates to XYZ tristimulus values. | Macleod Boynton: V = R+G, r = R/V, b = B/V | Note that R,G,B ~ L,M,S Args: :Vrb: | ndarray with V,r,b (Macleod-Boynton) color coordinates :cieobs: | luxpy._CIEOBS, optional | CMF set to use when getting the default M, which is the xyz to lms conversion matrix. :scaling: | list of scaling factors for r and b dimensions. :M: | None, optional | Conversion matrix for going from XYZ to RGB (LMS) | If None, :cieobs: determines the M (function does inversion) :Minverted: | False, optional | Bool that determines whether M should be inverted. Returns: :xyz: | ndarray with tristimulus values Reference: 1. `MacLeod DI, and Boynton RM (1979). Chromaticity diagram showing cone excitation by stimuli of equal luminance. J. Opt. Soc. Am. 69, 1183–1186. <https://www.osapublishing.org/josa/abstract.cfm?uri=josa-69-8-1183>`_ """ Vrb = np2d(Vrb) RGB = np.empty(Vrb.shape) RGB[..., 0] = Vrb[..., 1] * Vrb[..., 0] / scaling[0] RGB[..., 2] = Vrb[..., 2] * Vrb[..., 0] / scaling[1] RGB[..., 1] = Vrb[..., 0] - RGB[..., 0] if M is None: M = _CMF[cieobs]['M'] if Minverted == False: M = np.linalg.inv(M) if len(RGB.shape) == 3: return np.einsum('ij,klj->kli', M, RGB) else: return np.einsum('ij,lj->li', M, RGB)
def xyz_to_cct_HA(xyzw): """ Convert XYZ tristimulus values to correlated color temperature (CCT). Args: :xyzw: | ndarray of tristimulus values Returns: :cct: | ndarray of correlated color temperatures estimates References: 1. `Hernández-Andrés, Javier; Lee, RL; Romero, J (September 20, 1999). Calculating Correlated Color Temperatures Across the Entire Gamut of Daylight and Skylight Chromaticities. Applied Optics. 38 (27), 5703–5709. P <https://www.osapublishing.org/ao/abstract.cfm?uri=ao-38-27-5703>`_ Notes: According to paper small error from 3000 - 800 000 K, but a test with Planckians showed errors up to 20% around 500 000 K; e>0.05 for T>200 000, e>0.1 for T>300 000, ... """ if len(xyzw.shape)>2: raise Exception('xyz_to_cct_HA(): Input xyzw.ndim must be <= 2 !') out_of_range_code = np.nan xe = [0.3366, 0.3356] ye = [0.1735, 0.1691] A0 = [-949.86315, 36284.48953] A1 = [6253.80338, 0.00228] t1 = [0.92159, 0.07861] A2 = [28.70599, 5.4535*1e-36] t2 = [0.20039, 0.01543] A3 = [0.00004, 0.0] t3 = [0.07125,1.0] cct_ranges = np.array([[3000.0,50000.0],[50000.0,800000.0]]) Yxy = xyz_to_Yxy(xyzw) CCT = np.ones((1,Yxy.shape[0]))*out_of_range_code for i in range(2): n = (Yxy[:,1]-xe[i])/(Yxy[:,2]-ye[i]) CCT_i = np2d(np.array(A0[i] + A1[i]*np.exp(np.divide(-n,t1[i])) + A2[i]*np.exp(np.divide(-n,t2[i])) + A3[i]*np.exp(np.divide(-n,t3[i])))) p = (CCT_i >= (1.0-0.05*(i == 0))*cct_ranges[i][0]) & (CCT_i < (1.0+0.05*(i == 0))*cct_ranges[i][1]) CCT[p] = CCT_i[p] p = (CCT_i < (1.0-0.05)*cct_ranges[0][0]) #smaller than smallest valid CCT value CCT[p] = -1 if (np.isnan(CCT.sum()) == True) | (np.any(CCT == -1)): print("Warning: xyz_to_cct_HA(): one or more CCTs out of range! --> (CCT < 3 kK, CCT >800 kK) coded as (-1, NaN) 's") return CCT.T
def xyz_to_xyz(xyz, **kwargs): """ Convert XYZ tristimulus values to XYZ tristimulus values. Args: :xyz: | ndarray with tristimulus values Returns: :xyz: | ndarray with tristimulus values """ return np2d(xyz)
def xyz_to_wuv(xyz, xyzw=_COLORTF_DEFAULT_WHITE_POINT, **kwargs): """ Convert XYZ tristimulus values CIE 1964 U*V*W* color space. Args: :xyz: | ndarray with tristimulus values :xyzw: | ndarray with tristimulus values of white point, optional (Defaults to luxpy._COLORTF_DEFAULT_WHITE_POINT) Returns: :wuv: | ndarray with W*U*V* values """ Yuv = xyz_to_Yuv(np2d(xyz)) # convert to cie 1976 u'v' Yuvw = xyz_to_Yuv(np2d(xyzw)) wuv = np.empty(xyz.shape) wuv[..., 0] = 25.0 * (Yuv[..., 0]**(1 / 3)) - 17.0 wuv[..., 1] = 13.0 * wuv[..., 0] * (Yuv[..., 1] - Yuvw[..., 1]) wuv[..., 2] = 13.0 * wuv[..., 0] * (Yuv[..., 2] - Yuvw[..., 2]) * ( 2.0 / 3.0) #*(2/3) to convert to cie 1960 u, v return wuv
def xyz_to_srgb(xyz, gamma=2.4, **kwargs): """ Calculates IEC:61966 sRGB values from xyz. Args: :xyz: | ndarray with relative tristimulus values. :gamma: | 2.4, optional | compression in sRGB Returns: :rgb: | ndarray with R,G,B values (uint8). """ xyz = np2d(xyz) # define 3x3 matrix M = np.array([[3.2404542, -1.5371385, -0.4985314], [-0.9692660, 1.8760108, 0.0415560], [0.0556434, -0.2040259, 1.0572252]]) if len(xyz.shape) == 3: srgb = np.einsum('ij,klj->kli', M, xyz / 100) else: srgb = np.einsum('ij,lj->li', M, xyz / 100) # perform clipping: srgb[np.where(srgb > 1)] = 1 srgb[np.where(srgb < 0)] = 0 # test for the dark colours in the non-linear part of the function: dark = np.where(srgb <= 0.0031308) # apply gamma function: g = 1 / gamma # and scale to range 0-255: rgb = srgb.copy() rgb = (1.055 * rgb**g - 0.055) * 255 # non-linear bit for dark colours rgb[dark] = (srgb[dark].copy() * 12.92) * 255 # clip to range: rgb[rgb > 255] = 255 rgb[rgb < 0] = 0 return rgb
def Vrb_mb_to_xyz(Vrb,cieobs = _CIEOBS, scaling = [1,1], M = None, Minverted = False, **kwargs): """ Convert V,r,b (Macleod-Boynton) color coordinates to XYZ tristimulus values. | Macleod Boynton: V = R+G, r = R/V, b = B/V | Note that R,G,B ~ L,M,S Args: :Vrb: | ndarray with V,r,b (Macleod-Boynton) color coordinates :cieobs: | luxpy._CIEOBS, optional | CMF set to use when getting the default M, which is the xyz to lms conversion matrix. :scaling: | list of scaling factors for r and b dimensions. :M: | None, optional | Conversion matrix for going from XYZ to RGB (LMS) | If None, :cieobs: determines the M (function does inversion) :Minverted: | False, optional | Bool that determines whether M should be inverted. Returns: :xyz: | ndarray with tristimulus values Reference: 1. `MacLeod DI, and Boynton RM (1979). Chromaticity diagram showing cone excitation by stimuli of equal luminance. J. Opt. Soc. Am. 69, 1183–1186. <https://www.osapublishing.org/josa/abstract.cfm?uri=josa-69-8-1183>`_ """ Vrb = np2d(Vrb) V,r,b = asplit(Vrb) R = r*V / scaling[0] B = b*V / scaling[1] G = V-R if M is None: M = _CMF[cieobs]['M'] if Minverted == False: M = np.linalg.inv(M) X, Y, Z = [M[i,0]*R + M[i,1]*G + M[i,2]*B for i in range(3)] return ajoin((X,Y,Z))
def xyz_to_Vrb_mb(xyz, cieobs=_CIEOBS, scaling=[1, 1], M=None, **kwargs): """ Convert XYZ tristimulus values to V,r,b (Macleod-Boynton) color coordinates. | Macleod Boynton: V = R+G, r = R/V, b = B/V | Note that R,G,B ~ L,M,S Args: :xyz: | ndarray with tristimulus values :cieobs: | luxpy._CIEOBS, optional | CMF set to use when getting the default M, which is the xyz to lms conversion matrix. :scaling: | list of scaling factors for r and b dimensions. :M: | None, optional | Conversion matrix for going from XYZ to RGB (LMS) | If None, :cieobs: determines the M (function does inversion) Returns: :Vrb: | ndarray with V,r,b (Macleod-Boynton) color coordinates Reference: 1. `MacLeod DI, and Boynton RM (1979). Chromaticity diagram showing cone excitation by stimuli of equal luminance. J. Opt. Soc. Am. 69, 1183–1186. <https://www.osapublishing.org/josa/abstract.cfm?uri=josa-69-8-1183>`_ """ xyz = np2d(xyz) if M is None: M = _CMF[cieobs]['M'] if len(xyz.shape) == 3: RGB = np.einsum('ij,klj->kli', M, xyz) else: RGB = np.einsum('ij,lj->li', M, xyz) Vrb = np.empty(xyz.shape) Vrb[..., 0] = RGB[..., 0] + RGB[..., 1] Vrb[..., 1] = RGB[..., 0] / Vrb[..., 0] * scaling[0] Vrb[..., 2] = RGB[..., 2] / Vrb[..., 0] * scaling[1] return Vrb
def lab_to_xyz(lab, xyzw = None, cieobs = _CIEOBS, **kwargs): """ Convert CIE 1976 L*a*b* (CIELAB) color coordinates to XYZ tristimulus values. Args: :lab: | ndarray with CIE 1976 L*a*b* (CIELAB) color coordinates :xyzw: | None or ndarray with tristimulus values of white point, optional | None defaults to xyz of CIE D65 using the :cieobs: observer. :cieobs: | luxpy._CIEOBS, optional | CMF set to use when calculating xyzw. Returns: :xyz: | ndarray with tristimulus values """ lab = np2d(lab) if xyzw is None: xyzw = spd_to_xyz(_CIE_ILLUMINANTS['D65'],cieobs = cieobs) # make xyzw same shape as data: xyzw = xyzw*np.ones(lab.shape) # set knee point of function: k=(24/116) #(24/116)**3**(1/3) # get L*, a*, b* and Xw, Yw, Zw: L,a,b = asplit(lab) Xw,Yw,Zw = asplit(xyzw) fy = (L + 16.0) / 116.0 fx = a / 500.0 + fy fz = fy - b/200.0 # apply 3rd power: X,Y,Z = [xw*(x**3.0) for (x,xw) in ((fx,Xw),(fy,Yw),(fz,Zw))] # Now calculate T where T/Tn is below the knee point: p,q,r = [np.where(x<k) for x in (fx,fy,fz)] X[p],Y[q],Z[r] = [np.squeeze(xw[xp]*((x[xp] - 16.0/116.0) / (841/108))) for (x,xw,xp) in ((fx,Xw,p),(fy,Yw,q),(fz,Zw,r))] return ajoin((X,Y,Z))
def normalize_3x3_matrix(M, xyz0 = np.array([[1.0,1.0,1.0]])): """ Normalize 3x3 matrix M to xyz0 -- > [1,1,1] If M.shape == (1,9): M is reshaped to (3,3) Args: :M: | ndarray((3,3) or ndarray((1,9)) :xyz0: | 2darray, optional Returns: :returns: | normalized matrix such that M*xyz0 = [1,1,1] """ M = np2d(M) if M.shape[-1]==9: M = M.reshape(3,3) return np.dot(np.diagflat(1/(np.dot(M,xyz0.T))),M)
def xyz_to_Yxy(xyz, **kwargs): """ Convert XYZ tristimulus values CIE Yxy chromaticity values. Args: :xyz: | ndarray with tristimulus values Returns: :Yxy: | ndarray with Yxy chromaticity values (Y value refers to luminance or luminance factor) """ xyz = np2d(xyz) X,Y,Z = asplit(xyz) sumxyz = X + Y + Z x = X / sumxyz y = Y / sumxyz return ajoin((Y,x,y))
def Yxy_to_xyz(Yxy, **kwargs): """ Convert CIE Yxy chromaticity values to XYZ tristimulus values. Args: :Yxy: | ndarray with Yxy chromaticity values (Y value refers to luminance or luminance factor) Returns: :xyz: | ndarray with tristimulus values """ Yxy = np2d(Yxy) Y,x,y = asplit(Yxy) X = Y*x/y Z = Y*(1.0-x-y)/y return ajoin((X,Y,Z))
def Yuv_to_xyz2(Yuv, **kwargs): """ Convert CIE 1976 Yu'v' chromaticity values to XYZ tristimulus values. Args: :Yuv: | ndarray with CIE 1976 Yu'v' chromaticity values (Y value refers to luminance or luminance factor) Returns: :xyz: | ndarray with tristimulus values """ Yuv = np2d(Yuv) xyz = Yuv.astype(np.float) xyz[...,1] = Yuv[...,0] xyz[...,0] = Yuv[...,0]*(9.0*Yuv[...,1])/(4.0*Yuv[...,2]) xyz[...,2] = Yuv[...,0]*(12.0 - 3.0*Yuv[...,1] - 20.0*Yuv[...,2])/(4.0*Yuv[...,2]) return xyz
def Yxy_to_xyz(Yxy, **kwargs): """ Convert CIE Yxy chromaticity values to XYZ tristimulus values. Args: :Yxy: | ndarray with Yxy chromaticity values (Y value refers to luminance or luminance factor) Returns: :xyz: | ndarray with tristimulus values """ Yxy = np2d(Yxy) xyz = np.empty(Yxy.shape) xyz[..., 1] = Yxy[..., 0] xyz[..., 0] = Yxy[..., 0] * Yxy[..., 1] / Yxy[..., 2] xyz[..., 2] = Yxy[..., 0] * (1.0 - Yxy[..., 1] - Yxy[..., 2]) / Yxy[..., 2] return xyz
def Yuv_to_xyz(Yuv, **kwargs): """ Convert CIE 1976 Yu'v' chromaticity values to XYZ tristimulus values. Args: :Yuv: | ndarray with CIE 1976 Yu'v' chromaticity values (Y value refers to luminance or luminance factor) Returns: :xyz: | ndarray with tristimulus values """ Yuv = np2d(Yuv) Y,u,v = asplit(Yuv) X = Y*(9.0*u)/(4.0*v) Z = Y*(12.0 - 3.0*u - 20.0*v)/(4.0*v) return ajoin((X,Y,Z))
def xyz_to_lab(xyz, xyzw=None, cieobs=_CIEOBS, **kwargs): """ Convert XYZ tristimulus values to CIE 1976 L*a*b* (CIELAB) coordinates. Args: :xyz: | ndarray with tristimulus values :xyzw: | None or ndarray with tristimulus values of white point, optional | None defaults to xyz of CIE D65 using the :cieobs: observer. :cieobs: | luxpy._CIEOBS, optional | CMF set to use when calculating xyzw. Returns: :lab: | ndarray with CIE 1976 L*a*b* (CIELAB) color coordinates """ xyz = np2d(xyz) if xyzw is None: xyzw = spd_to_xyz(_CIE_ILLUMINANTS['D65'], cieobs=cieobs) # get and normalize (X,Y,Z) to white point: XYZr = xyz / xyzw # Apply cube-root compression: fXYZr = XYZr**(1.0 / 3.0) # Check for T/Tn <= 0.008856: (Note (24/116)**3 = 0.008856) pqr = XYZr <= (24 / 116)**3 # calculate f(T) for T/Tn <= 0.008856: (Note:(1/3)*((116/24)**2) = 841/108 = 7.787) fXYZr[pqr] = ((841 / 108) * XYZr[pqr] + 16.0 / 116.0) # calculate L*, a*, b*: Lab = np.empty(xyz.shape) Lab[..., 0] = 116.0 * (fXYZr[..., 1]) - 16.0 Lab[pqr[..., 1], 0] = 903.3 * XYZr[pqr[..., 1], 1] Lab[..., 1] = 500.0 * (fXYZr[..., 0] - fXYZr[..., 1]) Lab[..., 2] = 200.0 * (fXYZr[..., 1] - fXYZr[..., 2]) return Lab
def luv_to_xyz(luv, xyzw = None, cieobs = _CIEOBS, **kwargs): """ Convert CIE 1976 L*u*v* (CIELUVB) coordinates to XYZ tristimulus values. Args: :luv: | ndarray with CIE 1976 L*u*v* (CIELUV) color coordinates :xyzw: | None or ndarray with tristimulus values of white point, optional | None defaults to xyz of CIE D65 using the :cieobs: observer. :cieobs: | luxpy._CIEOBS, optional | CMF set to use when calculating xyzw. Returns: :xyz: | ndarray with tristimulus values """ luv = np2d(luv) if xyzw is None: xyzw = spd_to_xyz(_CIE_ILLUMINANTS['D65'],cieobs = cieobs) # Make xyzw same shape as luv: Yuvw = todim(xyz_to_Yuv(xyzw), luv.shape, equal_shape = True) # Get Yw, uw,vw: Yw,uw,vw = asplit(Yuvw) # calculate u'v' from u*,v*: L,u,v = asplit(luv) up,vp = [(x / (13*L)) + xw for (x,xw) in ((u,uw),(v,vw))] up[np.where(L == 0.0)] = 0.0 vp[np.where(L == 0.0)] = 0.0 fy = (L + 16.0) / 116.0 Y = Yw*(fy**3.0) p = np.where((Y/Yw) < ((6.0/29.0)**3.0)) Y[p] = Yw[p]*(L[p]/((29.0/3.0)**3.0)) return Yuv_to_xyz(ajoin((Y,up,vp)))
def rms(data,axis = 0, keepdims = False): """ Calculate root-mean-square along axis. Args: :data: | list of values or ndarray :axis: | 0, optional | Axis along which to calculate rms. :keepdims: | False or True, optional | Keep original dimensions of array. Returns: :returns: | ndarray with rms values. """ data = np2d(data) return np.sqrt(np.power(data,2).mean(axis=axis, keepdims = keepdims))
def geomean(data, axis = 0, keepdims = False): """ Calculate geometric mean along axis. Args: :data: | list of values or ndarray :axis: | 0, optional | Axis along which to calculate geomean. :keepdims: | False or True, optional | Keep original dimensions of array. Returns: :returns: | ndarray with geomean values. """ data = np2d(data) return np.power(data.prod(axis=axis, keepdims = keepdims),1/data.shape[axis])
def xyz_to_Yuv(xyz, **kwargs): """ Convert XYZ tristimulus values CIE 1976 Yu'v' chromaticity values. Args: :xyz: | ndarray with tristimulus values Returns: :Yuv: | ndarray with CIE 1976 Yu'v' chromaticity values (Y value refers to luminance or luminance factor) """ xyz = np2d(xyz) Yuv = np.empty(xyz.shape) denom = xyz[..., 0] + 15.0 * xyz[..., 1] + 3.0 * xyz[..., 2] Yuv[..., 0] = xyz[..., 1] Yuv[..., 1] = 4.0 * xyz[..., 0] / denom Yuv[..., 2] = 9.0 * xyz[..., 1] / denom return Yuv
def xyz_to_Yxy(xyz, **kwargs): """ Convert XYZ tristimulus values CIE Yxy chromaticity values. Args: :xyz: | ndarray with tristimulus values Returns: :Yxy: | ndarray with Yxy chromaticity values (Y value refers to luminance or luminance factor) """ xyz = np2d(xyz) Yxy = np.empty(xyz.shape) sumxyz = xyz[..., 0] + xyz[..., 1] + xyz[..., 2] Yxy[..., 0] = xyz[..., 1] Yxy[..., 1] = xyz[..., 0] / sumxyz Yxy[..., 2] = xyz[..., 1] / sumxyz return Yxy