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.
- openfcd-0.0.1/.gitignore +58 -0
- openfcd-0.0.1/LICENSE +21 -0
- openfcd-0.0.1/PKG-INFO +226 -0
- openfcd-0.0.1/README.md +180 -0
- openfcd-0.0.1/openfcd/__init__.py +1 -0
- openfcd-0.0.1/openfcd/__main__.py +4 -0
- openfcd-0.0.1/openfcd/cli/__init__.py +242 -0
- openfcd-0.0.1/openfcd/cli/_output.py +42 -0
- openfcd-0.0.1/openfcd/cli/cmd_calibrate.py +120 -0
- openfcd-0.0.1/openfcd/cli/cmd_new.py +17 -0
- openfcd-0.0.1/openfcd/cli/cmd_project.py +61 -0
- openfcd-0.0.1/openfcd/cli/cmd_qc.py +145 -0
- openfcd-0.0.1/openfcd/cli/cmd_replay.py +169 -0
- openfcd-0.0.1/openfcd/cli/cmd_run.py +852 -0
- openfcd-0.0.1/openfcd/core/__init__.py +31 -0
- openfcd-0.0.1/openfcd/core/fcd.py +525 -0
- openfcd-0.0.1/openfcd/core/figures.py +30 -0
- openfcd-0.0.1/openfcd/core/flatfield.py +26 -0
- openfcd-0.0.1/openfcd/core/heading.py +44 -0
- openfcd-0.0.1/openfcd/core/inpaint.py +56 -0
- openfcd-0.0.1/openfcd/core/mask.py +324 -0
- openfcd-0.0.1/openfcd/core/polygon_source.py +102 -0
- openfcd-0.0.1/openfcd/core/profile.py +88 -0
- openfcd-0.0.1/openfcd/core/profile_composite.py +980 -0
- openfcd-0.0.1/openfcd/core/reference_builder.py +127 -0
- openfcd-0.0.1/openfcd/core/registration.py +132 -0
- openfcd-0.0.1/openfcd/core/temporal.py +80 -0
- openfcd-0.0.1/openfcd/geometry/__init__.py +0 -0
- openfcd-0.0.1/openfcd/geometry/optical.py +66 -0
- openfcd-0.0.1/openfcd/gui/__init__.py +5 -0
- openfcd-0.0.1/openfcd/gui/__main__.py +3 -0
- openfcd-0.0.1/openfcd/gui/app.py +18 -0
- openfcd-0.0.1/openfcd/gui/controllers/__init__.py +6 -0
- openfcd-0.0.1/openfcd/gui/controllers/run_controller.py +204 -0
- openfcd-0.0.1/openfcd/gui/controllers/session_controller.py +260 -0
- openfcd-0.0.1/openfcd/gui/controllers/view_router.py +28 -0
- openfcd-0.0.1/openfcd/gui/dialogs/__init__.py +5 -0
- openfcd-0.0.1/openfcd/gui/dialogs/frame_picker.py +147 -0
- openfcd-0.0.1/openfcd/gui/dialogs/image_picker.py +324 -0
- openfcd-0.0.1/openfcd/gui/dialogs/new_project_wizard.py +384 -0
- openfcd-0.0.1/openfcd/gui/icons.py +144 -0
- openfcd-0.0.1/openfcd/gui/mainwindow.py +2131 -0
- openfcd-0.0.1/openfcd/gui/panels/__init__.py +7 -0
- openfcd-0.0.1/openfcd/gui/panels/properties.py +1099 -0
- openfcd-0.0.1/openfcd/gui/panels/scene_tabs.py +91 -0
- openfcd-0.0.1/openfcd/gui/panels/sim_tree.py +571 -0
- openfcd-0.0.1/openfcd/gui/preferences.py +279 -0
- openfcd-0.0.1/openfcd/gui/renderers/__init__.py +14 -0
- openfcd-0.0.1/openfcd/gui/renderers/_eta_view.py +64 -0
- openfcd-0.0.1/openfcd/gui/renderers/eta_heatmap.py +46 -0
- openfcd-0.0.1/openfcd/gui/renderers/eta_timeseries.py +23 -0
- openfcd-0.0.1/openfcd/gui/renderers/rms_map.py +21 -0
- openfcd-0.0.1/openfcd/gui/renderers/sample_grid.py +28 -0
- openfcd-0.0.1/openfcd/gui/renderers/wavelength_profile.py +51 -0
- openfcd-0.0.1/openfcd/gui/resources/__init__.py +0 -0
- openfcd-0.0.1/openfcd/gui/resources/brand/app_icon.svg +39 -0
- openfcd-0.0.1/openfcd/gui/resources/brand/logo_mark.svg +23 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/__init__.py +0 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/arrow_back.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/arrow_forward.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/brush.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/check_circle.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/close.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/crop_square.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/dark_mode.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/error.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/folder_open.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/light_mode.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/note_add.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/pan_tool.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/pentagon.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/photo.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/photo_library.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/pie_chart.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/play_arrow.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/rectangle.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/save.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/schedule.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/speed.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/star.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/stop.svg +1 -0
- openfcd-0.0.1/openfcd/gui/resources/icons/warning.svg +1 -0
- openfcd-0.0.1/openfcd/gui/scenes/__init__.py +8 -0
- openfcd-0.0.1/openfcd/gui/scenes/annotation.py +919 -0
- openfcd-0.0.1/openfcd/gui/scenes/composite.py +36 -0
- openfcd-0.0.1/openfcd/gui/scenes/eta_map.py +133 -0
- openfcd-0.0.1/openfcd/gui/scenes/eta_map_scene.py +152 -0
- openfcd-0.0.1/openfcd/gui/scenes/image_list.py +817 -0
- openfcd-0.0.1/openfcd/gui/scenes/profile_scene.py +546 -0
- openfcd-0.0.1/openfcd/gui/scenes/rms_scene.py +179 -0
- openfcd-0.0.1/openfcd/gui/scenes/run_monitor.py +73 -0
- openfcd-0.0.1/openfcd/gui/scenes/scene_container.py +82 -0
- openfcd-0.0.1/openfcd/gui/scenes/scene_view_placeholder.py +38 -0
- openfcd-0.0.1/openfcd/gui/scenes/viz_defaults.py +83 -0
- openfcd-0.0.1/openfcd/gui/tokens.py +144 -0
- openfcd-0.0.1/openfcd/gui/widgets/__init__.py +8 -0
- openfcd-0.0.1/openfcd/gui/widgets/display_mode.py +44 -0
- openfcd-0.0.1/openfcd/gui/widgets/mpl_canvas.py +14 -0
- openfcd-0.0.1/openfcd/gui/widgets/preview_widget.py +966 -0
- openfcd-0.0.1/openfcd/gui/widgets/stage_timeline.py +166 -0
- openfcd-0.0.1/openfcd/gui/widgets/status_bar.py +119 -0
- openfcd-0.0.1/openfcd/gui/widgets/title_bar.py +170 -0
- openfcd-0.0.1/openfcd/gui/widgets/toolbar.py +364 -0
- openfcd-0.0.1/openfcd/io/__init__.py +9 -0
- openfcd-0.0.1/openfcd/io/annotation.py +215 -0
- openfcd-0.0.1/openfcd/io/image.py +48 -0
- openfcd-0.0.1/openfcd/io/project.py +178 -0
- openfcd-0.0.1/openfcd/io/result.py +159 -0
- openfcd-0.0.1/openfcd/io/scene.py +72 -0
- openfcd-0.0.1/openfcd/io/store.py +217 -0
- openfcd-0.0.1/openfcd/pipeline/__init__.py +0 -0
- openfcd-0.0.1/openfcd/pipeline/aggregate.py +49 -0
- openfcd-0.0.1/openfcd/pipeline/base.py +50 -0
- openfcd-0.0.1/openfcd/pipeline/compute.py +558 -0
- openfcd-0.0.1/openfcd/pipeline/frame.py +615 -0
- openfcd-0.0.1/openfcd/pipeline/qc.py +282 -0
- openfcd-0.0.1/openfcd/pipeline/runner.py +34 -0
- openfcd-0.0.1/pyproject.toml +58 -0
- openfcd-0.0.1/scripts/_grpmtg_make_figs.py +226 -0
- openfcd-0.0.1/scripts/_grpmtg_make_pptx.py +126 -0
- openfcd-0.0.1/scripts/golden_run_compare.py +90 -0
- openfcd-0.0.1/scripts/icon_qa.py +347 -0
- openfcd-0.0.1/tests/__init__.py +0 -0
- openfcd-0.0.1/tests/conftest.py +15 -0
- openfcd-0.0.1/tests/golden/__init__.py +0 -0
- openfcd-0.0.1/tests/golden/conftest.py +43 -0
- openfcd-0.0.1/tests/golden/test_core_golden.py +196 -0
- openfcd-0.0.1/tests/golden/test_pipeline_golden.py +195 -0
- openfcd-0.0.1/tests/integration/__init__.py +0 -0
- openfcd-0.0.1/tests/integration/test_cli.py +631 -0
- openfcd-0.0.1/tests/integration/test_compute_frame_no_wave_filament_false_positive.py +68 -0
- openfcd-0.0.1/tests/integration/test_profile_etamap_scale_parity.py +106 -0
- openfcd-0.0.1/tests/integration/test_profile_vs_preview_parity.py +86 -0
- openfcd-0.0.1/tests/integration/test_run_vs_preview_parity.py +119 -0
- openfcd-0.0.1/tests/integration/test_session_open_auto_ref.py +283 -0
- openfcd-0.0.1/tests/test_calibrate_grid.py +41 -0
- openfcd-0.0.1/tests/test_display_mode.py +82 -0
- openfcd-0.0.1/tests/test_eta_cleanup.py +257 -0
- openfcd-0.0.1/tests/test_eta_map.py +47 -0
- openfcd-0.0.1/tests/test_export_png.py +52 -0
- openfcd-0.0.1/tests/test_frame_picker.py +43 -0
- openfcd-0.0.1/tests/test_frame_picker_disabled.py +62 -0
- openfcd-0.0.1/tests/test_geometry.py +61 -0
- openfcd-0.0.1/tests/test_image_picker.py +52 -0
- openfcd-0.0.1/tests/test_image_scene_decouple.py +35 -0
- openfcd-0.0.1/tests/test_integration.py +307 -0
- openfcd-0.0.1/tests/test_main_menus.py +150 -0
- openfcd-0.0.1/tests/test_mask_filaments.py +22 -0
- openfcd-0.0.1/tests/test_optical_repair.py +134 -0
- openfcd-0.0.1/tests/test_preferences.py +89 -0
- openfcd-0.0.1/tests/test_preview_toggle.py +110 -0
- openfcd-0.0.1/tests/test_profile.py +38 -0
- openfcd-0.0.1/tests/test_profile_composite.py +352 -0
- openfcd-0.0.1/tests/test_properties.py +101 -0
- openfcd-0.0.1/tests/test_restore_state.py +253 -0
- openfcd-0.0.1/tests/test_run_controller.py +308 -0
- openfcd-0.0.1/tests/test_scaffold.py +20 -0
- openfcd-0.0.1/tests/test_scene_eta_map.py +111 -0
- openfcd-0.0.1/tests/test_scene_io.py +52 -0
- openfcd-0.0.1/tests/test_scene_persistence.py +84 -0
- openfcd-0.0.1/tests/test_scene_profile.py +330 -0
- openfcd-0.0.1/tests/test_scene_rms.py +108 -0
- openfcd-0.0.1/tests/test_scene_run_followup.py +116 -0
- openfcd-0.0.1/tests/test_session_controller.py +113 -0
- openfcd-0.0.1/tests/test_simtree_multiselect.py +73 -0
- openfcd-0.0.1/tests/test_simtree_scenes.py +34 -0
- openfcd-0.0.1/tests/test_simtree_session_reset.py +42 -0
- openfcd-0.0.1/tests/test_single_frame.py +349 -0
- openfcd-0.0.1/tests/test_speedup_regression.py +331 -0
- openfcd-0.0.1/tests/test_ux_polish.py +54 -0
- openfcd-0.0.1/tests/test_view_router.py +64 -0
- openfcd-0.0.1/tests/test_viz_defaults.py +80 -0
- openfcd-0.0.1/tests/test_viz_settings.py +230 -0
- openfcd-0.0.1/tests/test_window_geometry.py +238 -0
- openfcd-0.0.1/tests/test_wizard_prefs.py +81 -0
- openfcd-0.0.1/tests/test_zz_batch_disable_compute.py +111 -0
- openfcd-0.0.1/tests/unit/__init__.py +0 -0
- openfcd-0.0.1/tests/unit/test_annotation_flush.py +181 -0
- openfcd-0.0.1/tests/unit/test_build_reference_from_paths.py +105 -0
- openfcd-0.0.1/tests/unit/test_core.py +305 -0
- openfcd-0.0.1/tests/unit/test_eta_view.py +72 -0
- openfcd-0.0.1/tests/unit/test_figures.py +58 -0
- openfcd-0.0.1/tests/unit/test_filament_no_wave_false_positive.py +82 -0
- openfcd-0.0.1/tests/unit/test_frame_purity.py +88 -0
- openfcd-0.0.1/tests/unit/test_image_picker_auto_ref_ui.py +51 -0
- openfcd-0.0.1/tests/unit/test_io.py +154 -0
- openfcd-0.0.1/tests/unit/test_profile_composite_alignment.py +117 -0
- openfcd-0.0.1/tests/unit/test_qc.py +123 -0
- openfcd-0.0.1/tests/unit/test_registration.py +93 -0
- openfcd-0.0.1/tests/unit/test_schema.py +158 -0
openfcd-0.0.1/.gitignore
ADDED
|
@@ -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.
|
openfcd-0.0.1/README.md
ADDED
|
@@ -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"
|