Fast Checkerboard Demodulation for free-surface reconstruction — desktop app + CLI.
Reconstructs the free-surface height field η(x, y, t) from high-speed video of a checkerboard pattern viewed through water, using the Wildeman / Moisy synthetic schlieren FCD algorithm. Designed for thin-film wave studies, sloshing experiments, mm-scale swimming-robot wakes, and any free-surface experiment that films a patterned backdrop through a transparent fluid.
v0.0.2 — early release. Core pipeline and GUI are functional; expect rough edges. Contributions welcome.
- Frame management — Shift multi-select + batch disable / enable / compute on frames; FramePickerDialog excludes disabled frames and the reference frame from selection; SimTree disabled-frame state resets on project switch.
- QC CLI —
openfcd qc-summary/noise-floor/sensitivitysubcommands for per-frame QC verdicts, flat-water noise-floor stats, and calibration-sensitivity analysis. - GUI polish — HiDPI-aware preview colorbar at native resolution; profile-composite heatmap & waveform x-axes aligned; unified η color range across EtaMap / EtaHeatmap / ProfileScene / ProfileComposite; Qt palette theming; annotation flushed to disk before each run.
- End-to-end desktop GUI (PyQt6) with a STAR-CCM+-style 3-pane layout: simulation tree · scene tabs · properties.
- First-class CLI (
openfcd run path.ofcd) — GUI and CLI share the same pipeline, producing byte-identicalresults.h5. - Per-frame debug mode — compute a single frame, tune parameters live with a slider, re-compute without re-running the whole sequence.
- Robust to imperfect reference frames:
- Automatic carrier-period scale normalization when the reference was captured at a slightly different zoom
- Optional spatial high-pass to remove non-physical low-frequency drift from cross-session ref/def pairs
- Project files on disk —
*.ofcd/directories contain a git-friendlyproject.yaml, snapshot runs underruns/{id}/, and annotations underannotations/default.json. - HDF5 results with SWMR so scripts can tail the output while a run is in progress.
- Session resume on re-open — re-opening a previously-computed
.ofcdlands directly on the last saved state: frames are filtered, reference is set, the most recent run's η overlay is restored, and the annotation ROI/mask is populated. Stalelast_run_idself-heals when the result file is missing. Use File → Re-import Frames… to re-run the import flow (blocked while a run is active). - Window/splitter geometry persisted across sessions via
QSettings.
Requires Python 3.11+.
# CLI only
pip install openfcd
# CLI + desktop GUI (PyQt6)
pip install 'openfcd[gui]'git clone https://github.com/xunhe730/OpenFCD.git
cd OpenFCD
pip install -e '.[gui,dev]' # editable, with GUI + test/lint extraspython -m openfcd.gui
# or
python -m openfcdWorkflow:
- File → New Project → point at a folder of image frames, set the checker cell side length (mm), window thickness, fluid depth.
- Click a frame in the tree → Set as Reference (pick a flat-water frame captured in the same session).
- Draw an ROI + optional occlusion polygon on the preview.
- Click Compute This Frame to preview one frame.
- Tune the Highpass σ slider if the result shows large-scale drift.
- Click Run All Frames when satisfied.
# Create a new project
openfcd new myproject --image-folder /path/to/frames --pattern 'Img*.jpg'
# Edit myproject.ofcd/project.yaml for geometry & process parameters,
# then run
openfcd run myproject.ofcd
# Results land under myproject.ofcd/runs/{timestamp}/results.h5
# Render QC panels for a frame or for all frames in a run
openfcd qc-summary myproject.ofcd --run run-YYYYMMDD-HHMMSS --frame 0 -o qc.png
openfcd qc-summary myproject.ofcd --run run-YYYYMMDD-HHMMSS --all --output-dir qc/
# Estimate flat-water noise floor and calibration sensitivity
openfcd noise-floor myproject.ofcd --from-run run-YYYYMMDD-HHMMSS -o noise_floor.json
openfcd sensitivity myproject.ofcd --run run-YYYYMMDD-HHMMSS --frame 0 -o sensitivity.jsonFCD measures η by comparing the apparent displacement of a checkerboard pattern seen through the water surface. The algorithm steps:
- Flatfield (
openfcd.core.flatfield) — divide out smooth illumination variation using a Gaussian background. - Carrier detection (
openfcd.core.fcd.calculate_carriers) — find the two dominant spatial-frequency peaks of the checkerboard in k-space. - Scale normalization (
openfcd.core.registration) — if the reference and deformed frames were shot at slightly different zooms, rescale the reference to match the deformed frame's carrier period. Triggers at 0.5 % mismatch. - Occlusion inpaint — mask the robot/object and replace with a synthesized carrier field so the FCD demodulation sees continuous texture.
- Cosine taper — soften image boundaries to suppress FFT edge artifacts.
- Demodulation + phase unwrap (
openfcd.core.fcd.fcd) — extract the pixel-displacement field (u, v) at each carrier. - Poisson integration (
fftinvgrad) — integrate (u, v) to get the apparent depth, then calibrate to mm using the optical stack geometry. - Post-processing — plane detrend, filament suppression, optional high-pass filter, edge NaN margin.
geometry.pattern_period_mm is the legacy project-file key, but its physical
meaning is checker_cell_mm: the side length of one single checkerboard cell,
not the full black-white cycle. The carrier calibration assumes
|k_phys| = π√2 / checker_cell_mm.
Each computed frame stores a QC verdict (PASS, WARN, or FAIL) plus the
stats and QC maps used to reach it. The default warning/fail thresholds are
kept under project.qc: saturated ratio 0.001, invalid ratio 0.20,
carrier amplitude median minimum 1e-6, normalized Poisson/curl residual
warning 0.10 and fail 0.20, and phase warning at 0.7π / fail at 0.9π. These
are conservative starting points and should be calibrated against flat-water
data for each optical setup.
Physical correctness depends on the reference frame. Best results come from
a flat-water reference captured in the same acquisition session as the
deformed frames. For cross-session references the high-pass filter (slider
in the GUI, process.highpass_sigma_px in project.yaml) removes
low-frequency drift.
openfcd/
├── core/ # FCD algorithm (fcd, flatfield, inpaint, mask, …)
├── geometry/ # Optical-stack calibration
├── io/ # project.yaml schema, HDF5 results, annotations
├── pipeline/ # Stage protocol + orchestration
├── cli/ # Typer CLI (new / run / replay / project)
└── gui/ # PyQt6 GUI (main window, panels, scenes, renderers)
# Run tests
pytest
# Lint
ruff check openfcd testsGolden-comparison tests (tests/golden/) require a local BOS reference
dataset. Set OPENFCD_BOS_ROOT to the dataset root to enable them; otherwise
they are skipped.
MIT — see LICENSE for full text.
The FCD method is due to Moisy, Rabaud & Salsac (2009), "A synthetic Schlieren method for the measurement of the topography of a liquid interface", and Wildeman (2018), "Real-time quantitative Schlieren imaging by fast Fourier demodulation of a checkered backdrop". This project's reference implementation follows Wildeman's pyfcd approach, extended with a PyQt6 GUI, CLI, and engineering polish for experimental use.