shinestacker 0.5.0__tar.gz → 1.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.

Potentially problematic release.


This version of shinestacker might be problematic. Click here for more details.

Files changed (147) hide show
  1. {shinestacker-0.5.0 → shinestacker-1.0.1}/CHANGELOG.md +17 -0
  2. {shinestacker-0.5.0/src/shinestacker.egg-info → shinestacker-1.0.1}/PKG-INFO +8 -2
  3. {shinestacker-0.5.0 → shinestacker-1.0.1}/README.md +7 -1
  4. {shinestacker-0.5.0 → shinestacker-1.0.1}/docs/balancing.md +1 -0
  5. {shinestacker-0.5.0 → shinestacker-1.0.1}/docs/gui.md +4 -3
  6. {shinestacker-0.5.0 → shinestacker-1.0.1}/docs/vignetting.md +0 -1
  7. shinestacker-1.0.1/img/gui-project-new.png +0 -0
  8. shinestacker-1.0.1/src/shinestacker/_version.py +1 -0
  9. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/algorithms/align.py +4 -12
  10. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/algorithms/balance.py +11 -9
  11. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/algorithms/depth_map.py +0 -30
  12. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/algorithms/utils.py +10 -0
  13. shinestacker-1.0.1/src/shinestacker/algorithms/vignetting.py +210 -0
  14. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/app/about_dialog.py +37 -16
  15. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/app/gui_utils.py +1 -1
  16. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/app/help_menu.py +1 -1
  17. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/app/main.py +2 -2
  18. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/app/project.py +2 -2
  19. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/config/constants.py +4 -1
  20. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/config/gui_constants.py +3 -4
  21. shinestacker-1.0.1/src/shinestacker/gui/action_config.py +363 -0
  22. shinestacker-0.5.0/src/shinestacker/gui/action_config.py → shinestacker-1.0.1/src/shinestacker/gui/action_config_dialog.py +20 -372
  23. shinestacker-1.0.1/src/shinestacker/gui/base_form_dialog.py +18 -0
  24. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/gui/colors.py +5 -6
  25. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/gui/gui_logging.py +0 -1
  26. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/gui/gui_run.py +54 -106
  27. shinestacker-1.0.1/src/shinestacker/gui/ico/shinestacker.icns +0 -0
  28. shinestacker-1.0.1/src/shinestacker/gui/ico/shinestacker.ico +0 -0
  29. shinestacker-1.0.1/src/shinestacker/gui/ico/shinestacker.png +0 -0
  30. shinestacker-1.0.1/src/shinestacker/gui/ico/shinestacker.svg +60 -0
  31. shinestacker-1.0.1/src/shinestacker/gui/main_window.py +555 -0
  32. shinestacker-1.0.1/src/shinestacker/gui/menu_manager.py +236 -0
  33. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/gui/new_project.py +75 -20
  34. shinestacker-0.5.0/src/shinestacker/gui/actions_window.py → shinestacker-1.0.1/src/shinestacker/gui/project_controller.py +166 -79
  35. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/gui/project_converter.py +6 -6
  36. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/gui/project_editor.py +248 -165
  37. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/gui/project_model.py +2 -7
  38. shinestacker-1.0.1/src/shinestacker/gui/tab_widget.py +81 -0
  39. shinestacker-1.0.1/src/shinestacker/gui/time_progress_bar.py +95 -0
  40. shinestacker-1.0.1/src/shinestacker/retouch/base_filter.py +261 -0
  41. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/brush_preview.py +0 -10
  42. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/brush_tool.py +2 -5
  43. shinestacker-1.0.1/src/shinestacker/retouch/denoise_filter.py +12 -0
  44. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/exif_data.py +10 -13
  45. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/file_loader.py +1 -1
  46. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/filter_manager.py +1 -4
  47. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/image_editor_ui.py +318 -40
  48. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/image_viewer.py +34 -11
  49. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/io_gui_handler.py +34 -30
  50. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/layer_collection.py +2 -0
  51. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/shortcuts_help.py +12 -0
  52. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/unsharp_mask_filter.py +10 -10
  53. shinestacker-1.0.1/src/shinestacker/retouch/vignetting_filter.py +69 -0
  54. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/white_balance_filter.py +46 -14
  55. {shinestacker-0.5.0 → shinestacker-1.0.1/src/shinestacker.egg-info}/PKG-INFO +8 -2
  56. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker.egg-info/SOURCES.txt +8 -4
  57. shinestacker-0.5.0/img/gui-project-new.png +0 -0
  58. shinestacker-0.5.0/src/shinestacker/_version.py +0 -1
  59. shinestacker-0.5.0/src/shinestacker/algorithms/vignetting.py +0 -164
  60. shinestacker-0.5.0/src/shinestacker/app/app_config.py +0 -22
  61. shinestacker-0.5.0/src/shinestacker/gui/ico/shinestacker.icns +0 -0
  62. shinestacker-0.5.0/src/shinestacker/gui/ico/shinestacker.ico +0 -0
  63. shinestacker-0.5.0/src/shinestacker/gui/ico/shinestacker.png +0 -0
  64. shinestacker-0.5.0/src/shinestacker/gui/main_window.py +0 -651
  65. shinestacker-0.5.0/src/shinestacker/retouch/base_filter.py +0 -128
  66. shinestacker-0.5.0/src/shinestacker/retouch/denoise_filter.py +0 -51
  67. shinestacker-0.5.0/src/shinestacker/retouch/image_editor.py +0 -197
  68. shinestacker-0.5.0/src/shinestacker/retouch/image_filters.py +0 -69
  69. {shinestacker-0.5.0 → shinestacker-1.0.1}/.coveragerc +0 -0
  70. {shinestacker-0.5.0 → shinestacker-1.0.1}/.flake8 +0 -0
  71. {shinestacker-0.5.0 → shinestacker-1.0.1}/.github/workflows/ci-multiplatform.yml +0 -0
  72. {shinestacker-0.5.0 → shinestacker-1.0.1}/.github/workflows/pylint.yml +0 -0
  73. {shinestacker-0.5.0 → shinestacker-1.0.1}/.github/workflows/pypi-publish.yml +0 -0
  74. {shinestacker-0.5.0 → shinestacker-1.0.1}/.github/workflows/release.yml +0 -0
  75. {shinestacker-0.5.0 → shinestacker-1.0.1}/.gitignore +0 -0
  76. {shinestacker-0.5.0 → shinestacker-1.0.1}/.pylintrc +0 -0
  77. {shinestacker-0.5.0 → shinestacker-1.0.1}/.readthedocs.yaml +0 -0
  78. {shinestacker-0.5.0 → shinestacker-1.0.1}/LICENSE +0 -0
  79. {shinestacker-0.5.0 → shinestacker-1.0.1}/MANIFEST.in +0 -0
  80. {shinestacker-0.5.0 → shinestacker-1.0.1}/THIRD_PARTY_LICENSES.txt +0 -0
  81. {shinestacker-0.5.0 → shinestacker-1.0.1}/docs/alignment.md +0 -0
  82. {shinestacker-0.5.0 → shinestacker-1.0.1}/docs/api.md +0 -0
  83. {shinestacker-0.5.0 → shinestacker-1.0.1}/docs/conf.py +0 -0
  84. {shinestacker-0.5.0 → shinestacker-1.0.1}/docs/focus_stacking.md +0 -0
  85. {shinestacker-0.5.0 → shinestacker-1.0.1}/docs/index.md +0 -0
  86. {shinestacker-0.5.0 → shinestacker-1.0.1}/docs/job.md +0 -0
  87. {shinestacker-0.5.0 → shinestacker-1.0.1}/docs/main.md +0 -0
  88. {shinestacker-0.5.0 → shinestacker-1.0.1}/docs/multilayer.md +0 -0
  89. {shinestacker-0.5.0 → shinestacker-1.0.1}/docs/noise.md +0 -0
  90. {shinestacker-0.5.0 → shinestacker-1.0.1}/docs/requirements.txt +0 -0
  91. {shinestacker-0.5.0 → shinestacker-1.0.1}/img/coffee.gif +0 -0
  92. {shinestacker-0.5.0 → shinestacker-1.0.1}/img/coffee_stack.jpg +0 -0
  93. {shinestacker-0.5.0 → shinestacker-1.0.1}/img/extreme-vignetting.jpg +0 -0
  94. {shinestacker-0.5.0 → shinestacker-1.0.1}/img/flies.gif +0 -0
  95. {shinestacker-0.5.0 → shinestacker-1.0.1}/img/flies_stack.jpg +0 -0
  96. {shinestacker-0.5.0 → shinestacker-1.0.1}/img/flow-diagram.png +0 -0
  97. {shinestacker-0.5.0 → shinestacker-1.0.1}/img/gui-finder.png +0 -0
  98. {shinestacker-0.5.0 → shinestacker-1.0.1}/img/gui-project-run.png +0 -0
  99. {shinestacker-0.5.0 → shinestacker-1.0.1}/img/gui-retouch.png +0 -0
  100. {shinestacker-0.5.0 → shinestacker-1.0.1}/pyproject.toml +0 -0
  101. {shinestacker-0.5.0 → shinestacker-1.0.1}/requirements.txt +0 -0
  102. {shinestacker-0.5.0 → shinestacker-1.0.1}/scripts/build_release.py +0 -0
  103. {shinestacker-0.5.0 → shinestacker-1.0.1}/scripts/git-rev-list.sh +0 -0
  104. {shinestacker-0.5.0 → shinestacker-1.0.1}/scripts/validate-tomli.py +0 -0
  105. {shinestacker-0.5.0 → shinestacker-1.0.1}/setup.cfg +0 -0
  106. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/__init__.py +0 -0
  107. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/algorithms/__init__.py +0 -0
  108. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
  109. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/algorithms/denoise.py +0 -0
  110. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/algorithms/exif.py +0 -0
  111. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/algorithms/multilayer.py +0 -0
  112. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/algorithms/noise_detection.py +0 -0
  113. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/algorithms/pyramid.py +0 -0
  114. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/algorithms/sharpen.py +0 -0
  115. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/algorithms/stack.py +0 -0
  116. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/algorithms/stack_framework.py +0 -0
  117. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/algorithms/white_balance.py +0 -0
  118. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/app/__init__.py +0 -0
  119. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/app/open_frames.py +0 -0
  120. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/app/retouch.py +0 -0
  121. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/config/__init__.py +0 -0
  122. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/config/config.py +0 -0
  123. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/core/__init__.py +0 -0
  124. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/core/colors.py +0 -0
  125. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/core/core_utils.py +0 -0
  126. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/core/exceptions.py +0 -0
  127. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/core/framework.py +0 -0
  128. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/core/logging.py +0 -0
  129. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/gui/__init__.py +0 -0
  130. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/gui/gui_images.py +0 -0
  131. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
  132. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
  133. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
  134. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
  135. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
  136. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/gui/select_path_widget.py +0 -0
  137. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/__init__.py +0 -0
  138. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/brush.py +0 -0
  139. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/brush_gradient.py +0 -0
  140. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/display_manager.py +0 -0
  141. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/icon_container.py +0 -0
  142. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/io_manager.py +0 -0
  143. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker/retouch/undo_manager.py +0 -0
  144. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker.egg-info/dependency_links.txt +0 -0
  145. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker.egg-info/entry_points.txt +0 -0
  146. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker.egg-info/requires.txt +0 -0
  147. {shinestacker-0.5.0 → shinestacker-1.0.1}/src/shinestacker.egg-info/top_level.txt +0 -0
