def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): """Color difference as given by the CIEDE 2000 standard. CIEDE 2000 is a major revision of CIDE94. The perceptual calibration is largely based on experience with automotive paint on smooth surfaces. Parameters ---------- lab1 : array_like reference color (Lab colorspace) lab2 : array_like comparison color (Lab colorspace) kL : float (range), optional lightness scale factor, 1 for "acceptably close"; 2 for "imperceptible" see deltaE_cmc kC : float (range), optional chroma scale factor, usually 1 kH : float (range), optional hue scale factor, usually 1 Returns ------- deltaE : array_like The distance between `lab1` and `lab2` Notes ----- CIEDE 2000 assumes parametric weighting factors for the lightness, chroma, and hue (`kL`, `kC`, `kH` respectively). These default to 1. References ---------- .. [1] https://en.wikipedia.org/wiki/Color_difference .. [2] http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf :DOI:`10.1364/AO.33.008069` .. [3] M. Melgosa, J. Quesada, and E. Hita, "Uniformity of some recent color metrics tested with an accurate color-difference tolerance dataset," Appl. Opt. 33, 8069-8077 (1994). """ warnings.warn( "The numerical accuracy of this function on the GPU is reduced " "relative to the CPU version" ) unroll = False if lab1.ndim == 1 and lab2.ndim == 1: unroll = True if lab1.ndim == 1: lab1 = lab1[None, :] if lab2.ndim == 1: lab2 = lab2[None, :] L1, a1, b1 = cp.rollaxis(lab1, -1)[:3] L2, a2, b2 = cp.rollaxis(lab2, -1)[:3] # distort `a` based on average chroma # then convert to lch coordines from distorted `a` # all subsequence calculations are in the new coordiantes # (often denoted "prime" in the literature) Cbar = 0.5 * (cp.hypot(a1, b1) + cp.hypot(a2, b2)) c7 = Cbar ** 7 G = 0.5 * (1 - cp.sqrt(c7 / (c7 + 25 ** 7))) scale = 1 + G C1, h1 = _cart2polar_2pi(a1 * scale, b1) C2, h2 = _cart2polar_2pi(a2 * scale, b2) # recall that c, h are polar coordiantes. c==r, h==theta # cide2000 has four terms to delta_e: # 1) Luminance term # 2) Hue term # 3) Chroma term # 4) hue Rotation term # lightness term Lbar = 0.5 * (L1 + L2) tmp = Lbar - 50 tmp *= tmp SL = 1 + 0.015 * tmp / cp.sqrt(20 + tmp) L_term = (L2 - L1) / (kL * SL) # chroma term Cbar = 0.5 * (C1 + C2) # new coordiantes SC = 1 + 0.045 * Cbar C_term = (C2 - C1) / (kC * SC) # hue term h_diff = h2 - h1 h_sum = h1 + h2 CC = C1 * C2 dH = h_diff.copy() dH[h_diff > np.pi] -= 2 * np.pi dH[h_diff < -np.pi] += 2 * np.pi dH[CC == 0.] = 0. # if r == 0, dtheta == 0 dH_term = 2 * cp.sqrt(CC) * cp.sin(dH / 2) Hbar = h_sum.copy() mask = cp.logical_and(CC != 0., cp.abs(h_diff) > np.pi) Hbar[mask * (h_sum < 2 * np.pi)] += 2 * np.pi Hbar[mask * (h_sum >= 2 * np.pi)] -= 2 * np.pi Hbar[CC == 0.] *= 2 Hbar *= 0.5 T = (1 - 0.17 * cp.cos(Hbar - np.deg2rad(30)) + 0.24 * cp.cos(2 * Hbar) + 0.32 * cp.cos(3 * Hbar + np.deg2rad(6)) - 0.20 * cp.cos(4 * Hbar - np.deg2rad(63)) ) SH = 1 + 0.015 * Cbar * T H_term = dH_term / (kH * SH) # hue rotation c7 = Cbar ** 7 Rc = 2 * cp.sqrt(c7 / (c7 + 25 ** 7)) tmp = (cp.rad2deg(Hbar) - 275) / 25 tmp *= tmp dtheta = np.deg2rad(30) * cp.exp(-tmp) R_term = -cp.sin(2 * dtheta) * Rc * C_term * H_term # put it all together dE2 = L_term * L_term dE2 += C_term * C_term dE2 += H_term * H_term dE2 += R_term cp.sqrt(cp.maximum(dE2, 0, out=dE2), out=dE2) if unroll: dE2 = dE2[0] return dE2
def deltaE_cmc(lab1, lab2, kL=1, kC=1): """Color difference from the CMC l:c standard. This color difference was developed by the Colour Measurement Committee (CMC) of the Society of Dyers and Colourists (United Kingdom). It is intended for use in the textile industry. The scale factors `kL`, `kC` set the weight given to differences in lightness and chroma relative to differences in hue. The usual values are ``kL=2``, ``kC=1`` for "acceptability" and ``kL=1``, ``kC=1`` for "imperceptibility". Colors with ``dE > 1`` are "different" for the given scale factors. Parameters ---------- lab1 : array_like reference color (Lab colorspace) lab2 : array_like comparison color (Lab colorspace) Returns ------- dE : array_like distance between colors `lab1` and `lab2` Notes ----- deltaE_cmc the defines the scales for the lightness, hue, and chroma in terms of the first color. Consequently ``deltaE_cmc(lab1, lab2) != deltaE_cmc(lab2, lab1)`` References ---------- .. [1] https://en.wikipedia.org/wiki/Color_difference .. [2] http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html .. [3] F. J. J. Clarke, R. McDonald, and B. Rigg, "Modification to the JPC79 colour-difference formula," J. Soc. Dyers Colour. 100, 128-132 (1984). """ L1, C1, h1 = cp.rollaxis(lab2lch(lab1), -1)[:3] L2, C2, h2 = cp.rollaxis(lab2lch(lab2), -1)[:3] dC = C1 - C2 dL = L1 - L2 dH2 = get_dH2(lab1, lab2) T = cp.where(cp.logical_and(cp.rad2deg(h1) >= 164, cp.rad2deg(h1) <= 345), 0.56 + 0.2 * cp.abs(np.cos(h1 + cp.deg2rad(168))), 0.36 + 0.4 * cp.abs(np.cos(h1 + cp.deg2rad(35))) ) c1_4 = C1 ** 4 F = cp.sqrt(c1_4 / (c1_4 + 1900)) SL = cp.where(L1 < 16, 0.511, 0.040975 * L1 / (1. + 0.01765 * L1)) SC = 0.638 + 0.0638 * C1 / (1. + 0.0131 * C1) SH = SC * (F * T + 1 - F) dE2 = (dL / (kL * SL)) ** 2 dE2 += (dC / (kC * SC)) ** 2 dE2 += dH2 / (SH ** 2) return cp.sqrt(cp.maximum(dE2, 0, out=dE2), out=dE2)
def slope_aspect_gpu(dem, resolution_x, resolution_y, ve_factor=1, output_units="radian"): """ Procedure can return terrain slope and aspect in radian units (default) or in alternative units (if specified). Slope is defined as 0 for Hz plane and pi/2 for vertical plane. Aspect iz defined as geographic azimuth: clockwise increasing, 0 or 2pi for the North direction. Currently applied finite difference method. Parameters ---------- dem : input dem 2D cupy array resolution_x : dem resolution in X direction resolution_y : DEM resolution in Y direction ve_factor : vertical exaggeration factor (must be greater than 0) output_units : percent, degree, radians Returns ------- {"slope": slope_out, "aspect": aspect_out} : dictionaries with 2D numpy arrays """ if ve_factor <= 0: raise Exception( "rvt.vis.slope_aspect: ve_factor must be a positive number!") if resolution_x < 0 or resolution_y < 0: raise Exception( "rvt.vis.slope_aspect: resolution must be a positive number!") dem = dem.astype(np.float32) if ve_factor != 1: dem = dem * ve_factor # add frame of 0 (additional row up bottom and column left right) dem = cp.pad(dem, pad_width=1, mode="constant", constant_values=0) # derivatives in X and Y direction dzdx = ((cp.roll(dem, 1, axis=1) - cp.roll(dem, -1, axis=1)) / 2) / resolution_x dzdy = ((cp.roll(dem, -1, axis=0) - cp.roll(dem, 1, axis=0)) / 2) / resolution_y tan_slope = cp.sqrt(dzdx**2 + dzdy**2) # Compute slope if output_units == "percent": slope_out = tan_slope * 100 elif output_units == "degree": slope_out = cp.rad2deg(np.arctan(tan_slope)) elif output_units == "radian": slope_out = cp.arctan(tan_slope) else: raise Exception( "rvt.vis.calculate_slope: Wrong function input 'output_units'!") # compute Aspect # aspect identifies the down slope direction of the maximum rate of change in value from each cell to its neighbors: # 0 # 270 90 # 180 dzdy[ dzdy == 0] = 10e-9 # important for numeric stability - where dzdy is zero, make tangens to really high value aspect_out = cp.arctan2(dzdx, dzdy) # atan2 took care of the quadrants if output_units == "degree": aspect_out = cp.rad2deg(aspect_out) # remove the frame (padding) slope_out = slope_out[1:-1, 1:-1] aspect_out = aspect_out[1:-1, 1:-1] # edges to -1 slope_out[:, 0] = -1 slope_out[0, :] = -1 slope_out[:, -1] = -1 slope_out[-1, :] = -1 aspect_out[:, 0] = -1 aspect_out[0, :] = -1 aspect_out[:, -1] = -1 aspect_out[-1, :] = -1 return {"slope": slope_out, "aspect": aspect_out}