shinestacker 1.0.0__tar.gz → 1.0.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.

Potentially problematic release.


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

Files changed (136) hide show
  1. {shinestacker-1.0.0 → shinestacker-1.0.2}/CHANGELOG.md +24 -0
  2. {shinestacker-1.0.0/src/shinestacker.egg-info → shinestacker-1.0.2}/PKG-INFO +8 -14
  3. {shinestacker-1.0.0 → shinestacker-1.0.2}/README.md +7 -13
  4. shinestacker-1.0.2/src/shinestacker/_version.py +1 -0
  5. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/gui_run.py +2 -2
  6. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/main_window.py +7 -4
  7. shinestacker-1.0.2/src/shinestacker/gui/project_controller.py +354 -0
  8. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/io_gui_handler.py +21 -17
  9. {shinestacker-1.0.0 → shinestacker-1.0.2/src/shinestacker.egg-info}/PKG-INFO +8 -14
  10. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker.egg-info/SOURCES.txt +1 -0
  11. shinestacker-1.0.0/src/shinestacker/_version.py +0 -1
  12. {shinestacker-1.0.0 → shinestacker-1.0.2}/.coveragerc +0 -0
  13. {shinestacker-1.0.0 → shinestacker-1.0.2}/.flake8 +0 -0
  14. {shinestacker-1.0.0 → shinestacker-1.0.2}/.github/workflows/ci-multiplatform.yml +0 -0
  15. {shinestacker-1.0.0 → shinestacker-1.0.2}/.github/workflows/pylint.yml +0 -0
  16. {shinestacker-1.0.0 → shinestacker-1.0.2}/.github/workflows/pypi-publish.yml +0 -0
  17. {shinestacker-1.0.0 → shinestacker-1.0.2}/.github/workflows/release.yml +0 -0
  18. {shinestacker-1.0.0 → shinestacker-1.0.2}/.gitignore +0 -0
  19. {shinestacker-1.0.0 → shinestacker-1.0.2}/.pylintrc +0 -0
  20. {shinestacker-1.0.0 → shinestacker-1.0.2}/.readthedocs.yaml +0 -0
  21. {shinestacker-1.0.0 → shinestacker-1.0.2}/LICENSE +0 -0
  22. {shinestacker-1.0.0 → shinestacker-1.0.2}/MANIFEST.in +0 -0
  23. {shinestacker-1.0.0 → shinestacker-1.0.2}/THIRD_PARTY_LICENSES.txt +0 -0
  24. {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/alignment.md +0 -0
  25. {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/api.md +0 -0
  26. {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/balancing.md +0 -0
  27. {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/conf.py +0 -0
  28. {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/focus_stacking.md +0 -0
  29. {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/gui.md +0 -0
  30. {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/index.md +0 -0
  31. {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/job.md +0 -0
  32. {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/main.md +0 -0
  33. {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/multilayer.md +0 -0
  34. {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/noise.md +0 -0
  35. {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/requirements.txt +0 -0
  36. {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/vignetting.md +0 -0
  37. {shinestacker-1.0.0 → shinestacker-1.0.2}/img/coffee.gif +0 -0
  38. {shinestacker-1.0.0 → shinestacker-1.0.2}/img/coffee_stack.jpg +0 -0
  39. {shinestacker-1.0.0 → shinestacker-1.0.2}/img/extreme-vignetting.jpg +0 -0
  40. {shinestacker-1.0.0 → shinestacker-1.0.2}/img/flies.gif +0 -0
  41. {shinestacker-1.0.0 → shinestacker-1.0.2}/img/flies_stack.jpg +0 -0
  42. {shinestacker-1.0.0 → shinestacker-1.0.2}/img/flow-diagram.png +0 -0
  43. {shinestacker-1.0.0 → shinestacker-1.0.2}/img/gui-finder.png +0 -0
  44. {shinestacker-1.0.0 → shinestacker-1.0.2}/img/gui-project-new.png +0 -0
  45. {shinestacker-1.0.0 → shinestacker-1.0.2}/img/gui-project-run.png +0 -0
  46. {shinestacker-1.0.0 → shinestacker-1.0.2}/img/gui-retouch.png +0 -0
  47. {shinestacker-1.0.0 → shinestacker-1.0.2}/pyproject.toml +0 -0
  48. {shinestacker-1.0.0 → shinestacker-1.0.2}/requirements.txt +0 -0
  49. {shinestacker-1.0.0 → shinestacker-1.0.2}/scripts/build_release.py +0 -0
  50. {shinestacker-1.0.0 → shinestacker-1.0.2}/scripts/git-rev-list.sh +0 -0
  51. {shinestacker-1.0.0 → shinestacker-1.0.2}/scripts/validate-tomli.py +0 -0
  52. {shinestacker-1.0.0 → shinestacker-1.0.2}/setup.cfg +0 -0
  53. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/__init__.py +0 -0
  54. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/__init__.py +0 -0
  55. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/align.py +0 -0
  56. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/balance.py +0 -0
  57. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
  58. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/denoise.py +0 -0
  59. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/depth_map.py +0 -0
  60. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/exif.py +0 -0
  61. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/multilayer.py +0 -0
  62. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/noise_detection.py +0 -0
  63. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/pyramid.py +0 -0
  64. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/sharpen.py +0 -0
  65. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/stack.py +0 -0
  66. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/stack_framework.py +0 -0
  67. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/utils.py +0 -0
  68. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/vignetting.py +0 -0
  69. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/white_balance.py +0 -0
  70. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/app/__init__.py +0 -0
  71. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/app/about_dialog.py +0 -0
  72. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/app/gui_utils.py +0 -0
  73. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/app/help_menu.py +0 -0
  74. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/app/main.py +0 -0
  75. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/app/open_frames.py +0 -0
  76. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/app/project.py +0 -0
  77. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/app/retouch.py +0 -0
  78. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/config/__init__.py +0 -0
  79. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/config/config.py +0 -0
  80. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/config/constants.py +0 -0
  81. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/config/gui_constants.py +0 -0
  82. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/core/__init__.py +0 -0
  83. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/core/colors.py +0 -0
  84. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/core/core_utils.py +0 -0
  85. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/core/exceptions.py +0 -0
  86. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/core/framework.py +0 -0
  87. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/core/logging.py +0 -0
  88. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/__init__.py +0 -0
  89. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/action_config.py +0 -0
  90. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/action_config_dialog.py +0 -0
  91. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/base_form_dialog.py +0 -0
  92. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/colors.py +0 -0
  93. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/gui_images.py +0 -0
  94. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/gui_logging.py +0 -0
  95. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
  96. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
  97. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
  98. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/ico/shinestacker.png +0 -0
  99. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
  100. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
  101. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
  102. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
  103. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
  104. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/menu_manager.py +0 -0
  105. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/new_project.py +0 -0
  106. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/project_converter.py +0 -0
  107. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/project_editor.py +0 -0
  108. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/project_model.py +0 -0
  109. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/select_path_widget.py +0 -0
  110. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/tab_widget.py +0 -0
  111. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/time_progress_bar.py +0 -0
  112. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/__init__.py +0 -0
  113. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/base_filter.py +0 -0
  114. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/brush.py +0 -0
  115. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/brush_gradient.py +0 -0
  116. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/brush_preview.py +0 -0
  117. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/brush_tool.py +0 -0
  118. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/denoise_filter.py +0 -0
  119. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/display_manager.py +0 -0
  120. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/exif_data.py +0 -0
  121. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/file_loader.py +0 -0
  122. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/filter_manager.py +0 -0
  123. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/icon_container.py +0 -0
  124. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/image_editor_ui.py +0 -0
  125. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/image_viewer.py +0 -0
  126. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/io_manager.py +0 -0
  127. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/layer_collection.py +0 -0
  128. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/shortcuts_help.py +0 -0
  129. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/undo_manager.py +0 -0
  130. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/unsharp_mask_filter.py +0 -0
  131. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/vignetting_filter.py +0 -0
  132. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/white_balance_filter.py +0 -0
  133. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker.egg-info/dependency_links.txt +0 -0
  134. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker.egg-info/entry_points.txt +0 -0
  135. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker.egg-info/requires.txt +0 -0
  136. {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker.egg-info/top_level.txt +0 -0
@@ -2,6 +2,26 @@
2
2
 
3
3
  This page reports the main releases only and the main changes therein.
4
4
 
5
+ ## [v1.0.2] - 2025-08-25
6
+ **Bug fixes**
7
+
8
+ ### Changes
9
+
10
+ * fixed context menu
11
+ * fixed retouch callback for shiestacker-project app
12
+ * fixed double image loading
13
+
14
+ ---
15
+
16
+ ## [v1.0.1] - 2025-08-25
17
+ **First stable release**
18
+
19
+ ### Changes
20
+
21
+ * added source file missing by mistake in v1.0.0
22
+
23
+ ---
24
+
5
25
  ## [v1.0.0] - 2025-08-25
6
26
  **First stable release**
7
27
 
@@ -17,6 +37,10 @@ This page reports the main releases only and the main changes therein.
17
37
  * code refactoring and various cleanup
18
38
  * bug fixes
19
39
 
40
+ Note
41
+
42
+ A source file was missing in this tag, and was added in v1.0.1
43
+
20
44
  ---
21
45
 
22
46
  ## [v0.5.0] - 2025-08-20
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shinestacker
3
- Version: 1.0.0
3
+ Version: 1.0.2
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
@@ -76,14 +78,6 @@ The GUI has two main working areas:
76
78
 
77
79
  The first version of the core focus stack algorithm was initially inspired by the [Laplacian pyramids method](https://github.com/sjawhar/focus-stacking) implementation by Sami Jawhar that was used under permission of the author. The implementation in the latest releases was rewritten from the original code.
78
80
 
79
- <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/src/shinestacker/gui/ico/shinestacker.png' width="256" referrerpolicy="no-referrer" alt="Shine Stacker Logo">
80
-
81
- ## Logo attribution
82
-
83
- The Shine Stacker logo was designed by [Alessandro Lista](https://linktr.ee/alelista).
84
- Copyright © Alessandro Lista. All rights reserved.
85
- The logo is not covered by the LGPL-3.0 license of this project.
86
-
87
81
  # Resources
88
82
 
89
83
  * [Pyramid Methods in Image Processing](https://www.researchgate.net/publication/246727904_Pyramid_Methods_in_Image_Processing), E. H. Adelson, C. H. Anderson, J. R. Bergen, P. J. Burt, J. M. Ogden, RCA Engineer, 29-6, Nov/Dec 1984
@@ -92,11 +86,11 @@ Pyramid methods in image processing
92
86
 
93
87
  # License
94
88
 
95
- - **Code**: <img src="https://www.gnu.org/graphics/lgplv3-147x51.png" alt="LGPL 3 logo">
96
- 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.
97
- - **Logo**: The Shine Stacker logo was designed by [Alessandro Lista](https://linktr.ee/alelista).
98
- Copyright © Alessandro Lista. All rights reserved.
99
- The logo is not covered by the LGPL-3.0 license of this project.
89
+ <img src="https://www.gnu.org/graphics/lgplv3-147x51.png" alt="LGPL 3 logo">
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.
100
94
 
101
95
  # Attribution request
102
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
@@ -45,14 +47,6 @@ The GUI has two main working areas:
45
47
 
46
48
  The first version of the core focus stack algorithm was initially inspired by the [Laplacian pyramids method](https://github.com/sjawhar/focus-stacking) implementation by Sami Jawhar that was used under permission of the author. The implementation in the latest releases was rewritten from the original code.
47
49
 
48
- <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/src/shinestacker/gui/ico/shinestacker.png' width="256" referrerpolicy="no-referrer" alt="Shine Stacker Logo">
49
-
50
- ## Logo attribution
51
-
52
- The Shine Stacker logo was designed by [Alessandro Lista](https://linktr.ee/alelista).
53
- Copyright © Alessandro Lista. All rights reserved.
54
- The logo is not covered by the LGPL-3.0 license of this project.
55
-
56
50
  # Resources
57
51
 
58
52
  * [Pyramid Methods in Image Processing](https://www.researchgate.net/publication/246727904_Pyramid_Methods_in_Image_Processing), E. H. Adelson, C. H. Anderson, J. R. Bergen, P. J. Burt, J. M. Ogden, RCA Engineer, 29-6, Nov/Dec 1984
@@ -61,11 +55,11 @@ Pyramid methods in image processing
61
55
 
62
56
  # License
63
57
 
64
- - **Code**: <img src="https://www.gnu.org/graphics/lgplv3-147x51.png" alt="LGPL 3 logo">
65
- 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.
66
- - **Logo**: The Shine Stacker logo was designed by [Alessandro Lista](https://linktr.ee/alelista).
67
- Copyright © Alessandro Lista. All rights reserved.
68
- The logo is not covered by the LGPL-3.0 license of this project.
58
+ <img src="https://www.gnu.org/graphics/lgplv3-147x51.png" alt="LGPL 3 logo">
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.
69
63
 
70
64
  # Attribution request
71
65
  📸 If you publish images created with Shine Stacker, please consider adding a note such as:
@@ -0,0 +1 @@
1
+ __version__ = '1.0.2'
@@ -133,11 +133,11 @@ class RunWindow(QTextEditLogger):
133
133
  def find_parent(widget, class_name):
134
134
  current = widget
135
135
  while current is not None:
136
- if current.__class__.__name__ == class_name:
136
+ if current.objectName() == class_name:
137
137
  return current
138
138
  current = current.parent()
139
139
  return None
140
- parent = find_parent(self, "MainWindow")
140
+ parent = find_parent(self, "mainWindow")
141
141
  if parent:
142
142
  parent.retouch_callback(path[1])
143
143
  else:
@@ -52,6 +52,7 @@ class MainWindow(QMainWindow, LogManager):
52
52
  def __init__(self):
53
53
  QMainWindow.__init__(self)
54
54
  LogManager.__init__(self)
55
+ self.setObjectName("mainWindow")
55
56
  self.project_controller = ProjectController(self)
56
57
  self.project_editor = self.project_controller.project_editor
57
58
  actions = {
@@ -139,6 +140,8 @@ class MainWindow(QMainWindow, LogManager):
139
140
  self.project_controller.activate_window_requested.connect(self.activateWindow)
140
141
  self.project_controller.enable_save_actions_requested.connect(
141
142
  self.menu_manager.save_actions_set_enabled)
143
+ self.project_controller.enable_sub_actions_requested.connect(
144
+ self.menu_manager.set_enabled_sub_actions_gui)
142
145
 
143
146
  def modified(self):
144
147
  return self.project_editor.modified()
@@ -286,9 +289,9 @@ class MainWindow(QMainWindow, LogManager):
286
289
  if current_action:
287
290
  menu = QMenu(self)
288
291
  if current_action.enabled():
289
- menu.addAction(self.disable_action)
292
+ menu.addAction(self.menu_manager.disable_action)
290
293
  else:
291
- menu.addAction(self.enable_action)
294
+ menu.addAction(self.menu_manager.enable_action)
292
295
  edit_config_action = QAction("Edit configuration")
293
296
  edit_config_action.triggered.connect(self.edit_current_action)
294
297
  menu.addAction(edit_config_action)
@@ -332,8 +335,8 @@ class MainWindow(QMainWindow, LogManager):
332
335
  self.browse_output_path_action.triggered.connect(self.browse_output_path)
333
336
  menu.addAction(self.browse_output_path_action)
334
337
  menu.addSeparator()
335
- menu.addAction(self.run_job_action)
336
- menu.addAction(self.run_all_jobs_action)
338
+ menu.addAction(self.menu_manager.run_job_action)
339
+ menu.addAction(self.menu_manager.run_all_jobs_action)
337
340
  if current_action.type_name == constants.ACTION_JOB:
338
341
  retouch_path = self.get_retouch_path(current_action)
339
342
  if len(retouch_path) > 0:
@@ -0,0 +1,354 @@
1
+ # pylint: disable=C0114, C0115, C0116, E0611, R0913, R0917, R0914, R0912, R0904, R0915, W0718
2
+ import os
3
+ import os.path
4
+ import traceback
5
+ import json
6
+ import jsonpickle
7
+ from PySide6.QtCore import Signal, QObject
8
+ from PySide6.QtWidgets import QMessageBox, QFileDialog, QDialog
9
+ from .. config.constants import constants
10
+ from .. core.core_utils import get_app_base_path
11
+ from .project_model import ActionConfig
12
+ from .new_project import NewProjectDialog
13
+ from .project_model import Project
14
+ from .project_editor import ProjectEditor
15
+
16
+
17
+ class ProjectController(QObject):
18
+ update_title_requested = Signal()
19
+ refresh_ui_requested = Signal(int, int)
20
+ activate_window_requested = Signal()
21
+ enable_save_actions_requested = Signal(bool)
22
+ enable_sub_actions_requested = Signal(bool)
23
+
24
+ def __init__(self, parent):
25
+ super().__init__(parent)
26
+ self.parent = parent
27
+ self.project_editor = ProjectEditor(parent)
28
+
29
+ def refresh_ui(self, job_row=-1, action_row=-1):
30
+ self.refresh_ui_requested.emit(job_row, action_row)
31
+
32
+ def mark_as_modified(self, modified=True):
33
+ self.project_editor.mark_as_modified(modified)
34
+
35
+ def modified(self):
36
+ return self.project_editor.modified()
37
+
38
+ def set_project(self, project):
39
+ self.project_editor.set_project(project)
40
+
41
+ def project(self):
42
+ return self.project_editor.project()
43
+
44
+ def project_jobs(self):
45
+ return self.project_editor.project_jobs()
46
+
47
+ def project_job(self, i):
48
+ return self.project_editor.project_job(i)
49
+
50
+ def add_job_to_project(self, job):
51
+ self.project_editor.add_job_to_project(job)
52
+
53
+ def num_project_jobs(self):
54
+ return self.project_editor.num_project_jobs()
55
+
56
+ def save_actions_set_enabled(self, enabled):
57
+ self.enable_save_actions_requested.emit(enabled)
58
+
59
+ def current_file_path(self):
60
+ return self.project_editor.current_file_path()
61
+
62
+ def current_file_directory(self):
63
+ return self.project_editor.current_file_directory()
64
+
65
+ def current_file_name(self):
66
+ return self.project_editor.current_file_name()
67
+
68
+ def set_current_file_path(self, path):
69
+ self.project_editor.set_current_file_path(path)
70
+
71
+ def job_list(self):
72
+ return self.project_editor.job_list()
73
+
74
+ def action_list(self):
75
+ return self.project_editor.action_list()
76
+
77
+ def current_job_index(self):
78
+ return self.project_editor.current_job_index()
79
+
80
+ def current_action_index(self):
81
+ return self.project_editor.current_action_index()
82
+
83
+ def set_current_job(self, index):
84
+ return self.project_editor.set_current_job(index)
85
+
86
+ def set_current_action(self, index):
87
+ return self.project_editor.set_current_action(index)
88
+
89
+ def job_list_count(self):
90
+ return self.project_editor.job_list_count()
91
+
92
+ def action_list_count(self):
93
+ return self.project_editor.action_list_count()
94
+
95
+ def job_list_item(self, index):
96
+ return self.project_editor.job_list_item(index)
97
+
98
+ def action_list_item(self, index):
99
+ return self.project_editor.action_list_item(index)
100
+
101
+ def job_list_has_focus(self):
102
+ return self.project_editor.job_list_has_focus()
103
+
104
+ def action_list_has_focus(self):
105
+ return self.project_editor.action_list_has_focus()
106
+
107
+ def clear_job_list(self):
108
+ self.project_editor.clear_job_list()
109
+
110
+ def clear_action_list(self):
111
+ self.project_editor.clear_action_list()
112
+
113
+ def num_selected_jobs(self):
114
+ return self.project_editor.num_selected_jobs()
115
+
116
+ def num_selected_actions(self):
117
+ return self.project_editor.num_selected_actions()
118
+
119
+ def get_current_action_at(self, job, action_index):
120
+ return self.project_editor.get_current_action_at(job, action_index)
121
+
122
+ def action_config_dialog(self, action):
123
+ return self.project_editor.action_config_dialog(action)
124
+
125
+ def on_job_selected(self, index):
126
+ return self.project_editor.on_job_selected(index)
127
+
128
+ def update_title(self):
129
+ self.update_title_requested.emit()
130
+
131
+ def close_project(self):
132
+ if self.check_unsaved_changes():
133
+ self.set_project(Project())
134
+ self.set_current_file_path('')
135
+ self.update_title()
136
+ self.clear_job_list()
137
+ self.clear_action_list()
138
+ self.mark_as_modified(False)
139
+
140
+ def new_project(self):
141
+ if not self.check_unsaved_changes():
142
+ return
143
+ os.chdir(get_app_base_path())
144
+ self.set_current_file_path('')
145
+ self.mark_as_modified(False)
146
+ self.update_title()
147
+ self.clear_job_list()
148
+ self.clear_action_list()
149
+ self.set_project(Project())
150
+ self.save_actions_set_enabled(False)
151
+ dialog = NewProjectDialog(self.parent)
152
+ if dialog.exec() == QDialog.Accepted:
153
+ self.save_actions_set_enabled(True)
154
+ input_folder = dialog.get_input_folder().split('/')
155
+ working_path = '/'.join(input_folder[:-1])
156
+ input_path = input_folder[-1]
157
+ if dialog.get_noise_detection():
158
+ job_noise = ActionConfig(constants.ACTION_JOB,
159
+ {'name': 'detect-noise', 'working_path': working_path,
160
+ 'input_path': input_path})
161
+ noise_detection = ActionConfig(constants.ACTION_NOISEDETECTION,
162
+ {'name': 'detect-noise'})
163
+ job_noise.add_sub_action(noise_detection)
164
+ self.add_job_to_project(job_noise)
165
+ job = ActionConfig(constants.ACTION_JOB,
166
+ {'name': 'focus-stack', 'working_path': working_path,
167
+ 'input_path': input_path})
168
+ if dialog.get_noise_detection() or dialog.get_vignetting_correction() or \
169
+ dialog.get_align_frames() or dialog.get_balance_frames():
170
+ combo_action = ActionConfig(constants.ACTION_COMBO, {'name': 'align'})
171
+ if dialog.get_noise_detection():
172
+ mask_noise = ActionConfig(constants.ACTION_MASKNOISE, {'name': 'mask-noise'})
173
+ combo_action.add_sub_action(mask_noise)
174
+ if dialog.get_vignetting_correction():
175
+ vignetting = ActionConfig(constants.ACTION_VIGNETTING, {'name': 'vignetting'})
176
+ combo_action.add_sub_action(vignetting)
177
+ if dialog.get_align_frames():
178
+ align = ActionConfig(constants.ACTION_ALIGNFRAMES, {'name': 'align'})
179
+ combo_action.add_sub_action(align)
180
+ if dialog.get_balance_frames():
181
+ balance = ActionConfig(constants.ACTION_BALANCEFRAMES, {'name': 'balance'})
182
+ combo_action.add_sub_action(balance)
183
+ job.add_sub_action(combo_action)
184
+ if dialog.get_bunch_stack():
185
+ bunch_stack = ActionConfig(constants.ACTION_FOCUSSTACKBUNCH,
186
+ {'name': 'bunches', 'frames': dialog.get_bunch_frames(),
187
+ 'overlap': dialog.get_bunch_overlap()})
188
+ job.add_sub_action(bunch_stack)
189
+ if dialog.get_focus_stack_pyramid():
190
+ focus_pyramid = ActionConfig(constants.ACTION_FOCUSSTACK,
191
+ {'name': 'focus-stack-pyramid',
192
+ 'stacker': constants.STACK_ALGO_PYRAMID})
193
+ job.add_sub_action(focus_pyramid)
194
+ if dialog.get_focus_stack_depth_map():
195
+ focus_depth_map = ActionConfig(constants.ACTION_FOCUSSTACK,
196
+ {'name': 'focus-stack-depth-map',
197
+ 'stacker': constants.STACK_ALGO_DEPTH_MAP})
198
+ job.add_sub_action(focus_depth_map)
199
+ if dialog.get_multi_layer():
200
+ input_path = []
201
+ if dialog.get_focus_stack_pyramid():
202
+ input_path.append("focus-stack-pyramid")
203
+ if dialog.get_focus_stack_depth_map():
204
+ input_path.append("focus-stack-depth-map")
205
+ if dialog.get_bunch_stack():
206
+ input_path.append("bunches")
207
+ else:
208
+ input_path.append(input_path)
209
+ multi_layer = ActionConfig(constants.ACTION_MULTILAYER,
210
+ {'name': 'multi-layer',
211
+ 'input_path': ','.join(input_path)})
212
+ job.add_sub_action(multi_layer)
213
+ self.add_job_to_project(job)
214
+ self.mark_as_modified(True)
215
+ self.refresh_ui(0, -1)
216
+
217
+ def open_project(self, file_path=False):
218
+ if not self.check_unsaved_changes():
219
+ return
220
+ if file_path is False:
221
+ file_path, _ = QFileDialog.getOpenFileName(
222
+ self.parent, "Open Project", "", "Project Files (*.fsp);;All Files (*)")
223
+ if file_path:
224
+ try:
225
+ self.set_current_file_path(file_path)
226
+ with open(self.current_file_path(), 'r', encoding="utf-8") as file:
227
+ json_obj = json.load(file)
228
+ project = Project.from_dict(json_obj['project'])
229
+ if project is None:
230
+ raise RuntimeError(f"Project from file {file_path} produced a null project.")
231
+ self.set_project(project)
232
+ self.mark_as_modified(False)
233
+ self.refresh_ui(0, -1)
234
+ if self.job_list_count() > 0:
235
+ self.set_current_job(0)
236
+ except Exception as e:
237
+ traceback.print_tb(e.__traceback__)
238
+ QMessageBox.critical(
239
+ self.parent, "Error", f"Cannot open file {file_path}:\n{str(e)}")
240
+ if self.num_project_jobs() > 0:
241
+ self.set_current_job(0)
242
+ self.activate_window_requested.emit()
243
+ self.save_actions_set_enabled(True)
244
+ for job in self.project_jobs():
245
+ if 'working_path' in job.params.keys():
246
+ working_path = job.params['working_path']
247
+ if not os.path.isdir(working_path):
248
+ QMessageBox.warning(
249
+ self.parent, "Working path not found",
250
+ f'''The working path specified in the project file for the job:
251
+ "{job.params['name']}"
252
+ was not found.\n
253
+ Please, select a valid working path.''')
254
+ self.edit_action(job)
255
+ for action in job.sub_actions:
256
+ if 'working_path' in job.params.keys():
257
+ working_path = job.params['working_path']
258
+ if working_path != '' and not os.path.isdir(working_path):
259
+ QMessageBox.warning(
260
+ self.parent, "Working path not found",
261
+ f'''The working path specified in the project file for the job:
262
+ "{job.params['name']}"
263
+ was not found.\n
264
+ Please, select a valid working path.''')
265
+ self.edit_action(action)
266
+
267
+ def save_project(self):
268
+ path = self.current_file_path()
269
+ if path:
270
+ self.do_save(path)
271
+ else:
272
+ self.save_project_as()
273
+
274
+ def save_project_as(self):
275
+ file_path, _ = QFileDialog.getSaveFileName(
276
+ self.parent, "Save Project As", "", "Project Files (*.fsp);;All Files (*)")
277
+ if file_path:
278
+ if not file_path.endswith('.fsp'):
279
+ file_path += '.fsp'
280
+ self.do_save(file_path)
281
+ self.set_current_file_path(file_path)
282
+ os.chdir(os.path.dirname(file_path))
283
+
284
+ def do_save(self, file_path):
285
+ try:
286
+ json_obj = jsonpickle.encode({
287
+ 'project': self.project().to_dict(), 'version': 1
288
+ })
289
+ with open(file_path, 'w', encoding="utf-8") as f:
290
+ f.write(json_obj)
291
+ self.mark_as_modified(False)
292
+ except Exception as e:
293
+ QMessageBox.critical(self.parent, "Error", f"Cannot save file:\n{str(e)}")
294
+
295
+ def check_unsaved_changes(self) -> bool:
296
+ if self.modified():
297
+ reply = QMessageBox.question(
298
+ self.parent, "Unsaved Changes",
299
+ "The project has unsaved changes. Do you want to continue?",
300
+ QMessageBox.Save | QMessageBox.Discard | QMessageBox.Cancel
301
+ )
302
+ if reply == QMessageBox.Save:
303
+ self.save_project()
304
+ return True
305
+ return reply == QMessageBox.Discard
306
+ return True
307
+
308
+ def on_job_edit(self, item):
309
+ index = self.job_list().row(item)
310
+ if 0 <= index < self.num_project_jobs():
311
+ job = self.project_job(index)
312
+ dialog = self.action_config_dialog(job)
313
+ if dialog.exec() == QDialog.Accepted:
314
+ current_row = self.current_job_index()
315
+ if current_row >= 0:
316
+ self.job_list_item(current_row).setText(job.params['name'])
317
+ self.refresh_ui()
318
+
319
+ def on_action_edit(self, item):
320
+ job_index = self.current_job_index()
321
+ if 0 <= job_index < self.num_project_jobs():
322
+ job = self.project_job(job_index)
323
+ action_index = self.action_list().row(item)
324
+ current_action, is_sub_action = self.get_current_action_at(job, action_index)
325
+ if current_action:
326
+ if not is_sub_action:
327
+ self.enable_sub_actions_requested.emit(
328
+ current_action.type_name == constants.ACTION_COMBO)
329
+ dialog = self.action_config_dialog(current_action)
330
+ if dialog.exec() == QDialog.Accepted:
331
+ self.on_job_selected(job_index)
332
+ self.refresh_ui()
333
+ self.set_current_job(job_index)
334
+ self.set_current_action(action_index)
335
+
336
+ def edit_current_action(self):
337
+ current_action = None
338
+ job_row = self.current_job_index()
339
+ if 0 <= job_row < self.num_project_jobs():
340
+ job = self.project_job(job_row)
341
+ if self.job_list_has_focus():
342
+ current_action = job
343
+ elif self.action_list_has_focus():
344
+ job_row, _action_row, pos = self.get_current_action()
345
+ if pos.actions is not None:
346
+ current_action = pos.action if not pos.is_sub_action else pos.sub_action
347
+ if current_action is not None:
348
+ self.edit_action(current_action)
349
+
350
+ def edit_action(self, action):
351
+ dialog = self.action_config_dialog(action)
352
+ if dialog.exec() == QDialog.Accepted:
353
+ self.on_job_selected(self.current_job_index())
354
+ self.mark_as_modified()
@@ -56,8 +56,9 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
56
56
  self.set_master_layer(master_layer)
57
57
  self.undo_manager.reset()
58
58
  self.blank_layer = np.zeros(master_layer.shape[:2])
59
- self.finish_loading_setup(stack, None, master_layer,
60
- f"Loaded: {self.current_file_path()}")
59
+ self.finish_loading_setup(
60
+ stack, None, master_layer, False,
61
+ f"Loaded: {self.current_file_path()}")
61
62
 
62
63
  def on_file_error(self, error_msg):
63
64
  QApplication.restoreOverrideCursor()
@@ -136,23 +137,26 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
136
137
  msg.setText(str(e))
137
138
  msg.exec()
138
139
  return
139
- self.finish_loading_setup(stack, labels, master, "Selected frames imported")
140
+ self.finish_loading_setup(
141
+ stack, labels, master, True,
142
+ "Selected frames imported")
140
143
 
141
- def finish_loading_setup(self, stack, labels, master, message):
142
- if self.layer_stack() is None and len(stack) > 0:
143
- self.set_layer_stack(np.array(stack))
144
- if labels is None:
145
- labels = self.layer_labels()
144
+ def finish_loading_setup(self, stack, labels, master, add_layers, message):
145
+ if add_layers:
146
+ if self.layer_stack() is None and len(stack) > 0:
147
+ self.set_layer_stack(np.array(stack))
148
+ if labels is None:
149
+ labels = self.layer_labels()
150
+ else:
151
+ self.set_layer_labels(labels)
152
+ self.set_master_layer(master)
153
+ self.blank_layer = np.zeros(master.shape[:2])
146
154
  else:
147
- self.set_layer_labels(labels)
148
- self.set_master_layer(master)
149
- self.blank_layer = np.zeros(master.shape[:2])
150
- else:
151
- if labels is None:
152
- labels = self.layer_labels()
153
- for img, label in zip(stack, labels):
154
- self.add_layer_label(label)
155
- self.add_layer(img)
155
+ if labels is None:
156
+ labels = self.layer_labels()
157
+ for img, label in zip(stack, labels):
158
+ self.add_layer_label(label)
159
+ self.add_layer(img)
156
160
  self.display_manager.update_thumbnails()
157
161
  self.mark_as_modified_requested.emit(True)
158
162
  self.change_layer_requested.emit(0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shinestacker
3
- Version: 1.0.0
3
+ Version: 1.0.2
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
@@ -76,14 +78,6 @@ The GUI has two main working areas:
76
78
 
77
79
  The first version of the core focus stack algorithm was initially inspired by the [Laplacian pyramids method](https://github.com/sjawhar/focus-stacking) implementation by Sami Jawhar that was used under permission of the author. The implementation in the latest releases was rewritten from the original code.
78
80
 
79
- <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/src/shinestacker/gui/ico/shinestacker.png' width="256" referrerpolicy="no-referrer" alt="Shine Stacker Logo">
80
-
81
- ## Logo attribution
82
-
83
- The Shine Stacker logo was designed by [Alessandro Lista](https://linktr.ee/alelista).
84
- Copyright © Alessandro Lista. All rights reserved.
85
- The logo is not covered by the LGPL-3.0 license of this project.
86
-
87
81
  # Resources
88
82
 
89
83
  * [Pyramid Methods in Image Processing](https://www.researchgate.net/publication/246727904_Pyramid_Methods_in_Image_Processing), E. H. Adelson, C. H. Anderson, J. R. Bergen, P. J. Burt, J. M. Ogden, RCA Engineer, 29-6, Nov/Dec 1984
@@ -92,11 +86,11 @@ Pyramid methods in image processing
92
86
 
93
87
  # License
94
88
 
95
- - **Code**: <img src="https://www.gnu.org/graphics/lgplv3-147x51.png" alt="LGPL 3 logo">
96
- 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.
97
- - **Logo**: The Shine Stacker logo was designed by [Alessandro Lista](https://linktr.ee/alelista).
98
- Copyright © Alessandro Lista. All rights reserved.
99
- The logo is not covered by the LGPL-3.0 license of this project.
89
+ <img src="https://www.gnu.org/graphics/lgplv3-147x51.png" alt="LGPL 3 logo">
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.
100
94
 
101
95
  # Attribution request
102
96
  📸 If you publish images created with Shine Stacker, please consider adding a note such as:
@@ -93,6 +93,7 @@ src/shinestacker/gui/gui_run.py
93
93
  src/shinestacker/gui/main_window.py
94
94
  src/shinestacker/gui/menu_manager.py
95
95
  src/shinestacker/gui/new_project.py
96
+ src/shinestacker/gui/project_controller.py
96
97
  src/shinestacker/gui/project_converter.py
97
98
  src/shinestacker/gui/project_editor.py
98
99
  src/shinestacker/gui/project_model.py
@@ -1 +0,0 @@
1
- __version__ = '1.0.0'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes