geopyv-dev 0.2.0__tar.gz → 0.2.2__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 (160) hide show
  1. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/CLAUDE.md +7 -1
  2. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/Cargo.lock +4 -3
  3. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/Cargo.toml +2 -2
  4. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/PKG-INFO +1 -1
  5. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/tutorials/02_subset.rst +12 -4
  6. geopyv_dev-0.2.2/geopyv-gui/Cargo.toml +24 -0
  7. geopyv_dev-0.2.2/geopyv-gui/src/app.rs +287 -0
  8. geopyv_dev-0.2.2/geopyv-gui/src/colormap.rs +107 -0
  9. geopyv_dev-0.2.2/geopyv-gui/src/draw.rs +805 -0
  10. geopyv_dev-0.2.2/geopyv-gui/src/field_tab.rs +1011 -0
  11. geopyv_dev-0.2.2/geopyv-gui/src/field_view.rs +1046 -0
  12. geopyv_dev-0.2.2/geopyv-gui/src/image_viewer.rs +254 -0
  13. geopyv_dev-0.2.2/geopyv-gui/src/lib.rs +44 -0
  14. geopyv_dev-0.2.2/geopyv-gui/src/main.rs +3 -0
  15. geopyv_dev-0.2.2/geopyv-gui/src/main_window.rs +1211 -0
  16. geopyv_dev-0.2.2/geopyv-gui/src/mesh_tab.rs +1607 -0
  17. geopyv_dev-0.2.2/geopyv-gui/src/overlay.rs +476 -0
  18. geopyv_dev-0.2.2/geopyv-gui/src/particle_tab.rs +896 -0
  19. geopyv_dev-0.2.2/geopyv-gui/src/progress.rs +76 -0
  20. geopyv_dev-0.2.2/geopyv-gui/src/project.rs +294 -0
  21. geopyv_dev-0.2.2/geopyv-gui/src/recent.rs +50 -0
  22. geopyv_dev-0.2.2/geopyv-gui/src/sequence_tab.rs +1720 -0
  23. geopyv_dev-0.2.2/geopyv-gui/src/subset_tab.rs +1668 -0
  24. geopyv_dev-0.2.2/geopyv_dev/gui.py +21 -0
  25. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/geopyv_dev/plots.py +48 -83
  26. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/pyproject.toml +4 -1
  27. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/python/Cargo.toml +2 -1
  28. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/python/src/lib.rs +2 -0
  29. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/python/src/py_field.rs +66 -1
  30. geopyv_dev-0.2.2/python/src/py_gui.rs +21 -0
  31. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/python/src/py_sequence.rs +1 -1
  32. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/python/src/py_subset.rs +6 -10
  33. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/error.rs +4 -0
  34. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/field.rs +605 -3
  35. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/geometry/utilities.rs +33 -0
  36. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/image.rs +3 -0
  37. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/io.rs +171 -9
  38. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/lib.rs +1 -0
  39. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/masks.rs +21 -0
  40. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/mesh.rs +72 -30
  41. geopyv_dev-0.2.2/src/progress.rs +118 -0
  42. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/sequence.rs +117 -29
  43. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/subset.rs +209 -30
  44. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/validation.rs +2 -0
  45. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/tests/python/io/test_io.py +1 -1
  46. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/tests/python/plots/test_plots.py +113 -0
  47. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/tests/python/subset/test_subset.py +36 -0
  48. geopyv_dev-0.2.2/tests/solve_timing.rs +181 -0
  49. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/.github/workflows/release.yml +0 -0
  50. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/.gitignore +0 -0
  51. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/.readthedocs.yaml +0 -0
  52. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/LICENSE +0 -0
  53. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/Makefile +0 -0
  54. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/README.md +0 -0
  55. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/basictest.py +0 -0
  56. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/_static/gifs/intro_hero.gif +0 -0
  57. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/_static/gifs/quad_deformation.gif +0 -0
  58. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/_static/gifs/region_motion.gif +0 -0
  59. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/_static/gifs/sequence_distortion.gif +0 -0
  60. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/_static/gifs/subset_shape_order.gif +0 -0
  61. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/_static/img/c2_subset.png +0 -0
  62. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/_static/img/combined_25_standard_error.png +0 -0
  63. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/_static/img/mask_local_vs_global.png +0 -0
  64. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/_static/img/mesh_solve_flow.png +0 -0
  65. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/_static/img/sequence_solve_flow.png +0 -0
  66. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/_static/img/subset_quality.png +0 -0
  67. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/api/index.rst +0 -0
  68. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/conf.py +0 -0
  69. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/index.rst +0 -0
  70. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/installation.rst +0 -0
  71. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/requirements-docs.txt +0 -0
  72. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/scripts/build_images_assets.py +0 -0
  73. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/scripts/build_intro_gif.py +0 -0
  74. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/scripts/build_mesh_flow.py +0 -0
  75. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/scripts/build_region_motion_gif.py +0 -0
  76. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/scripts/build_sequence_distortion_gif.py +0 -0
  77. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/scripts/build_sequence_flow.py +0 -0
  78. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/scripts/build_subset_assets.py +0 -0
  79. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/scripts/common.py +0 -0
  80. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/tutorials/00_introduction.rst +0 -0
  81. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/tutorials/01_images_and_masks.rst +0 -0
  82. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/tutorials/03_mesh.rst +0 -0
  83. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/tutorials/04_sequence.rst +0 -0
  84. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/tutorials/05_particle.rst +0 -0
  85. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/tutorials/06_field.rst +0 -0
  86. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/tutorials/07_validation.rst +0 -0
  87. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/tutorials/08_calibration.rst +0 -0
  88. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/docs/tutorials/index.rst +0 -0
  89. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/geopyv-dev-tutorials/00_introduction.ipynb +0 -0
  90. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/geopyv-dev-tutorials/01_images_and_masks.ipynb +0 -0
  91. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/geopyv-dev-tutorials/02_subset.ipynb +0 -0
  92. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/geopyv-dev-tutorials/03_mesh.ipynb +0 -0
  93. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/geopyv-dev-tutorials/04_sequence.ipynb +0 -0
  94. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/geopyv-dev-tutorials/05_particle_and_field.ipynb +0 -0
  95. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/geopyv-dev-tutorials/data/sequence/seq_0.jpg +0 -0
  96. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/geopyv-dev-tutorials/data/sequence/seq_1.jpg +0 -0
  97. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/geopyv-dev-tutorials/data/sequence/seq_2.jpg +0 -0
  98. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/geopyv-dev-tutorials/data/sequence/seq_3.jpg +0 -0
  99. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/geopyv-dev-tutorials/data/sequence/seq_4.jpg +0 -0
  100. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/geopyv-dev-tutorials/data/single/intro_0.jpg +0 -0
  101. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/geopyv-dev-tutorials/data/single/intro_1.jpg +0 -0
  102. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/geopyv_dev/__init__.py +0 -0
  103. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/geopyv_dev/calibration.py +0 -0
  104. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/geopyv_dev/wrappers.py +0 -0
  105. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/SB/SB_0.jpg +0 -0
  106. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/SB/SB_1.jpg +0 -0
  107. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/SB/SB_2.jpg +0 -0
  108. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/SB/SB_3.jpg +0 -0
  109. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/SB/SB_4.jpg +0 -0
  110. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/SB/SB_5.jpg +0 -0
  111. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/SB/speckle_SB.pyv +0 -0
  112. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/comp/compression_0.jpg +0 -0
  113. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/comp/compression_1.jpg +0 -0
  114. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/comp/compression_10.jpg +0 -0
  115. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/comp/compression_2.jpg +0 -0
  116. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/comp/compression_3.jpg +0 -0
  117. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/comp/compression_4.jpg +0 -0
  118. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/comp/compression_5.jpg +0 -0
  119. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/comp/compression_6.jpg +0 -0
  120. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/comp/compression_7.jpg +0 -0
  121. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/comp/compression_8.jpg +0 -0
  122. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/comp/compression_9.jpg +0 -0
  123. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/quality/quality_speckle.jpg +0 -0
  124. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/shear/shear_0.jpg +0 -0
  125. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/shear/shear_1.jpg +0 -0
  126. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/shear/shear_2.jpg +0 -0
  127. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/shear/shear_3.jpg +0 -0
  128. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/shear/shear_4.jpg +0 -0
  129. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/images/shear/speckle_shear.pyv +0 -0
  130. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/python/geopyv-dev/__init__.py +0 -0
  131. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/python/src/py_calibration.rs +0 -0
  132. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/python/src/py_geometry.rs +0 -0
  133. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/python/src/py_image.rs +0 -0
  134. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/python/src/py_io.rs +0 -0
  135. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/python/src/py_mask.rs +0 -0
  136. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/python/src/py_mesh.rs +0 -0
  137. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/python/src/py_particle.rs +0 -0
  138. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/python/src/py_speckle.rs +0 -0
  139. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/python/src/py_validation.rs +0 -0
  140. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/python/src/utils.rs +0 -0
  141. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/calibration.rs +0 -0
  142. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/geometry/meshing.rs +0 -0
  143. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/geometry/mod.rs +0 -0
  144. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/geometry/rasterize.rs +0 -0
  145. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/geometry/region.rs +0 -0
  146. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/geometry/triangulation.rs +0 -0
  147. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/particle.rs +0 -0
  148. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/src/speckle.rs +0 -0
  149. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/tests/python/calibration/test_calibration.py +0 -0
  150. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/tests/python/field/test_field.py +0 -0
  151. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/tests/python/geometry/test_geometry.py +0 -0
  152. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/tests/python/image/test_image.py +0 -0
  153. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/tests/python/mesh/test_mesh.py +0 -0
  154. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/tests/python/particle/test_particle.py +0 -0
  155. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/tests/python/plots/__init__.py +0 -0
  156. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/tests/python/plots/conftest.py +0 -0
  157. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/tests/python/sequence/test_sequence.py +0 -0
  158. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/tests/python/templates/test_templates.py +0 -0
  159. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/tests/python/validation/test_validation.py +0 -0
  160. {geopyv_dev-0.2.0 → geopyv_dev-0.2.2}/tests/python/validation/test_validation_solve.py +0 -0
