openfcd 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (190) hide show
  1. openfcd-0.0.1/.gitignore +58 -0
  2. openfcd-0.0.1/LICENSE +21 -0
  3. openfcd-0.0.1/PKG-INFO +226 -0
  4. openfcd-0.0.1/README.md +180 -0
  5. openfcd-0.0.1/openfcd/__init__.py +1 -0
  6. openfcd-0.0.1/openfcd/__main__.py +4 -0
  7. openfcd-0.0.1/openfcd/cli/__init__.py +242 -0
  8. openfcd-0.0.1/openfcd/cli/_output.py +42 -0
  9. openfcd-0.0.1/openfcd/cli/cmd_calibrate.py +120 -0
  10. openfcd-0.0.1/openfcd/cli/cmd_new.py +17 -0
  11. openfcd-0.0.1/openfcd/cli/cmd_project.py +61 -0
  12. openfcd-0.0.1/openfcd/cli/cmd_qc.py +145 -0
  13. openfcd-0.0.1/openfcd/cli/cmd_replay.py +169 -0
  14. openfcd-0.0.1/openfcd/cli/cmd_run.py +852 -0
  15. openfcd-0.0.1/openfcd/core/__init__.py +31 -0
  16. openfcd-0.0.1/openfcd/core/fcd.py +525 -0
  17. openfcd-0.0.1/openfcd/core/figures.py +30 -0
  18. openfcd-0.0.1/openfcd/core/flatfield.py +26 -0
  19. openfcd-0.0.1/openfcd/core/heading.py +44 -0
  20. openfcd-0.0.1/openfcd/core/inpaint.py +56 -0
  21. openfcd-0.0.1/openfcd/core/mask.py +324 -0
  22. openfcd-0.0.1/openfcd/core/polygon_source.py +102 -0
  23. openfcd-0.0.1/openfcd/core/profile.py +88 -0
  24. openfcd-0.0.1/openfcd/core/profile_composite.py +980 -0
  25. openfcd-0.0.1/openfcd/core/reference_builder.py +127 -0
  26. openfcd-0.0.1/openfcd/core/registration.py +132 -0
  27. openfcd-0.0.1/openfcd/core/temporal.py +80 -0
  28. openfcd-0.0.1/openfcd/geometry/__init__.py +0 -0
  29. openfcd-0.0.1/openfcd/geometry/optical.py +66 -0
  30. openfcd-0.0.1/openfcd/gui/__init__.py +5 -0
  31. openfcd-0.0.1/openfcd/gui/__main__.py +3 -0
  32. openfcd-0.0.1/openfcd/gui/app.py +18 -0
  33. openfcd-0.0.1/openfcd/gui/controllers/__init__.py +6 -0
  34. openfcd-0.0.1/openfcd/gui/controllers/run_controller.py +204 -0
  35. openfcd-0.0.1/openfcd/gui/controllers/session_controller.py +260 -0
  36. openfcd-0.0.1/openfcd/gui/controllers/view_router.py +28 -0
  37. openfcd-0.0.1/openfcd/gui/dialogs/__init__.py +5 -0
  38. openfcd-0.0.1/openfcd/gui/dialogs/frame_picker.py +147 -0
  39. openfcd-0.0.1/openfcd/gui/dialogs/image_picker.py +324 -0
  40. openfcd-0.0.1/openfcd/gui/dialogs/new_project_wizard.py +384 -0
  41. openfcd-0.0.1/openfcd/gui/icons.py +144 -0
  42. openfcd-0.0.1/openfcd/gui/mainwindow.py +2131 -0
  43. openfcd-0.0.1/openfcd/gui/panels/__init__.py +7 -0
  44. openfcd-0.0.1/openfcd/gui/panels/properties.py +1099 -0
  45. openfcd-0.0.1/openfcd/gui/panels/scene_tabs.py +91 -0
  46. openfcd-0.0.1/openfcd/gui/panels/sim_tree.py +571 -0
  47. openfcd-0.0.1/openfcd/gui/preferences.py +279 -0
  48. openfcd-0.0.1/openfcd/gui/renderers/__init__.py +14 -0
  49. openfcd-0.0.1/openfcd/gui/renderers/_eta_view.py +64 -0
  50. openfcd-0.0.1/openfcd/gui/renderers/eta_heatmap.py +46 -0
  51. openfcd-0.0.1/openfcd/gui/renderers/eta_timeseries.py +23 -0
  52. openfcd-0.0.1/openfcd/gui/renderers/rms_map.py +21 -0
  53. openfcd-0.0.1/openfcd/gui/renderers/sample_grid.py +28 -0
  54. openfcd-0.0.1/openfcd/gui/renderers/wavelength_profile.py +51 -0
  55. openfcd-0.0.1/openfcd/gui/resources/__init__.py +0 -0
  56. openfcd-0.0.1/openfcd/gui/resources/brand/app_icon.svg +39 -0
  57. openfcd-0.0.1/openfcd/gui/resources/brand/logo_mark.svg +23 -0
  58. openfcd-0.0.1/openfcd/gui/resources/icons/__init__.py +0 -0
  59. openfcd-0.0.1/openfcd/gui/resources/icons/arrow_back.svg +1 -0
  60. openfcd-0.0.1/openfcd/gui/resources/icons/arrow_forward.svg +1 -0
  61. openfcd-0.0.1/openfcd/gui/resources/icons/brush.svg +1 -0
  62. openfcd-0.0.1/openfcd/gui/resources/icons/check_circle.svg +1 -0
  63. openfcd-0.0.1/openfcd/gui/resources/icons/close.svg +1 -0
  64. openfcd-0.0.1/openfcd/gui/resources/icons/crop_square.svg +1 -0
  65. openfcd-0.0.1/openfcd/gui/resources/icons/dark_mode.svg +1 -0
  66. openfcd-0.0.1/openfcd/gui/resources/icons/error.svg +1 -0
  67. openfcd-0.0.1/openfcd/gui/resources/icons/folder_open.svg +1 -0
  68. openfcd-0.0.1/openfcd/gui/resources/icons/light_mode.svg +1 -0
  69. openfcd-0.0.1/openfcd/gui/resources/icons/note_add.svg +1 -0
  70. openfcd-0.0.1/openfcd/gui/resources/icons/pan_tool.svg +1 -0
  71. openfcd-0.0.1/openfcd/gui/resources/icons/pentagon.svg +1 -0
  72. openfcd-0.0.1/openfcd/gui/resources/icons/photo.svg +1 -0
  73. openfcd-0.0.1/openfcd/gui/resources/icons/photo_library.svg +1 -0
  74. openfcd-0.0.1/openfcd/gui/resources/icons/pie_chart.svg +1 -0
  75. openfcd-0.0.1/openfcd/gui/resources/icons/play_arrow.svg +1 -0
  76. openfcd-0.0.1/openfcd/gui/resources/icons/rectangle.svg +1 -0
  77. openfcd-0.0.1/openfcd/gui/resources/icons/save.svg +1 -0
  78. openfcd-0.0.1/openfcd/gui/resources/icons/schedule.svg +1 -0
  79. openfcd-0.0.1/openfcd/gui/resources/icons/speed.svg +1 -0
  80. openfcd-0.0.1/openfcd/gui/resources/icons/star.svg +1 -0
  81. openfcd-0.0.1/openfcd/gui/resources/icons/stop.svg +1 -0
  82. openfcd-0.0.1/openfcd/gui/resources/icons/warning.svg +1 -0
  83. openfcd-0.0.1/openfcd/gui/scenes/__init__.py +8 -0
  84. openfcd-0.0.1/openfcd/gui/scenes/annotation.py +919 -0
  85. openfcd-0.0.1/openfcd/gui/scenes/composite.py +36 -0
  86. openfcd-0.0.1/openfcd/gui/scenes/eta_map.py +133 -0
  87. openfcd-0.0.1/openfcd/gui/scenes/eta_map_scene.py +152 -0
  88. openfcd-0.0.1/openfcd/gui/scenes/image_list.py +817 -0
  89. openfcd-0.0.1/openfcd/gui/scenes/profile_scene.py +546 -0
  90. openfcd-0.0.1/openfcd/gui/scenes/rms_scene.py +179 -0
  91. openfcd-0.0.1/openfcd/gui/scenes/run_monitor.py +73 -0
  92. openfcd-0.0.1/openfcd/gui/scenes/scene_container.py +82 -0
  93. openfcd-0.0.1/openfcd/gui/scenes/scene_view_placeholder.py +38 -0
  94. openfcd-0.0.1/openfcd/gui/scenes/viz_defaults.py +83 -0
  95. openfcd-0.0.1/openfcd/gui/tokens.py +144 -0
  96. openfcd-0.0.1/openfcd/gui/widgets/__init__.py +8 -0
  97. openfcd-0.0.1/openfcd/gui/widgets/display_mode.py +44 -0
  98. openfcd-0.0.1/openfcd/gui/widgets/mpl_canvas.py +14 -0
  99. openfcd-0.0.1/openfcd/gui/widgets/preview_widget.py +966 -0
  100. openfcd-0.0.1/openfcd/gui/widgets/stage_timeline.py +166 -0
  101. openfcd-0.0.1/openfcd/gui/widgets/status_bar.py +119 -0
  102. openfcd-0.0.1/openfcd/gui/widgets/title_bar.py +170 -0
  103. openfcd-0.0.1/openfcd/gui/widgets/toolbar.py +364 -0
  104. openfcd-0.0.1/openfcd/io/__init__.py +9 -0
  105. openfcd-0.0.1/openfcd/io/annotation.py +215 -0
  106. openfcd-0.0.1/openfcd/io/image.py +48 -0
  107. openfcd-0.0.1/openfcd/io/project.py +178 -0
  108. openfcd-0.0.1/openfcd/io/result.py +159 -0
  109. openfcd-0.0.1/openfcd/io/scene.py +72 -0
  110. openfcd-0.0.1/openfcd/io/store.py +217 -0
  111. openfcd-0.0.1/openfcd/pipeline/__init__.py +0 -0
  112. openfcd-0.0.1/openfcd/pipeline/aggregate.py +49 -0
  113. openfcd-0.0.1/openfcd/pipeline/base.py +50 -0
  114. openfcd-0.0.1/openfcd/pipeline/compute.py +558 -0
  115. openfcd-0.0.1/openfcd/pipeline/frame.py +615 -0
  116. openfcd-0.0.1/openfcd/pipeline/qc.py +282 -0
  117. openfcd-0.0.1/openfcd/pipeline/runner.py +34 -0
  118. openfcd-0.0.1/pyproject.toml +58 -0
  119. openfcd-0.0.1/scripts/_grpmtg_make_figs.py +226 -0
  120. openfcd-0.0.1/scripts/_grpmtg_make_pptx.py +126 -0
  121. openfcd-0.0.1/scripts/golden_run_compare.py +90 -0
  122. openfcd-0.0.1/scripts/icon_qa.py +347 -0
  123. openfcd-0.0.1/tests/__init__.py +0 -0
  124. openfcd-0.0.1/tests/conftest.py +15 -0
  125. openfcd-0.0.1/tests/golden/__init__.py +0 -0
  126. openfcd-0.0.1/tests/golden/conftest.py +43 -0
  127. openfcd-0.0.1/tests/golden/test_core_golden.py +196 -0
  128. openfcd-0.0.1/tests/golden/test_pipeline_golden.py +195 -0
  129. openfcd-0.0.1/tests/integration/__init__.py +0 -0
  130. openfcd-0.0.1/tests/integration/test_cli.py +631 -0
  131. openfcd-0.0.1/tests/integration/test_compute_frame_no_wave_filament_false_positive.py +68 -0
  132. openfcd-0.0.1/tests/integration/test_profile_etamap_scale_parity.py +106 -0
  133. openfcd-0.0.1/tests/integration/test_profile_vs_preview_parity.py +86 -0
  134. openfcd-0.0.1/tests/integration/test_run_vs_preview_parity.py +119 -0
  135. openfcd-0.0.1/tests/integration/test_session_open_auto_ref.py +283 -0
  136. openfcd-0.0.1/tests/test_calibrate_grid.py +41 -0
  137. openfcd-0.0.1/tests/test_display_mode.py +82 -0
  138. openfcd-0.0.1/tests/test_eta_cleanup.py +257 -0
  139. openfcd-0.0.1/tests/test_eta_map.py +47 -0
  140. openfcd-0.0.1/tests/test_export_png.py +52 -0
  141. openfcd-0.0.1/tests/test_frame_picker.py +43 -0
  142. openfcd-0.0.1/tests/test_frame_picker_disabled.py +62 -0
  143. openfcd-0.0.1/tests/test_geometry.py +61 -0
  144. openfcd-0.0.1/tests/test_image_picker.py +52 -0
  145. openfcd-0.0.1/tests/test_image_scene_decouple.py +35 -0
  146. openfcd-0.0.1/tests/test_integration.py +307 -0
  147. openfcd-0.0.1/tests/test_main_menus.py +150 -0
  148. openfcd-0.0.1/tests/test_mask_filaments.py +22 -0
  149. openfcd-0.0.1/tests/test_optical_repair.py +134 -0
  150. openfcd-0.0.1/tests/test_preferences.py +89 -0
  151. openfcd-0.0.1/tests/test_preview_toggle.py +110 -0
  152. openfcd-0.0.1/tests/test_profile.py +38 -0
  153. openfcd-0.0.1/tests/test_profile_composite.py +352 -0
  154. openfcd-0.0.1/tests/test_properties.py +101 -0
  155. openfcd-0.0.1/tests/test_restore_state.py +253 -0
  156. openfcd-0.0.1/tests/test_run_controller.py +308 -0
  157. openfcd-0.0.1/tests/test_scaffold.py +20 -0
  158. openfcd-0.0.1/tests/test_scene_eta_map.py +111 -0
  159. openfcd-0.0.1/tests/test_scene_io.py +52 -0
  160. openfcd-0.0.1/tests/test_scene_persistence.py +84 -0
  161. openfcd-0.0.1/tests/test_scene_profile.py +330 -0
  162. openfcd-0.0.1/tests/test_scene_rms.py +108 -0
  163. openfcd-0.0.1/tests/test_scene_run_followup.py +116 -0
  164. openfcd-0.0.1/tests/test_session_controller.py +113 -0
  165. openfcd-0.0.1/tests/test_simtree_multiselect.py +73 -0
  166. openfcd-0.0.1/tests/test_simtree_scenes.py +34 -0
  167. openfcd-0.0.1/tests/test_simtree_session_reset.py +42 -0
  168. openfcd-0.0.1/tests/test_single_frame.py +349 -0
  169. openfcd-0.0.1/tests/test_speedup_regression.py +331 -0
  170. openfcd-0.0.1/tests/test_ux_polish.py +54 -0
  171. openfcd-0.0.1/tests/test_view_router.py +64 -0
  172. openfcd-0.0.1/tests/test_viz_defaults.py +80 -0
  173. openfcd-0.0.1/tests/test_viz_settings.py +230 -0
  174. openfcd-0.0.1/tests/test_window_geometry.py +238 -0
  175. openfcd-0.0.1/tests/test_wizard_prefs.py +81 -0
  176. openfcd-0.0.1/tests/test_zz_batch_disable_compute.py +111 -0
  177. openfcd-0.0.1/tests/unit/__init__.py +0 -0
  178. openfcd-0.0.1/tests/unit/test_annotation_flush.py +181 -0
  179. openfcd-0.0.1/tests/unit/test_build_reference_from_paths.py +105 -0
  180. openfcd-0.0.1/tests/unit/test_core.py +305 -0
  181. openfcd-0.0.1/tests/unit/test_eta_view.py +72 -0
  182. openfcd-0.0.1/tests/unit/test_figures.py +58 -0
  183. openfcd-0.0.1/tests/unit/test_filament_no_wave_false_positive.py +82 -0
  184. openfcd-0.0.1/tests/unit/test_frame_purity.py +88 -0
  185. openfcd-0.0.1/tests/unit/test_image_picker_auto_ref_ui.py +51 -0
  186. openfcd-0.0.1/tests/unit/test_io.py +154 -0
  187. openfcd-0.0.1/tests/unit/test_profile_composite_alignment.py +117 -0
  188. openfcd-0.0.1/tests/unit/test_qc.py +123 -0
  189. openfcd-0.0.1/tests/unit/test_registration.py +93 -0
  190. openfcd-0.0.1/tests/unit/test_schema.py +158 -0
