From c0b52c1c841957b27e2dd82a11ecb3b4ff8db265 Mon Sep 17 00:00:00 2001 From: Paweł Redman Date: Tue, 23 Jun 2020 20:08:29 +0200 Subject: Update the tests again and remove some useless ones. --- gsoc_common.py | 60 +++++++++++++++------------- test_colorchecker.py | 27 +++++++++---- test_coverage.py | 104 ++++++++++++++++++++++++------------------------- test_diff.py | 59 ++++++++++++++-------------- test_divergence.py | 9 ----- test_error_function.py | 29 -------------- test_example.py | 28 ------------- test_interpolator.py | 29 +++++++------- 8 files changed, 148 insertions(+), 197 deletions(-) delete mode 100644 test_divergence.py delete mode 100644 test_error_function.py delete mode 100644 test_example.py diff --git a/gsoc_common.py b/gsoc_common.py index b73d81c..695edc6 100644 --- a/gsoc_common.py +++ b/gsoc_common.py @@ -3,47 +3,53 @@ from colour import * from colour.difference import delta_E_CIE1976 from colour.colorimetry import * from colour.plotting import * +from colour.models import RGB_COLOURSPACES from matplotlib import pyplot as plt + D65_xy = ILLUMINANTS["CIE 1931 2 Degree Standard Observer"]["D65"] D65 = SpectralDistribution(ILLUMINANT_SDS["D65"]) +colourspace = RGB_COLOURSPACES["ProPhoto RGB"] # The same wavelength grid is used throughout wvl = SpectralShape(360, 830, 5) wvlp = (wvl.range() - 360) / (830 - 360) + # This is the model of spectral reflectivity described in the article. def model(wvlp, ccp): - yy = ccp[0] * wvlp**2 + ccp[1] * wvlp + ccp[2] - return 1 / 2 + yy / (2 * np.sqrt(1 + yy ** 2)) + yy = ccp[0] * wvlp**2 + ccp[1] * wvlp + ccp[2] + return 1 / 2 + yy / (2 * np.sqrt(1 + yy ** 2)) + # Create a SpectralDistribution using given coefficients def model_sd(ccp, primed=True): - # FIXME: don't hardcode the wavelength grid; there should be a way - # of creating a SpectralDistribution from the function alone - grid = wvlp if primed else wvl.range() - return SpectralDistribution(model(grid, ccp), wvl.range(), name="Model") + # FIXME: don't hardcode the wavelength grid; there should be a way + # of creating a SpectralDistribution from the function alone + grid = wvlp if primed else wvl.range() + return SpectralDistribution(model(grid, ccp), wvl.range(), name="Model") + # Makes a comparison plot with SDs and swatches def plot_comparison(target, matched_sd, label, error, ill_sd, show=True): - if type(target) is SpectralDistribution: - target_XYZ = sd_to_XYZ(target, illuminant=ill_sd) / 100 - else: - target_XYZ = target - target_RGB = np.clip(XYZ_to_sRGB(target_XYZ), 0, 1) - target_swatch = ColourSwatch(label, target_RGB) - matched_XYZ = sd_to_XYZ(matched_sd, illuminant=ill_sd) / 100 - matched_RGB = np.clip(XYZ_to_sRGB(matched_XYZ), 0, 1) - matched_swatch = ColourSwatch("Model", matched_RGB) - - axes = plt.subplot(2, 1, 1) - plt.title(label) - if type(target) is SpectralDistribution: - plot_multi_sds([target, matched_sd], axes=axes, standalone=False) - else: - plot_single_sd(matched_sd, axes=axes, standalone=False) - - axes = plt.subplot(2, 1, 2) - plt.title("ΔE = %g" % error) - plot_multi_colour_swatches([target_swatch, matched_swatch], - standalone=show, axes=axes) + if type(target) is SpectralDistribution: + target_XYZ = sd_to_XYZ(target, illuminant=ill_sd) / 100 + else: + target_XYZ = target + target_RGB = np.clip(XYZ_to_sRGB(target_XYZ), 0, 1) + target_swatch = ColourSwatch(label, target_RGB) + matched_XYZ = sd_to_XYZ(matched_sd, illuminant=ill_sd) / 100 + matched_RGB = np.clip(XYZ_to_sRGB(matched_XYZ), 0, 1) + matched_swatch = ColourSwatch("Model", matched_RGB) + + axes = plt.subplot(2, 1, 1) + plt.title(label) + if type(target) is SpectralDistribution: + plot_multi_sds([target, matched_sd], axes=axes, standalone=False) + else: + plot_single_sd(matched_sd, axes=axes, standalone=False) + + axes = plt.subplot(2, 1, 2) + plt.title("ΔE = %g" % error) + plot_multi_colour_swatches([target_swatch, matched_swatch], + standalone=show, axes=axes) diff --git a/test_colorchecker.py b/test_colorchecker.py index 612d7fb..1503874 100644 --- a/test_colorchecker.py +++ b/test_colorchecker.py @@ -1,14 +1,25 @@ import numpy as np from colour import * -from colour.recovery import XYZ_to_sd_Jakob2019 -from gsoc_common import D65, plot_comparison +from colour.recovery import RGB_to_sd_Jakob2019 +from gsoc_common import * + # This demo goes through SDs in a color checker if __name__ == "__main__": - for name, sd in COLOURCHECKER_SDS['ColorChecker N Ohta'].items(): - XYZ = sd_to_XYZ(sd, illuminant=D65) / 100 + for name, sd in COLOURCHECKER_SDS['ColorChecker N Ohta'].items(): + XYZ = sd_to_XYZ(sd, illuminant=D65) / 100 + + RGB = XYZ_to_RGB( + XYZ, + D65_xy, + colourspace.whitepoint, + colourspace.XYZ_to_RGB_matrix, + ) + + recovered_sd, error = RGB_to_sd_Jakob2019( + RGB, + colourspace, + return_error=True + ) - print("Color checker: The target is '%s' with X=%g, Y=%g, Z=%g" - % (name, *XYZ)) - recovered_sd, error = XYZ_to_sd_Jakob2019(XYZ, return_error=True) - plot_comparison(sd, recovered_sd, name, error, D65) + plot_comparison(sd, recovered_sd, name, error, D65) diff --git a/test_coverage.py b/test_coverage.py index ee519f4..de2d8a2 100644 --- a/test_coverage.py +++ b/test_coverage.py @@ -7,69 +7,67 @@ from gsoc_common import model_sd, D65, D65_xy, plot_comparison # Solve for a specific RGB color def optimize_RGB(linear_RGB, coeffs_0): - RGB = eotf_inverse_sRGB(linear_RGB) - XYZ = sRGB_to_XYZ(RGB, D65_xy) - - coeffs, error = coefficients_Jakob2019( - XYZ, - dimensionalise=False, - coefficients_0=coeffs_0 - ) - - if error > 1e-6: - with open("out/bad.txt", "a") as fd: - fd.write("\n") - fd.write("linear_RGB=%s\n" % linear_RGB) - fd.write("XYZ=%s\n" % XYZ) - fd.write("coeffs_0=%s\n" % coeffs_0) - fd.write("coeffs=%s, error=%g\n" % (coeffs, error)) - - # A low budget graph to quickly see what's going on - log_error = np.log10(error) - bars = "|" * max(0, int(5 * (log_error + 9))) - - print("%12.5g %12.5g %12.5g %5.3f %s" % (*XYZ, log_error, bars)) - return coeffs + coeffs, error = coefficients_Jakob2019( + linear_RGB, + RGB_COLOURSPACES["sRGB"], + dimensionalise=False, + coefficients_0=coeffs_0 + ) + + if error > 1e-6: + with open("out/bad.txt", "a") as fd: + fd.write("\n") + fd.write("linear_RGB=%s\n" % linear_RGB) + fd.write("XYZ=%s\n" % XYZ) + fd.write("coeffs_0=%s\n" % coeffs_0) + fd.write("coeffs=%s, error=%g\n" % (coeffs, error)) + + # A low budget graph to quickly see what's going on + log_error = np.log10(error) + bars = "|" * max(0, int(5 * (log_error + 9))) + + print("%12.5g %12.5g %12.5g %5.3f %s" % (*linear_RGB, log_error, bars)) + return coeffs # Solve for all lightness values of a fully saturated RGB color def optimize_chromaticity(linear_RGB): - # alpha's aren't spaced equally (see the article) - def smoothstep(x): - return x**2 * (3 - 2 * x) + # alpha's aren't spaced equally (see the article) + def smoothstep(x): + return x**2 * (3 - 2 * x) - steps = np.arange(0, LIGHTNESS_STEPS) - alphas = smoothstep(smoothstep(steps / LIGHTNESS_STEPS)) + steps = np.arange(0, LIGHTNESS_STEPS) + alphas = smoothstep(smoothstep(steps / LIGHTNESS_STEPS)) - i_mid = LIGHTNESS_STEPS // 5 - coeffs_mid = optimize_RGB(linear_RGB * alphas[i_mid], (0, 0, 0)) + i_mid = LIGHTNESS_STEPS // 5 + coeffs_mid = optimize_RGB(linear_RGB * alphas[i_mid], (0, 0, 0)) - coeffs_0 = coeffs_mid - for i in range(i_mid + 1, LIGHTNESS_STEPS): - coeffs_0 = optimize_RGB(linear_RGB * alphas[i], coeffs_0) + coeffs_0 = coeffs_mid + for i in range(i_mid + 1, LIGHTNESS_STEPS): + coeffs_0 = optimize_RGB(linear_RGB * alphas[i], coeffs_0) - coeffs_0 = coeffs_mid - for i in reversed(range(0, i_mid)): - coeffs_0 = optimize_RGB(linear_RGB * alphas[i], coeffs_0) + coeffs_0 = coeffs_mid + for i in reversed(range(0, i_mid)): + coeffs_0 = optimize_RGB(linear_RGB * alphas[i], coeffs_0) # This program runs XYZ_to_sd_Jakob2019 converges on a large number of inputs, # covering an entire RGB gamut, to see if it'll diverge somewhere. if __name__ == "__main__": - CHROMA_STEPS = 8 - LIGHTNESS_STEPS = 64 - - with open("out/bad.txt", "w") as fd: - fd.write("Going through %dx%dx%d cubes\n" - % (LIGHTNESS_STEPS, CHROMA_STEPS, CHROMA_STEPS)) - - args = [] - for A in np.linspace(0, 1, CHROMA_STEPS): - for B in np.linspace(0, 1, CHROMA_STEPS): - for RGB in [np.array([1, A, B]), - np.array([A, 1, B]), - np.array([A, B, 1])]: - args.append(RGB) - - pool = multiprocessing.Pool() - pool.map(optimize_chromaticity, args) + CHROMA_STEPS = 8 + LIGHTNESS_STEPS = 64 + + with open("out/bad.txt", "w") as fd: + fd.write("Going through %dx%dx%d cubes\n" + % (LIGHTNESS_STEPS, CHROMA_STEPS, CHROMA_STEPS)) + + args = [] + for A in np.linspace(0, 1, CHROMA_STEPS): + for B in np.linspace(0, 1, CHROMA_STEPS): + for RGB in [np.array([1, A, B]), + np.array([A, 1, B]), + np.array([A, B, 1])]: + args.append(RGB) + + pool = multiprocessing.Pool() + pool.map(optimize_chromaticity, args) diff --git a/test_diff.py b/test_diff.py index 0ee5f58..4429894 100644 --- a/test_diff.py +++ b/test_diff.py @@ -5,46 +5,47 @@ from colour.recovery import error_function_Jakob2019 from matplotlib import pyplot as plt from gsoc_common import plot_comparison + # This test checks if derivatives are calculated correctly by comparing them # to finite differences. if __name__ == "__main__": - shape = SpectralShape(360, 830, 1) - cmfs = STANDARD_OBSERVER_CMFS["CIE 1931 2 Degree Standard Observer"].align(shape) + shape = SpectralShape(360, 830, 1) + cmfs = STANDARD_OBSERVER_CMFS["CIE 1931 2 Degree Standard Observer"].align(shape) - illuminant = SpectralDistribution(ILLUMINANT_SDS["D65"]).align(shape) - illuminant_XYZ = sd_to_XYZ(illuminant) / 100 + illuminant = SpectralDistribution(ILLUMINANT_SDS["D65"]).align(shape) + illuminant_XYZ = sd_to_XYZ(illuminant) / 100 - target = np.array([50, -20, 30]) # Some arbitrary Lab colour - xs = np.linspace(-10, 10, 500) - h = xs[1] - xs[0] + target = np.array([50, -20, 30]) # Some arbitrary Lab colour + xs = np.linspace(-10, 10, 500) + h = xs[1] - xs[0] - # Vary one coefficient at a time - for c_index in range(3): - errors = np.empty(len(xs)) - derrors = np.empty(len(xs)) + # Vary one coefficient at a time + for c_index in range(3): + errors = np.empty(len(xs)) + derrors = np.empty(len(xs)) - for i, x in enumerate(xs): - c = np.array([1.0, 1, 1]) - c[c_index] = x + for i, x in enumerate(xs): + c = np.array([1.0, 1, 1]) + c[c_index] = x - error, derror_dc = error_function_Jakob2019( - c, target, shape, cmfs, illuminant, illuminant_XYZ - ) + error, derror_dc = error_function_Jakob2019( + c, target, shape, cmfs, illuminant, illuminant_XYZ + ) - errors[i] = error - derrors[i] = derror_dc[c_index] + errors[i] = error + derrors[i] = derror_dc[c_index] - plt.subplot(2, 3, 1 + c_index) - plt.xlabel("c%d" % c_index) - plt.ylabel("ΔE") - plt.plot(xs, errors) + plt.subplot(2, 3, 1 + c_index) + plt.xlabel("c%d" % c_index) + plt.ylabel("ΔE") + plt.plot(xs, errors) - plt.subplot(2, 3, 4 + c_index) - plt.xlabel("c%d" % c_index) - plt.ylabel("dΔE/dc%d" % c_index) + plt.subplot(2, 3, 4 + c_index) + plt.xlabel("c%d" % c_index) + plt.ylabel("dΔE/dc%d" % c_index) - plt.plot(xs, derrors, "k-") - plt.plot(xs[:-1] + h / 2, np.diff(errors) / h, "r:") + plt.plot(xs, derrors, "k-") + plt.plot(xs[:-1] + h / 2, np.diff(errors) / h, "r:") - plt.show() + plt.show() diff --git a/test_divergence.py b/test_divergence.py deleted file mode 100644 index fc7956c..0000000 --- a/test_divergence.py +++ /dev/null @@ -1,9 +0,0 @@ -import numpy as np -from colour import * -from colour.recovery import XYZ_to_sd_Jakob2019 -from gsoc_common import D65, plot_comparison - -if __name__ == "__main__": - XYZ = np.array([0.1788, 0.3576, 0.0596]) - found_sd, error = XYZ_to_sd_Jakob2019(XYZ, return_error=True) - plot_comparison(XYZ, found_sd, "Target", error, D65) diff --git a/test_error_function.py b/test_error_function.py deleted file mode 100644 index 99e1268..0000000 --- a/test_error_function.py +++ /dev/null @@ -1,29 +0,0 @@ -import numpy as np -from colour import * -from colour.recovery import error_function_Jakob2019 -from gsoc_common import model_sd, D65_xy - -if __name__ == "__main__": - shape = SpectralShape(360, 830, 1) - cmfs = STANDARD_OBSERVER_CMFS["CIE 1931 2 Degree Standard Observer"].align(shape) - - illuminant = SpectralDistribution(ILLUMINANT_SDS["D65"]).align(shape) - illuminant_XYZ = sd_to_XYZ(illuminant) / 100 - - coefficients = np.array([8.70184886, -13.19804478, 2.12180137]) - target = np.array([20, 50, 30]) - - error, derror_dc, R, XYZ, Lab = error_function_Jakob2019( - coefficients, target, shape, cmfs, - illuminant, illuminant_XYZ, True - ) - - sd = model_sd(coefficients) - good_XYZ = sd_to_XYZ(sd, illuminant=illuminant) - good_Lab = XYZ_to_Lab(good_XYZ / 100, D65_xy) - - print("Good XYZ: %g %g %g" % (*good_XYZ,)) - print(" EF XYZ: %g %g %g" % (*XYZ,)) - print("Good Lab: %g %g %g" % (*good_Lab,)) - print(" EF Lab: %g %g %g" % (*Lab,)) - print(" EF* Lab: %g %g %g" % (*XYZ_to_Lab(XYZ / 100, D65_xy),)) diff --git a/test_example.py b/test_example.py deleted file mode 100644 index a4104b1..0000000 --- a/test_example.py +++ /dev/null @@ -1,28 +0,0 @@ -import numpy as np -from colour import * -from colour.models import eotf_inverse_sRGB -from colour.difference import delta_E_CIE1976 -from colour.recovery import XYZ_to_sd_Jakob2019 -from gsoc_common import model_sd, D65, D65_xy, plot_comparison - -if __name__ == "__main__": - # These numbers are taken from Jakob and Hanika's Jupyter notebook. - RGB_ref = np.array([0.79264853, 0.4, 0.63703843]) # *linear* sRGB - cc_ref = np.array([ 18.70184886, -13.19804478, 2.12180137]) - - XYZ = sRGB_to_XYZ(eotf_inverse_sRGB(RGB_ref), D65_xy) - Lab = XYZ_to_Lab(XYZ, D65_xy) - print("Target: X=%g, Y=%g, Z=%g, L=%g, a=%g, b=%g" % (*XYZ, *Lab)) - - reference_sd = model_sd(cc_ref) - reference_XYZ = sd_to_XYZ(reference_sd, illuminant=D65) / 100 - reference_Lab = XYZ_to_Lab(reference_XYZ, D65_xy) - - found_sd, error = XYZ_to_sd_Jakob2019(XYZ, return_error=True) - found_XYZ = sd_to_XYZ(found_sd, illuminant=D65) / 100 - found_Lab = XYZ_to_Lab(found_XYZ, D65_xy) - - print("Our results differ from the reference by ΔE = %g" \ - % delta_E_CIE1976(reference_Lab, found_Lab)) - - plot_comparison(XYZ, found_sd, "Reference", error, D65) diff --git a/test_interpolator.py b/test_interpolator.py index ab542f3..ba476da 100644 --- a/test_interpolator.py +++ b/test_interpolator.py @@ -3,25 +3,26 @@ from colour import * from colour.difference import delta_E_CIE1976 from colour.models import eotf_inverse_sRGB from colour.recovery import Jakob2019Interpolator -from gsoc_common import D65, D65_xy, model_sd, plot_comparison +from gsoc_common import * + # This script tests if the interpolator correctly handles multi-dimensional # inputs. if __name__ == "__main__": - interp = Jakob2019Interpolator() - interp.from_file("data/srgb.coeff") + interp = Jakob2019Interpolator() + interp.from_file("data/srgb.coeff") - RGBs = np.random.random((7, 6, 5, 4, 3)) - ccs = interp.coefficients(RGBs) + RGBs = np.random.random((7, 6, 5, 4, 3)) + ccs = interp.coefficients(RGBs) - RGB = eotf_inverse_sRGB(RGBs[0, 0, 0, 0, :]) - XYZ = sRGB_to_XYZ(RGB) - Lab = XYZ_to_Lab(XYZ, D65_xy) - cc = ccs[0, 0, 0, 0, :] + RGB = eotf_inverse_sRGB(RGBs[0, 0, 0, 0, :]) + XYZ = sRGB_to_XYZ(RGB) + Lab = XYZ_to_Lab(XYZ, D65_xy) + cc = ccs[0, 0, 0, 0, :] - matched_sd = model_sd(cc, primed=False) - matched_XYZ = sd_to_XYZ(matched_sd, illuminant=D65) / 100 - matched_Lab = XYZ_to_Lab(matched_XYZ, D65_xy) - error = delta_E_CIE1976(Lab, matched_Lab) + matched_sd = model_sd(cc, primed=False) + matched_XYZ = sd_to_XYZ(matched_sd, illuminant=D65) / 100 + matched_Lab = XYZ_to_Lab(matched_XYZ, D65_xy) + error = delta_E_CIE1976(Lab, matched_Lab) - plot_comparison(XYZ, matched_sd, "Model", error, D65) + plot_comparison(XYZ, matched_sd, "Model", error, D65) -- cgit