Files
lightcraft/plan/phase-2.md
T
Andrew FrahnandClaude Opus 5.5 6cbc0eb5a9 Phase 0: configurable brand, MIT licence, plan/ and Immich tracker
- 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>
2026-10-10 10:30:06 +11:00

234 lines
12 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](../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.md` with 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:**
1. **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 `Software` tag, never from
Adobe-converted DNGs) can seed `camdb` entries.
2. **Our own chart calibration** (2.2). This is the main source of "measured" entries.
3. **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-harvest` over the CC0 corpus;
- **black and white levels, crops, CFA layout** from the files and our decoders;
- **noise profiles**:
- `app-cli noise-profile` fits 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.
4. **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 calibrate` takes 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 `camdb` entry 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 contribute` packages 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`/`lensdb` entries.
- **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.rs` does.
- **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.rs` checks per model: sensor decode (not preview), plausible levels, colour sanity
against the camera JPEG (median ΔE threshold);
- a coverage report in `docs/cameras.md` is generated by xtask.
---
## 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 in
`data/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-calibrate` with distortion from straight-line or grid shots, vignetting from flat
fields and TCA from edges, writing `data/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 `camdb` noise 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).
- **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 and `best_offset`;
- `algo/src/content_aware.rs`: restricted sampling area, rotation/scale/mirror adaptation, Poisson colour
adaptation.
- Put them in a new L2 **`inpaint`** crate and copy-rename them as LightCraft did with `heif`.
- 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.
- **Metrics:** ΔE2000 (mean, p95), SSIM, histogram distance, and highlight-clipping location per sweep step.
- **Tuning targets, in order:**
1. default rendering;
2. exposure, highlights, shadows, whites, blacks;
3. texture, clarity, dehaze;
4. NR and sharpening;
5. 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 `moxcms` for output transforms.
- Keep one CMS path, so the preview equals the export.
- **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 ✅.