![]() |
Ansel 0.0
A darktable fork - bloat + design vision
|
Functions | |
| xyz_D50_to_Yrg (xyz) | |
| rgb_work_to_Yrg (rgb) | |
| chroma_hue (Yrg) | |
| tone_map (rgb) | |
| bracket_matrices (insets, rotations) | |
| boundary_colors () | |
| _lab_d65_to_work (L, a, b) | |
| chroma_trajectory (sample, M, M_inv) | |
| target_profile (baseline) | |
| residuals (params, drift_target_rad_per_ev) | |
| brk (p) | |
| chroma_hue_batch (RGB) | |
| variant_bracket (v) | |
| measure_batch (S, C_in, H_in, M, Mo) | |
| hk_drift_batch (S, M, Mo) | |
| delta_e_yrg (cr, dh_deg) | |
| nayatani_hk_excess (RGB) | |
| scene_ab_target (S_refl, hk_retention=1.0) | |
| skin_and_reflective_sets () | |
| rec2020_worst_boundary_luminance (M, Mo) | |
| gamut_violation (M, Mo) | |
| fit_midpoint (lo_key, hi_key, inset_lo, inset_hi, seed_insets) | |
| print_midpoint_constants (p, mode, endpoints) | |
| print_variant_entry (name, fit, inset, irot, outset, orot) | |
| print_c_case (case_name, fit, inset, irot, outset, orot) | |
| report_variants () | |
| per_hue_ab_and_drift (v, S, C, H, bin_idx, nbins) | |
| report_hue_continuity () | |
| print_diagnostics (p, message) | |
| main () | |
Variables | |
| XYZ_D50_to_D65_CAT16 | |
| XYZ_D65_to_LMS_2006 | |
| filmlightRGB_to_LMS | |
| LMS_to_filmlightRGB = np.linalg.inv(filmlightRGB_to_LMS) | |
| Y_2006_COEFFS = np.array([0.68990272, 0.34832189, 0.0]) | |
| REC2020_TO_XYZ_D50 | |
| BLACK_EV | |
| WHITE_EV | |
| tuple | CURVE_DEFAULTS = (1.18, 10.0, 0.0, 1.5, 1.5) |
| CURVE = curve_factory(BLACK_EV, WHITE_EV, *CURVE_DEFAULTS, shoulder_slope_matched=True) | |
| WHITE_YRG = rgb_work_to_Yrg(np.ones(3)) | |
| EVS = np.arange(-2.0, WHITE_EV + 0.51, 0.5) | |
| int | HUE_STEPS = 24 |
| SHOULDER_EV = BLACK_EV + CURVE.sh_x * (WHITE_EV - BLACK_EV) | |
| list | _SKIN_LAB |
| _D65_WHITE = np.array([0.95047, 1.0, 1.08883]) | |
| IDENTITY = np.eye(3) | |
| dict | BASELINES = {tuple(s): chroma_trajectory(s, IDENTITY, IDENTITY)[0] for s in boundary_colors()} |
| dict | SHIPPED_VARIANTS |
| LUT_X = np.linspace(0.0, 1.0, 8193) | |
| LUT_Y = np.array([CURVE(v) for v in LUT_X]) | |
| list | _BND |
| tuple | _BND_EV = (-8, -6, -4, -2, -1, 0, 1, 2, 3) |
Fit the inset/rotation anchor constants of filmic RGB's "AgX" color science
(v8) — see doc/filmic-agx.md and filmic_agx_prepare_bracket() in
src/iop/filmicrgb.c, whose PROVISIONAL constants this script is meant to replace.
Model (mirrors the C pixel path, working profile = linear Rec2020):
work RGB -> inset matrix -> per-channel [log2 encoding -> sigmoid spline ->
hardness power] -> exact-inverse outset -> measure chroma / hue in Kirk Yrg.
Objectives:
1. purity-vs-exposure: colors on the working-gamut boundary must reach
achromatic (chroma ratio ~ 0) at the white end of the curve, monotonically;
2. hue drift: dh/dEV in Yrg matches a chosen uniform target across the wheel
(0 for a neutral character, or a uniform warm bias);
3. hard constraints as penalties: displaced primaries stay inside the working
gamut triangle with a margin (positivity of the bracket) and away from
degeneracy (conditioning).
Usage:
python3 tools/derive_filmic_agx_primaries.py --max-desat FRAC (recommended)
python3 tools/derive_filmic_agx_primaries.py --fit-priority
python3 tools/derive_filmic_agx_primaries.py [--drift-target DEG_PER_EV]
The recommended mode is --max-desat : you state the maximum average chroma loss
over the priority set (skin + reflective) you accept, and the solver returns the
bracket with the best hue match at or below that budget. Sweep it to trace the
hue/chroma frontier. See its --help for the full method.
Prints the C arrays to paste into filmic_agx_prepare_bracket(). Requires
numpy + scipy (run with python3.12). Constants below are copied verbatim from the
C code so the model matches the pipeline bit-for-bit in spirit (float64 here,
float32 there).
|
protected |
Definition at line 165 of file derive_filmic_agx_primaries.py.
References f.
Referenced by skin_and_reflective_sets().
| derive_filmic_agx_primaries.boundary_colors | ( | ) |
saturated colors on the working-gamut boundary, one channel at zero
Definition at line 135 of file derive_filmic_agx_primaries.py.
Referenced by residuals().
| derive_filmic_agx_primaries.bracket_matrices | ( | insets, | |
| rotations | |||
| ) |
inset matrix M (work->rendering) and its exact inverse, exactly as the C code.
Definition at line 108 of file derive_filmic_agx_primaries.py.
References xyz_D50_to_Yrg().
Referenced by brk(), print_midpoint_constants(), residuals(), and variant_bracket().
| derive_filmic_agx_primaries.brk | ( | p | ) |
Definition at line 276 of file derive_filmic_agx_primaries.py.
References bracket_matrices().
Referenced by main().
| derive_filmic_agx_primaries.chroma_hue | ( | Yrg | ) |
Definition at line 92 of file derive_filmic_agx_primaries.py.
Referenced by chroma_trajectory().
| derive_filmic_agx_primaries.chroma_hue_batch | ( | RGB | ) |
(N,3) working-linear-Rec2020 -> (chroma, hue) in Kirk Yrg, relative to white.
Definition at line 281 of file derive_filmic_agx_primaries.py.
Referenced by main(), measure_batch(), per_hue_ab_and_drift(), report_hue_continuity(), and report_variants().
| derive_filmic_agx_primaries.chroma_trajectory | ( | sample, | |
| M, | |||
| M_inv | |||
| ) |
chroma ratio and hue drift vs exposure, through the bracketed curve
Definition at line 173 of file derive_filmic_agx_primaries.py.
References chroma_hue(), max, rgb_work_to_Yrg(), and tone_map().
Referenced by residuals().
| derive_filmic_agx_primaries.delta_e_yrg | ( | cr, | |
| dh_deg | |||
| ) |
Perceptual color-shift distance in the chroma-NORMALIZED Yrg plane : the input sits at (1, 0) — chroma ratio 1, zero hue drift — and the output at (cr*cos, cr*sin), so the distance between them folds chroma loss AND hue drift into one number. 0 = colour unchanged ; ~1 = fully bleached ; up to 2 = hue-flipped at full chroma. NOTE: subtracting the (1, 0) reference is the whole point. An earlier version computed hypot(cr*cos, cr*sin), which reduces algebraically to just cr — the output vector's LENGTH, not its distance from the input — so it was not a real delta-E and cancelled the desaturation term to a constant. Both --min-bleach and --max-desat now use THIS function.
Definition at line 317 of file derive_filmic_agx_primaries.py.
Referenced by main(), and report_variants().
| derive_filmic_agx_primaries.fit_midpoint | ( | lo_key, | |
| hi_key, | |||
| inset_lo, | |||
| inset_hi, | |||
| seed_insets | |||
| ) |
Solve for the bracket that best reproduces the AVERAGE of two variants' processed outputs — the perceptual midpoint. Target = 0.5*(post_bracket(lo) + post_bracket(hi)) over a skin + reflective + Rec2020-boundary sample set (skin weighted x2), fit in least squares under Rec2020 gamut safety, positivity and conditioning. Returns the 10-parameter bracket (uniform inset, inset rot[3], outset[3], outset rot[3]) or None.
Definition at line 450 of file derive_filmic_agx_primaries.py.
References max, min, rec2020_worst_boundary_luminance(), skin_and_reflective_sets(), and variant_bracket().
Referenced by main().
| derive_filmic_agx_primaries.gamut_violation | ( | M, | |
| Mo | |||
| ) |
Definition at line 438 of file derive_filmic_agx_primaries.py.
References max.
Referenced by main().
| derive_filmic_agx_primaries.hk_drift_batch | ( | S, | |
| M, | |||
| Mo | |||
| ) |
Helmholtz-Kohlrausch apparent-brightness drift : excess(output) - excess(input), per sample, through the same inset -> curve -> outset path as measure_batch. Positive = the bracket made the colour read brighter-for-its-luminance than the original (H-K inflation) ; negative = deflation. The look wants this as close to 0 as it can, so the rendered colours keep the SAME apparent-brightness balance as the scene.
Definition at line 306 of file derive_filmic_agx_primaries.py.
References nayatani_hk_excess().
Referenced by report_variants().
| derive_filmic_agx_primaries.main | ( | ) |
Definition at line 692 of file derive_filmic_agx_primaries.py.
References brk(), chroma_hue_batch(), delta_e_yrg(), fit_midpoint(), gamut_violation(), main(), max, measure_batch(), min, nayatani_hk_excess(), print_c_case(), print_diagnostics(), print_midpoint_constants(), print_variant_entry(), rec2020_worst_boundary_luminance(), report_hue_continuity(), report_variants(), scene_ab_target(), skin_and_reflective_sets(), and variant_bracket().
Referenced by main().
| derive_filmic_agx_primaries.measure_batch | ( | S, | |
| C_in, | |||
| H_in, | |||
| M, | |||
| Mo | |||
| ) |
(N,3) samples through inset -> per-channel curve -> outset ; returns (chroma_ratio post output<=input clamp, hue_drift_deg), both (N,).
Definition at line 295 of file derive_filmic_agx_primaries.py.
References chroma_hue_batch().
Referenced by main(), and report_variants().
| derive_filmic_agx_primaries.nayatani_hk_excess | ( | RGB | ) |
Helmholtz-Kohlrausch apparent-brightness excess (Gamma - 1), Nayatani (1997) VAC model, for a batch of working-linear-Rec2020 colours. This is the FRACTIONAL amount by which a chromatic colour looks brighter than an equally-luminous grey — a real perceptual effect that per-channel tone mapping amplifies unevenly by hue. It is ~0 for neutrals and yellow-greens and largest for saturated blue / red / magenta (measured : gray 0.00, red 0.32, green 0.12, blue 0.43 at equal luminance), which is exactly why an over-saturated red reads brighter than an equally-bright green. Luminance-independent (a fractional boost), so penalizing it targets chroma+hue only.
Definition at line 331 of file derive_filmic_agx_primaries.py.
Referenced by hk_drift_batch(), main(), per_hue_ab_and_drift(), and scene_ab_target().
| derive_filmic_agx_primaries.per_hue_ab_and_drift | ( | v, | |
| S, | |||
| C, | |||
| H, | |||
| bin_idx, | |||
| nbins | |||
| ) |
For one variant, return (apparent_brightness[nbins], signed_hue_drift_deg[nbins], output_chroma[nbins]) binned over the reflective hue circle. APPARENT BRIGHTNESS = output luminance x (1 + Nayatani H-K excess) — how bright a colour READS, not just its luminance. OUTPUT CHROMA = Yrg saturation — must DECREASE monotonically no->extra (the non-monotone- saturation bug lived here). Hue drift is signed, only where output chroma survives (ratio > 0.2).
Definition at line 615 of file derive_filmic_agx_primaries.py.
References chroma_hue_batch(), nayatani_hk_excess(), and variant_bracket().
Referenced by report_hue_continuity().
| derive_filmic_agx_primaries.print_c_case | ( | case_name, | |
| fit, | |||
| inset, | |||
| irot, | |||
| outset, | |||
| orot | |||
| ) |
Print a C switch-case block for the fitted bracket constants.
Definition at line 542 of file derive_filmic_agx_primaries.py.
Referenced by main(), and print_diagnostics().
| derive_filmic_agx_primaries.print_diagnostics | ( | p, | |
| message | |||
| ) |
Definition at line 671 of file derive_filmic_agx_primaries.py.
References print_c_case(), print_variant_entry(), and report_variants().
Referenced by main().
| derive_filmic_agx_primaries.print_midpoint_constants | ( | p, | |
| mode, | |||
| endpoints | |||
| ) |
Print the C constant block for a fitted midpoint bracket (or a failure note).
Definition at line 513 of file derive_filmic_agx_primaries.py.
References bracket_matrices(), max, and rec2020_worst_boundary_luminance().
Referenced by main().
| derive_filmic_agx_primaries.print_variant_entry | ( | name, | |
| fit, | |||
| inset, | |||
| irot, | |||
| outset, | |||
| orot | |||
| ) |
Print a SHIPPED_VARIANTS entry that can be pasted directly into the script.
Definition at line 532 of file derive_filmic_agx_primaries.py.
Referenced by main(), and print_diagnostics().
| derive_filmic_agx_primaries.rec2020_worst_boundary_luminance | ( | M, | |
| Mo | |||
| ) |
Worst output luminance over the Rec2020 primaries and secondaries across the tonal range. Rec2020 IS the working space, so its primaries are the most saturated colors that can occur — the worst case. A strongly over-expanding outset can push them to NEGATIVE luminance (the pixel renders BLACK); this must stay > 0. This is the gamut-safety check that caught the no-bleach blue-goes-black bug.
Definition at line 408 of file derive_filmic_agx_primaries.py.
References min.
Referenced by fit_midpoint(), main(), print_midpoint_constants(), and report_variants().
| derive_filmic_agx_primaries.report_hue_continuity | ( | ) |
Per-hue colour-CONTINUITY diagnostic across the bleach ladder (reflective set). For each of 12 hue bins it prints, for every shipped variant, the APPARENT BRIGHTNESS (output luminance x (1 + H-K excess)) and the SIGNED hue drift, with the per-step deltas. A well-behaved ladder is MONOTONE with EVEN steps per hue ; a big jump (e.g. reds/magentas over-brightening at one step) or a sign reversal (a hue rotating against the ramp) marks a variant that left the ramp. This is the tool behind the no-bleach red-darkening + low-bleach hue-divergence fixes.
Definition at line 635 of file derive_filmic_agx_primaries.py.
References chroma_hue_batch(), per_hue_ab_and_drift(), and skin_and_reflective_sets().
Referenced by main().
| derive_filmic_agx_primaries.report_variants | ( | ) |
Print the {avg,max} x {desaturation, hue drift, delta-E} x {skin,reflective} table
for the shipped variants, plus a Rec2020 gamut-safety check. Desaturation is over all
colors that carry chroma ; hue drift is measured where chroma survives (ratio > 0.2 —
a bleached color has no meaningful hue) ; delta-E is the combined chroma+hue fidelity
(delta_e_yrg) over ALL samples, the single tie-breaker metric. Source of the tables in
doc/filmic-agx.md and the user docs.
Definition at line 552 of file derive_filmic_agx_primaries.py.
References chroma_hue_batch(), delta_e_yrg(), hk_drift_batch(), measure_batch(), rec2020_worst_boundary_luminance(), row, skin_and_reflective_sets(), and variant_bracket().
Referenced by main(), and print_diagnostics().
| derive_filmic_agx_primaries.residuals | ( | params, | |
| drift_target_rad_per_ev | |||
| ) |
Definition at line 197 of file derive_filmic_agx_primaries.py.
References boundary_colors(), bracket_matrices(), chroma_trajectory(), max, and target_profile().
| derive_filmic_agx_primaries.rgb_work_to_Yrg | ( | rgb | ) |
Definition at line 87 of file derive_filmic_agx_primaries.py.
References xyz_D50_to_Yrg().
Referenced by chroma_trajectory().
| derive_filmic_agx_primaries.scene_ab_target | ( | S_refl, | |
hk_retention = 1.0 |
|||
| ) |
PRINCIPLED uniform apparent-brightness target for the apparent-brightness stabilizers
(--ab-pull on min-bleach, and the reference level for --ab-stabilize on extra), replacing a
hand-tuned constant. = mean over the reflective set of
curve(L_in) * (1 + hk_retention * H-K_excess(input))
i.e. the apparent brightness a colour gets if the ACHROMATIC tone curve maps its luminance and
it keeps `hk_retention` of its OWN natural (input) Helmholtz-Kohlrausch excess :
hk_retention = 1 -> SCENE-PRESERVING ceiling (full natural H-K pop, ~0.379)
hk_retention = 0 -> GRAY-EQUIVALENT floor (H-K fully neutralized, ~0.336)
hk_retention = 0.5 -> midway (~0.357) ; LINEAR in hk_retention, so 0.5 IS the average of the two.
Holding every hue at this value preserves the scene's apparent-brightness STRUCTURE (no hue
over/under-brightened relative to the others) at the chosen H-K-retention level. Hue-independent
(the set places every hue at luminance GREY*2^EV, matched), curve-relative (recomputes if the
default curve or the set changes). A chroma-preserving end (min-bleach) wants retention 1 ; a
bleaching end (extra), whose colours lose chroma/H-K, sits lower in the [floor, ceiling] band.
Definition at line 358 of file derive_filmic_agx_primaries.py.
References nayatani_hk_excess().
Referenced by main().
| derive_filmic_agx_primaries.skin_and_reflective_sets | ( | ) |
The SINGLE canonical colour-constancy evaluation set — shared by --report AND every fit mode, so a "desaturation %" (or hue / delta-E / H-K drift) means the same thing everywhere. Two disjoint arrays, each swept over tonal placements : the skin-tone database, and the reflective hue circle. The reflective circle spans a purity sweep from LOW (0.3 : diffuse matte reflectances) to HIGH (0.9 : high-chroma memory colours) — these used to live in two separate builders (this one and priority_samples), which is what let the modes disagree on what "reflective" meant ; they are merged here.
Definition at line 379 of file derive_filmic_agx_primaries.py.
References _lab_d65_to_work().
Referenced by fit_midpoint(), main(), report_hue_continuity(), and report_variants().
| derive_filmic_agx_primaries.target_profile | ( | baseline | ) |
Design target for the bleaching : transparent (baseline) below the shoulder, then a smooth ramp to full achromatic exactly at the white endpoint. This pins the bleach *rate* through the shoulder — without it the fit has no optimum and rails the insets (endpoint-only objectives are monotone in the inset amount).
Definition at line 188 of file derive_filmic_agx_primaries.py.
Referenced by residuals().
| derive_filmic_agx_primaries.tone_map | ( | rgb | ) |
Definition at line 98 of file derive_filmic_agx_primaries.py.
Referenced by chroma_trajectory().
| derive_filmic_agx_primaries.variant_bracket | ( | v | ) |
(inset matrix M, applied outset matrix) for a SHIPPED_VARIANTS entry.
Definition at line 289 of file derive_filmic_agx_primaries.py.
References bracket_matrices().
Referenced by fit_midpoint(), main(), per_hue_ab_and_drift(), and report_variants().
| derive_filmic_agx_primaries.xyz_D50_to_Yrg | ( | xyz | ) |
Definition at line 80 of file derive_filmic_agx_primaries.py.
Referenced by bracket_matrices(), and rgb_work_to_Yrg().
|
protected |
Definition at line 434 of file derive_filmic_agx_primaries.py.
|
protected |
Definition at line 436 of file derive_filmic_agx_primaries.py.
|
protected |
Definition at line 163 of file derive_filmic_agx_primaries.py.
|
protected |
Definition at line 153 of file derive_filmic_agx_primaries.py.
| dict derive_filmic_agx_primaries.BASELINES = {tuple(s): chroma_trajectory(s, IDENTITY, IDENTITY)[0] for s in boundary_colors()} |
Definition at line 186 of file derive_filmic_agx_primaries.py.
| derive_filmic_agx_primaries.BLACK_EV |
Definition at line 74 of file derive_filmic_agx_primaries.py.
| derive_filmic_agx_primaries.CURVE = curve_factory(BLACK_EV, WHITE_EV, *CURVE_DEFAULTS, shoulder_slope_matched=True) |
Definition at line 76 of file derive_filmic_agx_primaries.py.
Referenced by tone_map().
| tuple derive_filmic_agx_primaries.CURVE_DEFAULTS = (1.18, 10.0, 0.0, 1.5, 1.5) |
Definition at line 75 of file derive_filmic_agx_primaries.py.
| derive_filmic_agx_primaries.EVS = np.arange(-2.0, WHITE_EV + 0.51, 0.5) |
Definition at line 132 of file derive_filmic_agx_primaries.py.
| derive_filmic_agx_primaries.filmlightRGB_to_LMS |
Definition at line 53 of file derive_filmic_agx_primaries.py.
| int derive_filmic_agx_primaries.HUE_STEPS = 24 |
Definition at line 133 of file derive_filmic_agx_primaries.py.
| derive_filmic_agx_primaries.IDENTITY = np.eye(3) |
Definition at line 185 of file derive_filmic_agx_primaries.py.
| derive_filmic_agx_primaries.LMS_to_filmlightRGB = np.linalg.inv(filmlightRGB_to_LMS) |
Definition at line 57 of file derive_filmic_agx_primaries.py.
| derive_filmic_agx_primaries.LUT_X = np.linspace(0.0, 1.0, 8193) |
Definition at line 263 of file derive_filmic_agx_primaries.py.
Definition at line 264 of file derive_filmic_agx_primaries.py.
| derive_filmic_agx_primaries.REC2020_TO_XYZ_D50 |
Definition at line 64 of file derive_filmic_agx_primaries.py.
| dict derive_filmic_agx_primaries.SHIPPED_VARIANTS |
Definition at line 221 of file derive_filmic_agx_primaries.py.
Definition at line 145 of file derive_filmic_agx_primaries.py.
| derive_filmic_agx_primaries.WHITE_EV |
Definition at line 74 of file derive_filmic_agx_primaries.py.
| derive_filmic_agx_primaries.WHITE_YRG = rgb_work_to_Yrg(np.ones(3)) |
Definition at line 90 of file derive_filmic_agx_primaries.py.
| derive_filmic_agx_primaries.XYZ_D50_to_D65_CAT16 |
Definition at line 43 of file derive_filmic_agx_primaries.py.
| derive_filmic_agx_primaries.XYZ_D65_to_LMS_2006 |
Definition at line 48 of file derive_filmic_agx_primaries.py.
| derive_filmic_agx_primaries.Y_2006_COEFFS = np.array([0.68990272, 0.34832189, 0.0]) |
Definition at line 59 of file derive_filmic_agx_primaries.py.