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

A newer version of the Gradio SDK is available: 6.24.0

Upgrade
metadata
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

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

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