""" Functions for calculating the "distance" between colors. Implicit in these definitions of "distance" is the notion of "Just Noticeable Distance" (JND). This represents the distance between colors where a human can perceive different colors. Humans are more sensitive to certain colors than others, which different deltaE metrics correct for with varying degrees of sophistication. The literature often mentions 1 as the minimum distance for visual differentiation, but more recent studies (Mahy 1994) peg JND at 2.3 The delta-E notation comes from the German word for "Sensation" (Empfindung). Reference --------- https://en.wikipedia.org/wiki/Color_difference """ import numpy as np from .colorconv import lab2lch, _cart2polar_2pi def deltaE_cie76(lab1, lab2): """Euclidean distance between two points in Lab color space 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` References ---------- .. [1] https://en.wikipedia.org/wiki/Color_difference .. [2] A. R. Robertson, "The CIE 1976 color-difference formulae," Color Res. Appl. 2, 7-11 (1977). """ lab1 = np.asarray(lab1) lab2 = np.asarray(lab2) L1, a1, b1 = np.rollaxis(lab1, -1)[:3] L2, a2, b2 = np.rollaxis(lab2, -1)[:3] return np.sqrt((L2 - L1) ** 2 + (a2 - a1) ** 2 + (b2 - b1) ** 2) def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): """Color difference according to CIEDE 94 standard Accommodates perceptual non-uniformities through the use of application specific scale factors (`kH`, `kC`, `kL`, `k1`, and `k2`). Parameters ---------- lab1 : array_like reference color (Lab colorspace) lab2 : array_like comparison color (Lab colorspace) kH : float, optional Hue scale kC : float, optional Chroma scale kL : float, optional Lightness scale k1 : float, optional first scale parameter k2 : float, optional second scale parameter Returns ------- dE : array_like color difference between `lab1` and `lab2` Notes ----- deltaE_ciede94 is not symmetric with respect to lab1 and lab2. CIEDE94 defines the scales for the lightness, hue, and chroma in terms of the first color. Consequently, the first color should be regarded as the "reference" color. `kL`, `k1`, `k2` depend on the application and default to the values suggested for graphic arts ========== ============== ========== Parameter Graphic Arts Textiles ========== ============== ========== `kL` 1.000 2.000 `k1` 0.045 0.048 `k2` 0.015 0.014 ========== ============== ========== References ---------- .. [1] https://en.wikipedia.org/wiki/Color_difference .. [2] http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html """ L1, C1 = np.rollaxis(lab2lch(lab1), -1)[:2] L2, C2 = np.rollaxis(lab2lch(lab2), -1)[:2] dL = L1 - L2 dC = C1 - C2 dH2 = get_dH2(lab1, lab2) SL = 1 SC = 1 + k1 * C1 SH = 1 + k2 * C1 dE2 = (dL / (kL * SL)) ** 2 dE2 += (dC / (kC * SC)) ** 2 dE2 += dH2 / (kH * SH) ** 2 return np.sqrt(np.maximum(dE2, 0)) 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). """ lab1 = np.asarray(lab1) lab2 = np.asarray(lab2) 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 = np.rollaxis(lab1, -1)[:3] L2, a2, b2 = np.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 * (np.hypot(a1, b1) + np.hypot(a2, b2)) c7 = Cbar ** 7 G = 0.5 * (1 - np.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) ** 2 SL = 1 + 0.015 * tmp / np.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 * np.sqrt(CC) * np.sin(dH / 2) Hbar = h_sum.copy() mask = np.logical_and(CC != 0., np.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 * np.cos(Hbar - np.deg2rad(30)) + 0.24 * np.cos(2 * Hbar) + 0.32 * np.cos(3 * Hbar + np.deg2rad(6)) - 0.20 * np.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 * np.sqrt(c7 / (c7 + 25 ** 7)) dtheta = np.deg2rad(30) * np.exp(-((np.rad2deg(Hbar) - 275) / 25) ** 2) R_term = -np.sin(2 * dtheta) * Rc * C_term * H_term # put it all together dE2 = L_term ** 2 dE2 += C_term ** 2 dE2 += H_term ** 2 dE2 += R_term ans = np.sqrt(np.maximum(dE2, 0)) if unroll: ans = ans[0] return ans 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 = np.rollaxis(lab2lch(lab1), -1)[:3] L2, C2, h2 = np.rollaxis(lab2lch(lab2), -1)[:3] dC = C1 - C2 dL = L1 - L2 dH2 = get_dH2(lab1, lab2) T = np.where(np.logical_and(np.rad2deg(h1) >= 164, np.rad2deg(h1) <= 345), 0.56 + 0.2 * np.abs(np.cos(h1 + np.deg2rad(168))), 0.36 + 0.4 * np.abs(np.cos(h1 + np.deg2rad(35))) ) c1_4 = C1 ** 4 F = np.sqrt(c1_4 / (c1_4 + 1900)) SL = np.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 np.sqrt(np.maximum(dE2, 0)) def get_dH2(lab1, lab2): """squared hue difference term occurring in deltaE_cmc and deltaE_ciede94 Despite its name, "dH" is not a simple difference of hue values. We avoid working directly with the hue value, since differencing angles is troublesome. The hue term is usually written as: c1 = sqrt(a1**2 + b1**2) c2 = sqrt(a2**2 + b2**2) term = (a1-a2)**2 + (b1-b2)**2 - (c1-c2)**2 dH = sqrt(term) However, this has poor roundoff properties when a or b is dominant. Instead, ab is a vector with elements a and b. The same dH term can be re-written as: |ab1-ab2|**2 - (|ab1| - |ab2|)**2 and then simplified to: 2*|ab1|*|ab2| - 2*dot(ab1, ab2) """ lab1 = np.asarray(lab1) lab2 = np.asarray(lab2) a1, b1 = np.rollaxis(lab1, -1)[1:3] a2, b2 = np.rollaxis(lab2, -1)[1:3] # magnitude of (a, b) is the chroma C1 = np.hypot(a1, b1) C2 = np.hypot(a2, b2) term = (C1 * C2) - (a1 * a2 + b1 * b2) return 2 * term