Prompt-to-LUT · dataset + two-stage architecture

Two models. One color contract, every decision measured.

An interpreter reads the request into a measured AttributeSpec; a generator turns that spec plus the image into a 64-token LUT. Follow the corpus from source registration to verified shards, then watch one frozen row move through deterministic evaluation and a ship decision.

A 3D LUT remaps every color in an image. Drag to compare. Illustrative grade.

0
color transforms sourced
0
gated pipeline stages
0
rows in the active set
0 tok
to encode one full LUT
Live data collection

From scattered sources to a certified corpus.

Watch 7,760 real color transforms move through provenance, deduplication, canonicalization, quality gates, leakage-safe splits, AttributeSpec + captioning, selection, and verified delivery.

Scroll from source inventory to verified shards. Every count matches the current corpus manifests.

scroll to scrub ↓tap a stage
collection_run / corpus_current
reading inventory
00 · declared inputs

Open the source inventory

Every candidate starts with a named family, access path, and acquisition policy. Nothing enters from an unknown origin.

PPR10K experts4,055
Scraped web1,695
FiveK experts800
FreshLUTs727
HaldCLUT film344
ON1 pack89
Procedural50
raw candidates admitted to registration7,760
The shape of it

One pipeline, seven stages, one honest number at the end.

Raw material enters wide and messy. Each stage removes what a single global LUT cannot faithfully represent, then certifies what remains. Nothing is kept on trust alone.

01
Collection
02
Canonicalize
03
Quality gates
04
Leakage splits
05
Spec + captions
06
Active set
07
Delivery
Sourced raw candidates
7,760
Derived to a LUT
7,741
Passed quality gates
4,620
Active set for training
3,033

Bars are proportional to the 7,760 candidates that entered. Gate survivors span the gold and diagnostic tiers; the active set is the curated, deduplicated selection.

STAGE 01

Collection

Every candidate comes from a declared source with recorded provenance. Seven sources feed the corpus, from curated expert photo sets to a broad web discovery sweep, plus a controlled procedural generator for coverage gaps.

A single machine-readable inventory names every source, its family, and how it was accessed. Each raw item lands in a provenance registry, one line per candidate.
SCRAPED-WEB DEDUPLICATION
0candidates → 0distinct

Content-hash matching drops exact duplicates before anything reaches the gates.

REFUSED AT THE DOOR

Whole families are excluded up front, because no single global LUT can reproduce them.

local edit masks HDR+ ISP camera pipeline unknown log domain dped
RAW CANDIDATES BY SOURCE
PPR10K experts4,055
Scraped web1,695
FiveK experts800
FreshLUTs727
HaldCLUT film344
ON1 pack89
Procedural50

Curated expert corpora (PPR10K, MIT-Adobe FiveK) anchor the set. Community and film-simulation sources add breadth. The expert share is later capped so no single family dominates.

STAGE 02

One canonical form

Sources arrive in different formats, grid sizes, and color spaces. Before any comparison is meaningful, everything is resampled to a single representation so that a difference in the data means a difference in the color, not the container.

0.0input domain1.0
GRID
17³ = 4,913
nodes per LUT
COLOR SPACE
sRGB, D65
display referred
INTERPOLATION
trilinear
between nodes
FORMAT
.cube
canonical output
LUTs authored in wider spaces are color-managed into the canonical domain. Anything whose source color space cannot be resolved is recorded and rejected, never guessed.

Some sources are not LUTs at all. Expert photo pairs are turned into a LUT by fitting a global transform to the source-and-target images, then validating the fit against held-out pixels. Only fits that hold up are kept.

STAGE 03

Quality gates sort every LUT into three tiers

Two gate layers run on the canonical LUT. Representability asks whether a global transform can faithfully reproduce the look. Safety and quality ask whether the result is well behaved: no clipping, no folding, no skin-tone damage. The verdict is a tier.

Gold
0
Faithful global fit within the strictest error bounds. The only tier eligible for headline evaluation.
Diagnostic
0
Usable and stylistically valid, but outside gold error bounds. Kept for training coverage, not for headline scoring.
Rejected
0
Fails a representability or safety threshold. Removed from the corpus entirely.
Mean color error, gold
ΔE00 ≤ 2.5
Mean color error, accept
ΔE00 ≤ 3.0
Pixel support
≥ 0.98
Clip rate
≤ 0.5%
Neutral drift
ΔE00 ≤ 3.0
Skin hue drift, p95
≤ 8°

ΔE00 (CIEDE2000) is a perceptual color-difference metric. Lower means closer to the intended look. A dedicated skin-locus gate protects skin tones, which the eye judges harshly.

STAGE 04

Leakage-safe splits

Survivors are grouped into split units by how similar their color transforms are, so near-identical LUTs cannot land on both sides of the train and evaluation boundary. Without this, scores look better than the model really is.

Train 80%
Eval 10%
Train · 80%
Eval · 10%
Diagnostic · 7%
Qualitative · 3%
LEAKAGE-SAFE UNITS
0
✓ leakage check: pass

Grouping runs on the color-transform geometry itself, using near-neighbor analysis of the LUT tensor. The split is frozen under a fixed seed and a recorded policy version, so the exact same rows recur on every run. The identifier split_43e6cd41 pins it.

STAGE 05

Grounded language, and a two-stage split

Each kept LUT is probed into a behavior_v2 measurement, then serialized into one AttributeSpec. Captioning writes many phrasings — literal, metaphor, mood, concept, slang — that all map back to that single spec, so language is diverse but never ungrounded. At run time an interpreter turns free text into the same AttributeSpec plus a route; the generator is conditioned on the serialized spec, not the raw words. The measurement, not the caption, is the authority for every color claim.

route = grade→ 64 LUT tokens
“Give it a warm, faded cinematic look with lifted shadows.”
Interpreter reads text → AttributeSpec + route
↓
route=grade | warmer=+2.3 muted=+2.0 lifted_shadows=+1.5 | conf=0.82
↓
Generator reads spec + image → a LUT as
route = clarify / refuse→ <unsupported>
“Remove the person in the background and brighten only the sky.”
Interpreter classifies every request
↓
clarify — valid but under-specified intent
refuse — out_of_scope or out_of_gamut

Try the boundary yourself

Pick a request. A single global LUT can shift color everywhere at once, but it cannot target a region, change content, or relight a scene — and a few global looks fall outside what the frozen tokenizer can represent.

Select Choose a request above to see how the interpreter routes it.

The corpus carries 272 refuse, clarify, and mixed prompts. Refusals split into out_of_scope (11 categories a global LUT cannot do) and out_of_gamut (3 global looks the tokenizer cannot represent); mixed prompts pair a valid global change with an impossible one, and the whole request is still declined.

STAGE 06

The active set

A selector assembles the final training rows with quotas on source, style, and scene, so the set is diverse rather than merely large. The result is scored against twelve acceptance criteria before it can be frozen.

Supported · 2,761 rows
Unsupported · 272 rows (8.97%)
3,033 total
SUPPORTED ROWS BY SOURCE FAMILY
PPR10K956
Scraped web799
FiveK538
FreshLUTs197
HaldCLUT191
Public packs41
Procedural39

Largest single family is 32% of the full 3,033-row active set, under the 50% dominance cap. Expert sources combined (PPR10K + FiveK) sit at 49%, under their own 50% cap. Both fractions are measured over the whole active set.

ACCEPTANCE: 12 OF 12 PASS
✓Scale
✓No family dominance
✓No split leakage
✓Provenance and behavior
✓Canonical domain
✓Tags backed by checks
✓Behavior fully handled
✓Unsupported coverage
✓Manifests present
✓Expert source capped
✓Reconstruction check
✓Paired-image support

All twelve criteria pass on the post-audit, materialized active set — acceptance.overall = pass in the corpus active_manifest.json on the private dataset root, with every one of the 2,761 supported rows carrying a representability tier, 64 materialized tokens, and a paired input image. Acceptance is evaluated at freeze; the active-set version is still active_set_pending_freeze.

HOW A LUT BECOMES A TRAINING TARGET
4,913 color nodes
the full .cube surface
compress
→
64 tokens
from a 256-entry codebook

A vector-quantized encoder is frozen and shared across the set. It reconstructs the held-out LUTs at a mean ΔE00 of 1.31 and mean PSNR near 37 dB, so the compact target loses almost nothing the eye can see.

STAGE 07

Staging and delivery

The finished corpus is packed into verifiable shards and published to a durable, private dataset root, then pulled down and checked byte for byte wherever training runs.

Pack

Raw LUTs, canonical residuals, registries, splits, and the frozen tokenizer are bundled into structure-preserving tar shards, each with a manifest listing member counts, bytes, and hashes.

corpus-*.tar shards · ~9.85 GB

Publish

Shards sync to a private Hugging Face dataset root as the durable source of truth. Packing is idempotent by content, so re-runs never duplicate work.

private dataset root

Verify

On the training machine, every shard is pulled, checked against its sha256, and extracted with resume markers. A corrupted or partial shard cannot slip into a run.

sha256 verified
Straight talk

Where the dataset stands today

The two-stage design and the methodology are fixed, the corpus is assembled, and every supported row now carries a behavior_v2 AttributeSpec. The generator is still migrating from the free-text checkpoint onto the spec, so the honest state is below.

Locked and complete

Sources and provenance. All 7,760 candidates registered with recorded origin.
Quality gates. Every LUT tiered; thresholds fixed.
Split. Frozen, unit-aware, leakage check passing, reproducible by seed.
Tokenizer. Frozen with a signed-off fidelity review.
Behavior + spec. All 2,761 supported rows re-measured to behavior_v2 and serialized to an AttributeSpec, every asserted axis backed by measurement.

