Ansel 0.0
A darktable fork - bloat + design vision
Loading...
Searching...
No Matches
fit_agx_ladder.py
Go to the documentation of this file.
1#!/usr/bin/env python3.12
2"""Offline orchestrator for the filmic-AgX bleach ladder — run this PLUGGED IN.
3
4It fits the four non-anchor variants in dependency order via successive BISECTION of the
5no-bleach / extra-bleach space, so every step is confined between its neighbours (monotone,
6even per-hue apparent-brightness AND hue-drift steps), and auto-patches BOTH sources:
7 - SHIPPED_VARIANTS in tools/derive_filmic_agx_primaries.py (single source of truth)
8 - the DT_FILMIC_COLORSCIENCE_V6..V10 case blocks in src/iop/filmicrgb.c
9
10no-bleach is NOT re-fit here (it is the settled bottom anchor, --min-bleach --ab-pull 200).
11
12Order:
13 1. extra-bleach = --fit-extra-bleach --ab-stabilize AB_STAB (stabilise reds/magentas so
14 bleaching does not over-brighten them ; apparent brightness stays ~put)
15 2. medium-bleach = bisect(no-bleach, extra-bleach)
16 3. low-bleach = bisect(no-bleach, medium-bleach)
17 4. high-bleach = bisect(medium-bleach, extra-bleach)
18
19Then it writes tools/agx_diagnose.log + tools/agx_report.md and prints the rebuild command.
20
21Tunables (edit + re-run if --diagnose shows a step off the ramp): AB_STAB below, and W_AB /
22W_HD inside the --fit-bisect branch of derive_filmic_agx_primaries.py.
23
24Usage: python3.12 tools/fit_agx_ladder.py
25Then: cmake --build build --target filmicrgb # verify constants + build
26"""
27import os, re, subprocess, sys
28
29AB_STAB = 70.0 # extra-bleach apparent-brightness UNIFORMITY weight (target-free)
30AB_LEVEL = 10.0 # gentle pull of the MEAN AB toward the target level (kept LOW:
31 # high level-weight makes the fit hyper-sensitive to the exact
32 # ab target — a 0.003 change flips blue into a distorting basin)
33BLEACH_NUGDE = 0.5
34ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
35PY = os.path.join(ROOT, "tools", "derive_filmic_agx_primaries.py")
36C = os.path.join(ROOT, "src", "iop", "filmicrgb.c")
37DERIVE = [sys.executable, "-u", PY]
38
39# (variant name, C enum suffix, fit flags, human-readable fit string for the SHIPPED entry)
40STEPS = [
41 ("extra-bleach", "V10", ["--fit-extra-bleach", "--ab-stabilize", str(AB_STAB), "--ab-level", str(AB_LEVEL), "--bleach-nudge", str(BLEACH_NUGDE)],
42 "--fit-extra-bleach --ab-stabilize %g --ab-level %g --bleach-nudge %g" % (AB_STAB, AB_LEVEL, BLEACH_NUGDE)),
43 ("medium-bleach", "V8", ["--fit-bisect", "no-bleach", "extra-bleach"],
44 "--fit-bisect no-bleach extra-bleach"),
45 ("low-bleach", "V7", ["--fit-bisect", "no-bleach", "medium-bleach"],
46 "--fit-bisect no-bleach medium-bleach"),
47 ("high-bleach", "V9", ["--fit-bisect", "medium-bleach", "extra-bleach"],
48 "--fit-bisect medium-bleach extra-bleach"),
49]
50
51
52def run(flags):
53 r = subprocess.run(DERIVE + flags, capture_output=True, text=True)
54 if r.returncode != 0:
55 sys.exit("FAILED: %s\n%s" % (" ".join(flags), r.stdout + r.stderr))
56 return r.stdout
57
58
59def parse_params(out):
60 """Pull inset/irot/outset/orot float lists from the 'paste this into SHIPPED_VARIANTS' block."""
61 if "paste this into SHIPPED_VARIANTS" not in out:
62 sys.exit("no SHIPPED block in fit output:\n" + out[-2000:])
63 blk = out.split("paste this into SHIPPED_VARIANTS", 1)[1]
64 def arr(key):
65 m = re.search(key + r"=\[([^\]]+)\]", blk)
66 return [float(x) for x in m.group(1).split(",")]
67 return arr("inset"), arr("irot"), arr("outset"), arr("orot")
68
69
70def patch_shipped(text, name, fit, ins, iro, outs, oro):
71 entry = (' "%s": dict(\n'
72 ' fit="%s",\n'
73 ' inset=[%s],\n'
74 ' irot=[%s],\n'
75 ' outset=[%s],\n'
76 ' orot=[%s]\n'
77 ' ),' % (name, fit,
78 ", ".join("%.7f" % v for v in ins),
79 ", ".join("%.7f" % v for v in iro),
80 ", ".join("%.6f" % v for v in outs),
81 ", ".join("%.7f" % v for v in oro)))
82 pat = re.compile(r' "%s":\s*dict\‍(.*?\n \‍),' % re.escape(name), re.S)
83 new, n = pat.subn(lambda m: entry, text, count=1)
84 if n != 1:
85 sys.exit("could not patch SHIPPED_VARIANTS['%s']" % name)
86 return new
87
88
89def patch_c(text, vnum, ins, iro, outs, oro):
90 block = (' inset_anchor[0] = %+.7ff; inset_anchor[1] = %+.7ff; inset_anchor[2] = %+.7ff;\n'
91 ' rotation_anchor[0] = %+.7ff; rotation_anchor[1] = %+.7ff; rotation_anchor[2] = %+.7ff;\n'
92 ' outset_anchor[0] = %.6ff; outset_anchor[1] = %.6ff; outset_anchor[2] = %.6ff;\n'
93 ' outset_rotation[0] = %+.7ff; outset_rotation[1] = %+.7ff; outset_rotation[2] = %+.7ff;'
94 % (ins[0], ins[1], ins[2], iro[0], iro[1], iro[2],
95 outs[0], outs[1], outs[2], oro[0], oro[1], oro[2]))
96 pat = re.compile(
97 r'(case DT_FILMIC_COLORSCIENCE_%s:.*?)'
98 r' inset_anchor\[0\][^\n]*\n'
99 r' rotation_anchor\[0\][^\n]*\n'
100 r' outset_anchor\[0\][^\n]*\n'
101 r' outset_rotation\[0\][^\n]*;' % vnum, re.S)
102 new, n = pat.subn(lambda m: m.group(1) + block, text, count=1)
103 if n != 1:
104 sys.exit("could not patch C case %s" % vnum)
105 return new
106
107
108def main():
109 for name, vnum, flags, fitstr in STEPS:
110 print("=== fitting %-14s (%s) : %s" % (name, vnum, " ".join(flags)))
111 out = run(flags)
112 for ln in out.splitlines():
113 if ln.startswith("// ") and ("bisect(" in ln or "extra best" in ln):
114 print(" " + ln)
115 ins, iro, outs, oro = parse_params(out)
116 with open(PY) as f:
117 py = f.read()
118 with open(PY, "w") as f:
119 f.write(patch_shipped(py, name, fitstr, ins, iro, outs, oro))
120 with open(C) as f:
121 cc = f.read()
122 with open(C, "w") as f:
123 f.write(patch_c(cc, vnum, ins, iro, outs, oro))
124 print(" patched SHIPPED_VARIANTS['%s'] + C %s inset=[%s]"
125 % (name, vnum, ", ".join("%.4f" % v for v in ins)))
126
127 diag = run(["--diagnose"])
128 rep = run(["--report"])
129 with open(os.path.join(ROOT, "tools", "agx_diagnose.log"), "w") as f:
130 f.write(diag)
131 with open(os.path.join(ROOT, "tools", "agx_report.md"), "w") as f:
132 f.write(rep)
133 print("\n=== per-hue continuity (tools/agx_diagnose.log) ===")
134 print(diag)
135 print("Wrote tools/agx_report.md . Now build + verify:\n"
136 " cmake --build build --target filmicrgb")
137
138
139if __name__ == "__main__":
140 main()
patch_shipped(text, name, fit, ins, iro, outs, oro)
patch_c(text, vnum, ins, iro, outs, oro)