RAW → NEGATIVE → GRADED FRAME → POST
The director sets the vibe.
Every frame develops to it.
pqtr is an ACES-correct colour pipeline: a camera RAW becomes an ACES2065-1 negative, a look is captured from a reference, the negative is developed under that look to a graded display frame, and post finishes it for publishing — deterministically, across every body, at event tempo.
Four engines and a delivery layer
Each engine is pure compute and stands alone, with its own README.
| unit | scope | runtime |
|---|---|---|
| dneg | RAW → ACES2065-1 negative — clean-room Sony decode, sensor parts (denoise, demosaic, lens, highlights) and the IDT; per-body bundles | CPU C++ → arm64 / WASM |
| aces | the colour unit — grade + look on a negative → display frame; ACES stages as typed parts, fused by the aggregator into one WGSL kernel | WGSL → WebGPU |
| vibe | look learning — capture a style from a reference (camera JPEG or pro edit) → exposure
key + colour .cube + tone curve |
JS + native vreg |
| post | display-referred finishing after aces — clarity (spatial), effects, geometry; explicitly not colour-managed | WGSL on the shared aces host |
| user | delivery — view/ the one browser front-end (Shoot | Edit | Post),
desk/ the desktop host, edge/ the Android host |
browser / Node / Android |
RAW ─────[ dneg ]─────► ACES2065-1 negative ─┐
├─►[ aces colour ]─► display RGB ─►[ post ]─► finished frame
reference ─[ vibe ]──► key + cube + curve ───┘
Two seams, both declared interfaces
Each consumer cares how none of its inputs were made.
the negative dneg → aces
A standard ACES2065-1 OpenEXR with provenance metadata. Below it the sensor is done; above it colour begins. It is dneg's only interface — and it is immutable.
display RGB aces → post
The output of the Output Transform. Below it colour is done; above it finishing begins. post has no ACES states, no sealed pipe and no Output Transform, by design.
The third contract is the look — an exposure key, a colour cube and a tone curve — which is what binds authoring to develop. aces only ever applies a look; it never authors one.
The one constraint: the runtime is browser-shaped
WASM + WebGPU in a tab. That binds every design choice below it.
- decode plain CPU C++, no GPU and no exiftool dependency — so dneg compiles to arm64 today and WASM next.
- develop WGSL compute → WebGPU, confirmed working in the Android WebView. The browser host is a model-agnostic composer + runner that both aces and post use.
- learning runs in the same browser as pure JS, with one native helper (vreg registration) shipped on the phone too.
- the result both hosts serve the same front-end over the same HTTP contract — the phone runs the pipeline byte-identical to the desktop.
The repo is self-contained — JS / C++ / WGSL, no Python — and no GPL is linked into the runtime.
Principles
Four commitments the code is held to.
- Canonical ACES backbone. IDT → working → LMT → Output Transform → ODT: a sealed pipe of colour-space states baked from OCIO. Not tunable — that is the reproducibility guarantee.
- Effects are typed parts. A part declares the colour state it operates in; the type checker admits it only where that state holds. Sealed and open parts differ in authority, not structure. Non-colour work is not an aces part at all — it lives in post.
- Lineage-first. Every canonical element names its oracle (OCIO,
darktable-cli) and a reference; creative parts say so honestly —
provisional, or no oracle at all. - Edits are direction, not baked pixels. The negative is immutable; an edit is a JSON recipe re-rendered on demand; a vibe is a portable directed recipe, never a per-frame copy.
The model is the authority
JSON the aggregator actually reads, so it cannot drift from the code.
states.json
The colour-state type system — what a part may declare, and what the checker enforces.
pipe.json
The sealed ACES transforms. Not parts, not editable, inserted by the aggregator.
parts.json
Every part — state, WGSL call, params, lineage. Load-bearing. post carries its own, the same shape, run by the same composer.
camera.json
dneg's per-body parts, with rgb_cam baked per camera and gated for colour parity.
Where prose and JSON disagree, the JSON is right and the prose is stale. That rule is why the documents on this site are served from the repo's own files.
Running it
Two hosts, one front-end, one HTTP contract.
Desktop
user/desk/stage-aces.sh
node user/desk/producer.mjs # watches user/desk/raws/ for RAWs
Then open http://localhost:8787.
Phone (Android, no Gradle)
user/edge/scripts/stage-view.sh user/edge/scripts/build.sh user/edge/scripts/sideload.sh
End to end, on the pro sample set
cp test/local.env.example test/local.env # set RAWS (your ARW dir) + DAWN test/e2e.sh DSC00645 # any qualified-body sample
A look is display-referred and body-agnostic: capture it once from a pro sample, apply it to a negative from any body.