Spaces:
Running on Zero
Running on Zero
| 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. | |