@@ -2,6 +2,23 @@
2
2
 
3
3
  This page reports the main releases only and the main changes therein.
4
4
 
5
+ ## [v1.0.0] - 2025-08-25
6
+ **First stable release**
7
+
8
+ ### Changes
9
+
10
+ * implemented vignetting correction filter
11
+ * improved vignetting performance using subsampling
12
+ * implemented fast subsample option in balance algorithms
13
+ * implemented hex color line editin white balance filter
14
+ * new application logo
15
+ * interface improvements: implemented master/layer toggle
16
+ * more informative GUI messages and colors
17
+ * code refactoring and various cleanup
18
+ * bug fixes
19
+
20
+ ---
21
+
5
22
  ## [v0.5.0] - 2025-08-20
6
23
  **GUI and robustness improvements**
7
24
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shinestacker
3
- Version: 0.5.0
3
+ Version: 1.0.1
4
4
  Summary: ShineStacker
5
5
  Author-email: Luca Lista <luka.lista@gmail.com>
6
6
  License-Expression: LGPL-3.0
@@ -29,6 +29,8 @@ Provides-Extra: dev
29
29
  Requires-Dist: pytest; extra == "dev"
30
30
  Dynamic: license-file
31
31
 
32
+ <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/src/shinestacker/gui/ico/shinestacker.png' width="150" referrerpolicy="no-referrer" alt="Shine Stacker Logo">
33
+
32
34
  # Shine Stacker