@@ -0,0 +1,58 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *$py.class
5
+ *.egg-info/
6
+ *.egg
7
+ build/
8
+ dist/
9
+ .eggs/
10
+ .pytest_cache/
11
+ .ruff_cache/
12
+ .mypy_cache/
13
+ .coverage
14
+ htmlcov/
15
+
16
+ # Virtual envs
17
+ .venv/
18
+ venv/
19
+ env/
20
+
21
+ # macOS
22
+ .DS_Store
23
+ ._*
24
+
25
+ # Editor
26
+ .idea/
27
+ .vscode/
28
+ *.swp
29
+ *~
30
+
31
+ # OpenFCD runtime / local state
32
+ *.ofcd/autosave/
33
+ *.ofcd/runs/
34
+ reference_built.npy
35
+
36
+ # Claude Code / OMC local orchestration
37
+ .omc/
38
+ .claude/
39
+ .omx/
40
+ .sisyphus/
41
+ .code-review-graph/
42
+
43
+ # Local scratch
44
+ test_crash*.py
45
+ progress.txt
46
+ scratch/
47
+ tmp/
48
+ tests/scratch/
49
+ .gitnexus
50
+ *.bak
51
+ ~$*
52
+
53
+ # Internal dev / reference material — not for distribution or remote
54
+ design/
55
+ docs/
56
+ AGENTS.md
57
+ CLAUDE.md
58
+ *.pptx
openfcd-0.0.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 OpenFCD contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
openfcd-0.0.1/PKG-INFO ADDED
@@ -0,0 +1,226 @@
1
+ Metadata-Version: 2.4
2
+ Name: openfcd
3
+ Version: 0.0.1
4
+ Summary: Fast Checkerboard Demodulation desktop app for free-surface reconstruction
5
+ License: MIT License
6
+
7
+ Copyright (c) 2026 OpenFCD contributors
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+ License-File: LICENSE
27
+ Requires-Python: >=3.11
28
+ Requires-Dist: h5py>=3.10
29
+ Requires-Dist: loky>=3.4
30
+ Requires-Dist: matplotlib>=3.8
31
+ Requires-Dist: numpy>=1.26
32
+ Requires-Dist: pydantic>=2.6
33
+ Requires-Dist: ruamel-yaml>=0.18
34
+ Requires-Dist: scikit-image>=0.22
35
+ Requires-Dist: scipy>=1.12
36
+ Requires-Dist: threadpoolctl>=3.0
37
+ Requires-Dist: typer>=0.12
38
+ Provides-Extra: dev
39
+ Requires-Dist: pytest-cov>=5.0; extra == 'dev'
40
+ Requires-Dist: pytest>=8.0; extra == 'dev'
41
+ Requires-Dist: ruff>=0.4; extra == 'dev'
42
+ Provides-Extra: gui
43
+ Requires-Dist: pyqt6>=6.6; extra == 'gui'
44
+ Requires-Dist: pyqtgraph>=0.13; extra == 'gui'
45
+ Description-Content-Type: text/markdown
46
+
47
+ # OpenFCD
48
+
49
+ **Fast Checkerboard Demodulation for free-surface reconstruction — desktop app + CLI.**
50
+
51
+ Reconstructs the free-surface height field η(x, y, t) from high-speed video of
52
+ a checkerboard pattern viewed through water, using the Wildeman / Moisy
53
+ *synthetic schlieren* FCD algorithm. Designed for thin-film wave studies,
54
+ sloshing experiments, mm-scale swimming-robot wakes, and any free-surface
55
+ experiment that films a patterned backdrop through a transparent fluid.
56
+
57
+ ## Status
58
+
59
+ **v0.0.1** — early release. Core pipeline and GUI are functional; expect
60
+ rough edges. Contributions welcome.
61
+
62
+ ## Features
63
+
64
+ - **End-to-end desktop GUI** (PyQt6) with a STAR-CCM+-style 3-pane layout:
65
+ simulation tree · scene tabs · properties.
66
+ - **First-class CLI** (`openfcd run path.ofcd`) — GUI and CLI share the same
67
+ pipeline, producing byte-identical `results.h5`.
68
+ - **Per-frame debug mode** — compute a single frame, tune parameters live with
69
+ a slider, re-compute without re-running the whole sequence.
70
+ - **Robust to imperfect reference frames**:
71
+ - Automatic carrier-period *scale normalization* when the reference was
72
+ captured at a slightly different zoom
73
+ - Optional spatial high-pass to remove non-physical low-frequency drift from
74
+ cross-session ref/def pairs
75
+ - **Project files on disk** — `*.ofcd/` directories contain a git-friendly
76
+ `project.yaml`, snapshot runs under `runs/{id}/`, and annotations under
77
+ `annotations/default.json`.
78
+ - **HDF5 results** with SWMR so scripts can tail the output while a run is in
79
+ progress.
80
+ - **Session resume on re-open** — re-opening a previously-computed `.ofcd`
81
+ lands directly on the last saved state: frames are filtered, reference is
82
+ set, the most recent run's η overlay is restored, and the annotation
83
+ ROI/mask is populated. Stale `last_run_id` self-heals when the result
84
+ file is missing. Use *File → Re-import Frames…* to re-run the import flow
85
+ (blocked while a run is active).
86
+ - **Window/splitter geometry persisted** across sessions via `QSettings`.
87
+
88
+ ## Installation
89
+
90
+ Requires Python 3.11+.
91
+
92
+ ```bash
93
+ # Clone and install in development mode with GUI
94
+ git clone https://github.com/xunhe730/OpenFCD.git
95
+ cd OpenFCD
96
+ pip install -e '.[gui,dev]'
97
+ ```
98
+
99
+ For CLI-only usage (no PyQt6 dependency):
100
+
101
+ ```bash
102
+ pip install -e '.[dev]'
103
+ ```
104
+
105
+ ## Quick start
106
+
107
+ ### GUI
108
+
109
+ ```bash
110
+ python -m openfcd.gui
111
+ # or
112
+ python -m openfcd
113
+ ```
114
+
115
+ Workflow:
116
+
117
+ 1. **File → New Project** → point at a folder of image frames, set the
118
+ checker cell side length (mm), window thickness, fluid depth.
119
+ 2. Click a frame in the tree → **Set as Reference** (pick a flat-water frame
120
+ captured in the same session).
121
+ 3. Draw an ROI + optional occlusion polygon on the preview.
122
+ 4. Click **Compute This Frame** to preview one frame.
123
+ 5. Tune the **Highpass σ** slider if the result shows large-scale drift.
124
+ 6. Click **Run All Frames** when satisfied.
125
+
126
+ ### CLI
127
+
128
+ ```bash
129
+ # Create a new project
130
+ openfcd new myproject --image-folder /path/to/frames --pattern 'Img*.jpg'
131
+
132
+ # Edit myproject.ofcd/project.yaml for geometry & process parameters,
133
+ # then run
134
+ openfcd run myproject.ofcd
135
+
136
+ # Results land under myproject.ofcd/runs/{timestamp}/results.h5
137
+
138
+ # Render QC panels for a frame or for all frames in a run
139
+ openfcd qc-summary myproject.ofcd --run run-YYYYMMDD-HHMMSS --frame 0 -o qc.png
140
+ openfcd qc-summary myproject.ofcd --run run-YYYYMMDD-HHMMSS --all --output-dir qc/
141
+
142
+ # Estimate flat-water noise floor and calibration sensitivity
143
+ openfcd noise-floor myproject.ofcd --from-run run-YYYYMMDD-HHMMSS -o noise_floor.json
144
+ openfcd sensitivity myproject.ofcd --run run-YYYYMMDD-HHMMSS --frame 0 -o sensitivity.json
145
+ ```
146
+
147
+ ## Algorithm notes
148
+
149
+ FCD measures η by comparing the apparent displacement of a checkerboard
150
+ pattern seen through the water surface. The algorithm steps:
151
+
152
+ 1. **Flatfield** (`openfcd.core.flatfield`) — divide out smooth illumination
153
+ variation using a Gaussian background.
154
+ 2. **Carrier detection** (`openfcd.core.fcd.calculate_carriers`) — find the
155
+ two dominant spatial-frequency peaks of the checkerboard in k-space.
156
+ 3. **Scale normalization** (`openfcd.core.registration`) — if the reference
157
+ and deformed frames were shot at slightly different zooms, rescale the
158
+ reference to match the deformed frame's carrier period. Triggers at 0.5 %
159
+ mismatch.
160
+ 4. **Occlusion inpaint** — mask the robot/object and replace with a
161
+ synthesized carrier field so the FCD demodulation sees continuous texture.
162
+ 5. **Cosine taper** — soften image boundaries to suppress FFT edge artifacts.
163
+ 6. **Demodulation + phase unwrap** (`openfcd.core.fcd.fcd`) — extract the
164
+ pixel-displacement field (u, v) at each carrier.
165
+ 7. **Poisson integration** (`fftinvgrad`) — integrate (u, v) to get the
166
+ apparent depth, then calibrate to mm using the optical stack geometry.
167
+ 8. **Post-processing** — plane detrend, filament suppression, optional
168
+ high-pass filter, edge NaN margin.
169
+
170
+ `geometry.pattern_period_mm` is the legacy project-file key, but its physical
171
+ meaning is `checker_cell_mm`: the side length of one single checkerboard cell,
172
+ not the full black-white cycle. The carrier calibration assumes
173
+ `|k_phys| = π√2 / checker_cell_mm`.
174
+
175
+ Each computed frame stores a QC verdict (`PASS`, `WARN`, or `FAIL`) plus the
176
+ stats and QC maps used to reach it. The default warning/fail thresholds are
177
+ kept under `project.qc`: saturated ratio 0.001, invalid ratio 0.20,
178
+ carrier amplitude median minimum 1e-6, normalized Poisson/curl residual
179
+ warning 0.10 and fail 0.20, and phase warning at 0.7π / fail at 0.9π. These
180
+ are conservative starting points and should be calibrated against flat-water
181
+ data for each optical setup.
182
+
183
+ Physical correctness depends on the reference frame. Best results come from
184
+ a flat-water reference captured in the same acquisition session as the
185
+ deformed frames. For cross-session references the high-pass filter (slider
186
+ in the GUI, `process.highpass_sigma_px` in project.yaml) removes
187
+ low-frequency drift.
188
+
189
+ ## Project structure
190
+
191
+ ```
192
+ openfcd/
193
+ ├── core/ # FCD algorithm (fcd, flatfield, inpaint, mask, …)
194
+ ├── geometry/ # Optical-stack calibration
195
+ ├── io/ # project.yaml schema, HDF5 results, annotations
196
+ ├── pipeline/ # Stage protocol + orchestration
197
+ ├── cli/ # Typer CLI (new / run / replay / project)
198
+ └── gui/ # PyQt6 GUI (main window, panels, scenes, renderers)
199
+ ```
200
+
201
+ ## Development
202
+
203
+ ```bash
204
+ # Run tests
205
+ pytest
206
+
207
+ # Lint
208
+ ruff check openfcd tests
209
+ ```
210
+
211
+ Golden-comparison tests (`tests/golden/`) require a local BOS reference
212
+ dataset. Set `OPENFCD_BOS_ROOT` to the dataset root to enable them; otherwise
213
+ they are skipped.
214
+
215
+ ## License
216
+
217
+ [MIT](LICENSE) — see `LICENSE` for full text.
218
+
219
+ ## Acknowledgements
220
+
221
+ The FCD method is due to Moisy, Rabaud & Salsac (2009), "A synthetic
222
+ Schlieren method for the measurement of the topography of a liquid
223
+ interface", and Wildeman (2018), "Real-time quantitative Schlieren imaging
224
+ by fast Fourier demodulation of a checkered backdrop". This project's
225
+ reference implementation follows Wildeman's **pyfcd** approach, extended
226
+ with a PyQt6 GUI, CLI, and engineering polish for experimental use.
@@ -0,0 +1,180 @@
1
+ # OpenFCD
2
+
3
+ **Fast Checkerboard Demodulation for free-surface reconstruction — desktop app + CLI.**
4
+
5
+ Reconstructs the free-surface height field η(x, y, t) from high-speed video of
6
+ a checkerboard pattern viewed through water, using the Wildeman / Moisy
7
+ *synthetic schlieren* FCD algorithm. Designed for thin-film wave studies,
8
+ sloshing experiments, mm-scale swimming-robot wakes, and any free-surface
9
+ experiment that films a patterned backdrop through a transparent fluid.
10
+
11
+ ## Status
12
+
13
+ **v0.0.1** — early release. Core pipeline and GUI are functional; expect
14
+ rough edges. Contributions welcome.
15
+
16
+ ## Features
17
+
18
+ - **End-to-end desktop GUI** (PyQt6) with a STAR-CCM+-style 3-pane layout:
19
+ simulation tree · scene tabs · properties.
20
+ - **First-class CLI** (`openfcd run path.ofcd`) — GUI and CLI share the same
21
+ pipeline, producing byte-identical `results.h5`.
22
+ - **Per-frame debug mode** — compute a single frame, tune parameters live with
23
+ a slider, re-compute without re-running the whole sequence.
24
+ - **Robust to imperfect reference frames**:
25
+ - Automatic carrier-period *scale normalization* when the reference was
26
+ captured at a slightly different zoom
27
+ - Optional spatial high-pass to remove non-physical low-frequency drift from
28
+ cross-session ref/def pairs
29
+ - **Project files on disk** — `*.ofcd/` directories contain a git-friendly
30
+ `project.yaml`, snapshot runs under `runs/{id}/`, and annotations under
31
+ `annotations/default.json`.
32
+ - **HDF5 results** with SWMR so scripts can tail the output while a run is in
33
+ progress.
34
+ - **Session resume on re-open** — re-opening a previously-computed `.ofcd`
35
+ lands directly on the last saved state: frames are filtered, reference is
36
+ set, the most recent run's η overlay is restored, and the annotation
37
+ ROI/mask is populated. Stale `last_run_id` self-heals when the result
38
+ file is missing. Use *File → Re-import Frames…* to re-run the import flow
39
+ (blocked while a run is active).
40
+ - **Window/splitter geometry persisted** across sessions via `QSettings`.
41
+
42
+ ## Installation
43
+
44
+ Requires Python 3.11+.
45
+
46
+ ```bash
47
+ # Clone and install in development mode with GUI
48
+ git clone https://github.com/xunhe730/OpenFCD.git
49
+ cd OpenFCD
50
+ pip install -e '.[gui,dev]'
51
+ ```
52
+
53
+ For CLI-only usage (no PyQt6 dependency):
54
+
55
+ ```bash
56
+ pip install -e '.[dev]'
57
+ ```
58
+
59
+ ## Quick start
60
+
61
+ ### GUI
62
+
63
+ ```bash
64
+ python -m openfcd.gui
65
+ # or
66
+ python -m openfcd
67
+ ```
68
+
69
+ Workflow:
70
+
71
+ 1. **File → New Project** → point at a folder of image frames, set the
72
+ checker cell side length (mm), window thickness, fluid depth.
73
+ 2. Click a frame in the tree → **Set as Reference** (pick a flat-water frame
74
+ captured in the same session).
75
+ 3. Draw an ROI + optional occlusion polygon on the preview.
76
+ 4. Click **Compute This Frame** to preview one frame.
77
+ 5. Tune the **Highpass σ** slider if the result shows large-scale drift.
78
+ 6. Click **Run All Frames** when satisfied.
79
+
80
+ ### CLI
81
+
82
+ ```bash
83
+ # Create a new project
84
+ openfcd new myproject --image-folder /path/to/frames --pattern 'Img*.jpg'
85
+
86
+ # Edit myproject.ofcd/project.yaml for geometry & process parameters,
87
+ # then run
88
+ openfcd run myproject.ofcd
89
+
90
+ # Results land under myproject.ofcd/runs/{timestamp}/results.h5
91
+
92
+ # Render QC panels for a frame or for all frames in a run
93
+ openfcd qc-summary myproject.ofcd --run run-YYYYMMDD-HHMMSS --frame 0 -o qc.png
94
+ openfcd qc-summary myproject.ofcd --run run-YYYYMMDD-HHMMSS --all --output-dir qc/
95
+
96
+ # Estimate flat-water noise floor and calibration sensitivity
97
+ openfcd noise-floor myproject.ofcd --from-run run-YYYYMMDD-HHMMSS -o noise_floor.json
98
+ openfcd sensitivity myproject.ofcd --run run-YYYYMMDD-HHMMSS --frame 0 -o sensitivity.json
99
+ ```
100
+
101
+ ## Algorithm notes
102
+
103
+ FCD measures η by comparing the apparent displacement of a checkerboard
104
+ pattern seen through the water surface. The algorithm steps:
105
+
106
+ 1. **Flatfield** (`openfcd.core.flatfield`) — divide out smooth illumination
107
+ variation using a Gaussian background.
108
+ 2. **Carrier detection** (`openfcd.core.fcd.calculate_carriers`) — find the
109
+ two dominant spatial-frequency peaks of the checkerboard in k-space.
110
+ 3. **Scale normalization** (`openfcd.core.registration`) — if the reference
111
+ and deformed frames were shot at slightly different zooms, rescale the
112
+ reference to match the deformed frame's carrier period. Triggers at 0.5 %
113
+ mismatch.
114
+ 4. **Occlusion inpaint** — mask the robot/object and replace with a
115
+ synthesized carrier field so the FCD demodulation sees continuous texture.
116
+ 5. **Cosine taper** — soften image boundaries to suppress FFT edge artifacts.
117
+ 6. **Demodulation + phase unwrap** (`openfcd.core.fcd.fcd`) — extract the
118
+ pixel-displacement field (u, v) at each carrier.
119
+ 7. **Poisson integration** (`fftinvgrad`) — integrate (u, v) to get the
120
+ apparent depth, then calibrate to mm using the optical stack geometry.
121
+ 8. **Post-processing** — plane detrend, filament suppression, optional
122
+ high-pass filter, edge NaN margin.
123
+
124
+ `geometry.pattern_period_mm` is the legacy project-file key, but its physical
125
+ meaning is `checker_cell_mm`: the side length of one single checkerboard cell,
126
+ not the full black-white cycle. The carrier calibration assumes
127
+ `|k_phys| = π√2 / checker_cell_mm`.
128
+
129
+ Each computed frame stores a QC verdict (`PASS`, `WARN`, or `FAIL`) plus the
130
+ stats and QC maps used to reach it. The default warning/fail thresholds are
131
+ kept under `project.qc`: saturated ratio 0.001, invalid ratio 0.20,
132
+ carrier amplitude median minimum 1e-6, normalized Poisson/curl residual
133
+ warning 0.10 and fail 0.20, and phase warning at 0.7π / fail at 0.9π. These
134
+ are conservative starting points and should be calibrated against flat-water
135
+ data for each optical setup.
136
+
137
+ Physical correctness depends on the reference frame. Best results come from
138
+ a flat-water reference captured in the same acquisition session as the
139
+ deformed frames. For cross-session references the high-pass filter (slider
140
+ in the GUI, `process.highpass_sigma_px` in project.yaml) removes
141
+ low-frequency drift.
142
+
143
+ ## Project structure
144
+
145
+ ```
146
+ openfcd/
147
+ ├── core/ # FCD algorithm (fcd, flatfield, inpaint, mask, …)
148
+ ├── geometry/ # Optical-stack calibration
149
+ ├── io/ # project.yaml schema, HDF5 results, annotations
150
+ ├── pipeline/ # Stage protocol + orchestration
151
+ ├── cli/ # Typer CLI (new / run / replay / project)
152
+ └── gui/ # PyQt6 GUI (main window, panels, scenes, renderers)
153
+ ```
154
+
155
+ ## Development
156
+
157
+ ```bash
158
+ # Run tests
159
+ pytest
160
+
161
+ # Lint
162
+ ruff check openfcd tests
163
+ ```
164
+
165
+ Golden-comparison tests (`tests/golden/`) require a local BOS reference
166
+ dataset. Set `OPENFCD_BOS_ROOT` to the dataset root to enable them; otherwise
167
+ they are skipped.
168
+
169
+ ## License
170
+
171
+ [MIT](LICENSE) — see `LICENSE` for full text.
172
+
173
+ ## Acknowledgements
174
+
175
+ The FCD method is due to Moisy, Rabaud & Salsac (2009), "A synthetic
176
+ Schlieren method for the measurement of the topography of a liquid
177
+ interface", and Wildeman (2018), "Real-time quantitative Schlieren imaging
178
+ by fast Fourier demodulation of a checkered backdrop". This project's
179
+ reference implementation follows Wildeman's **pyfcd** approach, extended
180
+ with a PyQt6 GUI, CLI, and engineering polish for experimental use.
@@ -0,0 +1 @@
1
+ __version__ = "0.0.1"
@@ -0,0 +1,4 @@
1
+ from openfcd.cli import app
2
+
3
+ if __name__ == "__main__":
4
+ app()