double-exposure / README.md
Eddie Faillace
double-exposure app deploy snapshot 2026-07-21 (WP-24 calibration pass)
7dff04f
|
Raw
History Blame Contribute Delete
3.45 kB
---
title: Double Exposure Recovery
emoji: 🎞️
colorFrom: indigo
colorTo: gray
sdk: gradio
sdk_version: 6.19.0
app_file: space_app.py
pinned: false
---
# Double Exposure Recovery
Recover the two original images from an accidentally **double-exposed film negative**.
Upload a scan of the combined negative; the app proposes multiple physically-consistent
scene separations, ranks them with a hybrid physics + perceptual loss, and returns the best
reconstruction.
> Results are best-effort AI reconstructions, not perfect recoveries. Perfect separation is
> information-theoretically impossible in many cases — the goal is the *most plausible*
> reconstruction consistent with the observed film densities and natural-image statistics.
## How it works
Recovery is treated as an inverse problem grounded in film physics. Two exposures add
**linearly in light energy** on the emulsion, so after densitometry (inverting the
Hurter–Driffield characteristic curve) the total exposure `Ĥ = H₁ + H₂` is directly
measurable per pixel. The problem then reduces to splitting a known non-negative field — the
same structure as single-image reflection removal — where priors do the disambiguation.
Candidate sources (all ranked by the same hybrid loss):
- **Heuristic splits** — fast percentile/spatial baselines (the offline demo floor).
- **Residual demixing + VLM front-end** — a physics-grounded alternating loop; uses hosted
image/VLM models when API keys are present.
- **Double-DIP** — two small untrained CNNs optimized per-image against the film physics.
Fully offline, no API key. On the 50-case 256px benchmark this is the strongest offline
source (beats the heuristics in 37/45 cases). Opt-in in the UI (slower: ~1–2 min/image).
- Optional **physics-guided refinement** of the best candidate (opt-in; re-score gated so it
never returns a result worse than its input on the ranking objective).
## Running locally
```bash
python -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
cp .env.example .env # optional: add REPLICATE_API_TOKEN / ANTHROPIC_API_KEY
python -m app.main # serves on http://127.0.0.1:7860
```
**Demo mode works with no API keys** — the heuristic and Double-DIP sources are fully
offline. API keys unlock the generative demixing candidates.
### Tests
```bash
pytest -m "not slow" # fast offline suite (~10 s)
pytest # includes the slow VAE-path test (downloads weights once)
```
## Project layout
- `film_physics.py` — differentiable `PiecewiseFilmCurve` (H&D curve) + stock presets.
- `densitometry.py` — scan → density → exposure, B&W and per-channel color.
- `hybrid_loss.py` — physics + LPIPS + anti-degeneracy loss (scoring and refinement).
- `latent_optimizer.py` — gradient-based refinement (VAE latent or pixel fallback).
- `baselines/double_dip.py` — the Double-DIP offline source.
- `app/` — Gradio UI, preprocessing, candidate generation, ranking.
- `synth/` — synthetic benchmark + metrics.
- `MASTERPLAN.md` — the binding technical strategy and work-package log.
- `docs/WP9_DESIGN.md` — design for projection-guided diffusion sampling (not yet built).
## Deployment
See [`DEPLOY.md`](DEPLOY.md). The repo is Hugging Face Spaces–ready (the header above
configures the Space); the free CPU tier runs demo mode, and API keys are supplied via Space
secrets, never committed.