33
35
 
34
36
  ## Focus Stacking Processing Framework and GUI
@@ -41,6 +43,7 @@ Dynamic: license-file
41
43
  [![codecov](https://codecov.io/github/lucalista/shinestacker/graph/badge.svg?token=Y5NKW6VH5G)](https://codecov.io/github/lucalista/shinestacker)
42
44
  [![Documentation Status](https://readthedocs.org/projects/shinestacker/badge/?version=latest)](https://shinestacker.readthedocs.io/en/latest/?badge=latest)
43
45
  [![License: LGPL v3](https://img.shields.io/badge/License-LGPL_v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
46
+ [![PyPI Downloads](https://static.pepy.tech/badge/shinestacker)](https://pepy.tech/projects/shinestacker)
44
47
 
45
48
  <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/flies.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/flies_stack.jpg' width="400" referrerpolicy="no-referrer">
46
49
 
@@ -84,7 +87,10 @@ Pyramid methods in image processing
84
87
  # License
85
88
 
86
89
  <img src="https://www.gnu.org/graphics/lgplv3-147x51.png" alt="LGPL 3 logo">
87
- The software is provided as is under the [GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html). See [LICENSE](https://github.com/lucalista/shinestacker/blob/main/LICENSE) for details.
90
+ - **Code**: The software is provided as is under the [GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html). See [LICENSE](https://github.com/lucalista/shinestacker/blob/main/LICENSE) for details.
91
+ <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/src/shinestacker/gui/ico/shinestacker.png' width="150" referrerpolicy="no-referrer" alt="Shine Stacker Logo">
92
+
93
+ - **Logo**: The Shine Stacker logo was designed by [Alessandro Lista](https://linktr.ee/alelista). Copyright © Alessandro Lista. All rights reserved. The logo is not covered by the LGPL-3.0 license of this project.
88
94
 
89
95
  # Attribution request
90
96
  📸 If you publish images created with Shine Stacker, please consider adding a note such as:
@@ -1,3 +1,5 @@
1
+ <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/src/shinestacker/gui/ico/shinestacker.png' width="150" referrerpolicy="no-referrer" alt="Shine Stacker Logo">
2
+
1
3
  # Shine Stacker
2
4
 
3
5
  ## Focus Stacking Processing Framework and GUI
@@ -10,6 +12,7 @@
10
12
  [![codecov](https://codecov.io/github/lucalista/shinestacker/graph/badge.svg?token=Y5NKW6VH5G)](https://codecov.io/github/lucalista/shinestacker)
11
13
  [![Documentation Status](https://readthedocs.org/projects/shinestacker/badge/?version=latest)](https://shinestacker.readthedocs.io/en/latest/?badge=latest)
12
14
  [![License: LGPL v3](https://img.shields.io/badge/License-LGPL_v3-blue.svg)](https://www.gnu.org/licenses/lgpl-3.0)
15
+ [![PyPI Downloads](https://static.pepy.tech/badge/shinestacker)](https://pepy.tech/projects/shinestacker)
13
16
 
14
17
  <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/flies.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/flies_stack.jpg' width="400" referrerpolicy="no-referrer">
15
18
 
@@ -53,7 +56,10 @@ Pyramid methods in image processing
53
56
  # License
54
57
 
55
58
  <img src="https://www.gnu.org/graphics/lgplv3-147x51.png" alt="LGPL 3 logo">
56
- The software is provided as is under the [GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html). See [LICENSE](https://github.com/lucalista/shinestacker/blob/main/LICENSE) for details.
59
+ - **Code**: The software is provided as is under the [GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html). See [LICENSE](https://github.com/lucalista/shinestacker/blob/main/LICENSE) for details.
60
+ <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/src/shinestacker/gui/ico/shinestacker.png' width="150" referrerpolicy="no-referrer" alt="Shine Stacker Logo">
61
+
62
+ - **Logo**: The Shine Stacker logo was designed by [Alessandro Lista](https://linktr.ee/alelista). Copyright © Alessandro Lista. All rights reserved. The logo is not covered by the LGPL-3.0 license of this project.
57
63
 
58
64
  # Attribution request
59
65
  📸 If you publish images created with Shine Stacker, please consider adding a note such as:
@@ -16,6 +16,7 @@ intensity_interval = {
16
16
  ```
17
17
  Note that for 8-bit images the maximum intensity is 255, while for 16-bit images the maximum intensity is 65536.
18
18
  * ```subsample``` (optional, default: 8): extracts intensity histogram using every n-th pixel in each dimension in order to reduce processing time. By default, it takes one every 8 pixels in horizontal and vertical directions, i.e.: one every 100 pixels in total. This option is not ised if ```corr_map``` is equal to ```BALANCE_MATCH_HIST```.
19
+ * ```fast_subsampling``` (optional, default: ```False```): perform fast image subsampling without interpolation. Used if ```subsample``` is set to ```True```.
19
20
  * ```corr_map``` (optional, default: ```BALANCE_LINEAR```, possible values: ```BALANCE_LINEAR```, ```BALANCE_GAMMA``` and ```MATCH_HIST```): specifies the type of intensity correction.
20
21
  * ```BALANCE_LINEAR```: a linear correction is applied in order to balance the average intensity of the corrected images to the reference image in the specified channels.
21
22
  * ```BALANCE_GAMMA```: a gamma correction, i.e.: a power law, is applied in order to balance the average intensity of the corrected images to reference image in the specified channels. The gamma correction avoids saturation of low or high intensity pixels which may occur for a linear coorection, but may introduce more distortion than a linear mapping.
@@ -107,13 +107,14 @@ Adjust in the top toolbar:
107
107
 
108
108
  | Action | Shortcut |
109
109
  |---------------------|---------------------------|
110
- | Zoom in/out | `Ctrl` + `+`/`- or mouse wheel |
110
+ | Zoom in/out | `Ctrl` + `+`/`- or mouse wheel or pinch on touchpad |
111
111
  | Reset view | `Ctrl` + `0` |
112
- | Pan | `Space` + mouse drag |
112
+ | Pan | `Space` + mouse drag or two fingers on touchpad |
113
113
  | Prev./next layer | `Up`/`Down` arrows |
114
114
  | View master layer | `M` |
115
115
  | View source layer | `L` |
116
- | Toggle master ↔ source | `X` |
116
+ | Toggle master ↔ layer | `T` |
117
+ | Temp. toggle master ↔ source | `X` |
117
118
 
118
119
  See help menu for complete list of shortcuts.
119
120
 
@@ -14,7 +14,6 @@ Arguments for the constructor of ```Vignetting``` are:
14
14
  * ```r_steps``` (optional, default: 100): number of radial steps to determine mean pixel luminosity.
15
15
  * ```black_threshold``` (optional, default: 1): apply correction only on pixels with luminosity greater than.
16
16
  * ```max_correction``` (optional, default: 1): if less than one, the correction is rescaled in order to be at most the specified valye.
17
- * ```apply_correction``` (optional, default: ```True```): if ```False```, the correction is computed but not applied to the image. It may be useful in order to determine a value of the parameter ```mask_size``` for the action ```BalanceFrames``` by looking at the output curve plot.
18
17
  * ```plot_correction``` (optional, default: ```False```): if ```True```, plot vignetting correction curve for each frame.
19
18
  * ```plot_summary``` (optional, default: ```False```): if ```True```, plot a summary histogram with the vignetting correction levels.
20
19
  * ```enabled``` (optional, default: ```True```): allows to switch on and off this module.
@@ -0,0 +1 @@
1
+ __version__ = '1.0.1'
@@ -6,8 +6,7 @@ import cv2
6
6
  from .. config.constants import constants
7
7
  from .. core.exceptions import AlignmentError, InvalidOptionError
8
8
  from .. core.colors import color_str
9
- from .utils import img_8bit, img_bw_8bit, save_plot
10
- from .utils import get_img_metadata, validate_image
9
+ from .utils import img_8bit, img_bw_8bit, save_plot, get_img_metadata, validate_image, img_subsample
11
10
  from .stack_framework import SubAction
12
11
 
13
12
  _DEFAULT_FEATURE_CONFIG = {
@@ -166,19 +165,12 @@ def align_images(img_1, img_0, feature_config=None, matching_config=None, alignm
166
165
  if callbacks and 'message' in callbacks:
167
166
  callbacks['message']()
168
167
  subsample = alignment_config['subsample']
168
+ fast_subsampling = alignment_config['fast_subsampling']
169
169
  min_good_matches = alignment_config['min_good_matches']
170
170
  while True:
171
171
  if subsample > 1:
172
- if alignment_config['fast_subsampling']:
173
- img_0_sub, img_1_sub = \
174
- img_0[::subsample, ::subsample], img_1[::subsample, ::subsample]
175
- else:
176
- img_0_sub = cv2.resize(img_0, (0, 0),
177
- fx=1 / subsample, fy=1 / subsample,
178
- interpolation=cv2.INTER_AREA)
179
- img_1_sub = cv2.resize(img_1, (0, 0),
180
- fx=1 / subsample, fy=1 / subsample,
181
- interpolation=cv2.INTER_AREA)
172
+ img_0_sub = img_subsample(img_0, subsample, fast_subsampling)
173
+ img_1_sub = img_subsample(img_1, subsample, fast_subsampling)
182
174
  else:
183
175
  img_0_sub, img_1_sub = img_0, img_1
184
176
  kp_0, kp_1, good_matches = detect_and_compute(img_0_sub, img_1_sub,
@@ -7,7 +7,7 @@ from scipy.interpolate import interp1d
7
7
  from .. config.constants import constants
8
8
  from .. core.exceptions import InvalidOptionError
9
9
  from .. core.colors import color_str
10
- from .utils import read_img, save_plot
10
+ from .utils import read_img, save_plot, img_subsample
11
11
  from .stack_framework import SubAction
12
12
 
13
13
 
@@ -122,13 +122,15 @@ class LinearMap(CorrectionMap):
122
122
 
123
123
  class Correction:
124
124
  def __init__(self, channels, mask_size=0, intensity_interval=None,
125
- subsample=-1, corr_map=constants.DEFAULT_CORR_MAP,
125
+ subsample=-1, fast_subsampling=constants.DEFAULT_BALANCE_FAST_SUBSAMPLING,
126
+ corr_map=constants.DEFAULT_CORR_MAP,
126
127
  plot_histograms=False, plot_summary=False):
127
128
  self.mask_size = mask_size
128
129
  self.intensity_interval = intensity_interval
129
130
  self.plot_histograms = plot_histograms
130
131
  self.plot_summary = plot_summary
131
132
  self.subsample = constants.DEFAULT_BALANCE_SUBSAMPLE if subsample == -1 else subsample
133
+ self.fast_subsampling = fast_subsampling
132
134
  self.corr_map = corr_map
133
135
  self.channels = channels
134
136
  self.dtype = None
@@ -154,17 +156,18 @@ class Correction:
154
156
  self.corrections = np.ones((size, self.channels))
155
157
 
156
158
  def calc_hist_1ch(self, image):
157
- img_subsample = image if self.subsample == 1 else image[::self.subsample, ::self.subsample]
159
+ img_sub = image if self.subsample == 1 \
160
+ else img_subsample(image, self.subsample, self.fast_subsampling)
158
161
  if self.mask_size == 0:
159
- image_sel = img_subsample
162
+ image_sel = img_sub
160
163
  else:
161
- height, width = img_subsample.shape[:2]
164
+ height, width = img_sub.shape[:2]
162
165
  xv, yv = np.meshgrid(
163
166
  np.linspace(0, width - 1, width),
164
167
  np.linspace(0, height - 1, height)
165
168
  )
166
169
  mask_radius = min(width, height) * self.mask_size / 2
167
- image_sel = img_subsample[
170
+ image_sel = img_sub[
168
171
  (xv - width / 2) ** 2 + (yv - height / 2) ** 2 <= mask_radius ** 2
169
172
  ]
170
173
  hist, _bins = np.histogram(
@@ -305,9 +308,6 @@ class Ch2Correction(Correction):
305
308
  def preprocess(self, image):
306
309
  assert False, 'abstract method'
307
310
 
308
- def get_labels(self):
309
- assert False, 'abstract method'
310
-
311
311
  def get_hist(self, image, idx):
312
312
  hist = [self.calc_hist_1ch(chan) for chan in cv2.split(image)]
313
313
  if self.plot_histograms:
@@ -370,6 +370,8 @@ class BalanceFrames(SubAction):
370
370
  self.shape = None
371
371
  corr_map = kwargs.get('corr_map', constants.DEFAULT_CORR_MAP)
372
372
  subsample = kwargs.get('subsample', constants.DEFAULT_BALANCE_SUBSAMPLE)
373
+ self.fast_subsampling = kwargs.get(
374
+ 'fast_subsampling', constants.DEFAULT_BALANCE_FAST_SUBSAMPLING)
373
375
  channel = kwargs.pop('channel', constants.DEFAULT_CHANNEL)
374
376
  kwargs['subsample'] = (
375
377
  1 if corr_map == constants.BALANCE_MATCH_HIST
@@ -61,36 +61,6 @@ class DepthMapStack(BaseStackAlgo):
61
61
  raise InvalidOptionError("map_type", self.map_type, details=f" valid values are "
62
62
  f"{constants.DM_MAP_AVERAGE} and {constants.DM_MAP_MAX}.")
63
63
 
64
- def pyramid_blend(self, images, weights):
65
- blended = None
66
- for i in range(images.shape[0]):
67
- img = images[i].astype(self.float_type)
68
- weight = weights[i]
69
- gp_img = [img]
70
- gp_weight = [weight]
71
- for _ in range(self.levels - 1):
72
- gp_img.append(cv2.pyrDown(gp_img[-1]))
73
- gp_weight.append(cv2.pyrDown(gp_weight[-1]))
74
- lp_img = [gp_img[-1]]
75
- for j in range(self.levels - 1, 0, -1):
76
- size = (gp_img[j - 1].shape[1], gp_img[j - 1].shape[0])
77
- expanded = cv2.pyrUp(gp_img[j], dstsize=size)
78
- lp_img.append(gp_img[j - 1] - expanded)
79
- current_blend = []
80
- for j in range(self.levels):
81
- w = gp_weight[self.levels - 1 - j][..., np.newaxis]
82
- current_blend.append(lp_img[j] * w)
83
- if blended is None:
84
- blended = current_blend
85
- else:
86
- for j in range(self.levels):
87
- blended[j] += current_blend[j]
88
- result = blended[0]
89
- for j in range(1, self.levels):
90
- size = (blended[j].shape[1], blended[j].shape[0])
91
- result = cv2.pyrUp(result, dstsize=size) + blended[j]
92
- return result
93
-
94
64
  def focus_stack(self, filenames):
95
65
  gray_images = []
96
66
  metadata = None
@@ -74,3 +74,13 @@ def save_plot(filename):
74
74
  if config.JUPYTER_NOTEBOOK:
75
75
  plt.show()
76
76
  plt.close('all')
77
+
78
+
79
+ def img_subsample(img, subsample, fast=True):
80
+ if fast:
81
+ img_sub = img[::subsample, ::subsample]
82
+ else:
83
+ img_sub = cv2.resize(img, (0, 0),
84
+ fx=1 / subsample, fy=1 / subsample,
85
+ interpolation=cv2.INTER_AREA)
86
+ return img_sub
@@ -0,0 +1,210 @@
1
+ # pylint: disable=C0114, C0115, C0116, R0902, E1101, W0718, W0640, R0913, R0917, R0914
2
+ import traceback
3
+ import logging
4
+ import numpy as np
5
+ import matplotlib.pyplot as plt
6
+ from scipy.optimize import curve_fit, fsolve
7
+ import cv2
8
+ from .. core.colors import color_str
9
+ from .. config.constants import constants
10
+ from .utils import img_8bit, save_plot, img_subsample
11
+ from .stack_framework import SubAction
12
+
13
+ CLIP_EXP = 10
14
+
15
+
16
+ def sigmoid_model(r, i0, k, r0):
17
+ return i0 / (1.0 +
18
+ np.exp(np.minimum(CLIP_EXP,
19
+ np.exp(np.clip(k * (r - r0),
20
+ -CLIP_EXP, CLIP_EXP)))))
21
+
22
+
23
+ def radial_mean_intensity(image, r_steps):
24
+ if len(image.shape) > 2:
25
+ raise ValueError("The image must be grayscale")
26
+ h, w = image.shape
27
+ w_2, h_2 = w / 2, h / 2
28
+ r_max = np.sqrt((w / 2)**2 + (h / 2)**2)
29
+ radii = np.linspace(0, r_max, r_steps + 1)
30
+ mean_intensities = np.zeros(r_steps)
31
+ y, x = np.ogrid[:h, :w]
32
+ dist_from_center = np.sqrt((x - w_2)**2 + (y - h_2)**2)
33
+ for i in range(r_steps):
34
+ mask = (dist_from_center >= radii[i]) & (dist_from_center < radii[i + 1])
35
+ if np.any(mask):
36
+ mean_intensities[i] = np.mean(image[mask])
37
+ else:
38
+ mean_intensities[i] = np.nan
39
+ return (radii[1:] + radii[:-1]) / 2, mean_intensities
40
+
41
+
42
+ def fit_sigmoid(radii, intensities):
43
+ valid_mask = ~np.isnan(intensities)
44
+ i_valid, r_valid = intensities[valid_mask], radii[valid_mask]
45
+ r_max = radii.max()
46
+ res = curve_fit(sigmoid_model, r_valid, i_valid,
47
+ p0=[2 * np.max(i_valid), 10 / r_max, 0.8 * r_max],
48
+ bounds=([0, 0, 0], ['inf', 'inf', 'inf']))[0]
49
+ return res
50
+
51
+
52
+ def img_subsampled(image, subsample=constants.DEFAULT_VIGN_SUBSAMPLE,
53
+ fast_subsampling=constants.DEFAULT_VIGN_FAST_SUBSAMPLING):
54
+ image_bw = cv2.cvtColor(img_8bit(image), cv2.COLOR_BGR2GRAY)
55
+ return image_bw if subsample == 1 else img_subsample(image_bw, subsample, fast_subsampling)
56
+
57
+
58
+ def compute_fit_parameters(
59
+ image, r_steps, radii=None, intensities=None,
60
+ subsample=constants.DEFAULT_VIGN_SUBSAMPLE,
61
+ fast_subsampling=constants.DEFAULT_VIGN_FAST_SUBSAMPLING):
62
+ image_sub = img_subsampled(image, subsample, fast_subsampling)
63
+ if radii is None and intensities is None:
64
+ radii, intensities = radial_mean_intensity(image_sub, r_steps)
65
+ params = fit_sigmoid(radii, intensities)
66
+ params[1] /= subsample # k
67
+ params[2] *= subsample # r0
68
+ return params
69
+
70
+
71
+ def correct_vignetting(
72
+ image, max_correction=constants.DEFAULT_MAX_CORRECTION,
73
+ black_threshold=constants.DEFAULT_BLACK_THRESHOLD,
74
+ r_steps=constants.DEFAULT_R_STEPS, params=None, v0=None,
75
+ subsample=constants.DEFAULT_VIGN_SUBSAMPLE,
76
+ fast_subsampling=constants.DEFAULT_VIGN_FAST_SUBSAMPLING):
77
+ if params is None:
78
+ if r_steps is None:
79
+ raise RuntimeError("Either r_steps or pars must not be None")
80
+ params = compute_fit_parameters(
81
+ image, r_steps, subsample=subsample, fast_subsampling=fast_subsampling)
82
+ if v0 is None:
83
+ v0 = sigmoid_model(0, *params)
84
+ h, w = image.shape[:2]
85
+ y, x = np.ogrid[:h, :w]
86
+ r = np.sqrt((x - w / 2)**2 + (y - h / 2)**2)
87
+ vignette = np.clip(sigmoid_model(r, *params) / v0, 1e-6, 1)
88
+ if max_correction < 1:
89
+ vignette = (1.0 - max_correction) + vignette * max_correction
90
+ threshold = black_threshold if image.dtype == np.uint8 else black_threshold * 256
91
+ if len(image.shape) == 3:
92
+ vignette = vignette[:, :, np.newaxis]
93
+ vignette[np.min(image, axis=2) < threshold, :] = 1
94
+ else:
95
+ vignette[image < black_threshold] = 1
96
+ return np.clip(image / vignette, 0, 255
97
+ if image.dtype == np.uint8 else 65535).astype(image.dtype)
98
+
99
+
100
+ class Vignetting(SubAction):
101
+ def __init__(self, enabled=True, percentiles=(0.05, 0.1, 0.25, 0.5, 0.75, 0.9, 0.95), **kwargs):
102
+ super().__init__(enabled)
103
+ self.r_steps = kwargs.get('r_steps', constants.DEFAULT_R_STEPS)
104
+ self.black_threshold = kwargs.get('black_threshold', constants.DEFAULT_BLACK_THRESHOLD)
105
+ self.plot_correction = kwargs.get('plot_correction', False)
106
+ self.plot_summary = kwargs.get('plot_summary', False)
107
+ self.max_correction = kwargs.get('max_correction', constants.DEFAULT_MAX_CORRECTION)
108
+ self.percentiles = np.sort(percentiles)
109
+ self.subsample = kwargs.get('subsample', constants.DEFAULT_VIGN_SUBSAMPLE)
110
+ self.fast_subsampling = kwargs.get(
111
+ 'fast_subsampling', constants.DEFAULT_VIGN_FAST_SUBSAMPLING)
112
+ self.w_2 = None
113
+ self.h_2 = None
114
+ self.v0 = None
115
+ self.r_max = None
116
+ self.process = None
117
+ self.corrections = None
118
+
119
+ def run_frame(self, idx, _ref_idx, img_0):
120
+ self.process.sub_message_r(color_str(": compute vignetting", "cyan"))
121
+ h, w = img_0.shape[:2]
122
+ self.w_2, self.h_2 = w / 2, h / 2
123
+ self.r_max = np.sqrt((w / 2)**2 + (h / 2)**2)
124
+ image_sub = img_subsampled(img_0, self.subsample, self.fast_subsampling)
125
+ radii, intensities = radial_mean_intensity(image_sub, self.r_steps)
126
+ try:
127
+ params = compute_fit_parameters(
128
+ img_0, self.r_steps, radii, intensities, self.subsample, self.fast_subsampling)
129
+ except Exception as e:
130
+ traceback.print_tb(e.__traceback__)
131
+ self.process.sub_message(
132
+ color_str(": could not find vignetting model", "red"), level=logging.WARNING)
133
+ params = None
134
+ if params is None:
135
+ return img_0
136
+ self.v0 = sigmoid_model(0, *params)
137
+ i0_fit, k_fit, r0_fit = params
138
+ self.process.sub_message(color_str(": vignetting model parameters: ", "cyan") +
139
+ color_str(f"i0={i0_fit / 2:.4f}, "
140
+ f"k={k_fit * self.r_max:.4f}, "
141
+ f"r0={r0_fit / self.r_max:.4f}",
142
+ "light_blue"),
143
+ level=logging.DEBUG)
144
+ if self.plot_correction:
145
+ plt.figure(figsize=(10, 5))
146
+ plt.plot(radii, intensities, label="image mean intensity")
147
+ plt.plot(radii, sigmoid_model(radii * self.subsample, *params), label="sigmoid fit")
148
+ plt.xlabel('radius (pixels)')
149
+ plt.ylabel('mean intensity')
150
+ plt.legend()
151
+ plt.xlim(radii[0], radii[-1])
152
+ plt.ylim(0)
153
+ idx_str = f"{idx:04d}"
154
+ plot_path = f"{self.process.working_path}/" \
155
+ f"{self.process.plot_path}/{self.process.name}-" \
156
+ f"radial-intensity-{idx_str}.pdf"
157
+ save_plot(plot_path)
158
+ plt.close('all')
159
+ self.process.callback(
160
+ 'save_plot', self.process.id,
161
+ f"{self.process.name}: intensity\nframe {idx_str}", plot_path)
162
+ for i, p in enumerate(self.percentiles):
163
+ self.corrections[i][idx] = fsolve(lambda x: sigmoid_model(x, *params) /
164
+ self.v0 - p, r0_fit)[0]
165
+ self.process.sub_message_r(color_str(": correct vignetting", "cyan"))
166
+ return correct_vignetting(
167
+ img_0, self.max_correction, self.black_threshold, None, params, self.v0,
168
+ self.subsample, self.fast_subsampling)
169
+
170
+ def begin(self, process):
171
+ self.process = process
172
+ self.corrections = [np.full(self.process.counts, None, dtype=float)
173
+ for p in self.percentiles]
174
+
175
+ def end(self):
176
+ if self.plot_summary:
177
+ plt.figure(figsize=(10, 5))
178
+ xs = np.arange(1, len(self.corrections[0]) + 1, dtype=int)
179
+ for i, p in enumerate(self.percentiles):
180
+ linestyle = 'solid'
181
+ if p == 0.5:
182
+ linestyle = '-.'
183
+ elif i in (0, len(self.percentiles) - 1):
184
+ linestyle = 'dotted'
185
+ plt.plot(xs, self.corrections[i], label=f"{p:.0%} correction",
186
+ linestyle=linestyle, color="blue")
187
+ plt.fill_between(xs, self.corrections[-1], self.corrections[0], color="#0000ff20")
188
+ iis = np.where(self.percentiles == 0.5)
189
+ if len(iis) > 0:
190
+ i = iis[0][0]
191
+ if 1 <= i < len(self.percentiles) - 1:
192
+ plt.fill_between(xs, self.corrections[i - 1], self.corrections[i + 1],
193
+ color="#0000ff20")
194
+ plt.plot(xs[[0, -1]], [self.r_max] * 2,
195
+ linestyle="--", label="max. radius", color="darkred")
196
+ plt.plot(xs[[0, -1]], [self.w_2] * 2,
197
+ linestyle="--", label="half width", color="limegreen")
198
+ plt.plot(xs[[0, -1]], [self.h_2] * 2,
199
+ linestyle="--", label="half height", color="darkgreen")
200
+ plt.xlabel('frame')
201
+ plt.ylabel('distance from center (pixels)')
202
+ plt.legend(ncols=2)
203
+ plt.xlim(xs[0], xs[-1])
204
+ plt.ylim(0, self.r_max * 1.05)
205
+ plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
206
+ f"{self.process.name}-r0.pdf"
207
+ save_plot(plot_path)
208
+ plt.close('all')
209
+ self.process.callback('save_plot', self.process.id,
210
+ f"{self.process.name}: vignetting", plot_path)
@@ -1,13 +1,41 @@
1
- # pylint: disable=C0114, C0116, E0611, W0718
1
+ # pylint: disable=C0114, C0115, C0116, E0611, W0718, R0903
2
2
  import json
3
3
  from urllib.request import urlopen, Request
4
4
  from urllib.error import URLError
5
- from PySide6.QtWidgets import QMessageBox
5
+ from PySide6.QtWidgets import QDialog, QVBoxLayout, QHBoxLayout, QPushButton, QLabel
6
6
  from PySide6.QtCore import Qt
7
7
  from .. import __version__
8
+ from .. retouch.icon_container import icon_container
8
9
  from .. config.constants import constants
9
10
 
10
11
 
12
+ class AboutDialog(QDialog):
13
+ def __init__(self, parent=None, about_text=""):
14
+ super().__init__(parent)
15
+ self.setWindowTitle("About")
16
+ self.resize(400, 300)
17
+ layout = QVBoxLayout(self)
18
+ layout.setAlignment(Qt.AlignTop)
19
+ icon_widget = icon_container()
20
+ icon_layout = QHBoxLayout()
21
+ icon_layout.addStretch()
22
+ icon_layout.addWidget(icon_widget)
23
+ icon_layout.addStretch()
24
+ layout.addLayout(icon_layout)
25
+ about_label = QLabel(about_text)
26
+ about_label.setWordWrap(True)
27
+ about_label.setAlignment(Qt.AlignLeft)
28
+ layout.addWidget(about_label)
29
+ button_layout = QHBoxLayout()
30
+ button_layout.addStretch()
31
+ button = QPushButton("OK")
32
+ button.setFixedWidth(100)
33
+ button.clicked.connect(self.accept)
34
+ button_layout.addWidget(button)
35
+ button_layout.addStretch()
36
+ layout.addLayout(button_layout)
37
+
38
+
11
39
  def compare_versions(current, latest):
12
40
  def parse_version(v):
13
41
  v = v.lstrip('v')
@@ -49,7 +77,7 @@ def get_latest_version():
49
77
  return None
50
78
 
51
79
 
52
- def show_about_dialog():
80
+ def show_about_dialog(parent):
53
81
  version_clean = __version__.split("+", maxsplit=1)[0]
54
82
  latest_version = None
55
83
  try:
@@ -57,7 +85,6 @@ def show_about_dialog():
57
85
  except Exception:
58
86
  pass
59
87
  update_text = ""
60
- # pyling: disable=XXX
61
88
  if latest_version:
62
89
  latest_clean = latest_version.lstrip('v')
63
90
  if compare_versions(version_clean, latest_clean) < 0:
@@ -68,17 +95,17 @@ def show_about_dialog():
68
95
  </p>
69
96
  """ # noqa E501
70
97
  else:
71
- update_text = f"""
98
+ update_text = """
72
99
  <p style="color: green; font-weight: bold;">
73
- You are using the lastet version: {latest_version}.
100
+ You are using the lastet version.
74
101
  </p>
75
102
  """
76
103
  about_text = f"""
77
104
  <h3>{constants.APP_TITLE}</h3>
78
105
  <h4>version: v{version_clean}</h4>
79
106
  {update_text}
80
- <p style='font-weight: normal;'>App and framework to combine multiple images
81
- into a single focused image.</p>
107
+ <p style='font-weight: normal;'>Focus stackign applications and framework.<br>
108
+ Combine multiple frames into a single focused image.</p>
82
109
  <p>Author: Luca Lista<br/>
83
110
  Email: <a href="mailto:luka.lista@gmail.com">luka.lista@gmail.com</a></p>
84
111
  <ul>
@@ -86,11 +113,5 @@ def show_about_dialog():
86
113
  <li><a href="https://github.com/lucalista/shinestacker">GitHub project repository</a></li>
87
114
  </ul>
88
115
  """
89
- # pyling: enable=XXX
90
- msg = QMessageBox()
91
- msg.setWindowTitle(f"About {constants.APP_STRING}")
92
- msg.setIcon(QMessageBox.Icon.Information)
93
- msg.setTextFormat(Qt.TextFormat.RichText)
94
- msg.setText(about_text)
95
- msg.setIcon(QMessageBox.Icon.NoIcon)
96
- msg.exec_()
116
+ dialog = AboutDialog(parent, about_text)
117
+ dialog.exec()
@@ -42,7 +42,7 @@ def disable_macos_special_menu_items():
42
42
 
43
43
  def fill_app_menu(app, app_menu):
44
44
  about_action = QAction(f"About {constants.APP_STRING}", app)
45
- about_action.triggered.connect(show_about_dialog)
45
+ about_action.triggered.connect(lambda: show_about_dialog(app))
46
46
  app_menu.addAction(about_action)
47
47
  app_menu.addSeparator()
48
48
  if config.DONT_USE_NATIVE_MENU:
@@ -14,4 +14,4 @@ def add_help_action(app):
14
14
 
15
15
 
16
16
  def browse_website():
17
- webbrowser.open("https://github.com/lucalista/shinestacker/blob/main/docs/gui.md")
17
+ webbrowser.open("https://shinestacker.readthedocs.io/en/latest/")
@@ -141,7 +141,7 @@ expert options are visible by default.
141
141
  filename = filenames[0]
142
142
  extension = filename.split('.')[-1]
143
143
  if len(filenames) == 1 and extension == 'fsp':
144
- main_app.project_window.open_project(filename)
144
+ main_app.project_window.project_controller.open_project(filename)
145
145
  main_app.project_window.setFocus()
146
146
  else:
147
147
  main_app.switch_to_retouch()
@@ -152,7 +152,7 @@ expert options are visible by default.
152
152
  main_app.switch_to_retouch()
153
153
  else:
154
154
  main_app.switch_to_project()
155
- QTimer.singleShot(100, main_app.project_window.new_project)
155
+ QTimer.singleShot(100, main_app.project_window.project_controller.new_project)
156
156
  QTimer.singleShot(100, main_app.setFocus)
157
157
  sys.exit(app.exec())
158
158
 
@@ -70,9 +70,9 @@ expert options are visible by default.
70
70
  window.show()
71
71
  filename = args['filename']
72
72
  if filename:
73
- QTimer.singleShot(100, lambda: window.open_project(filename))
73
+ QTimer.singleShot(100, lambda: window.project_controller.open_project(filename))
74
74
  else:
75
- QTimer.singleShot(100, window.new_project)
75
+ QTimer.singleShot(100, window.project_controller.new_project)
76
76
  sys.exit(app.exec())
77
77
 
78
78