From e866696d5de02515d299f9315015839ee47af149 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Sun, 21 Jul 2013 16:33:18 -0700 Subject: [PATCH 01/36] add color distance functions, ciede2000 tests --- skimage/color/__init__.py | 10 +- skimage/color/colorconv.py | 94 +++++++++ skimage/color/delta_e.py | 215 ++++++++++++++++++++ skimage/color/tests/ciede2000_test_data.txt | 37 ++++ skimage/color/tests/test_ciede2000.py | 61 ++++++ 5 files changed, 416 insertions(+), 1 deletion(-) create mode 100644 skimage/color/delta_e.py create mode 100644 skimage/color/tests/ciede2000_test_data.txt create mode 100644 skimage/color/tests/test_ciede2000.py diff --git a/skimage/color/__init__.py b/skimage/color/__init__.py index 19620cb1..2d072808 100644 --- a/skimage/color/__init__.py +++ b/skimage/color/__init__.py @@ -44,6 +44,12 @@ from .colorconv import (convert_colorspace, from .colorlabel import color_dict, label2rgb +from .delta_e import (deltaE_cie76, + deltaE_ciede94, + deltaE_ciede2000, + deltaE_cmc, + ) + __all__ = ['convert_colorspace', 'guess_spatial_dimensions', @@ -89,4 +95,6 @@ __all__ = ['convert_colorspace', 'is_rgb', 'is_gray', 'color_dict', - 'label2rgb'] + 'label2rgb', + 'deltaE_ciede2000', + ] diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index d2b20316..36fbeb1a 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -26,10 +26,13 @@ Supported color spaces Derived from the RGB CIE color space. Chosen such that ``x == y == z == 1/3`` at the whitepoint, and all color matching functions are greater than zero everywhere. +* LAB CIE : Lightness, a, b +* LCH CIE : Lightness, Chroma, Hue :author: Nicolas Pinto (rgb2hsv) :author: Ralf Gommers (hsv2rgb) :author: Travis Oliphant (XYZ and RGB CIE functions) +:author: Matt Terry (lab2lch) :license: modified BSD @@ -1026,3 +1029,94 @@ def combine_stains(stains, conv_matrix): logrgb2 = np.dot(-np.reshape(stains, (-1, 3)), conv_matrix) rgb2 = np.exp(logrgb2) return rescale_intensity(np.reshape(rgb2 - 2, stains.shape), in_range=(-1, 1)) + + +def lab2lch(lab): + """CIE-LAB to LCH color space conversion. + TODO: something about cylindrical representation + + Parameters + ---------- + lab : array_like + The image in CIE-LAB format, in a 3-D array of shape (.., .., 3). + + Returns + ------- + out : ndarray + The image in LCH format, in a 3-D array of shape (.., .., 3). + + Raises + ------ + ValueError + If `rgb` is not a 3-D array of shape (.., .., 3). + + References + ---------- + + Notes + ----- + The Hue is expressed as an angle between (0, 2*pi) + + Examples + -------- + >>> from skimage import data + >>> from skimage.color import rgb2lab, lab2lch + >>> lena = data.lena() + >>> lena_lab = rgb2lab(lena) + >>> lena_lch = lab2lch(lena_lab) + """ + shape = lab.shape + if len(shape) != 3 or shape[2] != 3: + raise ValueError("Input image expected to be LAB") + + lab = _prepare_colorarray(lab) + lch = np.empty_like(lab) + + a, b = lab[:, :, 1], lab[:, :, 2] + lch[:, :, 0] = lab[:, :, 0] + lch[:, :, 1] = np.sqrt(a**2 + b**2) # C + + H = lch[:, :, 2] = np.arctan2(b, a) + H[H < 0] += 2*np.pi # (-pi, pi) -> (0, 2*pi) + return lch + + +def lch2lab(lch): + """CIE-LCH to CIE-LAB color space conversion. + TODO: something about cylindrical representation + + Parameters + ---------- + lab : array_like + The image in CIE-LCH format, in a 3-D array of shape (.., .., 3). + + Returns + ------- + out : ndarray + The image in LAB format, in a 3-D array of shape (.., .., 3). + + Raises + ------ + ValueError + If `rgb` is not a 3-D array of shape (.., .., 3). + + References + ---------- + + Examples + -------- + >>> from skimage import data + >>> from skimage.color import rgb2lab, lch2lab + >>> lena = data.lena() + >>> lena_lab = rgb2lab(lena) + >>> lena_lch = lab2lch(lena_lab) + >>> lena_lab2 = lch2lab(lena_lch) + """ + lch = _prepare_colorarray(lch) + lab = np.empty_like(lch) + + c, h = lch[:, :, 1], lch[:, :, 2] + lab[:, :, 0] = lch[:, :, 0] + lab[:, :, 1] = c*np.cos(h) + lab[:, :, 2] = c*np.sin(h) + return lab diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py new file mode 100644 index 00000000..91072aff --- /dev/null +++ b/skimage/color/delta_e.py @@ -0,0 +1,215 @@ +""" +Functions for calculating the "distance" between colors. + +Implicit in these definitions of "distance" is the notion of "Just Noticible +Distance" (JND). This represents the distance between colors where a human can +percieve different colors. Humans are more sensitive to certain colors than +others, which different deltaE metrics correct for this with varying degrees of +sophistication. + +:author: Matt Terry + +:license: modified BSD + +Reference +--------- + +""" +from __future__ import division + +import numpy as np +from .colorconv import lab2lch + +DEG = np.pi/180 + + +def _unpack_last(x): + x = np.asarray(x) + shape = x.shape + return [x[..., i] for i in range(shape[-1])] + + +def _arctan2pi(b, a): + """np.arctan2 mapped to (0, 2*pi)""" + ans = np.arctan2(b, a) + ans += np.where(ans < 0, 2*np.pi, 0.) + assert ans.max() <= 2*np.pi + assert ans.min() >= 0. + return ans + + +def deltaE_cie76(lab1, lab2): + """ + "just noticible difference" ~ 2.3 + """ + l1, a1, b1 = _unpack_last(lab1) + l2, a2, b2 = _unpack_last(lab2) + return np.sqrt((l2-l1)**2 + (a2-a1)**2 + (b2-b1)**2) + + +def deltaE_ciede94(lab1, lab2, kC=1, kH=1, kL=1, k1=0.045, k2=0.015): + """ + kC, kH are weighting factors, usually unity (default) + + kL, k1, k2 depend on the application. Sample values are: + kL, k1, k2 = 1, 0.045, 0.015 (graphic arts, default) + kL, k1, k2 = 2, 0.048, 0.014 (textiles) + + Note: deltaE_ciede94 the defines the scales for the lightness, hue, and + chroma in terms of the first color. Consequently + deltaE_ciede94(lab1, lab2) != deltaE_ciede94(lab2, lab1) + """ + l1, a1, b1 = _unpack_last(lab1) + l2, a2, b2 = _unpack_last(lab2) + + dl = l1 - l2 + c1 = np.sqrt(a1**2 + b1**2) + c2 = np.sqrt(a2**2 + b2**2) + da = a1 - a2 + db = b1 - b2 + dc = c1 - c2 + dh_ab = np.sqrt(da**2 + db**2 + dc**2) + + SL = 1 + SC = 1 + k1*c1 + SH = 1 + k2*c1 + + ans = (dl/(kL*SL))**2 + (dc/(kC*SC))**2 + (dh_ab/(kH*SH))**2 + return np.sqrt(ans) + + +def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): + """ + Parameters + ---------- + lab1 : array_like + pass + lab2 : array_like + pass + kL : float (range), optional + pass + kC : float (range), optional + pass + kH : float (range), optional + pass + + Returns + ------- + deltaE : array_like + The distance between `lab1` and `lab2` + + Notes + ----- + CIEDE 2000 assumes parametric weighting factors for the luminance, chroma, + and hue (kL, kC, kH respectively). + kL = 1 # graphic arts + kL = 2 # textiles + + References + ---------- + http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf + """ + L1, a1, b1 = _unpack_last(lab1) + L2, a2, b2 = _unpack_last(lab2) + + c1 = np.sqrt(a1**2 + b1**2) + c2 = np.sqrt(a2**2 + b2**2) + cbar = 0.5*(c1 + c2) + c7 = cbar**7 + G = 0.5 * (1 - np.sqrt(c7/(c7 + 25**7))) + + dL_prime = L2 - L1 + Lbar = 0.5*(L1 + L2) + + a1_prime = a1 * (1 + G) + a2_prime = a2 * (1 + G) + + c1_prime = np.sqrt(a1_prime**2 + b1**2) + c2_prime = np.sqrt(a2_prime**2 + b2**2) + cbar_prime = 0.5*(c1_prime + c2_prime) + dC_prime = c2_prime - c1_prime + + h1_prime = _arctan2pi(b1, a1_prime) + h2_prime = _arctan2pi(b2, a2_prime) + + dh_prime = h2_prime - h1_prime + + cc = c1_prime * c2_prime + mask1 = cc == 0. + mask2 = (-mask1) * (dh_prime > np.pi) + mask3 = (-mask1) * (dh_prime < -np.pi) + dh_prime[mask1] = 0. + dh_prime[mask2] += 2*np.pi + dh_prime[mask3] -= 2*np.pi + + dH_prime = 2 * np.sqrt(cc) * np.sin(dh_prime/2) + + Hbar_prime = h1_prime + h2_prime + mask0 = np.logical_and(np.abs(h1_prime - h2_prime) > np.pi, cc != 0.) + mask1 = np.logical_and(mask0, Hbar_prime < 2*np.pi) + mask2 = np.logical_and(mask0, Hbar_prime >= 2*np.pi) + Hbar_prime[mask1] += 2*np.pi + Hbar_prime[mask2] -= 2*np.pi + Hbar_prime[cc == 0.] *= 2 + Hbar_prime *= 0.5 + + deg = np.pi/180. + T = (1 - + 0.17 * np.cos(Hbar_prime - 30*deg) + + 0.24 * np.cos(2*Hbar_prime) + + 0.32 * np.cos(3*Hbar_prime + 6*deg) - + 0.20 * np.cos(4*Hbar_prime - 63*deg) + ) + dTheta = 30*deg * np.exp(-((Hbar_prime/deg - 275)/25)**2) + c7 = cbar_prime**7 + Rc = 2 * np.sqrt(c7 / (c7 + 25**7)) + + term = (Lbar - 50)**2 + SL = 1 + 0.015*term/np.sqrt(20 + term) + SC = 1 + 0.045*cbar_prime + SH = 1 + 0.015*cbar_prime * T + + RT = -np.sin(2*dTheta) * Rc + + l_term = dL_prime / (kL * SL) + c_term = dC_prime / (kC * SC) + h_term = dH_prime / (kH * SH) + r_term = RT * c_term * h_term + + dE2 = l_term**2 + dE2 += c_term**2 + dE2 += h_term**2 + dE2 += r_term + return np.sqrt(dE2) + + +def deltaE_cmc(lab1, lab2): + """ + indistinguishable if < 1 + usual value for "different" is > 2 + + Note: 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) + """ + l1, c1, h1 = _unpack_last(lab2lch(lab1)) + l2, c2, h2 = _unpack_last(lab2lch(lab2)) + + sl = np.where(l1 < 16, 0.511, 0.040975*l1 / (1 + 0.01765*l1)) + sc = 0.638 + 0.0638*c1 / (1 + 0.0131*c1) + + c1_4 = c1**4 + f = np.sqrt(c1_4 / (c1_4 + 1900)) + t = np.where(np.logical_and(h1 >= 2.862, h1 <= 6.021), + 0.56 * 0.2 * np.abs(np.cos(h1 + 2.93)), + 0.36 + 0.4 * np.abs(np.cos(h1 + 0.611)) + ) + sh = sc * (f*t + 1-f) + + l, c = 1, 1 + ans = ((l2 - l1)/(l * sl))**2 + ans += ((c2 - c1)/(c * sc))**2 + deg = np.pi/180. + ans += ((h2 - h1)*deg/sh)**2 # metric defines h in terms of degrees + + return np.sqrt(ans) diff --git a/skimage/color/tests/ciede2000_test_data.txt b/skimage/color/tests/ciede2000_test_data.txt new file mode 100644 index 00000000..28503ce5 --- /dev/null +++ b/skimage/color/tests/ciede2000_test_data.txt @@ -0,0 +1,37 @@ +# input, intermediate, and output values for CIEDE2000 dE function +# data taken from "The CIEDE2000 Color-Difference Formula: Implementation Notes, ..." http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf +# pair 1 L1 a1 b1 ap1 cp1 hp1 hbar1 G T SL SC SH RT dE 2 L2 a2 b2 ap2 cp2 hp2 +1 1 50.0000 2.6772 -79.7751 2.6774 79.8200 271.9222 270.9611 0.0001 0.6907 1.0000 4.6578 1.8421 -1.7042 2.0425 2 50.0000 0.0000 -82.7485 0.0000 82.7485 270.0000 +2 1 50.0000 3.1571 -77.2803 3.1573 77.3448 272.3395 271.1698 0.0001 0.6843 1.0000 4.6021 1.8216 -1.7070 2.8615 2 50.0000 0.0000 -82.7485 0.0000 82.7485 270.0000 +3 1 50.0000 2.8361 -74.0200 2.8363 74.0743 272.1944 271.0972 0.0001 0.6865 1.0000 4.5285 1.8074 -1.7060 3.4412 2 50.0000 0.0000 -82.7485 0.0000 82.7485 270.0000 +4 1 50.0000 -1.3802 -84.2814 -1.3803 84.2927 269.0618 269.5309 0.0001 0.7357 1.0000 4.7584 1.9217 -1.6809 1.0000 2 50.0000 0.0000 -82.7485 0.0000 82.7485 270.0000 +5 1 50.0000 -1.1848 -84.8006 -1.1849 84.8089 269.1995 269.5997 0.0001 0.7335 1.0000 4.7700 1.9218 -1.6822 1.0000 2 50.0000 0.0000 -82.7485 0.0000 82.7485 270.0000 +6 1 50.0000 -0.9009 -85.5211 -0.9009 85.5258 269.3964 269.6982 0.0001 0.7303 1.0000 4.7862 1.9217 -1.6840 1.0000 2 50.0000 0.0000 -82.7485 0.0000 82.7485 270.0000 +7 1 50.0000 0.0000 0.0000 0.0000 0.0000 0.0000 126.8697 0.5000 1.2200 1.0000 1.0562 1.0229 0.0000 2.3669 2 50.0000 -1.0000 2.0000 -1.5000 2.5000 126.8697 +8 1 50.0000 -1.0000 2.0000 -1.5000 2.5000 126.8697 126.8697 0.5000 1.2200 1.0000 1.0562 1.0229 0.0000 2.3669 2 50.0000 0.0000 0.0000 0.0000 0.0000 0.0000 +9 1 50.0000 2.4900 -0.0010 3.7346 3.7346 359.9847 269.9854 0.4998 0.7212 1.0000 1.1681 1.0404 -0.0022 7.1792 2 50.0000 -2.4900 0.0009 -3.7346 3.7346 179.9862 +10 1 50.0000 2.4900 -0.0010 3.7346 3.7346 359.9847 269.9847 0.4998 0.7212 1.0000 1.1681 1.0404 -0.0022 7.1792 2 50.0000 -2.4900 0.0010 -3.7346 3.7346 179.9847 +11 1 50.0000 2.4900 -0.0010 3.7346 3.7346 359.9847 89.9839 0.4998 0.6175 1.0000 1.1681 1.0346 0.0000 7.2195 2 50.0000 -2.4900 0.0011 -3.7346 3.7346 179.9831 +12 1 50.0000 2.4900 -0.0010 3.7346 3.7346 359.9847 89.9831 0.4998 0.6175 1.0000 1.1681 1.0346 0.0000 7.2195 2 50.0000 -2.4900 0.0012 -3.7346 3.7346 179.9816 +13 1 50.0000 -0.0010 2.4900 -0.0015 2.4900 90.0345 180.0328 0.4998 0.9779 1.0000 1.1121 1.0365 0.0000 4.8045 2 50.0000 0.0009 -2.4900 0.0013 2.4900 270.0311 +14 1 50.0000 -0.0010 2.4900 -0.0015 2.4900 90.0345 180.0345 0.4998 0.9779 1.0000 1.1121 1.0365 0.0000 4.8045 2 50.0000 0.0010 -2.4900 0.0015 2.4900 270.0345 +15 1 50.0000 -0.0010 2.4900 -0.0015 2.4900 90.0345 0.0362 0.4998 1.3197 1.0000 1.1121 1.0493 0.0000 4.7461 2 50.0000 0.0011 -2.4900 0.0016 2.4900 270.0380 +16 1 50.0000 2.5000 0.0000 3.7496 3.7496 0.0000 315.0000 0.4998 0.8454 1.0000 1.1406 1.0396 -0.0001 4.3065 2 50.0000 0.0000 -2.5000 0.0000 2.5000 270.0000 +17 1 50.0000 2.5000 0.0000 3.4569 3.4569 0.0000 346.2470 0.3827 1.4453 1.1608 1.9547 1.4599 -0.0003 27.1492 2 73.0000 25.0000 -18.0000 34.5687 38.9743 332.4939 +18 1 50.0000 2.5000 0.0000 3.4954 3.4954 0.0000 51.7766 0.3981 0.6447 1.0640 1.7498 1.1612 0.0000 22.8977 2 61.0000 -5.0000 29.0000 -6.9907 29.8307 103.5532 +19 1 50.0000 2.5000 0.0000 3.5514 3.5514 0.0000 272.2362 0.4206 0.6521 1.0251 1.9455 1.2055 -0.8219 31.9030 2 56.0000 -27.0000 -3.0000 -38.3556 38.4728 184.4723 +20 1 50.0000 2.5000 0.0000 3.5244 3.5244 0.0000 11.9548 0.4098 1.1031 1.0400 1.9120 1.3353 0.0000 19.4535 2 58.0000 24.0000 15.0000 33.8342 37.0102 23.9095 +21 1 50.0000 2.5000 0.0000 3.7494 3.7494 0.0000 3.5056 0.4997 1.2616 1.0000 1.1923 1.0808 0.0000 1.0000 2 50.0000 3.1736 0.5854 4.7596 4.7954 7.0113 +22 1 50.0000 2.5000 0.0000 3.7493 3.7493 0.0000 0.0000 0.4997 1.3202 1.0000 1.1956 1.0861 0.0000 1.0000 2 50.0000 3.2972 0.0000 4.9450 4.9450 0.0000 +23 1 50.0000 2.5000 0.0000 3.7497 3.7497 0.0000 5.8190 0.4999 1.2197 1.0000 1.1486 1.0604 0.0000 1.0000 2 50.0000 1.8634 0.5757 2.7949 2.8536 11.6380 +24 1 50.0000 2.5000 0.0000 3.7493 3.7493 0.0000 1.9603 0.4997 1.2883 1.0000 1.1946 1.0836 0.0000 1.0000 2 50.0000 3.2592 0.3350 4.8879 4.8994 3.9206 +25 1 60.2574 -34.0099 36.2677 -34.0678 49.7590 133.2085 132.0835 0.0017 1.3010 1.1427 3.2946 1.9951 0.0000 1.2644 2 60.4626 -34.1751 39.4387 -34.2333 52.2238 130.9584 +26 1 63.0109 -31.0961 -5.8663 -32.6194 33.1427 190.1951 188.8221 0.0490 0.9402 1.1831 2.4549 1.4560 0.0000 1.2630 2 62.8187 -29.7946 -4.0864 -31.2542 31.5202 187.4490 +27 1 61.2901 3.7196 -5.3901 5.5668 7.7487 315.9240 310.0313 0.4966 0.6952 1.1586 1.3092 1.0717 -0.0032 1.8731 2 61.4292 2.2480 -4.9620 3.3644 5.9950 304.1385 +28 1 35.0831 -44.1164 3.7933 -44.3939 44.5557 175.1161 176.4290 0.0063 1.0168 1.2148 2.9105 1.6476 0.0000 1.8645 2 35.0232 -40.0716 1.5901 -40.3237 40.3550 177.7418 +29 1 22.7233 20.0904 -46.6940 20.1424 50.8532 293.3339 291.3809 0.0026 0.3636 1.4014 3.1597 1.2617 -1.2537 2.0373 2 23.0331 14.9730 -42.5619 15.0118 45.1317 289.4279 +30 1 36.4612 47.8580 18.3852 47.9197 51.3256 20.9901 21.8781 0.0013 0.9239 1.1943 3.3888 1.7357 0.0000 1.4146 2 36.2715 50.5065 21.2231 50.5716 54.8444 22.7660 +31 1 90.8027 -2.0831 1.4410 -3.1245 3.4408 155.2410 167.1011 0.4999 1.1546 1.6110 1.1329 1.0511 0.0000 1.4441 2 91.1528 -1.6435 0.0447 -2.4651 2.4655 178.9612 +32 1 90.9257 -0.5406 -0.9208 -0.8109 1.2270 228.6315 218.4363 0.5000 1.3916 1.5930 1.0620 1.0288 0.0000 1.5381 2 88.6381 -0.8985 -0.7239 -1.3477 1.5298 208.2412 +33 1 6.7747 -0.2908 -2.4247 -0.4362 2.4636 259.8025 263.0049 0.4999 0.9556 1.6517 1.1057 1.0337 -0.0004 0.6377 2 5.8714 -0.0985 -2.2286 -0.1477 2.2335 266.2073 +34 1 2.0776 0.0795 -1.1350 0.1192 1.1412 275.9978 268.0910 0.5000 0.7826 1.7246 1.0383 1.0100 0.0000 0.9082 2 0.9033 -0.0636 -0.5514 -0.0954 0.5596 260.18421 diff --git a/skimage/color/tests/test_ciede2000.py b/skimage/color/tests/test_ciede2000.py new file mode 100644 index 00000000..5c00fb24 --- /dev/null +++ b/skimage/color/tests/test_ciede2000.py @@ -0,0 +1,61 @@ +"""Test for correctness of color distance functions + +Authors +------- +Matt Terry + +:license: modified BSD +""" +from os.path import abspath, dirname, join as pjoin + +import numpy as np +from numpy.testing import assert_array_almost_equal + +from skimage.color import deltaE_ciede2000 + + +def test_ciede2000_dE(): + dtype = [('pair', int), + ('1', int), + ('L1', float), + ('a1', float), + ('b1', float), + ('a1_prime', float), + ('C1_prime', float), + ('h1_prime', float), + ('hbar_prime', float), + ('G', float), + ('T', float), + ('SL', float), + ('SC', float), + ('SH', float), + ('RT', float), + ('dE', float), + ('2', int), + ('L2', float), + ('a2', float), + ('b2', float), + ('a2_prime', float), + ('C2_prime', float), + ('h2_prime', float), + ] + + # note: ciede_test_data.txt contains several intermediate quantities + path = pjoin(dirname(abspath(__file__)), 'ciede_test_data.txt') + data = np.loadtxt(path, dtype=dtype) + + N = len(data) + + lab1 = np.zeros((N, 3)) + lab1[:, 0] = data['L1'] + lab1[:, 1] = data['a1'] + lab1[:, 2] = data['b1'] + + lab2 = np.zeros((N, 3)) + lab2[:, 0] = data['L2'] + lab2[:, 1] = data['a2'] + lab2[:, 2] = data['b2'] + + dE2 = deltaE_ciede2000(lab1, lab2) + + assert_array_almost_equal(dE2, data['dE']) From c50c926eae7a563301933ad58efded50a47d4565 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Mon, 22 Jul 2013 08:56:57 -0700 Subject: [PATCH 02/36] docs --- skimage/color/delta_e.py | 112 +++++++++++++++++++++++++++++++-------- 1 file changed, 90 insertions(+), 22 deletions(-) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index 91072aff..52b10e80 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -7,6 +7,11 @@ percieve different colors. Humans are more sensitive to certain colors than others, which different deltaE metrics correct for this 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). + :author: Matt Terry :license: modified BSD @@ -39,25 +44,68 @@ def _arctan2pi(b, a): def deltaE_cie76(lab1, lab2): - """ - "just noticible difference" ~ 2.3 + """Euclidian distance between two points in in Lab color space + + Parameters + ---------- + lab1 : array_like + reference color (Lab colorspace) + lab2 : array_like + comparision color (Lab colorspace) + + Returns + ------- + dE : array_like + distance between colors `lab1` and `lab2` """ l1, a1, b1 = _unpack_last(lab1) l2, a2, b2 = _unpack_last(lab2) return np.sqrt((l2-l1)**2 + (a2-a1)**2 + (b2-b1)**2) -def deltaE_ciede94(lab1, lab2, kC=1, kH=1, kL=1, k1=0.045, k2=0.015): - """ - kC, kH are weighting factors, usually unity (default) +def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): + """Color difference according to CIEDE 94 standard - kL, k1, k2 depend on the application. Sample values are: - kL, k1, k2 = 1, 0.045, 0.015 (graphic arts, default) - kL, k1, k2 = 2, 0.048, 0.014 (textiles) + Accomodates perceptual non-uniformites through the use of application + specific scale factors (kH, kC, kL, k1, and k2). - Note: deltaE_ciede94 the defines the scales for the lightness, hue, and - chroma in terms of the first color. Consequently - deltaE_ciede94(lab1, lab2) != deltaE_ciede94(lab2, lab1) + Parameters + ---------- + lab1 : array_like + reference color (Lab colorspace) + lab2 : array_like + comparision 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 """ l1, a1, b1 = _unpack_last(lab1) l2, a2, b2 = _unpack_last(lab2) @@ -79,13 +127,17 @@ def deltaE_ciede94(lab1, lab2, kC=1, kH=1, kL=1, k1=0.045, k2=0.015): 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 calibaration is + largely based on experience with automotive paint on smooth surfaces. + Parameters ---------- lab1 : array_like - pass + reference color (Lab colorspace) lab2 : array_like - pass + comparision color (Lab colorspace) kL : float (range), optional pass kC : float (range), optional @@ -101,13 +153,12 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): Notes ----- CIEDE 2000 assumes parametric weighting factors for the luminance, chroma, - and hue (kL, kC, kH respectively). - kL = 1 # graphic arts - kL = 2 # textiles + and hue (kL, kC, kH respectively). These default to 1. References ---------- - http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf + http://en.wikipedia.org/wiki/Color_difference + http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf (doi:10.1364/AO.33.008069) """ L1, a1, b1 = _unpack_last(lab1) L2, a2, b2 = _unpack_last(lab2) @@ -184,11 +235,28 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): def deltaE_cmc(lab1, lab2): - """ - indistinguishable if < 1 - usual value for "different" is > 2 + """Color difference from the CMC l:c standard. - Note: deltaE_cmc the defines the scales for the lightness, hue, and chroma + This color difference developed by the Colour Measurement Committee of the + Socieity of Dyes and Colourists of Great Britian (CMC). It is intended for + use in the textile industry. Color differences less than 1.0 are + officially indistiguisable, but 2.0 is the usual threshold. + + Parameters + ---------- + lab1 : array_like + reference color (Lab colorspace) + lab2 : array_like + comparision 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) """ From a821ad518ced72210e3dd7c315e8b96488ae8719 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Mon, 22 Jul 2013 09:35:25 -0700 Subject: [PATCH 03/36] better cmc implementation --- skimage/color/delta_e.py | 51 +++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 19 deletions(-) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index 52b10e80..4534ef07 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -234,13 +234,17 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): return np.sqrt(dE2) -def deltaE_cmc(lab1, lab2): +def deltaE_cmc(lab1, lab2, kL=1, kC=1): """Color difference from the CMC l:c standard. This color difference developed by the Colour Measurement Committee of the Socieity of Dyes and Colourists of Great Britian (CMC). It is intended for - use in the textile industry. Color differences less than 1.0 are - officially indistiguisable, but 2.0 is the usual threshold. + 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 "imperceptability". Colors with + dE > 1 are "different" for the given scale factors. Parameters ---------- @@ -260,24 +264,33 @@ def deltaE_cmc(lab1, lab2): in terms of the first color. Consequently deltaE_cmc(lab1, lab2) != deltaE_cmc(lab2, lab1) """ - l1, c1, h1 = _unpack_last(lab2lch(lab1)) - l2, c2, h2 = _unpack_last(lab2lch(lab2)) + l1, a1, b1 = _unpack_last(lab1) + l2, a2, b2 = _unpack_last(lab2) - sl = np.where(l1 < 16, 0.511, 0.040975*l1 / (1 + 0.01765*l1)) - sc = 0.638 + 0.0638*c1 / (1 + 0.0131*c1) + c1 = np.sqrt(a1**2 + b1**2) + c2 = np.sqrt(a2**2 + b2**2) + dC = c1 - c2 - c1_4 = c1**4 - f = np.sqrt(c1_4 / (c1_4 + 1900)) - t = np.where(np.logical_and(h1 >= 2.862, h1 <= 6.021), - 0.56 * 0.2 * np.abs(np.cos(h1 + 2.93)), - 0.36 + 0.4 * np.abs(np.cos(h1 + 0.611)) + da = a1 - a2 + db = b1 - b2 + dH = np.sqrt(da**2 + db**2 - dC**2) + + dL = l1 - l2 + + h1 = _arctan2pi(b1, a1) + T = np.where(np.logical_and(h1 >= 164*DEG, h1 <= 345*DEG), + 0.56 + 0.2 * np.abs(np.cos(h1 + 168*DEG)), + 0.36 + 0.4 * np.abs(np.cos(h1 + 35*DEG)) ) - sh = sc * (f*t + 1-f) + c1_4 = c1**4 + F = np.sqrt(c1_4 / (c1_4 + 1900)) - l, c = 1, 1 - ans = ((l2 - l1)/(l * sl))**2 - ans += ((c2 - c1)/(c * sc))**2 - deg = np.pi/180. - ans += ((h2 - h1)*deg/sh)**2 # metric defines h in terms of degrees + 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) - return np.sqrt(ans) + dE2 = (dL / (kL*SL))**2 + dE2 += (dC/(kC*SC))**2 + dE2 += (dH/SH)**2 + + return np.sqrt(dE2) From f9ca27a979323862c032c61df4c6a3b9af8168fa Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Mon, 22 Jul 2013 09:36:33 -0700 Subject: [PATCH 04/36] don't need lab2lch --- skimage/color/delta_e.py | 1 - 1 file changed, 1 deletion(-) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index 4534ef07..7c5399f6 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -23,7 +23,6 @@ Reference from __future__ import division import numpy as np -from .colorconv import lab2lch DEG = np.pi/180 From 28f58a2c504e908815f41fde0a952408553e646a Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Mon, 22 Jul 2013 15:31:27 -0700 Subject: [PATCH 05/36] add tests. cmc stops naning --- skimage/color/__init__.py | 3 + skimage/color/delta_e.py | 27 ++-- skimage/color/tests/ciede2000_test_data.txt | 1 + skimage/color/tests/test_ciede2000.py | 61 -------- skimage/color/tests/test_dE.py | 145 ++++++++++++++++++++ 5 files changed, 161 insertions(+), 76 deletions(-) delete mode 100644 skimage/color/tests/test_ciede2000.py create mode 100644 skimage/color/tests/test_dE.py diff --git a/skimage/color/__init__.py b/skimage/color/__init__.py index 2d072808..95ccb0c0 100644 --- a/skimage/color/__init__.py +++ b/skimage/color/__init__.py @@ -96,5 +96,8 @@ __all__ = ['convert_colorspace', 'is_gray', 'color_dict', 'label2rgb', + 'deltaE_cie76', + 'deltaE_ciede94', 'deltaE_ciede2000', + 'deltaE_cmc', ] diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index 7c5399f6..47bf6e1a 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -112,10 +112,8 @@ def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): dl = l1 - l2 c1 = np.sqrt(a1**2 + b1**2) c2 = np.sqrt(a2**2 + b2**2) - da = a1 - a2 - db = b1 - b2 dc = c1 - c2 - dh_ab = np.sqrt(da**2 + db**2 + dc**2) + dh_ab = np.sqrt(deltaE_cie76(lab1, lab2)**2 - dl**2 - dc**2) SL = 1 SC = 1 + k1*c1 @@ -186,11 +184,11 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): cc = c1_prime * c2_prime mask1 = cc == 0. - mask2 = (-mask1) * (dh_prime > np.pi) - mask3 = (-mask1) * (dh_prime < -np.pi) - dh_prime[mask1] = 0. - dh_prime[mask2] += 2*np.pi - dh_prime[mask3] -= 2*np.pi + mask2 = np.logical_and(-mask1, dh_prime > np.pi) + mask3 = np.logical_and(-mask1, dh_prime < -np.pi) + dh_prime = np.where(mask1, 0., dh_prime) + dh_prime += np.where(mask2, 2*np.pi, 0) + dh_prime -= np.where(mask3, 2*np.pi, 0) dH_prime = 2 * np.sqrt(cc) * np.sin(dh_prime/2) @@ -198,9 +196,10 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): mask0 = np.logical_and(np.abs(h1_prime - h2_prime) > np.pi, cc != 0.) mask1 = np.logical_and(mask0, Hbar_prime < 2*np.pi) mask2 = np.logical_and(mask0, Hbar_prime >= 2*np.pi) - Hbar_prime[mask1] += 2*np.pi - Hbar_prime[mask2] -= 2*np.pi - Hbar_prime[cc == 0.] *= 2 + + Hbar_prime += np.where(mask1, 2*np.pi, 0) + Hbar_prime -= np.where(mask2, 2*np.pi, 0) + Hbar_prime *= np.where(cc == 0., 2, 1) Hbar_prime *= 0.5 deg = np.pi/180. @@ -269,10 +268,8 @@ def deltaE_cmc(lab1, lab2, kL=1, kC=1): c1 = np.sqrt(a1**2 + b1**2) c2 = np.sqrt(a2**2 + b2**2) dC = c1 - c2 - - da = a1 - a2 - db = b1 - b2 - dH = np.sqrt(da**2 + db**2 - dC**2) + dl = l1 - l2 + dH = np.sqrt(deltaE_cie76(lab1, lab2)**2 - dl**2 - dC**2) dL = l1 - l2 diff --git a/skimage/color/tests/ciede2000_test_data.txt b/skimage/color/tests/ciede2000_test_data.txt index 28503ce5..b7e3fd57 100644 --- a/skimage/color/tests/ciede2000_test_data.txt +++ b/skimage/color/tests/ciede2000_test_data.txt @@ -1,5 +1,6 @@ # input, intermediate, and output values for CIEDE2000 dE function # data taken from "The CIEDE2000 Color-Difference Formula: Implementation Notes, ..." http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf +# tab delimited data # pair 1 L1 a1 b1 ap1 cp1 hp1 hbar1 G T SL SC SH RT dE 2 L2 a2 b2 ap2 cp2 hp2 1 1 50.0000 2.6772 -79.7751 2.6774 79.8200 271.9222 270.9611 0.0001 0.6907 1.0000 4.6578 1.8421 -1.7042 2.0425 2 50.0000 0.0000 -82.7485 0.0000 82.7485 270.0000 2 1 50.0000 3.1571 -77.2803 3.1573 77.3448 272.3395 271.1698 0.0001 0.6843 1.0000 4.6021 1.8216 -1.7070 2.8615 2 50.0000 0.0000 -82.7485 0.0000 82.7485 270.0000 diff --git a/skimage/color/tests/test_ciede2000.py b/skimage/color/tests/test_ciede2000.py deleted file mode 100644 index 5c00fb24..00000000 --- a/skimage/color/tests/test_ciede2000.py +++ /dev/null @@ -1,61 +0,0 @@ -"""Test for correctness of color distance functions - -Authors -------- -Matt Terry - -:license: modified BSD -""" -from os.path import abspath, dirname, join as pjoin - -import numpy as np -from numpy.testing import assert_array_almost_equal - -from skimage.color import deltaE_ciede2000 - - -def test_ciede2000_dE(): - dtype = [('pair', int), - ('1', int), - ('L1', float), - ('a1', float), - ('b1', float), - ('a1_prime', float), - ('C1_prime', float), - ('h1_prime', float), - ('hbar_prime', float), - ('G', float), - ('T', float), - ('SL', float), - ('SC', float), - ('SH', float), - ('RT', float), - ('dE', float), - ('2', int), - ('L2', float), - ('a2', float), - ('b2', float), - ('a2_prime', float), - ('C2_prime', float), - ('h2_prime', float), - ] - - # note: ciede_test_data.txt contains several intermediate quantities - path = pjoin(dirname(abspath(__file__)), 'ciede_test_data.txt') - data = np.loadtxt(path, dtype=dtype) - - N = len(data) - - lab1 = np.zeros((N, 3)) - lab1[:, 0] = data['L1'] - lab1[:, 1] = data['a1'] - lab1[:, 2] = data['b1'] - - lab2 = np.zeros((N, 3)) - lab2[:, 0] = data['L2'] - lab2[:, 1] = data['a2'] - lab2[:, 2] = data['b2'] - - dE2 = deltaE_ciede2000(lab1, lab2) - - assert_array_almost_equal(dE2, data['dE']) diff --git a/skimage/color/tests/test_dE.py b/skimage/color/tests/test_dE.py new file mode 100644 index 00000000..04ad933b --- /dev/null +++ b/skimage/color/tests/test_dE.py @@ -0,0 +1,145 @@ +"""Test for correctness of color distance functions + +Authors +------- +Matt Terry + +:license: modified BSD +""" +from os.path import abspath, dirname, join as pjoin + +import numpy as np +from numpy.testing import assert_array_almost_equal + +from skimage.color import (deltaE_cie76, + deltaE_ciede94, + deltaE_ciede2000, + deltaE_cmc) + + +def test_ciede2000_dE(): + data = load_ciede2000_data() + N = len(data) + lab1 = np.zeros((N, 3)) + lab1[:, 0] = data['L1'] + lab1[:, 1] = data['a1'] + lab1[:, 2] = data['b1'] + + lab2 = np.zeros((N, 3)) + lab2[:, 0] = data['L2'] + lab2[:, 1] = data['a2'] + lab2[:, 2] = data['b2'] + + dE2 = deltaE_ciede2000(lab1, lab2) + + assert_array_almost_equal(dE2, data['dE']) + + +def load_ciede2000_data(): + dtype = [('pair', int), + ('1', int), + ('L1', float), + ('a1', float), + ('b1', float), + ('a1_prime', float), + ('C1_prime', float), + ('h1_prime', float), + ('hbar_prime', float), + ('G', float), + ('T', float), + ('SL', float), + ('SC', float), + ('SH', float), + ('RT', float), + ('dE', float), + ('2', int), + ('L2', float), + ('a2', float), + ('b2', float), + ('a2_prime', float), + ('C2_prime', float), + ('h2_prime', float), + ] + + # note: ciede_test_data.txt contains several intermediate quantities + path = pjoin(dirname(abspath(__file__)), 'ciede_test_data.txt') + return np.loadtxt(path, dtype=dtype) + + +def test_cie76(): + data = load_ciede2000_data() + N = len(data) + lab1 = np.zeros((N, 3)) + lab1[:, 0] = data['L1'] + lab1[:, 1] = data['a1'] + lab1[:, 2] = data['b1'] + + lab2 = np.zeros((N, 3)) + lab2[:, 0] = data['L2'] + lab2[:, 1] = data['a2'] + lab2[:, 2] = data['b2'] + + dE2 = deltaE_cie76(lab1, lab2) + oracle = np.array([ + 4.00106328, 6.31415011, 9.1776999, 2.06270077, 2.36957073, + 2.91529271, 2.23606798, 2.23606798, 4.98000036, 4.9800004, + 4.98000044, 4.98000049, 4.98000036, 4.9800004, 4.98000044, + 3.53553391, 36.86800781, 31.91002977, 30.25309901, 27.40894015, + 0.89242934, 0.7972, 0.8583065, 0.82982507, 3.1819238, + 2.21334297, 1.53890382, 4.60630929, 6.58467989, 3.88641412, + 1.50514845, 2.3237848, 0.94413208, 1.31910843 + ]) + assert_array_almost_equal(dE2, oracle) + + +def test_ciede94(): + data = load_ciede2000_data() + N = len(data) + lab1 = np.zeros((N, 3)) + lab1[:, 0] = data['L1'] + lab1[:, 1] = data['a1'] + lab1[:, 2] = data['b1'] + + lab2 = np.zeros((N, 3)) + lab2[:, 0] = data['L2'] + lab2[:, 1] = data['a2'] + lab2[:, 2] = data['b2'] + + dE2 = deltaE_ciede94(lab1, lab2) + oracle = np.array([ + 1.39503887, 1.93410055, 2.45433566, 0.68449187, 0.6695627, + 0.69194527, 2.23606798, 2.03163832, 4.80069441, 4.80069445, + 4.80069449, 4.80069453, 4.80069441, 4.80069445, 4.80069449, + 3.40774352, 34.6891632, 29.44137328, 27.91408781, 24.93766082, + 0.82213163, 0.71658427, 0.8048753, 0.75284394, 1.39099471, + 1.24808929, 1.29795787, 1.82045088, 2.55613309, 1.42491303, + 1.41945261, 2.3225685, 0.93853308, 1.30654464 + ]) + assert_array_almost_equal(dE2, oracle) + + +def test_cmc(): + data = load_ciede2000_data() + N = len(data) + lab1 = np.zeros((N, 3)) + lab1[:, 0] = data['L1'] + lab1[:, 1] = data['a1'] + lab1[:, 2] = data['b1'] + + lab2 = np.zeros((N, 3)) + lab2[:, 0] = data['L2'] + lab2[:, 1] = data['a2'] + lab2[:, 2] = data['b2'] + + dE2 = deltaE_cmc(lab1, lab2) + oracle = np.array([ + 1.73873611, 2.49660844, 3.30494501, 0.85735576, 0.88332927, + 0.97822692, 3.50480874, 2.87930032, 6.5783807, 6.57838075, + 6.5783808, 6.57838086, 6.67492321, 6.67492326, 6.67492331, + 4.66852997, 42.10875485, 39.45889064, 38.36005919, 33.93663807, + 1.14400168, 1.00600419, 1.11302547, 1.05335328, 1.42822951, + 1.2548143, 1.76838061, 2.02583367, 3.08695508, 1.74893533, + 1.90095165, 1.70258148, 1.80317207, 2.44934417 + ]) + + assert_array_almost_equal(dE2, oracle) From 65d655e3ecdd20636bc093ec83268e1178f4c95c Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Mon, 22 Jul 2013 15:46:20 -0700 Subject: [PATCH 06/36] cleaner lch implementations --- skimage/color/colorconv.py | 31 +++++++++++-------------------- 1 file changed, 11 insertions(+), 20 deletions(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 36fbeb1a..b1e70571 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -1032,8 +1032,8 @@ def combine_stains(stains, conv_matrix): def lab2lch(lab): - """CIE-LAB to LCH color space conversion. - TODO: something about cylindrical representation + """CIE-LAB to CIE-LCH color space conversion. + LCH is the cylindrical representation of the LAB (cartesian) colorspace Parameters ---------- @@ -1065,25 +1065,19 @@ def lab2lch(lab): >>> lena_lab = rgb2lab(lena) >>> lena_lch = lab2lch(lena_lab) """ - shape = lab.shape - if len(shape) != 3 or shape[2] != 3: - raise ValueError("Input image expected to be LAB") + lch = _prepare_colorarray(lab).copy() - lab = _prepare_colorarray(lab) - lch = np.empty_like(lab) + a, b = lch[..., 1], lch[..., 2] + lch[..., 1], lch[..., 2] = np.sqrt(a**2 + b**2), np.arctan2(b, a) - a, b = lab[:, :, 1], lab[:, :, 2] - lch[:, :, 0] = lab[:, :, 0] - lch[:, :, 1] = np.sqrt(a**2 + b**2) # C - - H = lch[:, :, 2] = np.arctan2(b, a) + H = lch[..., 2] H[H < 0] += 2*np.pi # (-pi, pi) -> (0, 2*pi) return lch def lch2lab(lch): """CIE-LCH to CIE-LAB color space conversion. - TODO: something about cylindrical representation + LCH is the cylindrical representation of the LAB (cartesian) colorspace Parameters ---------- @@ -1112,11 +1106,8 @@ def lch2lab(lch): >>> lena_lch = lab2lch(lena_lab) >>> lena_lab2 = lch2lab(lena_lch) """ - lch = _prepare_colorarray(lch) - lab = np.empty_like(lch) + lch = _prepare_colorarray(lch).copy() - c, h = lch[:, :, 1], lch[:, :, 2] - lab[:, :, 0] = lch[:, :, 0] - lab[:, :, 1] = c*np.cos(h) - lab[:, :, 2] = c*np.sin(h) - return lab + c, h = lch[..., 1], lch[..., 2] + lch[..., 1], lch[..., 2] = c*np.cos(h), c*np.sin(h) + return lch From 4fd18d43003839f798037c1921497cc9a0c2984f Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Mon, 22 Jul 2013 15:56:12 -0700 Subject: [PATCH 07/36] fix path --- skimage/color/tests/test_dE.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/color/tests/test_dE.py b/skimage/color/tests/test_dE.py index 04ad933b..52e12724 100644 --- a/skimage/color/tests/test_dE.py +++ b/skimage/color/tests/test_dE.py @@ -62,7 +62,7 @@ def load_ciede2000_data(): ] # note: ciede_test_data.txt contains several intermediate quantities - path = pjoin(dirname(abspath(__file__)), 'ciede_test_data.txt') + path = pjoin(dirname(abspath(__file__)), 'ciede2000_test_data.txt') return np.loadtxt(path, dtype=dtype) From b2b101cc184a7c055fde1d04dc79bfd540c0a8cd Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Tue, 23 Jul 2013 09:13:38 -0700 Subject: [PATCH 08/36] set testing tolerance --- skimage/color/tests/test_dE.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/skimage/color/tests/test_dE.py b/skimage/color/tests/test_dE.py index 52e12724..230e6e83 100644 --- a/skimage/color/tests/test_dE.py +++ b/skimage/color/tests/test_dE.py @@ -9,7 +9,7 @@ Matt Terry from os.path import abspath, dirname, join as pjoin import numpy as np -from numpy.testing import assert_array_almost_equal +from numpy.testing import assert_allclose from skimage.color import (deltaE_cie76, deltaE_ciede94, @@ -32,7 +32,7 @@ def test_ciede2000_dE(): dE2 = deltaE_ciede2000(lab1, lab2) - assert_array_almost_equal(dE2, data['dE']) + assert_allclose(dE2, data['dE'], rtol=1.e-4) def load_ciede2000_data(): @@ -89,7 +89,7 @@ def test_cie76(): 2.21334297, 1.53890382, 4.60630929, 6.58467989, 3.88641412, 1.50514845, 2.3237848, 0.94413208, 1.31910843 ]) - assert_array_almost_equal(dE2, oracle) + assert_allclose(dE2, oracle, rtol=1.e-8) def test_ciede94(): @@ -115,7 +115,7 @@ def test_ciede94(): 1.24808929, 1.29795787, 1.82045088, 2.55613309, 1.42491303, 1.41945261, 2.3225685, 0.93853308, 1.30654464 ]) - assert_array_almost_equal(dE2, oracle) + assert_allclose(dE2, oracle, rtol=1.e-8) def test_cmc(): @@ -142,4 +142,4 @@ def test_cmc(): 1.90095165, 1.70258148, 1.80317207, 2.44934417 ]) - assert_array_almost_equal(dE2, oracle) + assert_allclose(dE2, oracle, rtol=1.e-8) From 01d8cb2f4d099d6cd1484c765f213a99fd000a2d Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Tue, 23 Jul 2013 09:16:18 -0700 Subject: [PATCH 09/36] make naming consistent --- skimage/color/tests/{test_dE.py => test_delta_e.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename skimage/color/tests/{test_dE.py => test_delta_e.py} (100%) diff --git a/skimage/color/tests/test_dE.py b/skimage/color/tests/test_delta_e.py similarity index 100% rename from skimage/color/tests/test_dE.py rename to skimage/color/tests/test_delta_e.py From 1dc46c827ff15133b7dc4c70183d856ee63e7c09 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Tue, 23 Jul 2013 09:21:48 -0700 Subject: [PATCH 10/36] add references --- skimage/color/delta_e.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index 47bf6e1a..3703254e 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -56,6 +56,10 @@ def deltaE_cie76(lab1, lab2): ------- dE : array_like distance between colors `lab1` and `lab2` + + References + ---------- + http://en.wikipedia.org/wiki/Color_difference """ l1, a1, b1 = _unpack_last(lab1) l2, a2, b2 = _unpack_last(lab2) @@ -105,6 +109,11 @@ def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): kL 1.000 2.000 k1 0.045 0.048 k2 0.015 0.014 + + References + ---------- + http://en.wikipedia.org/wiki/Color_difference + http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html """ l1, a1, b1 = _unpack_last(lab1) l2, a2, b2 = _unpack_last(lab2) @@ -261,6 +270,11 @@ def deltaE_cmc(lab1, lab2, kL=1, kC=1): 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 + ---------- + http://en.wikipedia.org/wiki/Color_difference + http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html """ l1, a1, b1 = _unpack_last(lab1) l2, a2, b2 = _unpack_last(lab2) From 4b3c11d23e2b799d06b71dac29080bd12c5c44f1 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Tue, 23 Jul 2013 09:32:57 -0700 Subject: [PATCH 11/36] references --- skimage/color/delta_e.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index 3703254e..9f8cd2e8 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -60,6 +60,7 @@ def deltaE_cie76(lab1, lab2): References ---------- http://en.wikipedia.org/wiki/Color_difference + A. R. Robertson, "The CIE 1976 color-difference formulae," Color Res. Appl. 2, 7-11 (1977). """ l1, a1, b1 = _unpack_last(lab1) l2, a2, b2 = _unpack_last(lab2) @@ -165,6 +166,7 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): ---------- http://en.wikipedia.org/wiki/Color_difference http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf (doi:10.1364/AO.33.008069) + 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). """ L1, a1, b1 = _unpack_last(lab1) L2, a2, b2 = _unpack_last(lab2) @@ -275,6 +277,7 @@ def deltaE_cmc(lab1, lab2, kL=1, kC=1): ---------- http://en.wikipedia.org/wiki/Color_difference http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html + 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, a1, b1 = _unpack_last(lab1) l2, a2, b2 = _unpack_last(lab2) From f67a088cce1621f0171179d298930a4dcc7af875 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Tue, 23 Jul 2013 09:33:55 -0700 Subject: [PATCH 12/36] references --- skimage/color/delta_e.py | 1 + 1 file changed, 1 insertion(+) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index 9f8cd2e8..70bbb99b 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -18,6 +18,7 @@ The delta-E notation comes from the German word for "Sensation" (Empfindung). Reference --------- +http://en.wikipedia.org/wiki/Color_difference """ from __future__ import division From 86f7c4a1d5aff9aa385539b3911936c0bc3796db Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Tue, 23 Jul 2013 21:32:37 -0700 Subject: [PATCH 13/36] expose lab/lch conversion --- skimage/color/__init__.py | 4 ++++ skimage/color/tests/test_colorconv.py | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/skimage/color/__init__.py b/skimage/color/__init__.py index 95ccb0c0..1c61020d 100644 --- a/skimage/color/__init__.py +++ b/skimage/color/__init__.py @@ -15,6 +15,8 @@ from .colorconv import (convert_colorspace, rgb2lab, rgb2hed, hed2rgb, + lab2lch, + lch2lab, separate_stains, combine_stains, rgb_from_hed, @@ -68,6 +70,8 @@ __all__ = ['convert_colorspace', 'rgb2lab', 'rgb2hed', 'hed2rgb', + 'lab2lch', + 'lch2lab', 'separate_stains', 'combine_stains', 'rgb_from_hed', diff --git a/skimage/color/tests/test_colorconv.py b/skimage/color/tests/test_colorconv.py index 4fdaa4c8..a184f807 100644 --- a/skimage/color/tests/test_colorconv.py +++ b/skimage/color/tests/test_colorconv.py @@ -34,6 +34,7 @@ from skimage.color import (rgb2hsv, hsv2rgb, xyz2lab, lab2xyz, lab2rgb, rgb2lab, is_rgb, is_gray, + lab2lch, lch2lab, guess_spatial_dimensions ) @@ -249,6 +250,13 @@ class TestColorconv(TestCase): img_rgb = img_as_float(self.img_rgb) assert_array_almost_equal(lab2rgb(rgb2lab(img_rgb)), img_rgb) + def test_lab_lch_roundtrip(self): + rgb = img_as_float(self.img_rgb) + lab = rgb2lab(rgb) + lab2 = lch2lab(lab2lch(lab)) + assert_array_almost_equal(lab2, lab) + + def test_gray2rgb(): x = np.array([0, 0.5, 1]) assert_raises(ValueError, gray2rgb, x) From 499141cd2b9131285e52d49f4d8683c4ddcf85e8 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Tue, 23 Jul 2013 21:32:49 -0700 Subject: [PATCH 14/36] docs --- skimage/color/colorconv.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index b1e70571..ae0c1262 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -27,7 +27,11 @@ Supported color spaces ``x == y == z == 1/3`` at the whitepoint, and all color matching functions are greater than zero everywhere. * LAB CIE : Lightness, a, b + Colorspace derived from XYZ CIE that is intented to be more + perceptually uniform * LCH CIE : Lightness, Chroma, Hue + Defined in terms of LAB CIE. C and H are the polar representation of + a and b. The polar angle C is defined to be on (0, 2*pi) :author: Nicolas Pinto (rgb2hsv) :author: Ralf Gommers (hsv2rgb) From 1cd918e5c06a03a5dce4dd94447cac24a248b025 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Wed, 24 Jul 2013 07:54:56 -0700 Subject: [PATCH 15/36] pep8 math operators --- skimage/color/colorconv.py | 6 +- skimage/color/delta_e.py | 114 ++++++++++++++++++------------------- 2 files changed, 60 insertions(+), 60 deletions(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index ae0c1262..726240bd 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -1072,10 +1072,10 @@ def lab2lch(lab): lch = _prepare_colorarray(lab).copy() a, b = lch[..., 1], lch[..., 2] - lch[..., 1], lch[..., 2] = np.sqrt(a**2 + b**2), np.arctan2(b, a) + lch[..., 1], lch[..., 2] = np.sqrt(a ** 2 + b ** 2), np.arctan2(b, a) H = lch[..., 2] - H[H < 0] += 2*np.pi # (-pi, pi) -> (0, 2*pi) + H[H < 0] += 2 * np.pi # (-pi, pi) -> (0, 2*pi) return lch @@ -1113,5 +1113,5 @@ def lch2lab(lch): lch = _prepare_colorarray(lch).copy() c, h = lch[..., 1], lch[..., 2] - lch[..., 1], lch[..., 2] = c*np.cos(h), c*np.sin(h) + lch[..., 1], lch[..., 2] = c * np.cos(h), c * np.sin(h) return lch diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index 70bbb99b..d5d5bb81 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -25,7 +25,7 @@ from __future__ import division import numpy as np -DEG = np.pi/180 +DEG = np.pi / 180 def _unpack_last(x): @@ -35,11 +35,9 @@ def _unpack_last(x): def _arctan2pi(b, a): - """np.arctan2 mapped to (0, 2*pi)""" + """np.arctan2 mapped to (0, 2 * pi)""" ans = np.arctan2(b, a) - ans += np.where(ans < 0, 2*np.pi, 0.) - assert ans.max() <= 2*np.pi - assert ans.min() >= 0. + ans += np.where(ans < 0, 2 * np.pi, 0.) return ans @@ -65,7 +63,7 @@ def deltaE_cie76(lab1, lab2): """ l1, a1, b1 = _unpack_last(lab1) l2, a2, b2 = _unpack_last(lab2) - return np.sqrt((l2-l1)**2 + (a2-a1)**2 + (b2-b1)**2) + 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): @@ -121,16 +119,19 @@ def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): l2, a2, b2 = _unpack_last(lab2) dl = l1 - l2 - c1 = np.sqrt(a1**2 + b1**2) - c2 = np.sqrt(a2**2 + b2**2) + c1 = np.sqrt(a1 ** 2 + b1 ** 2) + c2 = np.sqrt(a2 ** 2 + b2 ** 2) dc = c1 - c2 - dh_ab = np.sqrt(deltaE_cie76(lab1, lab2)**2 - dl**2 - dc**2) + dh_ab = np.sqrt(deltaE_cie76(lab1, lab2) ** 2 - dl ** 2 - dc ** 2) SL = 1 - SC = 1 + k1*c1 - SH = 1 + k2*c1 + SC = 1 + k1 * c1 + SH = 1 + k2 * c1 - ans = (dl/(kL*SL))**2 + (dc/(kC*SC))**2 + (dh_ab/(kH*SH))**2 + ans = ((dl / (kL * SL)) ** 2 + + (dc / (kC * SC)) ** 2 + + (dh_ab / (kH * SH)) ** 2 + ) return np.sqrt(ans) @@ -172,21 +173,21 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): L1, a1, b1 = _unpack_last(lab1) L2, a2, b2 = _unpack_last(lab2) - c1 = np.sqrt(a1**2 + b1**2) - c2 = np.sqrt(a2**2 + b2**2) - cbar = 0.5*(c1 + c2) - c7 = cbar**7 - G = 0.5 * (1 - np.sqrt(c7/(c7 + 25**7))) + c1 = np.sqrt(a1 ** 2 + b1 ** 2) + c2 = np.sqrt(a2 ** 2 + b2 ** 2) + cbar = 0.5 * (c1 + c2) + c7 = cbar ** 7 + G = 0.5 * (1 - np.sqrt(c7 / (c7 + 25 ** 7))) dL_prime = L2 - L1 - Lbar = 0.5*(L1 + L2) + Lbar = 0.5 * (L1 + L2) a1_prime = a1 * (1 + G) a2_prime = a2 * (1 + G) - c1_prime = np.sqrt(a1_prime**2 + b1**2) - c2_prime = np.sqrt(a2_prime**2 + b2**2) - cbar_prime = 0.5*(c1_prime + c2_prime) + c1_prime = np.sqrt(a1_prime ** 2 + b1 ** 2) + c2_prime = np.sqrt(a2_prime ** 2 + b2 ** 2) + cbar_prime = 0.5 * (c1_prime + c2_prime) dC_prime = c2_prime - c1_prime h1_prime = _arctan2pi(b1, a1_prime) @@ -199,47 +200,46 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): mask2 = np.logical_and(-mask1, dh_prime > np.pi) mask3 = np.logical_and(-mask1, dh_prime < -np.pi) dh_prime = np.where(mask1, 0., dh_prime) - dh_prime += np.where(mask2, 2*np.pi, 0) - dh_prime -= np.where(mask3, 2*np.pi, 0) + dh_prime += np.where(mask2, 2 * np.pi, 0) + dh_prime -= np.where(mask3, 2 * np.pi, 0) - dH_prime = 2 * np.sqrt(cc) * np.sin(dh_prime/2) + dH_prime = 2 * np.sqrt(cc) * np.sin(dh_prime / 2) Hbar_prime = h1_prime + h2_prime mask0 = np.logical_and(np.abs(h1_prime - h2_prime) > np.pi, cc != 0.) - mask1 = np.logical_and(mask0, Hbar_prime < 2*np.pi) - mask2 = np.logical_and(mask0, Hbar_prime >= 2*np.pi) + mask1 = np.logical_and(mask0, Hbar_prime < 2 * np.pi) + mask2 = np.logical_and(mask0, Hbar_prime >= 2 * np.pi) - Hbar_prime += np.where(mask1, 2*np.pi, 0) - Hbar_prime -= np.where(mask2, 2*np.pi, 0) + Hbar_prime += np.where(mask1, 2 * np.pi, 0) + Hbar_prime -= np.where(mask2, 2 * np.pi, 0) Hbar_prime *= np.where(cc == 0., 2, 1) Hbar_prime *= 0.5 - deg = np.pi/180. T = (1 - - 0.17 * np.cos(Hbar_prime - 30*deg) + - 0.24 * np.cos(2*Hbar_prime) + - 0.32 * np.cos(3*Hbar_prime + 6*deg) - - 0.20 * np.cos(4*Hbar_prime - 63*deg) + 0.17 * np.cos(Hbar_prime - 30 * DEG) + + 0.24 * np.cos(2 * Hbar_prime) + + 0.32 * np.cos(3 * Hbar_prime + 6 * DEG) - + 0.20 * np.cos(4 * Hbar_prime - 63 * DEG) ) - dTheta = 30*deg * np.exp(-((Hbar_prime/deg - 275)/25)**2) - c7 = cbar_prime**7 - Rc = 2 * np.sqrt(c7 / (c7 + 25**7)) + dTheta = 30 * DEG * np.exp(-((Hbar_prime / DEG - 275) / 25) ** 2) + c7 = cbar_prime ** 7 + Rc = 2 * np.sqrt(c7 / (c7 + 25 ** 7)) - term = (Lbar - 50)**2 - SL = 1 + 0.015*term/np.sqrt(20 + term) - SC = 1 + 0.045*cbar_prime - SH = 1 + 0.015*cbar_prime * T + term = (Lbar - 50) ** 2 + SL = 1 + 0.015 * term / np.sqrt(20 + term) + SC = 1 + 0.045 * cbar_prime + SH = 1 + 0.015 * cbar_prime * T - RT = -np.sin(2*dTheta) * Rc + RT = -np.sin(2 * dTheta) * Rc l_term = dL_prime / (kL * SL) c_term = dC_prime / (kC * SC) h_term = dH_prime / (kH * SH) r_term = RT * c_term * h_term - dE2 = l_term**2 - dE2 += c_term**2 - dE2 += h_term**2 + dE2 = l_term ** 2 + dE2 += c_term ** 2 + dE2 += h_term ** 2 dE2 += r_term return np.sqrt(dE2) @@ -283,28 +283,28 @@ def deltaE_cmc(lab1, lab2, kL=1, kC=1): l1, a1, b1 = _unpack_last(lab1) l2, a2, b2 = _unpack_last(lab2) - c1 = np.sqrt(a1**2 + b1**2) - c2 = np.sqrt(a2**2 + b2**2) + c1 = np.sqrt(a1 ** 2 + b1 ** 2) + c2 = np.sqrt(a2 ** 2 + b2 ** 2) dC = c1 - c2 dl = l1 - l2 - dH = np.sqrt(deltaE_cie76(lab1, lab2)**2 - dl**2 - dC**2) + dH = np.sqrt(deltaE_cie76(lab1, lab2) ** 2 - dl ** 2 - dC ** 2) dL = l1 - l2 h1 = _arctan2pi(b1, a1) - T = np.where(np.logical_and(h1 >= 164*DEG, h1 <= 345*DEG), - 0.56 + 0.2 * np.abs(np.cos(h1 + 168*DEG)), - 0.36 + 0.4 * np.abs(np.cos(h1 + 35*DEG)) + T = np.where(np.logical_and(h1 >= 164 * DEG, h1 <= 345 * DEG), + 0.56 + 0.2 * np.abs(np.cos(h1 + 168 * DEG)), + 0.36 + 0.4 * np.abs(np.cos(h1 + 35 * DEG)) ) - c1_4 = c1**4 + 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) + 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 += (dH/SH)**2 + dE2 = (dL / (kL * SL)) ** 2 + dE2 += (dC / (kC * SC)) ** 2 + dE2 += (dH / SH) ** 2 return np.sqrt(dE2) From b566ba496b20dc7b60de19f9faa524aff560413f Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Wed, 24 Jul 2013 08:07:14 -0700 Subject: [PATCH 16/36] use np.rollaxis --- skimage/color/delta_e.py | 22 ++++++++-------------- 1 file changed, 8 insertions(+), 14 deletions(-) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index d5d5bb81..1728464f 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -28,12 +28,6 @@ import numpy as np DEG = np.pi / 180 -def _unpack_last(x): - x = np.asarray(x) - shape = x.shape - return [x[..., i] for i in range(shape[-1])] - - def _arctan2pi(b, a): """np.arctan2 mapped to (0, 2 * pi)""" ans = np.arctan2(b, a) @@ -61,8 +55,8 @@ def deltaE_cie76(lab1, lab2): http://en.wikipedia.org/wiki/Color_difference A. R. Robertson, "The CIE 1976 color-difference formulae," Color Res. Appl. 2, 7-11 (1977). """ - l1, a1, b1 = _unpack_last(lab1) - l2, a2, b2 = _unpack_last(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) @@ -115,8 +109,8 @@ def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): http://en.wikipedia.org/wiki/Color_difference http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html """ - l1, a1, b1 = _unpack_last(lab1) - l2, a2, b2 = _unpack_last(lab2) + l1, a1, b1 = np.rollaxis(lab1, -1)[:3] + l2, a2, b2 = np.rollaxis(lab2, -1)[:3] dl = l1 - l2 c1 = np.sqrt(a1 ** 2 + b1 ** 2) @@ -170,8 +164,8 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf (doi:10.1364/AO.33.008069) 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). """ - L1, a1, b1 = _unpack_last(lab1) - L2, a2, b2 = _unpack_last(lab2) + L1, a1, b1 = np.rollaxis(lab1, -1)[:3] + L2, a2, b2 = np.rollaxis(lab2, -1)[:3] c1 = np.sqrt(a1 ** 2 + b1 ** 2) c2 = np.sqrt(a2 ** 2 + b2 ** 2) @@ -280,8 +274,8 @@ def deltaE_cmc(lab1, lab2, kL=1, kC=1): http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html 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, a1, b1 = _unpack_last(lab1) - l2, a2, b2 = _unpack_last(lab2) + l1, a1, b1 = np.rollaxis(lab1, -1)[:3] + l2, a2, b2 = np.rollaxis(lab2, -1)[:3] c1 = np.sqrt(a1 ** 2 + b1 ** 2) c2 = np.sqrt(a2 ** 2 + b2 ** 2) From 3326239962c1e3923efe391757b47416dfa9dae8 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Wed, 24 Jul 2013 08:20:21 -0700 Subject: [PATCH 17/36] reference formatting --- skimage/color/delta_e.py | 26 ++++++++++++++++---------- 1 file changed, 16 insertions(+), 10 deletions(-) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index 1728464f..f6ff87a5 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -52,8 +52,9 @@ def deltaE_cie76(lab1, lab2): References ---------- - http://en.wikipedia.org/wiki/Color_difference - A. R. Robertson, "The CIE 1976 color-difference formulae," Color Res. Appl. 2, 7-11 (1977). + .. [1] http://en.wikipedia.org/wiki/Color_difference + .. [2] A. R. Robertson, "The CIE 1976 color-difference formulae," + Color Res. Appl. 2, 7-11 (1977). """ l1, a1, b1 = np.rollaxis(lab1, -1)[:3] l2, a2, b2 = np.rollaxis(lab2, -1)[:3] @@ -106,8 +107,8 @@ def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): References ---------- - http://en.wikipedia.org/wiki/Color_difference - http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html + .. [1] http://en.wikipedia.org/wiki/Color_difference + .. [2] http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html """ l1, a1, b1 = np.rollaxis(lab1, -1)[:3] l2, a2, b2 = np.rollaxis(lab2, -1)[:3] @@ -160,9 +161,12 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): References ---------- - http://en.wikipedia.org/wiki/Color_difference - http://www.ece.rochester.edu/~gsharma/ciede2000/ciede2000noteCRNA.pdf (doi:10.1364/AO.33.008069) - 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). + .. [1] http://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). """ L1, a1, b1 = np.rollaxis(lab1, -1)[:3] L2, a2, b2 = np.rollaxis(lab2, -1)[:3] @@ -270,9 +274,11 @@ def deltaE_cmc(lab1, lab2, kL=1, kC=1): References ---------- - http://en.wikipedia.org/wiki/Color_difference - http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html - F. J. J. Clarke, R. McDonald, and B. Rigg, "Modification to the JPC79 colour-difference formula," J. Soc. Dyers Colour. 100, 128-132 (1984). + .. [1] http://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, a1, b1 = np.rollaxis(lab1, -1)[:3] l2, a2, b2 = np.rollaxis(lab2, -1)[:3] From e848ba1e6159565539fa910b4c86445fe63872ba Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Thu, 25 Jul 2013 15:17:11 -0700 Subject: [PATCH 18/36] copy editing docs --- skimage/color/colorconv.py | 2 ++ skimage/color/delta_e.py | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 726240bd..f4051c99 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -1037,6 +1037,7 @@ def combine_stains(stains, conv_matrix): def lab2lch(lab): """CIE-LAB to CIE-LCH color space conversion. + LCH is the cylindrical representation of the LAB (cartesian) colorspace Parameters @@ -1081,6 +1082,7 @@ def lab2lch(lab): def lch2lab(lch): """CIE-LCH to CIE-LAB color space conversion. + LCH is the cylindrical representation of the LAB (cartesian) colorspace Parameters diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index f6ff87a5..8029116b 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -36,7 +36,7 @@ def _arctan2pi(b, a): def deltaE_cie76(lab1, lab2): - """Euclidian distance between two points in in Lab color space + """Euclidian distance between two points in Lab color space Parameters ---------- From 547fa2fc6cde995fb680a77900c28e7efee6e7b8 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Thu, 25 Jul 2013 15:17:30 -0700 Subject: [PATCH 19/36] test for rgb2lch roundtrip --- skimage/color/tests/test_colorconv.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/skimage/color/tests/test_colorconv.py b/skimage/color/tests/test_colorconv.py index a184f807..f2e99095 100644 --- a/skimage/color/tests/test_colorconv.py +++ b/skimage/color/tests/test_colorconv.py @@ -256,6 +256,14 @@ class TestColorconv(TestCase): lab2 = lch2lab(lab2lch(lab)) assert_array_almost_equal(lab2, lab) + def test_rgb_lch_roundtrip(self): + rgb = img_as_float(self.img_rgb) + lab = rgb2lab(rgb) + lch = lab2lch(lab) + lab2 = lch2lab(lch) + rgb2 = lab2rgb(lab2) + assert_array_almost_equal(rgb, rgb2) + def test_gray2rgb(): x = np.array([0, 0.5, 1]) From 9db3ab19279f9068d7619a06c587d595fc15a06d Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Thu, 25 Jul 2013 15:18:47 -0700 Subject: [PATCH 20/36] deg2rad > DEG --- skimage/color/delta_e.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index 8029116b..cf77479e 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -25,8 +25,6 @@ from __future__ import division import numpy as np -DEG = np.pi / 180 - def _arctan2pi(b, a): """np.arctan2 mapped to (0, 2 * pi)""" @@ -114,8 +112,8 @@ def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): l2, a2, b2 = np.rollaxis(lab2, -1)[:3] dl = l1 - l2 - c1 = np.sqrt(a1 ** 2 + b1 ** 2) - c2 = np.sqrt(a2 ** 2 + b2 ** 2) + c1 = np.hypot(a1, b1) + c2 = np.hypot(a2, b2) dc = c1 - c2 dh_ab = np.sqrt(deltaE_cie76(lab1, lab2) ** 2 - dl ** 2 - dc ** 2) @@ -171,8 +169,8 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): L1, a1, b1 = np.rollaxis(lab1, -1)[:3] L2, a2, b2 = np.rollaxis(lab2, -1)[:3] - c1 = np.sqrt(a1 ** 2 + b1 ** 2) - c2 = np.sqrt(a2 ** 2 + b2 ** 2) + c1 = np.hypot(a1, b1) + c2 = np.hypot(a2, b2) cbar = 0.5 * (c1 + c2) c7 = cbar ** 7 G = 0.5 * (1 - np.sqrt(c7 / (c7 + 25 ** 7))) @@ -183,8 +181,8 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): a1_prime = a1 * (1 + G) a2_prime = a2 * (1 + G) - c1_prime = np.sqrt(a1_prime ** 2 + b1 ** 2) - c2_prime = np.sqrt(a2_prime ** 2 + b2 ** 2) + c1_prime = np.hypot(a1_prime, b1) + c2_prime = np.hypot(a2_prime, b2) cbar_prime = 0.5 * (c1_prime + c2_prime) dC_prime = c2_prime - c1_prime @@ -214,12 +212,14 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): Hbar_prime *= 0.5 T = (1 - - 0.17 * np.cos(Hbar_prime - 30 * DEG) + + 0.17 * np.cos(Hbar_prime - np.deg2rad(30)) + 0.24 * np.cos(2 * Hbar_prime) + - 0.32 * np.cos(3 * Hbar_prime + 6 * DEG) - - 0.20 * np.cos(4 * Hbar_prime - 63 * DEG) + 0.32 * np.cos(3 * Hbar_prime + np.deg2rad(6)) - + 0.20 * np.cos(4 * Hbar_prime - np.deg2rad(63)) ) - dTheta = 30 * DEG * np.exp(-((Hbar_prime / DEG - 275) / 25) ** 2) + dTheta = (np.deg2rad(30) * + np.exp(-((np.rad2deg(Hbar_prime) - 275) / 25) ** 2) + ) c7 = cbar_prime ** 7 Rc = 2 * np.sqrt(c7 / (c7 + 25 ** 7)) @@ -283,8 +283,8 @@ def deltaE_cmc(lab1, lab2, kL=1, kC=1): l1, a1, b1 = np.rollaxis(lab1, -1)[:3] l2, a2, b2 = np.rollaxis(lab2, -1)[:3] - c1 = np.sqrt(a1 ** 2 + b1 ** 2) - c2 = np.sqrt(a2 ** 2 + b2 ** 2) + c1 = np.hypot(a1, b1) + c2 = np.hypot(a2, b2) dC = c1 - c2 dl = l1 - l2 dH = np.sqrt(deltaE_cie76(lab1, lab2) ** 2 - dl ** 2 - dC ** 2) @@ -292,9 +292,9 @@ def deltaE_cmc(lab1, lab2, kL=1, kC=1): dL = l1 - l2 h1 = _arctan2pi(b1, a1) - T = np.where(np.logical_and(h1 >= 164 * DEG, h1 <= 345 * DEG), - 0.56 + 0.2 * np.abs(np.cos(h1 + 168 * DEG)), - 0.36 + 0.4 * np.abs(np.cos(h1 + 35 * DEG)) + 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)) From 42a2671a02c4bf18128ab430b4a302171ce39692 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Thu, 25 Jul 2013 15:22:43 -0700 Subject: [PATCH 21/36] skimage attribution convention --- CONTRIBUTORS.txt | 3 +++ skimage/color/delta_e.py | 4 ---- skimage/color/tests/test_delta_e.py | 9 +-------- 3 files changed, 4 insertions(+), 12 deletions(-) diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt index ac11457c..b304646b 100644 --- a/CONTRIBUTORS.txt +++ b/CONTRIBUTORS.txt @@ -145,3 +145,6 @@ - Jostein Bø Fløystad Reconstruction circle mode for Radon transform Simultaneous Algebraic Reconstruction Technique for inverse Radon transform + +- Matt Terry + Color difference functions diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index cf77479e..50f6e16c 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -12,10 +12,6 @@ differentiation, but more recent studies (Mahy 1994) peg JND at 2.3 The delta-E notation comes from the German word for "Sensation" (Empfindung). -:author: Matt Terry - -:license: modified BSD - Reference --------- http://en.wikipedia.org/wiki/Color_difference diff --git a/skimage/color/tests/test_delta_e.py b/skimage/color/tests/test_delta_e.py index 230e6e83..1bfcc4d0 100644 --- a/skimage/color/tests/test_delta_e.py +++ b/skimage/color/tests/test_delta_e.py @@ -1,11 +1,4 @@ -"""Test for correctness of color distance functions - -Authors -------- -Matt Terry - -:license: modified BSD -""" +"""Test for correctness of color distance functions""" from os.path import abspath, dirname, join as pjoin import numpy as np From c36d1cf248f5729445e458450b157cdfb501ac2e Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Thu, 25 Jul 2013 15:28:29 -0700 Subject: [PATCH 22/36] run_module_suite --- skimage/color/tests/test_delta_e.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/skimage/color/tests/test_delta_e.py b/skimage/color/tests/test_delta_e.py index 1bfcc4d0..4535a311 100644 --- a/skimage/color/tests/test_delta_e.py +++ b/skimage/color/tests/test_delta_e.py @@ -136,3 +136,8 @@ def test_cmc(): ]) assert_allclose(dE2, oracle, rtol=1.e-8) + + +if __name__ == "__main__": + from numpy.testing import run_module_suite + run_module_suite() From a0f8905b8c3e6a5305eccf0b96b257c396cdad10 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Thu, 25 Jul 2013 15:32:52 -0700 Subject: [PATCH 23/36] docs for deltaE_ciede2000 length scales --- skimage/color/delta_e.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index 50f6e16c..ee303e16 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -137,11 +137,12 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): lab2 : array_like comparision color (Lab colorspace) kL : float (range), optional - pass + luminance scale factor, 1 for "acceptably close"; 2 for "impercievable" + see deltaE_cmc kC : float (range), optional - pass + chroma scale factor, usually 1 kH : float (range), optional - pass + hue scale factor, usually 1 Returns ------- From 364ebd7418c035f355873a077d15d36ec08d6ff4 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Tue, 30 Jul 2013 10:50:43 -0700 Subject: [PATCH 24/36] copy editing --- skimage/color/colorconv.py | 6 +++--- skimage/color/delta_e.py | 30 +++++++++++++++--------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index f4051c99..a09e8bdc 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -27,7 +27,7 @@ Supported color spaces ``x == y == z == 1/3`` at the whitepoint, and all color matching functions are greater than zero everywhere. * LAB CIE : Lightness, a, b - Colorspace derived from XYZ CIE that is intented to be more + Colorspace derived from XYZ CIE that is intended to be more perceptually uniform * LCH CIE : Lightness, Chroma, Hue Defined in terms of LAB CIE. C and H are the polar representation of @@ -1038,7 +1038,7 @@ def combine_stains(stains, conv_matrix): def lab2lch(lab): """CIE-LAB to CIE-LCH color space conversion. - LCH is the cylindrical representation of the LAB (cartesian) colorspace + LCH is the cylindrical representation of the LAB (Cartesian) colorspace Parameters ---------- @@ -1083,7 +1083,7 @@ def lab2lch(lab): def lch2lab(lch): """CIE-LCH to CIE-LAB color space conversion. - LCH is the cylindrical representation of the LAB (cartesian) colorspace + LCH is the cylindrical representation of the LAB (Cartesian) colorspace Parameters ---------- diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index ee303e16..f7cc637c 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -1,10 +1,10 @@ """ Functions for calculating the "distance" between colors. -Implicit in these definitions of "distance" is the notion of "Just Noticible +Implicit in these definitions of "distance" is the notion of "Just Noticeable Distance" (JND). This represents the distance between colors where a human can -percieve different colors. Humans are more sensitive to certain colors than -others, which different deltaE metrics correct for this with varying degrees of +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 @@ -30,14 +30,14 @@ def _arctan2pi(b, a): def deltaE_cie76(lab1, lab2): - """Euclidian distance between two points in Lab color space + """Euclidean distance between two points in Lab color space Parameters ---------- lab1 : array_like reference color (Lab colorspace) lab2 : array_like - comparision color (Lab colorspace) + comparison color (Lab colorspace) Returns ------- @@ -58,7 +58,7 @@ def deltaE_cie76(lab1, lab2): def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): """Color difference according to CIEDE 94 standard - Accomodates perceptual non-uniformites through the use of application + Accommodates perceptual non-uniformities through the use of application specific scale factors (kH, kC, kL, k1, and k2). Parameters @@ -66,7 +66,7 @@ def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): lab1 : array_like reference color (Lab colorspace) lab2 : array_like - comparision color (Lab colorspace) + comparison color (Lab colorspace) kH : float, optional Hue scale kC : float, optional @@ -127,7 +127,7 @@ def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): 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 calibaration is + CIEDE 2000 is a major revision of CIDE94. The perceptual calibration is largely based on experience with automotive paint on smooth surfaces. Parameters @@ -135,9 +135,9 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): lab1 : array_like reference color (Lab colorspace) lab2 : array_like - comparision color (Lab colorspace) + comparison color (Lab colorspace) kL : float (range), optional - luminance scale factor, 1 for "acceptably close"; 2 for "impercievable" + luminance scale factor, 1 for "acceptably close"; 2 for "imperceivable" see deltaE_cmc kC : float (range), optional chroma scale factor, usually 1 @@ -242,13 +242,13 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): def deltaE_cmc(lab1, lab2, kL=1, kC=1): """Color difference from the CMC l:c standard. - This color difference developed by the Colour Measurement Committee of the - Socieity of Dyes and Colourists of Great Britian (CMC). It is intended for - use in the textile industry. + This color difference was developed by the Colour Measurement Committee + (CMC) of the Society of Dyers and Colourists (United Kingdom).0 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 "imperceptability". Colors with + for "acceptability" and kL=1, kC=1 for "imperceptibility". Colors with dE > 1 are "different" for the given scale factors. Parameters @@ -256,7 +256,7 @@ def deltaE_cmc(lab1, lab2, kL=1, kC=1): lab1 : array_like reference color (Lab colorspace) lab2 : array_like - comparision color (Lab colorspace) + comparison color (Lab colorspace) Returns ------- From 6585efb7634111911bebf55158e6f6f2b39b4c3e Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Tue, 30 Jul 2013 14:20:55 -0700 Subject: [PATCH 25/36] lab2lch/lch2lab for N-D images (w/tests) --- skimage/color/colorconv.py | 25 ++++++++++++++++++++----- skimage/color/tests/test_colorconv.py | 22 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 5 deletions(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index a09e8bdc..75f4eba2 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -1043,7 +1043,9 @@ def lab2lch(lab): Parameters ---------- lab : array_like - The image in CIE-LAB format, in a 3-D array of shape (.., .., 3). + The N-D image in CIE-LAB format. The last (`N+1`th) dimension must have + at least 3 elements, corresponding to the `L`, `a`, and `b` color + channels. Subsequent elements are copied. Returns ------- @@ -1070,13 +1072,13 @@ def lab2lch(lab): >>> lena_lab = rgb2lab(lena) >>> lena_lch = lab2lch(lena_lab) """ - lch = _prepare_colorarray(lab).copy() + lch = _prepare_lab_array(lab) a, b = lch[..., 1], lch[..., 2] lch[..., 1], lch[..., 2] = np.sqrt(a ** 2 + b ** 2), np.arctan2(b, a) H = lch[..., 2] - H[H < 0] += 2 * np.pi # (-pi, pi) -> (0, 2*pi) + H += np.where(H < 0, 2*np.pi, 0) # (-pi, pi) -> (0, 2*pi) return lch @@ -1088,7 +1090,9 @@ def lch2lab(lch): Parameters ---------- lab : array_like - The image in CIE-LCH format, in a 3-D array of shape (.., .., 3). + The N-D image in CIE-LCH format. The last (`N+1`th) dimension must have + at least 3 elements, corresponding to the `L`, `a`, and `b` color + channels. Subsequent elements are copied. Returns ------- @@ -1112,8 +1116,19 @@ def lch2lab(lch): >>> lena_lch = lab2lch(lena_lab) >>> lena_lab2 = lch2lab(lena_lch) """ - lch = _prepare_colorarray(lch).copy() + lch = _prepare_lab_array(lch) c, h = lch[..., 1], lch[..., 2] lch[..., 1], lch[..., 2] = c * np.cos(h), c * np.sin(h) return lch + + +def _prepare_lab_array(arr): + """Ensure input for lab2lch, lch2lab are well-posed. + + Arrays must be in floating point and have at least 3 elements in + last dimension. Return a new array. + """ + shape = arr.shape + assert shape[-1] >= 3 + return dtype.img_as_float(arr, force_copy=True) diff --git a/skimage/color/tests/test_colorconv.py b/skimage/color/tests/test_colorconv.py index f2e99095..fbec9ba6 100644 --- a/skimage/color/tests/test_colorconv.py +++ b/skimage/color/tests/test_colorconv.py @@ -264,6 +264,28 @@ class TestColorconv(TestCase): rgb2 = lab2rgb(lab2) assert_array_almost_equal(rgb, rgb2) + def test_lab_lch_0d(self): + lab0 = self._get_lab0() + lch0 = lab2lch(lab0) + lch2 = lab2lch(lab0[None, None, :]) + assert_array_almost_equal(lch0, lch2[0, 0, :]) + + def test_lab_lch_1d(self): + lab0 = self._get_lab0() + lch0 = lab2lch(lab0) + lch1 = lab2lch(lab0[None, :]) + assert_array_almost_equal(lch0, lch1[0, :]) + + def test_lab_lch_3d(self): + lab0 = self._get_lab0() + lch0 = lab2lch(lab0) + lch3 = lab2lch(lab0[None, None, None, :]) + assert_array_almost_equal(lch0, lch3[0, 0, 0, :]) + + def _get_lab0(self): + rgb = img_as_float(self.img_rgb[:1, :1, :]) + return rgb2lab(rgb)[0, 0, :] + def test_gray2rgb(): x = np.array([0, 0.5, 1]) From a8403d26a18531e44d3a83a4bc1e4ba9d37e7274 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Wed, 31 Jul 2013 06:40:24 -0700 Subject: [PATCH 26/36] last round of copy-edits --- skimage/color/delta_e.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index f7cc637c..2c9d2b19 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -137,7 +137,7 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): lab2 : array_like comparison color (Lab colorspace) kL : float (range), optional - luminance scale factor, 1 for "acceptably close"; 2 for "imperceivable" + luminance scale factor, 1 for "acceptably close"; 2 for "imperceptible" see deltaE_cmc kC : float (range), optional chroma scale factor, usually 1 @@ -243,7 +243,7 @@ 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).0 It is + (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 From dc692dddcc644b5b66333d64ecf37b85cbfbed79 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Mon, 5 Aug 2013 12:18:32 -0700 Subject: [PATCH 27/36] code cleanup --- skimage/color/colorconv.py | 15 +++- skimage/color/delta_e.py | 170 +++++++++++++++++-------------------- 2 files changed, 89 insertions(+), 96 deletions(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index 75f4eba2..b4b9b4c6 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -1075,13 +1075,20 @@ def lab2lch(lab): lch = _prepare_lab_array(lab) a, b = lch[..., 1], lch[..., 2] - lch[..., 1], lch[..., 2] = np.sqrt(a ** 2 + b ** 2), np.arctan2(b, a) - - H = lch[..., 2] - H += np.where(H < 0, 2*np.pi, 0) # (-pi, pi) -> (0, 2*pi) + lch[..., 1], lch[..., 2] = _cart2polar_2pi(a, b) return lch +def _cart2polar_2pi(x, y): + """convert cartesian coordiantes to polar (uses non-standard theta range!) + + NON-STANDARD RANGE! Maps to (0, 2*pi) rather than usual (-pi, +pi) + """ + r, t = np.hypot(x, y), np.arctan2(y, x) + t += np.where(t < 0., 2 * np.pi, 0) + return r, t + + def lch2lab(lch): """CIE-LCH to CIE-LAB color space conversion. diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index 2c9d2b19..602e8b66 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -21,12 +21,7 @@ from __future__ import division import numpy as np - -def _arctan2pi(b, a): - """np.arctan2 mapped to (0, 2 * pi)""" - ans = np.arctan2(b, a) - ans += np.where(ans < 0, 2 * np.pi, 0.) - return ans +from skimage.color.colorconv import lab2lch, _cart2polar_2pi def deltaE_cie76(lab1, lab2): @@ -50,9 +45,9 @@ def deltaE_cie76(lab1, lab2): .. [2] A. R. Robertson, "The CIE 1976 color-difference formulae," Color Res. Appl. 2, 7-11 (1977). """ - 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) + 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): @@ -104,22 +99,20 @@ def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): .. [1] http://en.wikipedia.org/wiki/Color_difference .. [2] http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html """ - l1, a1, b1 = np.rollaxis(lab1, -1)[:3] - l2, a2, b2 = np.rollaxis(lab2, -1)[:3] + L1, C1, h1 = np.rollaxis(lab2lch(lab1), -1)[:3] + L2, C2, h2 = np.rollaxis(lab2lch(lab2), -1)[:3] - dl = l1 - l2 - c1 = np.hypot(a1, b1) - c2 = np.hypot(a2, b2) - dc = c1 - c2 - dh_ab = np.sqrt(deltaE_cie76(lab1, lab2) ** 2 - dl ** 2 - dc ** 2) + dL = L1 - L2 + dC = C1 - C2 + dH_ab = np.sqrt(deltaE_cie76(lab1, lab2) ** 2 - dL ** 2 - dC ** 2) SL = 1 - SC = 1 + k1 * c1 - SH = 1 + k2 * c1 + SC = 1 + k1 * C1 + SH = 1 + k2 * C1 - ans = ((dl / (kL * SL)) ** 2 + - (dc / (kC * SC)) ** 2 + - (dh_ab / (kH * SH)) ** 2 + ans = ((dL / (kL * SL)) ** 2 + + (dC / (kC * SC)) ** 2 + + (dH_ab / (kH * SH)) ** 2 ) return np.sqrt(ans) @@ -166,76 +159,74 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): L1, a1, b1 = np.rollaxis(lab1, -1)[:3] L2, a2, b2 = np.rollaxis(lab2, -1)[:3] - c1 = np.hypot(a1, b1) - c2 = np.hypot(a2, b2) - cbar = 0.5 * (c1 + c2) - c7 = cbar ** 7 + # 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 - dL_prime = L2 - L1 + # cide2000 has four terms to delta_e: + # 1) Luminance term + # 2) Hue term + # 3) Chroma term + # 4) hue Rotation term + + # luminance 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) - a1_prime = a1 * (1 + G) - a2_prime = a2 * (1 + G) + # chroma term + Cbar = 0.5 * (C1 + C2) # new coordiantes + SC = 1 + 0.045 * Cbar + C_term = (C2 - C1) / (kC * SC) - c1_prime = np.hypot(a1_prime, b1) - c2_prime = np.hypot(a2_prime, b2) - cbar_prime = 0.5 * (c1_prime + c2_prime) - dC_prime = c2_prime - c1_prime + # hue term + h_diff = h2 - h1 + h_sum = h1 + h2 + CC = C1 * C2 - h1_prime = _arctan2pi(b1, a1_prime) - h2_prime = _arctan2pi(b2, a2_prime) + 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) - dh_prime = h2_prime - h1_prime - - cc = c1_prime * c2_prime - mask1 = cc == 0. - mask2 = np.logical_and(-mask1, dh_prime > np.pi) - mask3 = np.logical_and(-mask1, dh_prime < -np.pi) - dh_prime = np.where(mask1, 0., dh_prime) - dh_prime += np.where(mask2, 2 * np.pi, 0) - dh_prime -= np.where(mask3, 2 * np.pi, 0) - - dH_prime = 2 * np.sqrt(cc) * np.sin(dh_prime / 2) - - Hbar_prime = h1_prime + h2_prime - mask0 = np.logical_and(np.abs(h1_prime - h2_prime) > np.pi, cc != 0.) - mask1 = np.logical_and(mask0, Hbar_prime < 2 * np.pi) - mask2 = np.logical_and(mask0, Hbar_prime >= 2 * np.pi) - - Hbar_prime += np.where(mask1, 2 * np.pi, 0) - Hbar_prime -= np.where(mask2, 2 * np.pi, 0) - Hbar_prime *= np.where(cc == 0., 2, 1) - Hbar_prime *= 0.5 + 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_prime - np.deg2rad(30)) + - 0.24 * np.cos(2 * Hbar_prime) + - 0.32 * np.cos(3 * Hbar_prime + np.deg2rad(6)) - - 0.20 * np.cos(4 * Hbar_prime - np.deg2rad(63)) + 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)) ) - dTheta = (np.deg2rad(30) * - np.exp(-((np.rad2deg(Hbar_prime) - 275) / 25) ** 2) - ) - c7 = cbar_prime ** 7 + 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 - term = (Lbar - 50) ** 2 - SL = 1 + 0.015 * term / np.sqrt(20 + term) - SC = 1 + 0.045 * cbar_prime - SH = 1 + 0.015 * cbar_prime * T - - RT = -np.sin(2 * dTheta) * Rc - - l_term = dL_prime / (kL * SL) - c_term = dC_prime / (kC * SC) - h_term = dH_prime / (kH * SH) - r_term = RT * c_term * h_term - - dE2 = l_term ** 2 - dE2 += c_term ** 2 - dE2 += h_term ** 2 - dE2 += r_term + # put it all together + dE2 = L_term ** 2 + dE2 += C_term ** 2 + dE2 += H_term ** 2 + dE2 += R_term return np.sqrt(dE2) @@ -277,27 +268,22 @@ def deltaE_cmc(lab1, lab2, kL=1, kC=1): JPC79 colour-difference formula," J. Soc. Dyers Colour. 100, 128-132 (1984). """ - l1, a1, b1 = np.rollaxis(lab1, -1)[:3] - l2, a2, b2 = np.rollaxis(lab2, -1)[:3] + L1, C1, h1 = np.rollaxis(lab2lch(lab1), -1)[:3] + L2, C2, h2 = np.rollaxis(lab2lch(lab2), -1)[:3] - c1 = np.hypot(a1, b1) - c2 = np.hypot(a2, b2) - dC = c1 - c2 - dl = l1 - l2 - dH = np.sqrt(deltaE_cie76(lab1, lab2) ** 2 - dl ** 2 - dC ** 2) + dC = C1 - C2 + dL = L1 - L2 + dH = np.sqrt(deltaE_cie76(lab1, lab2) ** 2 - dL ** 2 - dC ** 2) - dL = l1 - l2 - - h1 = _arctan2pi(b1, a1) 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 + 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) + 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 From efe51c92a9f95ffc6ba8ede300abca9036b93d1b Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Mon, 5 Aug 2013 12:43:42 -0700 Subject: [PATCH 28/36] tuples are valid inputs --- skimage/color/colorconv.py | 1 + skimage/color/delta_e.py | 18 +++++++++++++++++- skimage/color/tests/test_delta_e.py | 24 ++++++++++++++++++++++++ 3 files changed, 42 insertions(+), 1 deletion(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index b4b9b4c6..dde30269 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -1136,6 +1136,7 @@ def _prepare_lab_array(arr): Arrays must be in floating point and have at least 3 elements in last dimension. Return a new array. """ + arr = np.asarray(arr) shape = arr.shape assert shape[-1] >= 3 return dtype.img_as_float(arr, force_copy=True) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index 602e8b66..e3f29a34 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -45,6 +45,8 @@ def deltaE_cie76(lab1, lab2): .. [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) @@ -99,6 +101,8 @@ def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): .. [1] http://en.wikipedia.org/wiki/Color_difference .. [2] http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html """ + lab1 = np.asarray(lab1) + lab2 = np.asarray(lab2) L1, C1, h1 = np.rollaxis(lab2lch(lab1), -1)[:3] L2, C2, h2 = np.rollaxis(lab2lch(lab2), -1)[:3] @@ -156,6 +160,15 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): 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] @@ -227,7 +240,10 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): dE2 += C_term ** 2 dE2 += H_term ** 2 dE2 += R_term - return np.sqrt(dE2) + ans = np.sqrt(dE2) + if unroll: + ans = ans[0] + return ans def deltaE_cmc(lab1, lab2, kL=1, kC=1): diff --git a/skimage/color/tests/test_delta_e.py b/skimage/color/tests/test_delta_e.py index 4535a311..52799c9d 100644 --- a/skimage/color/tests/test_delta_e.py +++ b/skimage/color/tests/test_delta_e.py @@ -138,6 +138,30 @@ def test_cmc(): assert_allclose(dE2, oracle, rtol=1.e-8) +def test_single_color_cie76(): + lab1 = (0.5, 0.5, 0.5) + lab2 = (0.4, 0.4, 0.4) + deltaE_cie76(lab1, lab2) + + +def test_single_color_cidede94(): + lab1 = (0.5, 0.5, 0.5) + lab2 = (0.4, 0.4, 0.4) + deltaE_ciede94(lab1, lab2) + + +def test_single_color_ciede2000(): + lab1 = (0.5, 0.5, 0.5) + lab2 = (0.4, 0.4, 0.4) + deltaE_ciede2000(lab1, lab2) + + +def test_single_color_cmc(): + lab1 = (0.5, 0.5, 0.5) + lab2 = (0.4, 0.4, 0.4) + deltaE_cmc(lab1, lab2) + + if __name__ == "__main__": from numpy.testing import run_module_suite run_module_suite() From e59a75bb4a7a4bfd8228481b139986bec7fd8bfd Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Mon, 5 Aug 2013 14:20:20 -0700 Subject: [PATCH 29/36] cleanup --- skimage/color/delta_e.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index e3f29a34..fa8a5701 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -103,22 +103,21 @@ def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): """ lab1 = np.asarray(lab1) lab2 = np.asarray(lab2) - L1, C1, h1 = np.rollaxis(lab2lch(lab1), -1)[:3] - L2, C2, h2 = np.rollaxis(lab2lch(lab2), -1)[:3] + L1, C1 = np.rollaxis(lab2lch(lab1), -1)[:2] + L2, C2 = np.rollaxis(lab2lch(lab2), -1)[:2] dL = L1 - L2 dC = C1 - C2 - dH_ab = np.sqrt(deltaE_cie76(lab1, lab2) ** 2 - dL ** 2 - dC ** 2) + dH = np.sqrt(deltaE_cie76(lab1, lab2) ** 2 - dL ** 2 - dC ** 2) SL = 1 SC = 1 + k1 * C1 SH = 1 + k2 * C1 - ans = ((dL / (kL * SL)) ** 2 + - (dC / (kC * SC)) ** 2 + - (dH_ab / (kH * SH)) ** 2 - ) - return np.sqrt(ans) + dE2 = (dL / (kL * SL)) ** 2 + dE2 += (dC / (kC * SC)) ** 2 + dE2 += (dH / (kH * SH)) ** 2 + return np.sqrt(dE2) def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): @@ -305,5 +304,4 @@ def deltaE_cmc(lab1, lab2, kL=1, kC=1): dE2 = (dL / (kL * SL)) ** 2 dE2 += (dC / (kC * SC)) ** 2 dE2 += (dH / SH) ** 2 - return np.sqrt(dE2) From d50af3f3bcca1f845072eab12421692345cc8008 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Mon, 5 Aug 2013 18:02:28 -0700 Subject: [PATCH 30/36] unneeded asarray --- skimage/color/delta_e.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index fa8a5701..1a84bb60 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -101,8 +101,6 @@ def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): .. [1] http://en.wikipedia.org/wiki/Color_difference .. [2] http://www.brucelindbloom.com/index.html?Eqn_DeltaE_CIE94.html """ - lab1 = np.asarray(lab1) - lab2 = np.asarray(lab2) L1, C1 = np.rollaxis(lab2lch(lab1), -1)[:2] L2, C2 = np.rollaxis(lab2lch(lab2), -1)[:2] From a84388ccdba185db88f259cbfd0364f061105b12 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Tue, 6 Aug 2013 06:38:50 -0700 Subject: [PATCH 31/36] doc cleanup --- skimage/color/colorconv.py | 23 +++++++++-------------- skimage/color/delta_e.py | 31 ++++++++++++++++--------------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/skimage/color/colorconv.py b/skimage/color/colorconv.py index dde30269..5d41a063 100644 --- a/skimage/color/colorconv.py +++ b/skimage/color/colorconv.py @@ -1044,21 +1044,18 @@ def lab2lch(lab): ---------- lab : array_like The N-D image in CIE-LAB format. The last (`N+1`th) dimension must have - at least 3 elements, corresponding to the `L`, `a`, and `b` color + at least 3 elements, corresponding to the ``L``, ``a``, and ``b`` color channels. Subsequent elements are copied. Returns ------- out : ndarray - The image in LCH format, in a 3-D array of shape (.., .., 3). + The image in LCH format, in a N-D array with same shape as input `lab`. Raises ------ ValueError - If `rgb` is not a 3-D array of shape (.., .., 3). - - References - ---------- + If `lch` does not have at least 3 color channels (i.e. l, a, b). Notes ----- @@ -1096,23 +1093,20 @@ def lch2lab(lch): Parameters ---------- - lab : array_like + lch : array_like The N-D image in CIE-LCH format. The last (`N+1`th) dimension must have - at least 3 elements, corresponding to the `L`, `a`, and `b` color + at least 3 elements, corresponding to the ``L``, ``a``, and ``b`` color channels. Subsequent elements are copied. Returns ------- out : ndarray - The image in LAB format, in a 3-D array of shape (.., .., 3). + The image in LAB format, with same shape as input `lch`. Raises ------ ValueError - If `rgb` is not a 3-D array of shape (.., .., 3). - - References - ---------- + If `lch` does not have at least 3 color channels (i.e. l, c, h). Examples -------- @@ -1138,5 +1132,6 @@ def _prepare_lab_array(arr): """ arr = np.asarray(arr) shape = arr.shape - assert shape[-1] >= 3 + if shape[-1] < 3: + raise ValueError('Input array has less than 3 color channels') return dtype.img_as_float(arr, force_copy=True) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index 1a84bb60..5ad6b3be 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -56,7 +56,7 @@ 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). + specific scale factors (`kH`, `kC`, `kL`, `k1`, and `k2`). Parameters ---------- @@ -87,14 +87,14 @@ def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): 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 + `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 + `kL` 1.000 2.000 + `k1` 0.045 0.048 + `k2` 0.015 0.014 References ---------- @@ -131,7 +131,7 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): lab2 : array_like comparison color (Lab colorspace) kL : float (range), optional - luminance scale factor, 1 for "acceptably close"; 2 for "imperceptible" + lightness scale factor, 1 for "acceptably close"; 2 for "imperceptible" see deltaE_cmc kC : float (range), optional chroma scale factor, usually 1 @@ -145,8 +145,8 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): Notes ----- - CIEDE 2000 assumes parametric weighting factors for the luminance, chroma, - and hue (kL, kC, kH respectively). These default to 1. + CIEDE 2000 assumes parametric weighting factors for the lightness, chroma, + and hue (`kL`, `kC`, `kH` respectively). These default to 1. References ---------- @@ -187,7 +187,7 @@ def deltaE_ciede2000(lab1, lab2, kL=1, kC=1, kH=1): # 3) Chroma term # 4) hue Rotation term - # luminance term + # lightness term Lbar = 0.5 * (L1 + L2) tmp = (Lbar - 50) ** 2 SL = 1 + 0.015 * tmp / np.sqrt(20 + tmp) @@ -250,10 +250,11 @@ def deltaE_cmc(lab1, lab2, kL=1, kC=1): (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. + 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 ---------- @@ -271,7 +272,7 @@ def deltaE_cmc(lab1, lab2, kL=1, kC=1): ----- 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) + ``deltaE_cmc(lab1, lab2) != deltaE_cmc(lab2, lab1)`` References ---------- From 111a992140fa8b1104277a8c27c5c8cb5c1afa14 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Mon, 12 Aug 2013 09:57:13 -0700 Subject: [PATCH 32/36] fix table formatting --- skimage/color/delta_e.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index 5ad6b3be..bf9e316e 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -90,11 +90,13 @@ def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): `kL`, `k1`, `k2` depend on the application and default to the values suggested for graphic arts - Parameter Graphic Arts Textiles - ---------- ------------- -------- + ========== ============== ========== + Parameter Graphic Arts Textiles + ========== ============== ========== `kL` 1.000 2.000 `k1` 0.045 0.048 `k2` 0.015 0.014 + ========== ============== ========== References ---------- From e5fcece9dce1cd570446dddaf3b93f0985e958a0 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Thu, 15 Aug 2013 09:15:39 -0700 Subject: [PATCH 33/36] spell ciede correctly --- skimage/color/tests/test_delta_e.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/skimage/color/tests/test_delta_e.py b/skimage/color/tests/test_delta_e.py index 52799c9d..84f13b48 100644 --- a/skimage/color/tests/test_delta_e.py +++ b/skimage/color/tests/test_delta_e.py @@ -144,7 +144,7 @@ def test_single_color_cie76(): deltaE_cie76(lab1, lab2) -def test_single_color_cidede94(): +def test_single_color_ciede94(): lab1 = (0.5, 0.5, 0.5) lab2 = (0.4, 0.4, 0.4) deltaE_ciede94(lab1, lab2) From 0182f7c98a7420e90a7b7da62eedf76f2243c731 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Thu, 15 Aug 2013 11:18:28 -0700 Subject: [PATCH 34/36] better roundoff handing of dH --- skimage/color/delta_e.py | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index bf9e316e..43b4a7b2 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -108,7 +108,7 @@ def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): dL = L1 - L2 dC = C1 - C2 - dH = np.sqrt(deltaE_cie76(lab1, lab2) ** 2 - dL ** 2 - dC ** 2) + dH = get_dH(lab1, lab2) SL = 1 SC = 1 + k1 * C1 @@ -289,7 +289,7 @@ def deltaE_cmc(lab1, lab2, kL=1, kC=1): dC = C1 - C2 dL = L1 - L2 - dH = np.sqrt(deltaE_cie76(lab1, lab2) ** 2 - dL ** 2 - dC ** 2) + dH = get_dH(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))), @@ -306,3 +306,27 @@ def deltaE_cmc(lab1, lab2, kL=1, kC=1): dE2 += (dC / (kC * SC)) ** 2 dE2 += (dH / SH) ** 2 return np.sqrt(dE2) + + +def get_dH(lab1, lab2): + """numerically well behaved calculation of dH + + Given a1, b1, a2, b2 + 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) + """ + lab1 = np.asarray(lab1) + lab2 = np.asarray(lab2) + + r1 = np.rollaxis(lab1, -1)[1:3] + r2 = np.rollaxis(lab2, -1)[1:3] + + C1 = np.hypot(r1[0], r1[1]) + C2 = np.hypot(r2[0], r2[1]) + + term = C1 * C2 + term -= (r1 * r2).sum(0) + term *= 2 + return np.sqrt(term) From 9fe1e26583f6809a1951774b5b0e3000d5e650d8 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Sat, 17 Aug 2013 11:06:00 -0700 Subject: [PATCH 35/36] more consistency in naming, better docstring --- skimage/color/delta_e.py | 47 +++++++++++++++++++++++----------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index 43b4a7b2..a7f9b775 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -108,7 +108,7 @@ def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): dL = L1 - L2 dC = C1 - C2 - dH = get_dH(lab1, lab2) + dH2 = get_dH2(lab1, lab2) SL = 1 SC = 1 + k1 * C1 @@ -116,7 +116,7 @@ def deltaE_ciede94(lab1, lab2, kH=1, kC=1, kL=1, k1=0.045, k2=0.015): dE2 = (dL / (kL * SL)) ** 2 dE2 += (dC / (kC * SC)) ** 2 - dE2 += (dH / (kH * SH)) ** 2 + dE2 += dH2 / (kH * SH) ** 2 return np.sqrt(dE2) @@ -289,7 +289,7 @@ def deltaE_cmc(lab1, lab2, kL=1, kC=1): dC = C1 - C2 dL = L1 - L2 - dH = get_dH(lab1, lab2) + 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))), @@ -304,29 +304,36 @@ def deltaE_cmc(lab1, lab2, kL=1, kC=1): dE2 = (dL / (kL * SL)) ** 2 dE2 += (dC / (kC * SC)) ** 2 - dE2 += (dH / SH) ** 2 + dE2 += dH2 / (SH ** 2) return np.sqrt(dE2) -def get_dH(lab1, lab2): - """numerically well behaved calculation of dH +def get_dH2(lab1, lab2): + """squared hue difference term occurring in deltaE_cmc and deltaE_ciede94 - Given a1, b1, a2, b2 - 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) + Despite its name "dH" is not a simple difference of hue values. We avoid + working directly with the hue value directly 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, r is a vector with elements a and b. The same dH term can be + re-written as: + |r1-r2|**2 - (|r1| - |r2|)**2 + and then simplified to: + 2*|r1|*|r2| - 2*dot(r1, r2) """ lab1 = np.asarray(lab1) lab2 = np.asarray(lab2) + a1, b1 = np.rollaxis(lab1, -1)[1:3] + a2, b2 = np.rollaxis(lab2, -1)[1:3] - r1 = np.rollaxis(lab1, -1)[1:3] - r2 = np.rollaxis(lab2, -1)[1:3] + # magnitude of (a, b) is the chroma + C1 = np.hypot(a1, b1) + C2 = np.hypot(a2, b2) - C1 = np.hypot(r1[0], r1[1]) - C2 = np.hypot(r2[0], r2[1]) - - term = C1 * C2 - term -= (r1 * r2).sum(0) - term *= 2 - return np.sqrt(term) + term = (C1 * C2) - (a1 * a2 + b1 * b2) + return 2*term From d86b624d433a7eed289db7b88ad232f564a4a653 Mon Sep 17 00:00:00 2001 From: Matt Terry Date: Sat, 17 Aug 2013 11:11:23 -0700 Subject: [PATCH 36/36] r is confusing, use ab instead --- skimage/color/delta_e.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/skimage/color/delta_e.py b/skimage/color/delta_e.py index a7f9b775..18cfca98 100644 --- a/skimage/color/delta_e.py +++ b/skimage/color/delta_e.py @@ -320,11 +320,11 @@ def get_dH2(lab1, lab2): dH = sqrt(term) However, this has poor roundoff properties when a or b is dominant. - Instead, r is a vector with elements a and b. The same dH term can be + Instead, ab is a vector with elements a and b. The same dH term can be re-written as: - |r1-r2|**2 - (|r1| - |r2|)**2 + |ab1-ab2|**2 - (|ab1| - |ab2|)**2 and then simplified to: - 2*|r1|*|r2| - 2*dot(r1, r2) + 2*|ab1|*|ab2| - 2*dot(ab1, ab2) """ lab1 = np.asarray(lab1) lab2 = np.asarray(lab2)