shinestacker 0.3.4__tar.gz → 0.3.6__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 (137) hide show
  1. {shinestacker-0.3.4 → shinestacker-0.3.6}/.coverage +0 -0
  2. {shinestacker-0.3.4 → shinestacker-0.3.6}/CHANGELOG.md +22 -0
  3. {shinestacker-0.3.4 → shinestacker-0.3.6}/PKG-INFO +10 -8
  4. {shinestacker-0.3.4 → shinestacker-0.3.6}/README.md +9 -7
  5. shinestacker-0.3.6/THIRD_PARTY_LICENSES.txt +80 -0
  6. {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/main.md +2 -5
  7. shinestacker-0.3.6/img/gui-project-new.png +0 -0
  8. shinestacker-0.3.6/src/shinestacker/_version.py +1 -0
  9. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/base_stack_algo.py +1 -2
  10. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/noise_detection.py +0 -3
  11. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/pyramid.py +7 -4
  12. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/stack.py +1 -2
  13. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/stack_framework.py +8 -2
  14. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/vignetting.py +5 -4
  15. shinestacker-0.3.6/src/shinestacker/app/app_config.py +22 -0
  16. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/app/help_menu.py +1 -1
  17. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/config/config.py +21 -16
  18. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/action_config.py +10 -35
  19. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/actions_window.py +22 -54
  20. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/new_project.py +5 -22
  21. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/project_editor.py +49 -20
  22. shinestacker-0.3.6/src/shinestacker/gui/select_path_widget.py +30 -0
  23. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/base_filter.py +12 -1
  24. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/denoise_filter.py +4 -10
  25. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/exif_data.py +3 -9
  26. shinestacker-0.3.6/src/shinestacker/retouch/icon_container.py +19 -0
  27. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/shortcuts_help.py +2 -13
  28. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/unsharp_mask_filter.py +3 -10
  29. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/white_balance_filter.py +5 -13
  30. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker.egg-info/PKG-INFO +10 -8
  31. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker.egg-info/SOURCES.txt +3 -3
  32. shinestacker-0.3.4/img/coins.gif +0 -0
  33. shinestacker-0.3.4/img/coins_stack.jpg +0 -0
  34. shinestacker-0.3.4/img/gui-project-new.png +0 -0
  35. shinestacker-0.3.4/src/shinestacker/_version.py +0 -1
  36. shinestacker-0.3.4/src/shinestacker/algorithms/core_utils.py +0 -22
  37. shinestacker-0.3.4/src/shinestacker/app/app_config.py +0 -40
  38. {shinestacker-0.3.4 → shinestacker-0.3.6}/.coveragerc +0 -0
  39. {shinestacker-0.3.4 → shinestacker-0.3.6}/.flake8 +0 -0
  40. {shinestacker-0.3.4 → shinestacker-0.3.6}/.github/workflows/ci-multiplatform.yml +0 -0
  41. {shinestacker-0.3.4 → shinestacker-0.3.6}/.github/workflows/pylint.yml +0 -0
  42. {shinestacker-0.3.4 → shinestacker-0.3.6}/.github/workflows/pypi-publish.yml +0 -0
  43. {shinestacker-0.3.4 → shinestacker-0.3.6}/.github/workflows/release.yml +0 -0
  44. {shinestacker-0.3.4 → shinestacker-0.3.6}/.gitignore +0 -0
  45. {shinestacker-0.3.4 → shinestacker-0.3.6}/.pylintrc +0 -0
  46. {shinestacker-0.3.4 → shinestacker-0.3.6}/.readthedocs.yaml +0 -0
  47. {shinestacker-0.3.4 → shinestacker-0.3.6}/LICENSE +0 -0
  48. {shinestacker-0.3.4 → shinestacker-0.3.6}/MANIFEST.in +0 -0
  49. {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/alignment.md +0 -0
  50. {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/api.md +0 -0
  51. {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/balancing.md +0 -0
  52. {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/conf.py +0 -0
  53. {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/focus_stacking.md +0 -0
  54. {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/gui.md +0 -0
  55. {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/index.md +0 -0
  56. {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/job.md +0 -0
  57. {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/multilayer.md +0 -0
  58. {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/noise.md +0 -0
  59. {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/requirements.txt +0 -0
  60. {shinestacker-0.3.4 → shinestacker-0.3.6}/docs/vignetting.md +0 -0
  61. {shinestacker-0.3.4 → shinestacker-0.3.6}/img/coffee.gif +0 -0
  62. {shinestacker-0.3.4 → shinestacker-0.3.6}/img/coffee_stack.jpg +0 -0
  63. {shinestacker-0.3.4 → shinestacker-0.3.6}/img/extreme-vignetting.jpg +0 -0
  64. {shinestacker-0.3.4 → shinestacker-0.3.6}/img/flies.gif +0 -0
  65. {shinestacker-0.3.4 → shinestacker-0.3.6}/img/flies_stack.jpg +0 -0
  66. {shinestacker-0.3.4 → shinestacker-0.3.6}/img/flow-diagram.png +0 -0
  67. {shinestacker-0.3.4 → shinestacker-0.3.6}/img/gui-finder.png +0 -0
  68. {shinestacker-0.3.4 → shinestacker-0.3.6}/img/gui-project-run.png +0 -0
  69. {shinestacker-0.3.4 → shinestacker-0.3.6}/img/gui-retouch.png +0 -0
  70. {shinestacker-0.3.4 → shinestacker-0.3.6}/pyproject.toml +0 -0
  71. {shinestacker-0.3.4 → shinestacker-0.3.6}/requirements.txt +0 -0
  72. {shinestacker-0.3.4 → shinestacker-0.3.6}/scripts/build_release.py +0 -0
  73. {shinestacker-0.3.4 → shinestacker-0.3.6}/scripts/validate-tomli.py +0 -0
  74. {shinestacker-0.3.4 → shinestacker-0.3.6}/setup.cfg +0 -0
  75. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/__init__.py +0 -0
  76. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/__init__.py +0 -0
  77. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/align.py +0 -0
  78. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/balance.py +0 -0
  79. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/denoise.py +0 -0
  80. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/depth_map.py +0 -0
  81. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/exif.py +0 -0
  82. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/multilayer.py +0 -0
  83. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/sharpen.py +0 -0
  84. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/utils.py +0 -0
  85. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/algorithms/white_balance.py +0 -0
  86. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/app/__init__.py +0 -0
  87. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/app/about_dialog.py +0 -0
  88. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/app/gui_utils.py +0 -0
  89. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/app/main.py +0 -0
  90. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/app/open_frames.py +0 -0
  91. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/app/project.py +0 -0
  92. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/app/retouch.py +0 -0
  93. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/config/__init__.py +0 -0
  94. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/config/constants.py +0 -0
  95. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/config/gui_constants.py +0 -0
  96. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/core/__init__.py +0 -0
  97. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/core/colors.py +0 -0
  98. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/core/core_utils.py +0 -0
  99. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/core/exceptions.py +0 -0
  100. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/core/framework.py +0 -0
  101. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/core/logging.py +0 -0
  102. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/__init__.py +0 -0
  103. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/colors.py +0 -0
  104. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/gui_images.py +0 -0
  105. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/gui_logging.py +0 -0
  106. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/gui_run.py +0 -0
  107. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
  108. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
  109. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
  110. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/ico/shinestacker.png +0 -0
  111. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
  112. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
  113. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
  114. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
  115. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/main_window.py +0 -0
  116. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/project_converter.py +0 -0
  117. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/gui/project_model.py +0 -0
  118. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/__init__.py +0 -0
  119. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/brush.py +0 -0
  120. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/brush_gradient.py +0 -0
  121. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/brush_preview.py +0 -0
  122. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/brush_tool.py +0 -0
  123. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/display_manager.py +0 -0
  124. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/file_loader.py +0 -0
  125. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/filter_manager.py +0 -0
  126. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/image_editor.py +0 -0
  127. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/image_editor_ui.py +0 -0
  128. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/image_filters.py +0 -0
  129. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/image_viewer.py +0 -0
  130. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/io_gui_handler.py +0 -0
  131. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/io_manager.py +0 -0
  132. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/layer_collection.py +0 -0
  133. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker/retouch/undo_manager.py +0 -0
  134. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker.egg-info/dependency_links.txt +0 -0
  135. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker.egg-info/entry_points.txt +0 -0
  136. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker.egg-info/requires.txt +0 -0
  137. {shinestacker-0.3.4 → shinestacker-0.3.6}/src/shinestacker.egg-info/top_level.txt +0 -0
@@ -4,6 +4,28 @@ This page reports the main releases only and the main changes therein.
4
4
 
5
5
  ---
6
6
 
7
+ ## [v-.-.-] - 2025-08-17
8
+ **Unreleased changes, for next release round**
9
+
10
+ ### Changes
11
+
12
+ * fixed a bug that prevented a complete clean up when "New Project" action is called
13
+ * fixed the management of project file path while loading and saving
14
+ * removed duplicated code, code clean up
15
+
16
+ ---
17
+
18
+ ## [v0.3.5] - 2025-08-17
19
+ **Bug fixes**
20
+
21
+ ### Changes
22
+
23
+ * fixed a bug that prevented to add sub-actions
24
+ * vignetting constrains model parameter in order to prevent searching for dark areas at the center of the image instead of at periphery
25
+ * updated sample images and documentation
26
+
27
+ ---
28
+
7
29
  ## [v0.3.4] - 2025-08-16
8
30
  **Code consolidation and fixes**
9
31
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shinestacker
3
- Version: 0.3.4
3
+ Version: 0.3.6
4
4
  Summary: ShineStacker
5
5
  Author-email: Luca Lista <luka.lista@gmail.com>
6
6
  License-Expression: LGPL-3.0
@@ -37,25 +37,22 @@ Dynamic: license-file
37
37
  [![PyPI version](https://img.shields.io/pypi/v/shinestacker?color=success)](https://pypi.org/project/shinestacker/)
38
38
  [![Python Versions](https://img.shields.io/pypi/pyversions/shinestacker)](https://pypi.org/project/shinestacker/)
39
39
  [![Qt Versions](https://img.shields.io/badge/Qt-6-blue.svg?&logo=Qt&logoWidth=18&logoColor=white)](https://www.qt.io/qt-for-python)
40
- [![codecov](https://codecov.io/github/lucalista/shinestacker/graph/badge.svg?token=Y5NKW6VH5G)](https://codecov.io/github/lucalista/shinestacker)
41
40
  [![pylint](https://img.shields.io/badge/PyLint-9.98-yellow?logo=python&logoColor=white)](https://github.com/lucalista/shinestacker/blob/main/.github/workflows/pylint.yml)
41
+ [![codecov](https://codecov.io/github/lucalista/shinestacker/graph/badge.svg?token=Y5NKW6VH5G)](https://codecov.io/github/lucalista/shinestacker)
42
42
  [![Documentation Status](https://readthedocs.org/projects/shinestacker/badge/?version=latest)](https://shinestacker.readthedocs.io/en/latest/?badge=latest)
43
43
 
44
44
 
45
-
46
45
  <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">
47
46
 
48
47
  <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coffee.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coffee_stack.jpg' width="400" referrerpolicy="no-referrer">
49
48
 
50
- <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coins.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coins_stack.jpg' width="400" referrerpolicy="no-referrer">
51
49
  > **Focus stacking** for microscopy, macro photography, and computational imaging
52
50
 
53
51
  ## Key Features
54
52
  - 🚀 **Batch Processing**: Align, balance, and stack hundreds of images
55
- - 🎨 **Hybrid Workflows**: Combine Python scripting with GUI refinement
56
53
  - 🧩 **Modular Architecture**: Mix-and-match processing modules
57
54
  - 🖌️ **Retouch Editing**: Final interactive retouch of stacked image from individual frames
58
- - 📊 **Jupyter Integration**: Reproducible research notebooks
55
+ - 📊 **Jupyter Integration**: Image processing python notebooks
59
56
 
60
57
  ## Interactive GUI
61
58
 
@@ -76,16 +73,21 @@ The GUI has two main working areas:
76
73
 
77
74
  # Credits
78
75
 
79
- The main pyramid 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 for initial versions of this package. The implementation in the latest releases was rewritten from the original code.
76
+ 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.
80
77
 
81
78
  # Resources
82
79
 
83
80
  * [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
84
81
  Pyramid methods in image processing
85
82
  * [A Multi-focus Image Fusion Method Based on Laplacian Pyramid](http://www.jcomputers.us/vol6/jcp0612-07.pdf), Wencheng Wang, Faliang Chang, Journal of Computers 6 (12), 2559, December 2011
86
- * Another [original implementation on GitHub](https://github.com/bznick98/Focus_Stacking) by Zongnan Bao
87
83
 
88
84
  # License
89
85
 
90
86
  The software is provided as is under the [GNU Lesser General Public License v3.0](https://choosealicense.com/licenses/lgpl-3.0/).
91
87
 
88
+ # Attribution request
89
+ 📸 If you publish images created with Shine Stacker, please consider adding a note such as:
90
+
91
+ *Created with Shine Stacker – https://github.com/lucalista/shinestacker*
92
+
93
+ This is not mandatory, but highly appreciated.
@@ -6,25 +6,22 @@
6
6
  [![PyPI version](https://img.shields.io/pypi/v/shinestacker?color=success)](https://pypi.org/project/shinestacker/)
7
7
  [![Python Versions](https://img.shields.io/pypi/pyversions/shinestacker)](https://pypi.org/project/shinestacker/)
8
8
  [![Qt Versions](https://img.shields.io/badge/Qt-6-blue.svg?&logo=Qt&logoWidth=18&logoColor=white)](https://www.qt.io/qt-for-python)
9
- [![codecov](https://codecov.io/github/lucalista/shinestacker/graph/badge.svg?token=Y5NKW6VH5G)](https://codecov.io/github/lucalista/shinestacker)
10
9
  [![pylint](https://img.shields.io/badge/PyLint-9.98-yellow?logo=python&logoColor=white)](https://github.com/lucalista/shinestacker/blob/main/.github/workflows/pylint.yml)
10
+ [![codecov](https://codecov.io/github/lucalista/shinestacker/graph/badge.svg?token=Y5NKW6VH5G)](https://codecov.io/github/lucalista/shinestacker)
11
11
  [![Documentation Status](https://readthedocs.org/projects/shinestacker/badge/?version=latest)](https://shinestacker.readthedocs.io/en/latest/?badge=latest)
12
12
 
13
13
 
14
-
15
14
  <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">
16
15
 
17
16
  <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coffee.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coffee_stack.jpg' width="400" referrerpolicy="no-referrer">
18
17
 
19
- <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coins.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coins_stack.jpg' width="400" referrerpolicy="no-referrer">
20
18
  > **Focus stacking** for microscopy, macro photography, and computational imaging
21
19
 
22
20
  ## Key Features
23
21
  - 🚀 **Batch Processing**: Align, balance, and stack hundreds of images
24
- - 🎨 **Hybrid Workflows**: Combine Python scripting with GUI refinement
25
22
  - 🧩 **Modular Architecture**: Mix-and-match processing modules
26
23
  - 🖌️ **Retouch Editing**: Final interactive retouch of stacked image from individual frames
27
- - 📊 **Jupyter Integration**: Reproducible research notebooks
24
+ - 📊 **Jupyter Integration**: Image processing python notebooks
28
25
 
29
26
  ## Interactive GUI
30
27
 
@@ -45,16 +42,21 @@ The GUI has two main working areas:
45
42
 
46
43
  # Credits
47
44
 
48
- The main pyramid 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 for initial versions of this package. The implementation in the latest releases was rewritten from the original code.
45
+ 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.
49
46
 
50
47
  # Resources
51
48
 
52
49
  * [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
53
50
  Pyramid methods in image processing
54
51
  * [A Multi-focus Image Fusion Method Based on Laplacian Pyramid](http://www.jcomputers.us/vol6/jcp0612-07.pdf), Wencheng Wang, Faliang Chang, Journal of Computers 6 (12), 2559, December 2011
55
- * Another [original implementation on GitHub](https://github.com/bznick98/Focus_Stacking) by Zongnan Bao
56
52
 
57
53
  # License
58
54
 
59
55
  The software is provided as is under the [GNU Lesser General Public License v3.0](https://choosealicense.com/licenses/lgpl-3.0/).
60
56
 
57
+ # Attribution request
58
+ 📸 If you publish images created with Shine Stacker, please consider adding a note such as:
59
+
60
+ *Created with Shine Stacker – https://github.com/lucalista/shinestacker*
61
+
62
+ This is not mandatory, but highly appreciated.
@@ -0,0 +1,80 @@
1
+ THIRD-PARTY LICENSES
2
+ ====================
3
+
4
+ This application bundles several third-party Python packages.
5
+ Each package is copyrighted by its respective authors and distributed under the following licenses.
6
+ Full license texts are reproduced below when required.
7
+
8
+ -------------------------------------------------------------------------------
9
+ matplotlib
10
+ License: PSF license + BSD style
11
+ https://matplotlib.org/stable/users/project/license.html
12
+
13
+ -------------------------------------------------------------------------------
14
+ imagecodecs
15
+ License: BSD-3-Clause
16
+ https://pypi.org/project/imagecodecs/
17
+
18
+ -------------------------------------------------------------------------------
19
+ jsonpickle
20
+ License: BSD-3-Clause
21
+ https://github.com/jsonpickle/jsonpickle
22
+
23
+ -------------------------------------------------------------------------------
24
+ numpy
25
+ License: BSD-3-Clause
26
+ https://numpy.org/
27
+
28
+ -------------------------------------------------------------------------------
29
+ opencv-python
30
+ License: Apache License 2.0
31
+ https://github.com/opencv/opencv-python
32
+
33
+ -------------------------------------------------------------------------------
34
+ pillow
35
+ License: Historical PIL Software License (similar to MIT)
36
+ https://python-pillow.org
37
+
38
+ -------------------------------------------------------------------------------
39
+ psdtags
40
+ License: MIT
41
+ https://pypi.org/project/psdtags/
42
+
43
+ -------------------------------------------------------------------------------
44
+ PySide6
45
+ License: GNU Lesser General Public License v3.0 (LGPL-3.0)
46
+ or commercial license from The Qt Company
47
+ https://doc.qt.io/qtforpython/
48
+
49
+ Full license text follows:
50
+
51
+ --- BEGIN LGPL-3.0 LICENSE ---
52
+ <qui incolla il testo completo della LGPL-3.0>
53
+ --- END LGPL-3.0 LICENSE ---
54
+
55
+ -------------------------------------------------------------------------------
56
+ scipy
57
+ License: BSD-3-Clause
58
+ https://scipy.org/
59
+
60
+ -------------------------------------------------------------------------------
61
+ tifffile
62
+ License: BSD-3-Clause
63
+ https://www.lfd.uci.edu/~gohlke/
64
+
65
+ -------------------------------------------------------------------------------
66
+ tqdm
67
+ License: Mozilla Public License 2.0 (MPL-2.0)
68
+ https://github.com/tqdm/tqdm
69
+
70
+ Full license text follows:
71
+
72
+ --- BEGIN MPL-2.0 LICENSE ---
73
+ <qui incolla il testo completo della MPL-2.0>
74
+ --- END MPL-2.0 LICENSE ---
75
+
76
+ -------------------------------------------------------------------------------
77
+
78
+ NOTE:
79
+ This file is provided for license compliance and attribution purposes.
80
+ Your application code is licensed separately under the GNU Lesser General Public License v3.0.
@@ -6,16 +6,13 @@
6
6
 
7
7
  <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coffee.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coffee_stack.jpg' width="400" referrerpolicy="no-referrer">
8
8
 
9
- <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coins.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coins_stack.jpg' width="400" referrerpolicy="no-referrer">
10
-
11
9
  > **Focus stacking** for microscopy, macro photography, and computational imaging
12
10
 
13
11
  ## Key Features
14
12
  - 🚀 **Batch Processing**: Align, balance, and stack hundreds of images
15
- - 🎨 **Hybrid Workflows**: Combine Python scripting with GUI refinement
16
13
  - 🧩 **Modular Architecture**: Mix-and-match processing modules
17
- - 🖌️ **Non-Destructive Editing**: Save multilayer TIFFs for retouching
18
- - 📊 **Jupyter Integration**: Reproducible research notebooks
14
+ - 🖌️ **Retouch Editing**: Final interactive retouch of stacked image from individual frames
15
+ - 📊 **Jupyter Integration**: Image processing python notebooks
19
16
 
20
17
 
21
18
  ## Quick start
@@ -0,0 +1 @@
1
+ __version__ = '0.3.6'
@@ -1,6 +1,5 @@
1
1
  # pylint: disable=C0114, C0115, C0116, E0602, R0903
2
2
  import numpy as np
3
- from .. core.colors import color_str
4
3
  from .. core.exceptions import InvalidOptionError, ImageLoadError
5
4
  from .. config.constants import constants
6
5
  from .utils import read_img, get_img_metadata, validate_image
@@ -28,7 +27,7 @@ class BaseStackAlgo:
28
27
  return self._steps_per_frame
29
28
 
30
29
  def print_message(self, msg):
31
- self.process.sub_message_r(color_str(msg, "light_blue"))
30
+ self.process.sub_message_r(msg)
32
31
 
33
32
  def read_image_and_update_metadata(self, img_path, metadata):
34
33
  img = read_img(img_path)
@@ -162,9 +162,6 @@ class MaskNoise(SubAction):
162
162
  else:
163
163
  raise ImageLoadError(path, "file not found.")
164
164
 
165
- def end(self):
166
- pass
167
-
168
165
  def run_frame(self, _idx, _ref_idx, image):
169
166
  self.process.sub_message_r(': mask noisy pixels')
170
167
  if len(image.shape) == 3:
@@ -12,7 +12,7 @@ class PyramidBase(BaseStackAlgo):
12
12
  kernel_size=constants.DEFAULT_PY_KERNEL_SIZE,
13
13
  gen_kernel=constants.DEFAULT_PY_GEN_KERNEL,
14
14
  float_type=constants.DEFAULT_PY_FLOAT):
15
- super().__init__("pyramid", 1, float_type)
15
+ super().__init__("pyramid", 2, float_type)
16
16
  self.min_size = min_size
17
17
  self.kernel_size = kernel_size
18
18
  self.pad_amount = (kernel_size - 1) // 2
@@ -151,11 +151,11 @@ class PyramidStack(PyramidBase):
151
151
  metadata = None
152
152
  all_laplacians = []
153
153
  levels = None
154
+ n = len(filenames)
154
155
  for i, img_path in enumerate(filenames):
155
156
  self.print_message(f": validating file {img_path.split('/')[-1]}")
156
157
 
157
158
  img, metadata, updated = self.read_image_and_update_metadata(img_path, metadata)
158
-
159
159
  if updated:
160
160
  self.dtype = metadata[1]
161
161
  self.num_pixel_values = constants.NUM_UINT8 \
@@ -163,14 +163,17 @@ class PyramidStack(PyramidBase):
163
163
  self.max_pixel_value = constants.MAX_UINT8 \
164
164
  if self.dtype == np.uint8 else constants.MAX_UINT16
165
165
  levels = int(np.log2(min(img.shape[:2]) / self.min_size))
166
-
167
166
  if self.do_step_callback:
168
167
  self.process.callback('after_step', self.process.id, self.process.name, i)
169
168
  if self.process.callback('check_running', self.process.id, self.process.name) is False:
170
169
  raise RunStopException(self.name)
171
- for img_path in filenames:
170
+ for i, img_path in enumerate(filenames):
172
171
  self.print_message(f": processing file {img_path.split('/')[-1]}")
173
172
  img = read_img(img_path)
174
173
  all_laplacians.append(self.process_single_image(img, levels))
174
+ if self.do_step_callback:
175
+ self.process.callback('after_step', self.process.id, self.process.name, i + n)
176
+ if self.process.callback('check_running', self.process.id, self.process.name) is False:
177
+ raise RunStopException(self.name)
175
178
  stacked_image = self.collapse(self.fuse_pyramids(all_laplacians))
176
179
  return stacked_image.astype(self.dtype)
@@ -2,7 +2,6 @@
2
2
  import os
3
3
  import numpy as np
4
4
  from .. config.constants import constants
5
- from .. core.colors import color_str
6
5
  from .. core.framework import JobBase
7
6
  from .. core.exceptions import InvalidOptionError
8
7
  from .utils import write_img
@@ -92,7 +91,7 @@ class FocusStackBunch(ActionList, FocusStackBase):
92
91
  ActionList.end(self)
93
92
 
94
93
  def run_step(self):
95
- self.print_message_r(color_str(f"fusing bunch: {self.count}", "blue"))
94
+ self.print_message_r(f"fusing bunch: {self.count}")
96
95
  self.focus_stack(self._chunks[self.count - 1])
97
96
  self.callback('after_step', self.id, self.name, self.count)
98
97
 
@@ -237,6 +237,12 @@ class SubAction:
237
237
  def __init__(self, enabled=True):
238
238
  self.enabled = enabled
239
239
 
240
+ def begin(self, process):
241
+ pass
242
+
243
+ def end(self):
244
+ pass
245
+
240
246
 
241
247
  class CombinedActions(FramesRefActions):
242
248
  def __init__(self, name, actions=[], enabled=True, **kwargs):
@@ -255,10 +261,10 @@ class CombinedActions(FramesRefActions):
255
261
  filename = self.filenames[idx]
256
262
  img = read_img((self.output_dir
257
263
  if self.step_process else self.input_full_path) + f"/{filename}")
258
- self.dtype = img.dtype
259
- self.shape = img.shape
260
264
  if img is None:
261
265
  raise RuntimeError(f"Invalid file: {self.input_full_path}/{filename}")
266
+ self.dtype = img.dtype
267
+ self.shape = img.shape
262
268
  return img
263
269
 
264
270
  def run_frame(self, idx, ref_idx):
@@ -59,7 +59,8 @@ class Vignetting(SubAction):
59
59
  i_valid, r_valid = intensities[valid_mask], radii[valid_mask]
60
60
  try:
61
61
  res = curve_fit(Vignetting.sigmoid, r_valid, i_valid,
62
- p0=[np.max(i_valid), 0.01, np.median(r_valid)])[0]
62
+ p0=[np.max(i_valid), 0.01, np.median(r_valid)],
63
+ bounds=([0, 0, 0], ['inf', 'inf', 'inf']))[0]
63
64
  except Exception:
64
65
  self.process.sub_message(
65
66
  color_str(": could not find vignetting model", "red"),
@@ -83,7 +84,7 @@ class Vignetting(SubAction):
83
84
  if image.dtype == np.uint8 else 65535).astype(image.dtype)
84
85
 
85
86
  def run_frame(self, idx, _ref_idx, img_0):
86
- self.process.sub_message_r(color_str(": compute vignetting", "light_blue"))
87
+ self.process.sub_message_r(": compute vignetting")
87
88
  img = cv2.cvtColor(img_8bit(img_0), cv2.COLOR_BGR2GRAY)
88
89
  radii, intensities = self.radial_mean_intensity(img)
89
90
  pars = self.fit_sigmoid(radii, intensities)
@@ -92,7 +93,7 @@ class Vignetting(SubAction):
92
93
  self.v0 = Vignetting.sigmoid(0, *pars)
93
94
  i0_fit, k_fit, r0_fit = pars
94
95
  self.process.sub_message(
95
- f": fit parameters: i0={i0_fit:.4f}, k={k_fit:.4f}, r0={r0_fit:.4f}",
96
+ f": vignetting model parameters: i0={i0_fit:.4f}, k={k_fit:.4f}, r0={r0_fit:.4f}",
96
97
  level=logging.DEBUG)
97
98
  if self.plot_correction:
98
99
  plt.figure(figsize=(10, 5))
@@ -116,7 +117,7 @@ class Vignetting(SubAction):
116
117
  self.corrections[i][idx] = fsolve(lambda x: Vignetting.sigmoid(x, *pars) /
117
118
  self.v0 - p, r0_fit)[0]
118
119
  if self.apply_correction:
119
- self.process.sub_message_r(color_str(": correct vignetting", "light_blue"))
120
+ self.process.sub_message_r(": correct vignetting")
120
121
  return self.correct_vignetting(img_0, pars)
121
122
  return img_0
122
123
 
@@ -0,0 +1,22 @@
1
+ # pylint: disable=C0114, C0115, C0116, C0103, W0201
2
+ from .. config.config import _ConfigBase
3
+
4
+
5
+ class _AppConfig(_ConfigBase):
6
+ def __new__(cls):
7
+ return _ConfigBase.__new__(cls)
8
+
9
+ def _init_defaults(self):
10
+ self._DONT_USE_NATIVE_MENU = True
11
+ self._COMBINED_APP = False
12
+
13
+ @property
14
+ def DONT_USE_NATIVE_MENU(self):
15
+ return self._DONT_USE_NATIVE_MENU
16
+
17
+ @property
18
+ def COMBINED_APP(self):
19
+ return self._COMBINED_APP
20
+
21
+
22
+ app_config = _AppConfig()
@@ -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/README.md")
17
+ webbrowser.open("https://github.com/lucalista/shinestacker/blob/main/docs/gui.md")
@@ -1,5 +1,5 @@
1
1
  # pylint: disable=C0114, C0115, C0116, C0103, R0903, W0718, W0104, W0201, E0602
2
- class _Config:
2
+ class _ConfigBase:
3
3
  _initialized = False
4
4
  _instance = None
5
5
 
@@ -9,16 +9,6 @@ class _Config:
9
9
  cls._instance._init_defaults()
10
10
  return cls._instance
11
11
 
12
- def _init_defaults(self):
13
- self._DISABLE_TQDM = False
14
- self._COMBINED_APP = False
15
- self._DONT_USE_NATIVE_MENU = True
16
- try:
17
- __IPYTHON__ # noqa
18
- self._JUPYTER_NOTEBOOK = True
19
- except Exception:
20
- self._JUPYTER_NOTEBOOK = False
21
-
22
12
  def init(self, **kwargs):
23
13
  if self._initialized:
24
14
  raise RuntimeError("Config already initialized")
@@ -29,6 +19,26 @@ class _Config:
29
19
  raise AttributeError(f"Invalid config key: {k}")
30
20
  self._initialized = True
31
21
 
22
+ def __setattr__(self, name, value):
23
+ if self._initialized and name.startswith('_'):
24
+ raise AttributeError("Can't change config after initialization")
25
+ super().__setattr__(name, value)
26
+
27
+ class _Config(_ConfigBase):
28
+
29
+ def __new__(cls):
30
+ return _ConfigBase.__new__(cls)
31
+
32
+ def _init_defaults(self):
33
+ self._DISABLE_TQDM = False
34
+ self._COMBINED_APP = False
35
+ self._DONT_USE_NATIVE_MENU = True
36
+ try:
37
+ __IPYTHON__ # noqa
38
+ self._JUPYTER_NOTEBOOK = True
39
+ except Exception:
40
+ self._JUPYTER_NOTEBOOK = False
41
+
32
42
  @property
33
43
  def DISABLE_TQDM(self):
34
44
  return self._DISABLE_TQDM
@@ -45,10 +55,5 @@ class _Config:
45
55
  def COMBINED_APP(self):
46
56
  return self._COMBINED_APP
47
57
 
48
- def __setattr__(self, name, value):
49
- if self._initialized and name.startswith('_'):
50
- raise AttributeError("Can't change config after initialization")
51
- super().__setattr__(name, value)
52
-
53
58
 
54
59
  config = _Config()
@@ -10,8 +10,10 @@ from PySide6.QtWidgets import (QWidget, QPushButton, QHBoxLayout, QFileDialog, Q
10
10
  QAbstractItemView, QListView)
11
11
  from PySide6.QtCore import Qt, QTimer
12
12
  from .. config.constants import constants
13
- from .project_model import ActionConfig
14
13
  from .. algorithms.align import validate_align_config
14
+ from .project_model import ActionConfig
15
+ from .select_path_widget import (create_select_file_paths_widget, create_layout_widget_no_margins,
16
+ create_layout_widget_and_connect)
15
17
 
16
18
  FIELD_TEXT = 'text'
17
19
  FIELD_ABS_PATH = 'abs_path'
@@ -200,25 +202,11 @@ class FieldBuilder:
200
202
  return edit
201
203
 
202
204
  def create_abs_path_field(self, tag, **kwargs):
203
- value = self.action.params.get(tag, '')
204
- edit = QLineEdit(value)
205
- edit.setPlaceholderText(kwargs.get('placeholder', ''))
206
- button = QPushButton("Browse...")
207
-
208
- def browse():
209
- path = QFileDialog.getExistingDirectory(None, f"Select {tag.replace('_', ' ')}")
210
- if path:
211
- edit.setText(path)
212
- button.clicked.connect(browse)
213
- button.setAutoDefault(False)
214
- layout = QHBoxLayout()
215
- layout.addWidget(edit)
216
- layout.addWidget(button)
217
- layout.setContentsMargins(0, 0, 0, 0)
218
- container = QWidget()
219
- container.setLayout(layout)
220
- container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
221
- return container
205
+ return create_select_file_paths_widget(
206
+ self.action.params.get(tag, ''),
207
+ kwargs.get('placeholder', ''),
208
+ tag.replace('_', ' ')
209
+ )
222
210
 
223
211
  def create_rel_path_field(self, tag, **kwargs):
224
212
  value = self.action.params.get(tag, kwargs.get('default', ''))
@@ -326,17 +314,8 @@ class FieldBuilder:
326
314
  except ValueError as e:
327
315
  traceback.print_tb(e.__traceback__)
328
316
  QMessageBox.warning(None, "Error", "Could not compute relative path")
317
+ return create_layout_widget_and_connect(button, edit, browse)
329
318
 
330
- button.clicked.connect(browse)
331
- button.setAutoDefault(False)
332
- layout = QHBoxLayout()
333
- layout.addWidget(edit)
334
- layout.addWidget(button)
335
- layout.setContentsMargins(0, 0, 0, 0)
336
- container = QWidget()
337
- container.setLayout(layout)
338
- container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
339
- return container
340
319
 
341
320
  def create_float_field(self, tag, default=0.0, min_val=0.0, max_val=1.0,
342
321
  step=0.1, decimals=2):
@@ -369,11 +348,7 @@ class FieldBuilder:
369
348
  layout.addWidget(label)
370
349
  layout.addWidget(spin)
371
350
  layout.setStretch(layout.count() - 1, 1)
372
- layout.setContentsMargins(0, 0, 0, 0)
373
- container = QWidget()
374
- container.setLayout(layout)
375
- container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
376
- return container
351
+ return create_layout_widget_no_margins(layout)
377
352
 
378
353
  def create_combo_field(self, tag, options=None, default=None, **kwargs):
379
354
  options = options or []