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.
- {shinestacker-1.0.0 → shinestacker-1.0.2}/CHANGELOG.md +24 -0
- {shinestacker-1.0.0/src/shinestacker.egg-info → shinestacker-1.0.2}/PKG-INFO +8 -14
- {shinestacker-1.0.0 → shinestacker-1.0.2}/README.md +7 -13
- shinestacker-1.0.2/src/shinestacker/_version.py +1 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/gui_run.py +2 -2
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/main_window.py +7 -4
- shinestacker-1.0.2/src/shinestacker/gui/project_controller.py +354 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/io_gui_handler.py +21 -17
- {shinestacker-1.0.0 → shinestacker-1.0.2/src/shinestacker.egg-info}/PKG-INFO +8 -14
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker.egg-info/SOURCES.txt +1 -0
- shinestacker-1.0.0/src/shinestacker/_version.py +0 -1
- {shinestacker-1.0.0 → shinestacker-1.0.2}/.coveragerc +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/.flake8 +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/.github/workflows/ci-multiplatform.yml +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/.github/workflows/pylint.yml +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/.github/workflows/pypi-publish.yml +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/.github/workflows/release.yml +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/.gitignore +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/.pylintrc +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/.readthedocs.yaml +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/LICENSE +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/MANIFEST.in +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/THIRD_PARTY_LICENSES.txt +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/alignment.md +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/api.md +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/balancing.md +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/conf.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/focus_stacking.md +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/gui.md +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/index.md +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/job.md +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/main.md +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/multilayer.md +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/noise.md +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/requirements.txt +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/docs/vignetting.md +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/img/coffee.gif +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/img/coffee_stack.jpg +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/img/extreme-vignetting.jpg +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/img/flies.gif +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/img/flies_stack.jpg +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/img/flow-diagram.png +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/img/gui-finder.png +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/img/gui-project-new.png +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/img/gui-project-run.png +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/img/gui-retouch.png +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/pyproject.toml +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/requirements.txt +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/scripts/build_release.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/scripts/git-rev-list.sh +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/scripts/validate-tomli.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/setup.cfg +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/__init__.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/__init__.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/align.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/balance.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/denoise.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/depth_map.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/exif.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/multilayer.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/noise_detection.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/pyramid.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/sharpen.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/stack.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/stack_framework.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/utils.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/vignetting.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/algorithms/white_balance.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/app/__init__.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/app/about_dialog.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/app/gui_utils.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/app/help_menu.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/app/main.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/app/open_frames.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/app/project.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/app/retouch.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/config/__init__.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/config/config.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/config/constants.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/config/gui_constants.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/core/__init__.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/core/colors.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/core/core_utils.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/core/exceptions.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/core/framework.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/core/logging.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/__init__.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/action_config.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/action_config_dialog.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/base_form_dialog.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/colors.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/gui_images.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/gui_logging.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/ico/shinestacker.png +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/menu_manager.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/new_project.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/project_converter.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/project_editor.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/project_model.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/select_path_widget.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/tab_widget.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/time_progress_bar.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/__init__.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/base_filter.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/brush.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/brush_gradient.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/brush_preview.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/brush_tool.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/denoise_filter.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/display_manager.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/exif_data.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/file_loader.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/filter_manager.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/icon_container.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/image_editor_ui.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/image_viewer.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/io_manager.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/layer_collection.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/shortcuts_help.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/undo_manager.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/unsharp_mask_filter.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/vignetting_filter.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/retouch/white_balance_filter.py +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker.egg-info/dependency_links.txt +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker.egg-info/entry_points.txt +0 -0
- {shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker.egg-info/requires.txt +0 -0
- {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.
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
-
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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.
|
|
136
|
+
if current.objectName() == class_name:
|
|
137
137
|
return current
|
|
138
138
|
current = current.parent()
|
|
139
139
|
return None
|
|
140
|
-
parent = find_parent(self, "
|
|
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(
|
|
60
|
-
|
|
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(
|
|
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
|
|
143
|
-
self.
|
|
144
|
-
|
|
145
|
-
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
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
98
|
-
|
|
99
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
{shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/img/close-round-line-icon.png
RENAMED
|
File without changes
|
|
File without changes
|
{shinestacker-1.0.0 → shinestacker-1.0.2}/src/shinestacker/gui/img/play-button-round-icon.png
RENAMED
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|