summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPaweł Redman <pawel.redman@gmail.com>2020-06-12 10:29:51 +0200
committerPaweł Redman <pawel.redman@gmail.com>2020-06-12 10:29:51 +0200
commit25cd4e2bfffb59a1663a0620a445081723e2627e (patch)
treeefdb4739afa69c68cc4c3a870a26800096b0d4cc
parent08b0124c835c9705cccf266fa933dea942ba02f3 (diff)
A test that computes coefficients for many colors, covering the entire sRGB gamut.
-rw-r--r--.gitignore2
-rw-r--r--test_bad_convergence.py24
-rw-r--r--test_coverage.py102
3 files changed, 128 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore
index eeb8a6e..628dec4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,3 @@
**/__pycache__
+out/
+
diff --git a/test_bad_convergence.py b/test_bad_convergence.py
new file mode 100644
index 0000000..5eff0d2
--- /dev/null
+++ b/test_bad_convergence.py
@@ -0,0 +1,24 @@
+import numpy as np
+from colour import *
+from jakob_hanika import jakob_hanika, model_sd
+from test_colorchecker import plot_comparison
+
+
+
+illuminant = "D65"
+ill_xy = ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][illuminant]
+ill_sd = SpectralDistribution(ILLUMINANTS_SDS[illuminant])
+
+
+
+# A set of inputs that make the Nelder-Mead solver work really hard.
+# This might not be interesting in future versions of jakob_hanika.py, once
+# the solver is more reliable. Make sure you've checked out the latest commit
+# that updates *this* file.
+if __name__ == "__main__":
+ XYZ = np.array([0.00263241, 0.00174905, 0.00092602])
+ ccp0 = np.array([5.11263599, -2.65344447, -2.37301856])
+
+ ccp, error = jakob_hanika(XYZ, ill_sd, ill_xy, ccp0)
+ if error > 0.1:
+ plot_comparison(XYZ, model_sd(ccp), "Target", error, ill_sd)
diff --git a/test_coverage.py b/test_coverage.py
new file mode 100644
index 0000000..07ceb08
--- /dev/null
+++ b/test_coverage.py
@@ -0,0 +1,102 @@
+import numpy as np, os, multiprocessing
+from colour import *
+from colour.models import eotf_inverse_sRGB
+from jakob_hanika import jakob_hanika, model_sd
+
+
+
+illuminant = "D65"
+ill_xy = ILLUMINANTS["CIE 1931 2 Degree Standard Observer"][illuminant]
+ill_sd = SpectralDistribution(ILLUMINANTS_SDS[illuminant]) # Note: changed in dev (to ILLUMINANTS_SD)
+
+# Resolution of the discretization cubes
+# Both should be 64, but this code is a bit too slow at the moment
+CHROMA_STEPS = 8
+LIGHTNESS_STEPS = 8
+
+
+
+# Solve for a specific RGB color
+def optimize_RGB(linear_RGB, ccp0):
+ RGB = eotf_inverse_sRGB(linear_RGB)
+ target = sRGB_to_XYZ(RGB, ill_xy)
+ ccp, error = jakob_hanika(target, ill_sd, ill_xy, ccp0, verbose=False)
+ if error > 0.1:
+ print("WARNING: convergence failed with L=%s, starting from %s" \
+ % (linear_RGB, ccp0))
+
+ return ccp, error
+
+# Solve for all lightness values of a fully saturated RGB color
+def optimize_chromaticity(argv):
+ linear_RGB, dirname = argv
+
+ # alpha's aren't spaced equally (see the article)
+ def smoothstep(x):
+ return x**2 * (3 - 2 * x)
+
+ ii = np.arange(0, LIGHTNESS_STEPS)
+ aa = smoothstep(smoothstep(ii / LIGHTNESS_STEPS))
+ ccps = np.empty((LIGHTNESS_STEPS, 3)) # FIXME: dtype?
+
+ def f(alpha, ccp0):
+ path = "%s/%.3f" % (dirname, alpha)
+ print("%s..." % path)
+
+ ccp, error = optimize_RGB(linear_RGB * alpha, ccp0)
+
+ try:
+ os.makedirs(os.path.dirname(path))
+ except:
+ pass
+ with open(path, "w") as fd:
+ fd.write("%s, %s" % (ccp, error))
+ if error > 0.1:
+ fd.write(" FAILED")
+ fd.write("\n")
+
+ return ccp, error
+
+
+ i_mid = LIGHTNESS_STEPS // 2
+ ccps[i_mid, :], _ = f(aa[i_mid], (0, 0, 0))
+
+ ccp0 = ccps[i_mid, :]
+ for i in range(i_mid + 1, LIGHTNESS_STEPS):
+ ccps[i, :], _ = f(aa[i], ccp0)
+
+ ccp0 = ccps[i_mid, :]
+ for i in reversed(range(0, i_mid)):
+ ccps[i, :], _ = f(aa[i], ccp0)
+
+
+
+# This demo discretizes the sRGB space to three 8x8x8 cubes and tries to find
+# the model parameters for all the colors. Coefficients are saved to a file
+# somewhere in the 'out' directory (each color gets its own file).
+# FIXME: This dumpster fire of an output format is to be replaced ASAP.
+# FIXME: This takes a good while (on the order of an hour).
+if __name__ == "__main__":
+ args = []
+
+ done = 0
+ total = 0
+ 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])]:
+
+ dirname = "out/R%.3fG%.3fB%.3f" % (*RGB,)
+ total += 1
+
+ if os.path.exists(dirname) and len(os.listdir(dirname)) == LIGHTNESS_STEPS:
+ done += 1
+ continue
+
+ args.append((RGB, dirname))
+
+ print("%d/%d already done" % (done, total))
+
+ pool = multiprocessing.Pool()
+ pool.map(optimize_chromaticity, args)