- brand.toml is the only place the product name lives; every crate and app
reads it through dac-brand (env vars with legacy fallback, settings folder
with copy-only migration, default library, preset extensions, i18n {app})
- cargo xtask brand check|test, run|install|package, docs, rename-crates,
deny and immich; packaging files are templates rendered from brand.toml
- icons regenerated from brand/icon.svg; upstream branding removed
- MIT licence (segment and fetch stay Apache-2.0); cargo deny licence policy
- clean-room rules in AGENTS.md/CLAUDE.md; docs/upstream-merge.md
- plan/ rebuilt and committed; parity tracker reopens Classic rows, adds
PLAN.md section 2 rows and the IMM Immich section; plan/immich.md and a
pinned Immich test server
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
12 KiB
Phase 2: Image quality and camera coverage
Copy of PLAN_phase_2.md at the repository root, taken 2026-10-10. The root file is the working copy; refresh this one when it changes.
Part of PLAN.md. Estimate: 110–180 agent-hours, partly data collection. The project is MIT, so all camera data is measured or reverse-engineered by us (PLAN.md §4). Depends on: Phase 1 (engine split and the 1:1 region renderer). Runs in parallel with: Phases 3 and 4.
Goal: a photographer opening their own raws sees correct colour, correct lens geometry and Lightroom-grade detail, on nearly every camera they own. This is the gap that decides whether people switch.
Clean-room rules hold throughout (PLAN.md §4.2):
- No Adobe matrices, DCP, LCP, presets or profiles.
- No darktable, Ansel, rawspeed, LibRaw or lensfun source or GPL data tables.
- Each data source is recorded in
docs/sources.mdwith its licence and how it was obtained.
2.1 camdb crate: per-camera data (≈ 15–30 h)
Contents per camera model (keyed by make, model and the EXIF unique model id):
- colour matrices for two illuminants;
- forward matrices when known;
- white-balance presets;
- noise profile (Poisson-Gaussian a/b per ISO per channel);
- black and white levels;
- default crop and masked areas;
- CFA layout;
- aliases (marketing names, regional variants).
Format: a compact binary table generated at build time from human-editable TOML in data/cameras/, lazily
loaded. One file per maker.
Sources, in priority order:
- Data the file carries itself:
- DNG ColorMatrix/ForwardMatrix at runtime;
- Olympus and Pentax maker-note matrices;
- camera-native DNGs (Leica, Pentax, Ricoh, Hasselblad, phones; checked via the
Softwaretag, never from Adobe-converted DNGs) can seedcamdbentries.
- Our own chart calibration (2.2). This is the main source of "measured" entries.
- Measured by our tools from raw files:
- WB presets read from each maker's own maker notes (many bodies store multipliers for every preset), via
xtask camdb-harvestover the CC0 corpus; - black and white levels, crops, CFA layout from the files and our decoders;
- noise profiles:
app-cli noise-profilefits a Poisson-Gaussian model (a, b per channel) from an ISO series of a defocused flat target;- the same model can also be estimated automatically from flat regions of ordinary corpus raws, as a fallback.
- WB presets read from each maker's own maker notes (many bodies store multipliers for every preset), via
- LightCraft's per-file embedded-JPEG fit, which already exists. It is the fallback when nothing above has the model, and is marked "estimated" in the Info panel.
Entries record provenance (source = "chart" | "native-dng" | "maker-note" | "estimated") and the contributor.
Pipeline use:
- the input transform chooses the matrix by illuminant (interpolating by CCT between the two);
- WB presets fill the white-balance menu;
- noise profiles feed 2.6.
Done when: every corpus camera resolves to a camdb entry or an explicit "estimated"; no camera falls back to
the neutral matrix silently.
2.2 Own camera calibration tool (≈ 8–12 h)
app-cli calibratetakes a chart shot (ColorChecker 24 in its 2000 and 2014 versions, ColorChecker SG, IT8.7/2), either auto-detected or with corners picked by the user.- It fits a 3×3 matrix (least squares on ΔE2000) and an optional 2.5D hue/sat LUT (thin-plate spline, the method darktable manual describes; implemented from that description, not from its code).
- Output is a TOML
camdbentry or a user camera profile. - Reference chart values come from published CIE/manufacturer data. CIE observer and illuminant tables are public data.
- A UI wizard in Develop → Profile → Calibrate Camera… gives users their own profiles.
- Community data pipeline. This is on the critical path, because there are no GPL tables to fall back on:
- a documented kit: which chart, lighting, ISO series, flat field and lens grid;
app-cli contributepackages the shots plus EXIF and a CC0 licence statement;- an upload target (Git LFS repo or object store);
- a CI job re-fits submitted data and opens a PR with the new
camdb/lensdbentries.
- Priority: the 50 most-used bodies, from public usage statistics, get a chart shot even if the maintainers have to buy or rent time on them.
2.3 Camera-matching profiles (≈ 6–10 h)
- For each supported maker, generate "Camera Standard / Portrait / Landscape / Neutral / Vivid"-style looks by fitting our pipeline to the camera's own JPEGs: tone curve plus a 3D LUT in our profile format.
- Use LightCraft's existing fitter (
docs/camera-preview-colour.md) on many shots per body, not per file. - Store them as our own profiles in
data/profiles/<maker>/, and list them in the profile browser under "Camera Matching".
2.4 Raw decoders: coverage (≈ 25–60 h)
| Item | Today | Work |
|---|---|---|
| Canon CR3: all CRX variants, sRAW/mRAW, dual-pixel | lossless and C-RAW verified on M50/R100/R8 only | every CRX variant and subsampled RAW; verify on ≥ 20 bodies |
| Olympus/OM compressed ORF | preview only | decoder |
| Samsung compressed SRW | preview only | decoder |
| Nikon lossy-after-split NEF | preview only | decoder; also NEFs labelled compressed but stored uncompressed |
| Long tail | none | 3FR/FFF (Hasselblad), IIQ (Phase One), MOS (Leaf), ERF (Epson), KDC/DCR (Kodak), MRW (Minolta), NRW, SRF/SR2, X3F if feasible |
| HEIC/HEIF, AVIF import | HEIC behind a feature flag | default-on HEIC decode (pure-Rust HEVC, shared heif crate), AVIF decode |
- Method: clean-room only. Use LightCraft's black-box analysis (as for compressed NEF and RW2) and prose format
descriptions. Never LibRaw, rawspeed or dcraw source. Each decoder documents its analysis in the module docs, as
vendor/rw2.rsdoes. - Risk: LightCraft rates this "high, 40–80 h". The budget covers the common formats first; the long tail falls back to embedded previews until done.
- Corpus:
- one CC0 sample per model from raw.pixls.us in
cargo xtask corpus; crates/raw/tests/corpus.rschecks per model: sensor decode (not preview), plausible levels, colour sanity against the camera JPEG (median ΔE threshold);- a coverage report in
docs/cameras.mdis generated by xtask.
- one CC0 sample per model from raw.pixls.us in
2.5 lensdb crate: lens corrections (≈ 10–18 h)
- lensfun database (CC-BY-SA 3.0, ~1,500 lenses), used as data only. lensfun's LGPL library code is never read;
the correction models are implemented from their published mathematical descriptions.
- Shipped as a separate data file, not compiled into the binary, so the MIT code stays separate from the CC-BY-SA data. It is converted at packaging time into a compact, lazily loaded table (Ansel's notes measured about 100 ms just to parse lensfun's XML).
- It can also be downloaded or updated at runtime from upstream.
- Attribution and the CC-BY-SA terms go in
ATTRIBUTION.md; edits we make to that data stay CC-BY-SA indata/lenses/and are offered back upstream. - Our own measured profiles (below) are CC0 and kept in a separate file.
- Lookup: EXIF lens id, lens model string, focal length and aperture, with crop-factor handling and mount filtering. Users can pick manually from make/model menus (the Lens Corrections → Profile UI).
- Models:
- distortion: poly3, poly5, PTLens;
- TCA: linear, poly3;
- vignetting: pa model.
- Applied in the existing geometry stage with a single resample.
- Amount sliders for distortion and vignetting.
- Our own profiles:
app-cli lens-calibratewith distortion from straight-line or grid shots, vignetting from flat fields and TCA from edges, writingdata/lenses/entries. Users can save custom profiles. - Done when: a profile is found for ≥ 80% of lens EXIFs in the corpus; straight lines stay straight within 0.5 px on test charts.
2.6 Detail: noise reduction, sharpening, Enhance (≈ 12–25 h)
- Profiled classical NR:
- use the
camdbnoise profiles (a variance-stabilising transform, then wavelet/non-local-means denoise in the stabilised domain); - keep the Luminance/Detail/Contrast and Color/Detail/Smoothness controls as they are;
- run colour NR at half resolution on both CPU and GPU (an open LightCraft M5 item).
- use the
- AI Denoise:
- LightCraft has Bayer-only inference with opt-in GPL weights that the user downloads separately; they are never bundled with the MIT app. The goal is our own CC0/MIT weights.
- Follow the design described in Ansel's prose note
doc/rawdenoiseai.md(not its code): a small U-Net conditioned on a per-pixel sigma map from the noise profile, deterministic, no external runtime. - Extend it to X-Trans and linear DNG.
- Output, as Lightroom does: a new DNG stacked with the original, plus Amount.
- Weights: train our own on CC0 / own pairs (preferred), or choose a permissively licensed model. That is an owner decision shared with Phase 5.
- Raw Details: better demosaic with reduced false colour and artifacts; offer the demosaic choice (AHD, PPG, and later RCD/AMaZE-class) as an "Enhance" output.
- Super Resolution (2×): classical edge-directed upscaling first; an ML model later (Phase 5).
- Sharpening keeps its masking preview (Alt-drag) at true 1:1, which Phase 1.6 makes possible.
2.7 Remove tool: content-aware (≈ 6–10 h)
- Port PhotoCraft:
algo/src/inpaint.rs: multi-scale Wexler EM with PatchMatch NNF andbest_offset;algo/src/content_aware.rs: restricted sampling area, rotation/scale/mirror adaptation, Poisson colour adaptation.
- Put them in a new L2
inpaintcrate and copy-rename them as LightCraft did withheif. - Make it resolution independent:
- store strokes in normalised coordinates;
- synthesise at preview scale for interaction and at full scale for export;
- cache results per stroke hash.
- Spot pin editing: drag the source, change the size, switch Remove/Heal/Clone, feather and opacity.
- Detect (dust spots):
- a "Visualize Spots" threshold already exists;
- add automatic spot detection as a sensor-dust finder: same position across photos from one body, low-contrast blobs;
- add sync of spots across a selection.
2.8 Render fidelity suite (≈ 8–14 h)
- References: renders made by the maintainer's own Lightroom Classic copy, from CC0 raws, with settings sweeps.
- They are stored locally only (never committed) in
plan/observations/fidelity/. - The reference pixels themselves are never shipped.
- They are stored locally only (never committed) in
- Metrics: ΔE2000 (mean, p95), SSIM, histogram distance, and highlight-clipping location per sweep step.
- Tuning targets, in order:
- default rendering;
- exposure, highlights, shadows, whites, blacks;
- texture, clarity, dehaze;
- NR and sharpening;
- vignette and grain.
- Decide tone-curve placement. LightCraft applies curves after display encoding; compare against reference behaviour and move the stage if needed, behind a process-version bump so old edits render unchanged.
- CI: a small public subset compared against our own golden renders, which guards against regressions.
2.9 Soft proofing with real ICC (≈ 6–10 h)
- Port PhotoCraft's
cms: pure-Rust ICC v2/v4 parse and write, LUT profiles including CMYK↔Lab, rendering intents, black point compensation, gamut check.- Replace or complement
moxcmsfor output transforms. - Keep one CMS path, so the preview equals the export.
- Replace or complement
- Soft proof:
- choose any installed or added printer/paper ICC;
- Perceptual or Relative intent;
- Simulate Paper & Ink (paper white plus black point);
- monitor and destination gamut warnings;
- "Create Proof Copy" (a virtual copy).
- Before/after shows proof against the original.
- Display profile: read the OS monitor profile (ICC from the platform, via an isolated crate if FFI is needed) and render through it.
- Done when: proofing through a known profile matches a reference transform within ΔE 1. Print (Phase 3) uses the same code.
Exit gate
- ≥ 95% of corpus camera models decode from sensor data; coverage report generated.
- Median ΔE2000 ≤ 2 against camera JPEGs (camera-matching profile) for supported bodies; no silent neutral-matrix fallback.
- Lens profile found for ≥ 80% of corpus lens EXIFs.
- Content-aware Remove ✅, spot editing ✅, dust detection ✅.
- Fidelity suite runs; default rendering mean ΔE2000 ≤ 3 against the local references.
- Soft proofing with printer ICC and Simulate Paper & Ink ✅.