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

12 KiB
Raw Blame History

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.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 ✅.