Migrating before final freeze

Captioning corpus. Diverse captions per LUT (five voices) built in code; full generation gated on teacher-API access.
Interpreter. Distilling text → AttributeSpec + route on the caption corpus; the small LM trains outside this loop.
Generator on the spec. Retraining on attribute_spec_text after the oracle upper-bound gate confirms the seam is not lossy (latest run was marginal, under review).
Headline evaluation. 382 headline-eligible items ready against an 800-plus target; headline-eligible supply is the bottleneck.
Live harness walkthrough

From frozen row to ship decision.

The same row, contract, and thresholds follow every checkpoint. Scroll to scrub the run, select a stage, or replay it end to end. Values shown in the run are illustrative; the contracts and artifacts match the implementation.

Scroll to scrub the full run. Values are illustrative; the flow and artifacts match the implementation.

scroll to scrub ↓tap a stage
illustrative_run / candidate_042
binding inputs
00 · immutable inputs

Bind the contract

A run starts from frozen rows, a pinned checkpoint, two decode modes, and recorded seeds. No threshold moves after seeing an output.

Frozen eval row
eval_seen_000184
headlinegoldroute=grade
“Make the image warmer with softer contrast.” → interpreter → warmer=+2.3 less_contrast=+1.8
Run binding
checkpoint_candidate_042
seeds 11 / 29 / 47config v1
Versions, hashes, and slice policy travel with the result.
$ python -m eval.run_eval \
  --rows frozen_eval.jsonl --modes free_generation,runtime_constrained \
  --seeds 11,29,47 --run-id candidate_042
Plain definitions

Glossary

The terms used throughout, defined the way they are used in this project.

A table that remaps every input color to an output color, applied to the whole image at once. It is a global color grade. It cannot target a region, change content, or relight a scene. That boundary is the whole supported-versus-unsupported distinction.
The text format that stores a LUT. Here it is standardized to 17 samples per axis, which is 4,913 color nodes, in sRGB at a D65 white point with trilinear interpolation between nodes.
CIEDE2000, a perceptual measure of how far apart two colors look. Lower is closer to the target. It is the fidelity metric used at fit acceptance, at the quality gates, and when checking the tokenizer.
Peak signal-to-noise ratio, in decibels, comparing a reconstructed LUT to the original. Higher is better. Used to confirm the compact token representation stays faithful.
The gate verdict. Gold is a faithful global fit and is the only tier eligible for headline scoring. Diagnostic is usable for training but outside gold bounds. Rejected fails a threshold and is dropped.
The color deltas probed from a LUT: temperature and tint, exposure, contrast shape (toe and shoulder), saturation, shadow and highlight shifts, a matte axis, a global hue cast with per-tone-region hue, per-hue saturation across seven hue sectors, plus skin-tone and smoothness safety. This measurement, not the caption, is the authority for every color claim.
A structured, high-resolution color-attribute request over the same behavior_v2 axes, serialized to a deterministic, round-trippable attribute_spec_text such as route=grade | warmer=+2.3 muted=+2.0 | conf=0.82. A measured spec (from a LUT) and a requested spec (from text) are directly comparable; every asserted axis must be backed by a measurable one.
A separate small language model that maps any user text to an AttributeSpec plus a route (grade, clarify, or refuse). It is trained by distillation outside the generator loop and never touches the frozen tokenizer or corpus.
The Qwen2.5-VL-3B QLoRA model, conditioned on the serialized AttributeSpec plus the image, that emits the 64 LUT code tokens (or a refusal). It is the same model as the earlier one-stage design; only its input changed from free text to the spec.
The interpreter's three-way decision. grade is a representable global look. clarify is a valid but under-specified request (no measurable direction). refuse is either out_of_scope (not one global LUT) or out_of_gamut (a global look the frozen tokenizer cannot represent).
A vision-capable teacher that writes many phrasings for one LUT — literal, metaphor, mood, concept, and slang — all mapping to the same measured AttributeSpec. Captioning, not a single teacher-written instruction, is v1's language supervision. A verifier checks that every caption is backed by the measurement.
A vector-quantized encoder compresses a LUT into 64 discrete tokens drawn from a 256-entry codebook. This is what the model learns to emit for a supported request, instead of thousands of raw numbers.
For a request a single global LUT cannot satisfy, the correct answer is the literal token <unsupported>. Refusals split into out_of_scope (local, semantic, content, relighting, geometry, reference-transfer work) and out_of_gamut (global looks the tokenizer cannot represent). The dataset teaches this on purpose, including mixed prompts that combine a valid change with an impossible one.
Training the generator on pairs of input (image plus attribute_spec_text) and target (64 LUT tokens or a refusal). The active set of 3,033 rows is exactly this training corpus.