@@ -34,13 +34,19 @@ pytest tests/python/mesh/test_mesh.py
34
34
  source .venv/bin/activate && python basictest.py
35
35
  ```
36
36
 
37
- ### Build the GUI (separate binary crate)
37
+ ### Build the GUI
38
38
 
39
39
  ```bash
40
40
  cargo build -p geopyv-gui
41
41
  cargo run -p geopyv-gui
42
42
  ```
43
43
 
44
+ `geopyv-gui` is a library (`geopyv_gui::run`) plus a thin binary. The Python
45
+ extension links it and exposes `run_gui`; `pyproject.toml` installs a
46
+ `geopyv-gui` console script (`geopyv_dev/gui.py`), so after `maturin develop`
47
+ or `pip install geopyv-dev` users just run `geopyv-gui`. The GUI is a thin
48
+ front-end: solving, defaults and derived quantities belong in the core.
49
+
44
50
  ### Publishing a release to PyPI
45
51
 
46
52
  The `.github/workflows/release.yml` workflow triggers **only on version tags** — pushing a branch does nothing. Steps:
@@ -1531,7 +1531,7 @@ dependencies = [
1531
1531
 
1532
1532
  [[package]]
1533
1533
  name = "geopyv-dev"
1534
- version = "0.2.0"
1534
+ version = "0.2.2"
1535
1535
  dependencies = [
1536
1536
  "bincode",
1537
1537
  "image",
@@ -1550,9 +1550,10 @@ dependencies = [
1550
1550
 
1551
1551
  [[package]]
1552
1552
  name = "geopyv-dev-python"
1553
- version = "0.2.0"
1553
+ version = "0.2.2"
1554
1554
  dependencies = [
1555
1555
  "geopyv-dev",
1556
+ "geopyv-gui",
1556
1557
  "ndarray",
1557
1558
  "numpy",
1558
1559
  "pyo3",
@@ -1560,7 +1561,7 @@ dependencies = [
1560
1561
 
1561
1562
  [[package]]
1562
1563
  name = "geopyv-gui"
1563
- version = "0.2.0"
1564
+ version = "0.2.2"
1564
1565
  dependencies = [
1565
1566
  "dirs",
1566
1567
  "eframe",
@@ -1,10 +1,10 @@
1
1
  [workspace]
2
- members = [".", "python"]
2
+ members = [".", "python", "geopyv-gui"]
3
3
  resolver = "2"
4
4
 
5
5
  [package]
6
6
  name = "geopyv-dev"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  edition = "2024"
9
9
 
10
10
  [dependencies]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: geopyv_dev
3
- Version: 0.2.0
3
+ Version: 0.2.2
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
@@ -33,8 +33,9 @@ A ``Subset`` is defined by four things:
33
33
  - The patch's radius (circle) or half-width (square), set on the
34
34
  ``local_mask`` itself
35
35
  * - Order
36
- - ``subset_order`` — 1 (affine) or 2 (quadratic); how complex a warp the
37
- solver is allowed to fit (see *The warp function*, below)
36
+ - ``subset_order`` — 0 (translation), 1 (affine), or 2 (quadratic); how
37
+ complex a warp the solver is allowed to fit (see *The warp function*,
38
+ below). Default ``1``.
38
39
 
39
40
  .. image:: /_static/gifs/subset_shape_order.gif
40
41
  :alt: A circle and a square subset template growing and shrinking, then fixed
@@ -117,7 +118,12 @@ The warp function
117
118
  -------------------
118
119
 
119
120
  The DIC solver describes how a subset deforms using a **warp vector** ``p``. For
120
- **order 1** (affine, 6 parameters)::
121
+ **order 0** (translation only, 2 parameters)::
122
+
123
+ u(x,y) = p[0]
124
+ v(x,y) = p[1]
125
+
126
+ For **order 1** (affine, 6 parameters)::
121
127
 
122
128
  u(x,y) = p[0] + p[2]·Δx + p[4]·Δy
123
129
  v(x,y) = p[1] + p[3]·Δx + p[5]·Δy
@@ -130,7 +136,9 @@ ordering used by ``Speckle``'s ``comp`` array in :doc:`00_introduction` and
130
136
 
131
137
  For **order 2** (quadratic, 12 parameters), six second-order terms
132
138
  (``p[6..11]``) are added. Most analyses use order 1 for ``Subset`` and order 2
133
- for ``Mesh``/``Sequence``.
139
+ for ``Mesh``/``Sequence``; order 0 is mainly useful as a deliberately
140
+ under-specified baseline, e.g. for warp-adequacy diagnostics that compare a
141
+ solve's residual against the modes a lower order couldn't fit.
134
142
 
135
143
  Solve
136
144
  ------
@@ -0,0 +1,24 @@
1
+ [package]
2
+ name = "geopyv-gui"
3
+ version = "0.2.2"
4
+ edition = "2024"
5
+
6
+ [lib]
7
+ name = "geopyv_gui"
8
+ path = "src/lib.rs"
9
+
10
+ [[bin]]
11
+ name = "geopyv-gui"
12
+ path = "src/main.rs"
13
+
14
+ [dependencies]
15
+ geopyv-dev = { path = ".." }
16
+ eframe = { version = "0.31", features = ["default"] }
17
+ egui = "0.31"
18
+ egui_plot = "0.31"
19
+ rfd = "0.15"
20
+ serde = { version = "1", features = ["derive"] }
21
+ serde_json = "1"
22
+ dirs = "6"
23
+ image = { version = "0.25", default-features = false, features = ["jpeg", "png", "tiff"] }
24
+ ndarray = "0.16"
@@ -0,0 +1,287 @@
1
+ use eframe::egui;
2
+
3
+ use crate::main_window::MainWindow;
4
+ use crate::project::Project;
5
+ use crate::recent::RecentProjects;
6
+
7
+ // ---------------------------------------------------------------------------
8
+ // Top-level application state
9
+ // ---------------------------------------------------------------------------
10
+
11
+ pub struct GeopyvApp {
12
+ state: AppState,
13
+ recent: RecentProjects,
14
+ error: Option<String>,
15
+ }
16
+
17
+ enum AppState {
18
+ Startup,
19
+ ProjectOpen {
20
+ project: Project,
21
+ window: MainWindow,
22
+ },
23
+ }
24
+
25
+ impl GeopyvApp {
26
+ pub fn new(cc: &eframe::CreationContext<'_>) -> Self {
27
+ configure_visuals(&cc.egui_ctx);
28
+ Self {
29
+ state: AppState::Startup,
30
+ recent: RecentProjects::load(),
31
+ error: None,
32
+ }
33
+ }
34
+
35
+ // -----------------------------------------------------------------------
36
+ // Startup window
37
+ // -----------------------------------------------------------------------
38
+
39
+ fn show_startup(&mut self, ctx: &egui::Context) {
40
+ // Constrain the startup window to a compact size.
41
+ ctx.send_viewport_cmd(egui::ViewportCommand::MinInnerSize(egui::vec2(400.0, 260.0)));
42
+ ctx.send_viewport_cmd(egui::ViewportCommand::MaxInnerSize(egui::vec2(560.0, 600.0)));
43
+
44
+ egui::CentralPanel::default().show(ctx, |ui| {
45
+ ui.vertical_centered(|ui| {
46
+ ui.add_space(40.0);
47
+
48
+ ui.heading(egui::RichText::new("geopyv").size(32.0).strong());
49
+ ui.add_space(4.0);
50
+ ui.label(
51
+ egui::RichText::new("Digital Image Correlation")
52
+ .size(16.0)
53
+ .color(ui.visuals().weak_text_color()),
54
+ );
55
+
56
+ ui.add_space(32.0);
57
+
58
+ let btn_size = egui::vec2(180.0, 36.0);
59
+
60
+ if ui
61
+ .add_sized(btn_size, egui::Button::new("New Project"))
62
+ .clicked()
63
+ {
64
+ self.action_new_project();
65
+ }
66
+
67
+ ui.add_space(8.0);
68
+
69
+ if ui
70
+ .add_sized(btn_size, egui::Button::new("Open Project"))
71
+ .clicked()
72
+ {
73
+ self.action_open_project();
74
+ }
75
+
76
+ self.show_recent_list(ui);
77
+ });
78
+ });
79
+ }
80
+
81
+ fn show_recent_list(&mut self, ui: &mut egui::Ui) {
82
+ if self.recent.paths.is_empty() {
83
+ return;
84
+ }
85
+
86
+ ui.add_space(28.0);
87
+ ui.separator();
88
+ ui.add_space(8.0);
89
+ ui.label(
90
+ egui::RichText::new("Recent")
91
+ .size(14.0)
92
+ .color(ui.visuals().weak_text_color()),
93
+ );
94
+ ui.add_space(4.0);
95
+
96
+ let mut open_path: Option<std::path::PathBuf> = None;
97
+ let mut remove_path: Option<std::path::PathBuf> = None;
98
+
99
+ for path in &self.recent.paths {
100
+ let label = path
101
+ .file_name()
102
+ .map(|n| n.to_string_lossy().into_owned())
103
+ .unwrap_or_else(|| path.to_string_lossy().into_owned());
104
+
105
+ let exists = path.is_dir() && path.join("project.json").exists();
106
+
107
+ ui.horizontal(|ui| {
108
+ let resp = ui.add(
109
+ egui::Button::new(
110
+ egui::RichText::new(&label).size(15.0).color(if exists {
111
+ ui.visuals().text_color()
112
+ } else {
113
+ ui.visuals().weak_text_color()
114
+ }),
115
+ )
116
+ .frame(false),
117
+ );
118
+
119
+ if resp.clicked() {
120
+ if exists {
121
+ open_path = Some(path.clone());
122
+ } else {
123
+ remove_path = Some(path.clone());
124
+ }
125
+ }
126
+
127
+ resp.on_hover_text(path.display().to_string());
128
+
129
+ if !exists {
130
+ ui.label(
131
+ egui::RichText::new("(missing)")
132
+ .size(13.0)
133
+ .color(ui.visuals().error_fg_color),
134
+ );
135
+ }
136
+ });
137
+ }
138
+
139
+ if let Some(p) = open_path {
140
+ self.open_project_at(p);
141
+ }
142
+ if let Some(p) = remove_path {
143
+ self.recent.remove(&p);
144
+ }
145
+ }
146
+
147
+ // -----------------------------------------------------------------------
148
+ // Project open / create actions
149
+ // -----------------------------------------------------------------------
150
+
151
+ fn action_new_project(&mut self) {
152
+ let Some(folder) = rfd::FileDialog::new()
153
+ .set_title("Choose location for new project")
154
+ .pick_folder()
155
+ else {
156
+ return;
157
+ };
158
+
159
+ match Project::create(&folder) {
160
+ Ok(project) => self.load_project(project),
161
+ Err(e) => self.error = Some(format!("Could not create project: {e}")),
162
+ }
163
+ }
164
+
165
+ fn action_open_project(&mut self) {
166
+ let Some(folder) = rfd::FileDialog::new()
167
+ .set_title("Open project folder")
168
+ .pick_folder()
169
+ else {
170
+ return;
171
+ };
172
+
173
+ self.open_project_at(folder);
174
+ }
175
+
176
+ fn open_project_at(&mut self, path: std::path::PathBuf) {
177
+ match Project::open(&path) {
178
+ Ok(project) => self.load_project(project),
179
+ Err(e) => self.error = Some(format!("Could not open project: {e}")),
180
+ }
181
+ }
182
+
183
+ fn load_project(&mut self, project: Project) {
184
+ self.recent.push(&project.root);
185
+ // Restore the main window to full size.
186
+ // (MaxInnerSize from startup mode must be lifted.)
187
+ self.state = AppState::ProjectOpen {
188
+ project,
189
+ window: MainWindow::new(),
190
+ };
191
+ }
192
+
193
+ // -----------------------------------------------------------------------
194
+ // Main window
195
+ // -----------------------------------------------------------------------
196
+
197
+ fn show_main(&mut self, ctx: &egui::Context) {
198
+ // Lift the startup size constraints.
199
+ ctx.send_viewport_cmd(egui::ViewportCommand::MinInnerSize(egui::vec2(800.0, 500.0)));
200
+ ctx.send_viewport_cmd(egui::ViewportCommand::MaxInnerSize(egui::vec2(f32::INFINITY, f32::INFINITY)));
201
+
202
+ let AppState::ProjectOpen { project, window } = &mut self.state else {
203
+ return;
204
+ };
205
+
206
+ ctx.send_viewport_cmd(egui::ViewportCommand::Title(
207
+ format!("geopyv \u{2014} {}", project.meta.name),
208
+ ));
209
+
210
+ window.show(ctx, project);
211
+ }
212
+
213
+ // -----------------------------------------------------------------------
214
+ // Error modal
215
+ // -----------------------------------------------------------------------
216
+
217
+ fn show_error_modal(&mut self, ctx: &egui::Context) {
218
+ let Some(msg) = self.error.clone() else {
219
+ return;
220
+ };
221
+
222
+ let mut open = true;
223
+ egui::Window::new("Error")
224
+ .collapsible(false)
225
+ .resizable(false)
226
+ .anchor(egui::Align2::CENTER_CENTER, egui::vec2(0.0, 0.0))
227
+ .open(&mut open)
228
+ .show(ctx, |ui| {
229
+ ui.label(&msg);
230
+ ui.add_space(8.0);
231
+ if ui.button("OK").clicked() {
232
+ self.error = None;
233
+ }
234
+ });
235
+
236
+ if !open {
237
+ self.error = None;
238
+ }
239
+ }
240
+ }
241
+
242
+ impl eframe::App for GeopyvApp {
243
+ fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) {
244
+ match &self.state {
245
+ AppState::Startup => self.show_startup(ctx),
246
+ AppState::ProjectOpen { .. } => self.show_main(ctx),
247
+ }
248
+
249
+ self.show_error_modal(ctx);
250
+ }
251
+ }
252
+
253
+ // ---------------------------------------------------------------------------
254
+ // Theme
255
+ // ---------------------------------------------------------------------------
256
+
257
+ fn configure_visuals(ctx: &egui::Context) {
258
+ let mut visuals = egui::Visuals::dark();
259
+
260
+ visuals.window_fill = egui::Color32::from_rgb(22, 22, 24);
261
+ visuals.panel_fill = egui::Color32::from_rgb(22, 22, 24);
262
+ visuals.faint_bg_color = egui::Color32::from_rgb(30, 30, 32);
263
+
264
+ visuals.window_corner_radius = egui::CornerRadius::same(6);
265
+ visuals.menu_corner_radius = egui::CornerRadius::same(4);
266
+
267
+ visuals.widgets.inactive.bg_fill = egui::Color32::from_rgb(42, 42, 46);
268
+ visuals.widgets.hovered.bg_fill = egui::Color32::from_rgb(58, 58, 64);
269
+ visuals.widgets.active.bg_fill = egui::Color32::from_rgb(70, 70, 80);
270
+
271
+ ctx.set_visuals(visuals);
272
+
273
+ let mut style = (*ctx.style()).clone();
274
+ style.text_styles.insert(
275
+ egui::TextStyle::Body,
276
+ egui::FontId::new(16.0, egui::FontFamily::Proportional),
277
+ );
278
+ style.text_styles.insert(
279
+ egui::TextStyle::Button,
280
+ egui::FontId::new(16.0, egui::FontFamily::Proportional),
281
+ );
282
+ style.text_styles.insert(
283
+ egui::TextStyle::Heading,
284
+ egui::FontId::new(22.0, egui::FontFamily::Proportional),
285
+ );
286
+ ctx.set_style(style);
287
+ }
@@ -0,0 +1,107 @@
1
+ use eframe::egui;
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Colormap type
5
+ // ---------------------------------------------------------------------------
6
+
7
+ #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8
+ pub enum ColormapType {
9
+ #[default]
10
+ Viridis,
11
+ Plasma,
12
+ Inferno,
13
+ }
14
+
15
+ impl ColormapType {
16
+ pub const ALL: &'static [ColormapType] = &[
17
+ ColormapType::Viridis,
18
+ ColormapType::Plasma,
19
+ ColormapType::Inferno,
20
+ ];
21
+
22
+ pub fn label(self) -> &'static str {
23
+ match self {
24
+ ColormapType::Viridis => "Viridis",
25
+ ColormapType::Plasma => "Plasma",
26
+ ColormapType::Inferno => "Inferno",
27
+ }
28
+ }
29
+ }
30
+
31
+ // ---------------------------------------------------------------------------
32
+ // 9-keyframe LUTs (t = 0, 0.125, 0.25, ..., 1.0)
33
+ // Values are (R, G, B) in [0, 255].
34
+ // ---------------------------------------------------------------------------
35
+
36
+ static VIRIDIS: [[u8; 3]; 9] = [
37
+ [68, 1, 84],
38
+ [71, 44, 122],
39
+ [59, 82, 139],
40
+ [44, 113, 142],
41
+ [32, 144, 140],
42
+ [35, 168, 132],
43
+ [93, 201, 99],
44
+ [161, 218, 62],
45
+ [253, 231, 37],
46
+ ];
47
+
48
+ static PLASMA: [[u8; 3]; 9] = [
49
+ [ 13, 8, 135],
50
+ [ 84, 2, 163],
51
+ [139, 10, 165],
52
+ [185, 50, 137],
53
+ [219, 92, 104],
54
+ [244, 136, 73],
55
+ [253, 183, 45],
56
+ [249, 230, 51],
57
+ [240, 249, 33],
58
+ ];
59
+
60
+ static INFERNO: [[u8; 3]; 9] = [
61
+ [ 0, 0, 4],
62
+ [ 40, 11, 84],
63
+ [101, 21, 110],
64
+ [159, 42, 99],
65
+ [212, 72, 66],
66
+ [245, 125, 21],
67
+ [252, 185, 20],
68
+ [253, 236, 98],
69
+ [252, 255, 164],
70
+ ];
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // Public API
74
+ // ---------------------------------------------------------------------------
75
+
76
+ /// Map `t ∈ [0, 1]` to an opaque colour via the chosen colormap.
77
+ pub fn sample(t: f32, cmap: ColormapType) -> egui::Color32 {
78
+ let lut: &[[u8; 3]; 9] = match cmap {
79
+ ColormapType::Viridis => &VIRIDIS,
80
+ ColormapType::Plasma => &PLASMA,
81
+ ColormapType::Inferno => &INFERNO,
82
+ };
83
+
84
+ let t = t.clamp(0.0, 1.0);
85
+ let scaled = t * 8.0; // 9 keyframes → 8 segments
86
+ let lo = (scaled as usize).min(7);
87
+ let hi = lo + 1;
88
+ let frac = scaled - lo as f32;
89
+
90
+ let r = lerp_u8(lut[lo][0], lut[hi][0], frac);
91
+ let g = lerp_u8(lut[lo][1], lut[hi][1], frac);
92
+ let b = lerp_u8(lut[lo][2], lut[hi][2], frac);
93
+ egui::Color32::from_rgb(r, g, b)
94
+ }
95
+
96
+ /// Map a scalar value to a colour given a [vmin, vmax] range.
97
+ pub fn map_value(v: f64, vmin: f64, vmax: f64, cmap: ColormapType) -> egui::Color32 {
98
+ if vmax <= vmin {
99
+ return sample(0.5, cmap);
100
+ }
101
+ let t = ((v - vmin) / (vmax - vmin)) as f32;
102
+ sample(t, cmap)
103
+ }
104
+
105
+ fn lerp_u8(a: u8, b: u8, t: f32) -> u8 {
106
+ (a as f32 + (b as f32 - a as f32) * t).round() as u8
107
+ }