shinestacker 1.3.1__tar.gz → 1.5.0__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.3.1 → shinestacker-1.5.0}/CHANGELOG.md +38 -1
- {shinestacker-1.3.1/src/shinestacker.egg-info → shinestacker-1.5.0}/PKG-INFO +7 -7
- {shinestacker-1.3.1 → shinestacker-1.5.0}/README.md +6 -6
- {shinestacker-1.3.1 → shinestacker-1.5.0}/docs/alignment.md +11 -2
- shinestacker-1.5.0/src/shinestacker/_version.py +1 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/align.py +198 -18
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/align_parallel.py +17 -1
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/balance.py +23 -13
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/noise_detection.py +3 -1
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/utils.py +21 -10
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/vignetting.py +2 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/app/main.py +1 -1
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/config/gui_constants.py +7 -2
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/core/core_utils.py +10 -1
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/action_config.py +172 -7
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/action_config_dialog.py +246 -285
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/gui_run.py +2 -2
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/main_window.py +14 -5
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/menu_manager.py +26 -2
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/project_controller.py +4 -0
- shinestacker-1.5.0/src/shinestacker/gui/recent_file_manager.py +93 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/base_filter.py +13 -15
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/brush_preview.py +3 -1
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/brush_tool.py +11 -11
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/display_manager.py +43 -59
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/image_editor_ui.py +161 -82
- shinestacker-1.5.0/src/shinestacker/retouch/image_view_status.py +65 -0
- shinestacker-1.5.0/src/shinestacker/retouch/image_viewer.py +129 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/io_gui_handler.py +12 -2
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/layer_collection.py +3 -0
- shinestacker-1.5.0/src/shinestacker/retouch/overlaid_view.py +215 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/shortcuts_help.py +13 -3
- shinestacker-1.5.0/src/shinestacker/retouch/sidebyside_view.py +477 -0
- shinestacker-1.5.0/src/shinestacker/retouch/transformation_manager.py +43 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/undo_manager.py +22 -3
- shinestacker-1.5.0/src/shinestacker/retouch/view_strategy.py +557 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0/src/shinestacker.egg-info}/PKG-INFO +7 -7
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker.egg-info/SOURCES.txt +6 -0
- shinestacker-1.3.1/src/shinestacker/_version.py +0 -1
- shinestacker-1.3.1/src/shinestacker/retouch/image_viewer.py +0 -465
- {shinestacker-1.3.1 → shinestacker-1.5.0}/.coveragerc +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/.flake8 +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/.github/workflows/ci-multiplatform.yml +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/.github/workflows/pylint.yml +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/.github/workflows/pypi-publish.yml +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/.github/workflows/release.yml +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/.gitignore +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/.pylintrc +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/.readthedocs.yaml +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/LICENSE +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/MANIFEST.in +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/THIRD_PARTY_LICENSES.txt +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/docs/api.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/docs/balancing.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/docs/conf.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/docs/focus_stacking.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/docs/gui.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/docs/index.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/docs/job.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/docs/main.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/docs/multilayer.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/docs/noise.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/docs/requirements.txt +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/docs/vignetting.md +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/img/coffee.gif +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/img/coffee_stack.jpg +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/img/extreme-vignetting.jpg +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/img/flies.gif +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/img/flies_stack.jpg +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/img/flow-diagram.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/img/gui-finder.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/img/gui-project-new.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/img/gui-project-run.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/img/gui-retouch.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/index.html +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/pyproject.toml +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/requirements.txt +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/scripts/build_release.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/scripts/git-rev-list.sh +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/scripts/validate-tomli.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/setup.cfg +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/__init__.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/__init__.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/align_auto.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/denoise.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/depth_map.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/exif.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/multilayer.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/pyramid.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/pyramid_auto.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/pyramid_tiles.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/sharpen.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/stack.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/stack_framework.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/algorithms/white_balance.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/app/__init__.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/app/about_dialog.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/app/gui_utils.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/app/help_menu.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/app/open_frames.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/app/project.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/app/retouch.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/config/__init__.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/config/config.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/config/constants.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/core/__init__.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/core/colors.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/core/exceptions.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/core/framework.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/core/logging.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/__init__.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/base_form_dialog.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/colors.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/flow_layout.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/folder_file_selection.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/gui_images.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/gui_logging.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/ico/shinestacker.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/new_project.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/project_converter.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/project_editor.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/project_model.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/select_path_widget.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/sys_mon.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/tab_widget.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/gui/time_progress_bar.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/__init__.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/brush.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/brush_gradient.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/denoise_filter.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/exif_data.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/file_loader.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/filter_manager.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/icon_container.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/io_manager.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/unsharp_mask_filter.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/vignetting_filter.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker/retouch/white_balance_filter.py +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker.egg-info/dependency_links.txt +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker.egg-info/entry_points.txt +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker.egg-info/requires.txt +0 -0
- {shinestacker-1.3.1 → shinestacker-1.5.0}/src/shinestacker.egg-info/top_level.txt +0 -0
|
@@ -2,11 +2,48 @@
|
|
|
2
2
|
|
|
3
3
|
This page reports the main releases only and the main changes therein.
|
|
4
4
|
|
|
5
|
+
## [v1.5.0] - 2025-09-16
|
|
6
|
+
**GUI updates and fixes**
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- implemented image rotation
|
|
10
|
+
- dotted cursor in secondary two-image view
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- fixed zoom in wheel events for side-by-side view
|
|
14
|
+
- restored standard cursor in empty retouch views
|
|
15
|
+
- lower/upper case GUI labels
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
- code refactoring and cleanup
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## [v1.4.0] - 2025-09-14
|
|
23
|
+
**GUI improvements**
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- added retouch view mode with master and frame side by side and top-bottom
|
|
27
|
+
- implemented "Open Recent" menu entry for both projects and retouch images
|
|
28
|
+
- expert options can be shown with a checkbox in each dialog
|
|
29
|
+
- optional summary plots for alignment transformation parameters
|
|
30
|
+
|
|
31
|
+
### Fixed
|
|
32
|
+
- fixed bug in plot generation
|
|
33
|
+
- fixes warning due to missing glyph in PDF generation on macOS
|
|
34
|
+
- safer parallel plot generation using a thread locks
|
|
35
|
+
|
|
36
|
+
### Changed
|
|
37
|
+
- code refactoring in various areas
|
|
38
|
+
|
|
39
|
+
|
|
5
40
|
## [v1.3.1] - 2025-09-08
|
|
41
|
+
**Fixes and optimizations**
|
|
6
42
|
|
|
7
|
-
|
|
43
|
+
### Fixed
|
|
8
44
|
- fixed input folder widget in job configuration
|
|
9
45
|
- better management of patological alignments
|
|
46
|
+
- restored alignment match plots
|
|
10
47
|
|
|
11
48
|
### Changed
|
|
12
49
|
- improved automatic parameters for parallel alignment
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shinestacker
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.5.0
|
|
4
4
|
Summary: ShineStacker
|
|
5
5
|
Author-email: Luca Lista <luka.lista@gmail.com>
|
|
6
6
|
License-Expression: LGPL-3.0
|
|
@@ -70,11 +70,11 @@ The GUI has two main working areas:
|
|
|
70
70
|
|
|
71
71
|
<img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/gui-retouch.png' width="600" referrerpolicy="no-referrer">
|
|
72
72
|
|
|
73
|
-
|
|
73
|
+
## Resources
|
|
74
74
|
|
|
75
75
|
🌍 [Website on WordPress](https://shinestacker.wordpress.com) • 📖 [Main documentation](https://shinestacker.readthedocs.io) • 📝 [Changelog](https://github.com/lucalista/shinestacker/blob/main/CHANGELOG.md)
|
|
76
76
|
|
|
77
|
-
|
|
77
|
+
## Note for macOS users
|
|
78
78
|
|
|
79
79
|
**The following note is only relevant if you download the application as compressed archive from the [release page](https://github.com/lucalista/shinestacker/releases).**
|
|
80
80
|
|
|
@@ -93,17 +93,17 @@ xattr -cr ~/Downloads/shinestacker/shinestacker.app
|
|
|
93
93
|
|
|
94
94
|
macOS adds a quarantine flag to all files downloaded from the internet. The above command removes that flag while preserving all other application functionality.
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
## Credits
|
|
97
97
|
|
|
98
98
|
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.
|
|
99
99
|
|
|
100
|
-
|
|
100
|
+
## Resources
|
|
101
101
|
|
|
102
102
|
* [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
|
|
103
103
|
Pyramid methods in image processing
|
|
104
104
|
* [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
|
|
105
105
|
|
|
106
|
-
|
|
106
|
+
## License
|
|
107
107
|
|
|
108
108
|
<img src="https://www.gnu.org/graphics/lgplv3-147x51.png" alt="LGPL 3 logo">
|
|
109
109
|
|
|
@@ -112,7 +112,7 @@ Pyramid methods in image processing
|
|
|
112
112
|
|
|
113
113
|
- **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.
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
## Attribution request
|
|
116
116
|
📸 If you publish images created with Shine Stacker, please consider adding a note such as:
|
|
117
117
|
|
|
118
118
|
*Created with Shine Stacker – https://github.com/lucalista/shinestacker*
|
|
@@ -38,11 +38,11 @@ The GUI has two main working areas:
|
|
|
38
38
|
|
|
39
39
|
<img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/gui-retouch.png' width="600" referrerpolicy="no-referrer">
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
## Resources
|
|
42
42
|
|
|
43
43
|
🌍 [Website on WordPress](https://shinestacker.wordpress.com) • 📖 [Main documentation](https://shinestacker.readthedocs.io) • 📝 [Changelog](https://github.com/lucalista/shinestacker/blob/main/CHANGELOG.md)
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
## Note for macOS users
|
|
46
46
|
|
|
47
47
|
**The following note is only relevant if you download the application as compressed archive from the [release page](https://github.com/lucalista/shinestacker/releases).**
|
|
48
48
|
|
|
@@ -61,17 +61,17 @@ xattr -cr ~/Downloads/shinestacker/shinestacker.app
|
|
|
61
61
|
|
|
62
62
|
macOS adds a quarantine flag to all files downloaded from the internet. The above command removes that flag while preserving all other application functionality.
|
|
63
63
|
|
|
64
|
-
|
|
64
|
+
## Credits
|
|
65
65
|
|
|
66
66
|
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.
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
## Resources
|
|
69
69
|
|
|
70
70
|
* [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
|
|
71
71
|
Pyramid methods in image processing
|
|
72
72
|
* [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
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
## License
|
|
75
75
|
|
|
76
76
|
<img src="https://www.gnu.org/graphics/lgplv3-147x51.png" alt="LGPL 3 logo">
|
|
77
77
|
|
|
@@ -80,7 +80,7 @@ Pyramid methods in image processing
|
|
|
80
80
|
|
|
81
81
|
- **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.
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
## Attribution request
|
|
84
84
|
📸 If you publish images created with Shine Stacker, please consider adding a note such as:
|
|
85
85
|
|
|
86
86
|
*Created with Shine Stacker – https://github.com/lucalista/shinestacker*
|
|
@@ -100,12 +100,21 @@ This class has extra parameters, in addition to the above ones:
|
|
|
100
100
|
|
|
101
101
|
* ```max_threads``` (optional, default: ```2```): number of parallel processes allowed. The number of actual threads will not be greater than the number of available CPU cores.
|
|
102
102
|
* ```chunk_submit``` (optional, default: ```True```): submit at most ```max_threads``` parallel processes. If ```chunk_submit``` is greater than ```max_threads``` a moderate performance gain is achieved at the cost of a possibly large memory occupancy.
|
|
103
|
-
* ```bw_matching``` (optional, default: ```False```): perform matches on black and white version of the images in order to save memory. Preliminary tests indicate that the gain with this option is marginal, and this option may be dropped in the future.
|
|
103
|
+
* ```bw_matching``` (optional, default: ```False```): perform matches on black and white version of the images in order to save memory. Preliminary tests indicate that the gain with this option is marginal, and this option may be dropped in the future.
|
|
104
|
+
|
|
105
|
+
## Automatic selection of processing strategy
|
|
106
|
+
|
|
107
|
+
A class ```AlignFramesAuto``` implements alignment with either sequential or parallel processing, and automatically tunes parallel processing parameters.
|
|
108
|
+
This class has extra parameters, in addition to the above ones:
|
|
109
|
+
|
|
110
|
+
* ```mode``` (optional, default: ```auto```): can be ```auto```, ```sequential``` or ```parallel```.
|
|
111
|
+
* ```memory_limit``` (optional, default: 8×1024<sup>3</sup>sup>): memory limit to determine optimal running parameters
|
|
112
|
+
|
|
104
113
|
|
|
105
114
|
## Allowed configurations
|
|
106
115
|
|
|
107
116
|
⚠️ Not all combinations of detector, descriptor and match methods are allowed. Combinations that are not allowed
|
|
108
|
-
give raise to an exception.
|
|
117
|
+
give raise to an exception. This is automatically prevented if one works with the GUI, but may occur when using python scripting. Below the table of the allowed combination with a comparison of CPU performances.
|
|
109
118
|
|
|
110
119
|
## CPU performances
|
|
111
120
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.5.0'
|
|
@@ -5,13 +5,13 @@ import logging
|
|
|
5
5
|
import numpy as np
|
|
6
6
|
import cv2
|
|
7
7
|
import matplotlib.pyplot as plt
|
|
8
|
-
import matplotlib
|
|
9
8
|
from .. config.constants import constants
|
|
10
9
|
from .. core.exceptions import InvalidOptionError
|
|
11
10
|
from .. core.colors import color_str
|
|
11
|
+
from .. core.core_utils import setup_matplotlib_mode
|
|
12
12
|
from .utils import img_8bit, img_bw_8bit, save_plot, img_subsample
|
|
13
13
|
from .stack_framework import SubAction
|
|
14
|
-
|
|
14
|
+
setup_matplotlib_mode()
|
|
15
15
|
|
|
16
16
|
_DEFAULT_FEATURE_CONFIG = {
|
|
17
17
|
'detector': constants.DEFAULT_DETECTOR,
|
|
@@ -135,14 +135,14 @@ def check_homography_distortion(m, img_shape, homography_thresholds=_HOMOGRAPHY_
|
|
|
135
135
|
(area_ratio, aspect_ratio, max_angle_dev)
|
|
136
136
|
|
|
137
137
|
|
|
138
|
-
def check_transform(m,
|
|
138
|
+
def check_transform(m, img_shape, transform_type,
|
|
139
139
|
affine_thresholds, homography_thresholds):
|
|
140
140
|
if transform_type == constants.ALIGN_RIGID:
|
|
141
141
|
return check_affine_matrix(
|
|
142
|
-
m,
|
|
142
|
+
m, img_shape, affine_thresholds)
|
|
143
143
|
if transform_type == constants.ALIGN_HOMOGRAPHY:
|
|
144
144
|
return check_homography_distortion(
|
|
145
|
-
m,
|
|
145
|
+
m, img_shape, homography_thresholds)
|
|
146
146
|
return False, f'invalid transfrom option {transform_type}', None
|
|
147
147
|
|
|
148
148
|
|
|
@@ -251,7 +251,10 @@ def find_transform(src_pts, dst_pts, transform=constants.DEFAULT_TRANSFORM,
|
|
|
251
251
|
confidence=align_confidence / 100.0,
|
|
252
252
|
refineIters=refine_iters)
|
|
253
253
|
else:
|
|
254
|
-
raise InvalidOptionError(
|
|
254
|
+
raise InvalidOptionError(
|
|
255
|
+
'transform', method,
|
|
256
|
+
f". Valid options are: {constants.ALIGN_HOMOGRAPHY}, {constants.ALIGN_RIGID}"
|
|
257
|
+
)
|
|
255
258
|
return result
|
|
256
259
|
|
|
257
260
|
|
|
@@ -349,9 +352,11 @@ def align_images(img_ref, img_0, feature_config=None, matching_config=None, alig
|
|
|
349
352
|
if m is None:
|
|
350
353
|
raise InvalidOptionError("transform", transform)
|
|
351
354
|
transform_type = alignment_config['transform']
|
|
352
|
-
is_valid, reason,
|
|
353
|
-
m, img_0, transform_type,
|
|
355
|
+
is_valid, reason, result = check_transform(
|
|
356
|
+
m, img_0.shape, transform_type,
|
|
354
357
|
affine_thresholds, homography_thresholds)
|
|
358
|
+
if callbacks and 'save_transform_result' in callbacks:
|
|
359
|
+
callbacks['save_transform_result'](result)
|
|
355
360
|
if not is_valid:
|
|
356
361
|
if callbacks and 'warning' in callbacks:
|
|
357
362
|
callbacks['warning'](f"invalid transformation: {reason}")
|
|
@@ -407,6 +412,18 @@ class AlignFramesBase(SubAction):
|
|
|
407
412
|
for k in self.alignment_config:
|
|
408
413
|
if k in kwargs:
|
|
409
414
|
self.alignment_config[k] = kwargs[k]
|
|
415
|
+
self._area_ratio = None
|
|
416
|
+
self._aspect_ratio = None
|
|
417
|
+
self._max_angle_dev = None
|
|
418
|
+
self._scale_x = None
|
|
419
|
+
self._scale_y = None
|
|
420
|
+
self._translation_x = None
|
|
421
|
+
self._translation_y = None
|
|
422
|
+
self._rotation = None
|
|
423
|
+
self._shear = None
|
|
424
|
+
|
|
425
|
+
def relative_transformation(self):
|
|
426
|
+
return None
|
|
410
427
|
|
|
411
428
|
def align_images(self, idx, img_ref, img_0):
|
|
412
429
|
pass
|
|
@@ -417,6 +434,15 @@ class AlignFramesBase(SubAction):
|
|
|
417
434
|
def begin(self, process):
|
|
418
435
|
self.process = process
|
|
419
436
|
self._n_good_matches = np.zeros(process.total_action_counts)
|
|
437
|
+
self._area_ratio = np.ones(process.total_action_counts)
|
|
438
|
+
self._aspect_ratio = np.ones(process.total_action_counts)
|
|
439
|
+
self._max_angle_dev = np.zeros(process.total_action_counts)
|
|
440
|
+
self._scale_x = np.ones(process.total_action_counts)
|
|
441
|
+
self._scale_y = np.ones(process.total_action_counts)
|
|
442
|
+
self._translation_x = np.zeros(process.total_action_counts)
|
|
443
|
+
self._translation_y = np.zeros(process.total_action_counts)
|
|
444
|
+
self._rotation = np.zeros(process.total_action_counts)
|
|
445
|
+
self._shear = np.zeros(process.total_action_counts)
|
|
420
446
|
|
|
421
447
|
def run_frame(self, idx, ref_idx, img_0):
|
|
422
448
|
if idx == self.process.ref_idx:
|
|
@@ -432,24 +458,29 @@ class AlignFramesBase(SubAction):
|
|
|
432
458
|
f"{os.path.basename(self.process.input_filepath(idx))}"
|
|
433
459
|
|
|
434
460
|
def end(self):
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
x = np.arange(1, len(
|
|
461
|
+
|
|
462
|
+
def get_coordinates(items):
|
|
463
|
+
x = np.arange(1, len(items) + 1, dtype=int)
|
|
438
464
|
no_ref = x != self.process.ref_idx + 1
|
|
439
465
|
x = x[no_ref]
|
|
440
|
-
y = np.array(
|
|
466
|
+
y = np.array(items)[no_ref]
|
|
441
467
|
if self.process.ref_idx == 0:
|
|
442
|
-
|
|
468
|
+
y_ref = y[1]
|
|
443
469
|
elif self.process.ref_idx >= len(y):
|
|
444
|
-
|
|
470
|
+
y_ref = y[-1]
|
|
445
471
|
else:
|
|
446
|
-
|
|
472
|
+
y_ref = (y[self.process.ref_idx - 1] + y[self.process.ref_idx]) / 2
|
|
473
|
+
return x, y, y_ref
|
|
447
474
|
|
|
475
|
+
if self.plot_summary:
|
|
476
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
477
|
+
x, y, y_ref = get_coordinates(self._n_good_matches)
|
|
448
478
|
plt.plot([self.process.ref_idx + 1, self.process.ref_idx + 1],
|
|
449
|
-
[0,
|
|
479
|
+
[0, y_ref], color='cornflowerblue', linestyle='--', label='reference frame')
|
|
450
480
|
plt.plot([x[0], x[-1]], [self.min_matches, self.min_matches], color='lightgray',
|
|
451
481
|
linestyle='--', label='min. matches')
|
|
452
482
|
plt.plot(x, y, color='navy', label='matches')
|
|
483
|
+
plt.title("Number of matches")
|
|
453
484
|
plt.xlabel('frame')
|
|
454
485
|
plt.ylabel('# of matches')
|
|
455
486
|
plt.legend()
|
|
@@ -458,15 +489,160 @@ class AlignFramesBase(SubAction):
|
|
|
458
489
|
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
459
490
|
f"{self.process.name}-matches.pdf"
|
|
460
491
|
save_plot(plot_path)
|
|
461
|
-
plt.close('all')
|
|
462
492
|
self.process.callback(constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
463
493
|
f"{self.process.name}: matches", plot_path)
|
|
494
|
+
transform = self.alignment_config['transform']
|
|
495
|
+
title = "Transformation parameters rel. to reference frame"
|
|
496
|
+
if transform == constants.ALIGN_RIGID:
|
|
497
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
498
|
+
x, y, y_ref = get_coordinates(self._rotation)
|
|
499
|
+
plt.plot([self.process.ref_idx + 1, self.process.ref_idx + 1],
|
|
500
|
+
[0, y_ref], color='cornflowerblue',
|
|
501
|
+
linestyle='--', label='reference frame')
|
|
502
|
+
plt.plot([x[0], x[-1]], [0, 0], color='cornflowerblue', linestyle='--')
|
|
503
|
+
plt.plot(x, y, color='navy', label='rotation (°)')
|
|
504
|
+
y_lim = max(abs(y.min()), abs(y.max())) * 1.1
|
|
505
|
+
plt.ylim(-y_lim, y_lim)
|
|
506
|
+
plt.title(title)
|
|
507
|
+
plt.xlabel('frame')
|
|
508
|
+
plt.ylabel('rotation angle (degrees)')
|
|
509
|
+
plt.legend()
|
|
510
|
+
plt.xlim(x[0], x[-1])
|
|
511
|
+
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
512
|
+
f"{self.process.name}-rotation.pdf"
|
|
513
|
+
save_plot(plot_path)
|
|
514
|
+
self.process.callback(constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
515
|
+
f"{self.process.name}: rotation", plot_path)
|
|
516
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
517
|
+
x, y_x, y_x_ref = get_coordinates(self._translation_x)
|
|
518
|
+
x, y_y, y_y_ref = get_coordinates(self._translation_y)
|
|
519
|
+
plt.plot([self.process.ref_idx + 1, self.process.ref_idx + 1],
|
|
520
|
+
[y_x_ref, y_y_ref], color='cornflowerblue',
|
|
521
|
+
linestyle='--', label='reference frame')
|
|
522
|
+
plt.plot([x[0], x[-1]], [0, 0], color='cornflowerblue', linestyle='--')
|
|
523
|
+
plt.plot(x, y_x, color='blue', label='translation, x (px)')
|
|
524
|
+
plt.plot(x, y_y, color='red', label='translation, y (px)')
|
|
525
|
+
y_lim = max(abs(y_x.min()), abs(y_x.max()), abs(y_y.min()), abs(y_y.max())) * 1.1
|
|
526
|
+
plt.ylim(-y_lim, y_lim)
|
|
527
|
+
plt.title(title)
|
|
528
|
+
plt.xlabel('frame')
|
|
529
|
+
plt.ylabel('translation (pixels)')
|
|
530
|
+
plt.legend()
|
|
531
|
+
plt.xlim(x[0], x[-1])
|
|
532
|
+
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
533
|
+
f"{self.process.name}-translation.pdf"
|
|
534
|
+
save_plot(plot_path)
|
|
535
|
+
self.process.callback(constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
536
|
+
f"{self.process.name}: translation", plot_path)
|
|
537
|
+
|
|
538
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
539
|
+
x, y, y_ref = get_coordinates(self._scale_x)
|
|
540
|
+
plt.plot([self.process.ref_idx + 1, self.process.ref_idx + 1],
|
|
541
|
+
[1, y_ref], color='cornflowerblue',
|
|
542
|
+
linestyle='--', label='reference frame')
|
|
543
|
+
plt.plot([x[0], x[-1]], [1, 1], color='cornflowerblue', linestyle='--')
|
|
544
|
+
plt.plot(x, y, color='blue', label='scale factor')
|
|
545
|
+
d_max = max(abs(y.min() - 1), abs(y.max() - 1)) * 1.1
|
|
546
|
+
plt.ylim(1.0 - d_max, 1.0 + d_max)
|
|
547
|
+
plt.title(title)
|
|
548
|
+
plt.xlabel('frame')
|
|
549
|
+
plt.ylabel('scale factor')
|
|
550
|
+
plt.legend()
|
|
551
|
+
plt.xlim(x[0], x[-1])
|
|
552
|
+
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
553
|
+
f"{self.process.name}-scale.pdf"
|
|
554
|
+
save_plot(plot_path)
|
|
555
|
+
self.process.callback(constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
556
|
+
f"{self.process.name}: scale", plot_path)
|
|
557
|
+
elif transform == constants.ALIGN_HOMOGRAPHY:
|
|
558
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
559
|
+
x, y, y_ref = get_coordinates(self._area_ratio)
|
|
560
|
+
plt.plot([self.process.ref_idx + 1, self.process.ref_idx + 1],
|
|
561
|
+
[0, y_ref], color='cornflowerblue',
|
|
562
|
+
linestyle='--', label='reference frame')
|
|
563
|
+
plt.plot([x[0], x[-1]], [0, 0], color='cornflowerblue', linestyle='--')
|
|
564
|
+
plt.plot(x, y, color='navy', label='area ratio')
|
|
565
|
+
d_max = max(abs(y.min() - 1), abs(y.max() - 1)) * 1.1
|
|
566
|
+
plt.ylim(1.0 - d_max, 1.0 + d_max)
|
|
567
|
+
plt.title(title)
|
|
568
|
+
plt.xlabel('frame')
|
|
569
|
+
plt.ylabel('warped area ratio')
|
|
570
|
+
plt.legend()
|
|
571
|
+
plt.xlim(x[0], x[-1])
|
|
572
|
+
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
573
|
+
f"{self.process.name}-area-ratio.pdf"
|
|
574
|
+
save_plot(plot_path)
|
|
575
|
+
self.process.callback(constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
576
|
+
f"{self.process.name}: area ratio", plot_path)
|
|
577
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
578
|
+
x, y, y_ref = get_coordinates(self._aspect_ratio)
|
|
579
|
+
plt.plot([self.process.ref_idx + 1, self.process.ref_idx + 1],
|
|
580
|
+
[0, y_ref], color='cornflowerblue',
|
|
581
|
+
linestyle='--', label='reference frame')
|
|
582
|
+
plt.plot([x[0], x[-1]], [0, 0], color='cornflowerblue', linestyle='--')
|
|
583
|
+
plt.plot(x, y, color='navy', label='aspect ratio')
|
|
584
|
+
y_min, y_max = y.min(), y.max()
|
|
585
|
+
delta = y_max - y_min
|
|
586
|
+
plt.ylim(y_min - 0.05 * delta, y_max + 0.05 * delta)
|
|
587
|
+
plt.title(title)
|
|
588
|
+
plt.xlabel('frame')
|
|
589
|
+
plt.ylabel('aspect ratio')
|
|
590
|
+
plt.legend()
|
|
591
|
+
plt.xlim(x[0], x[-1])
|
|
592
|
+
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
593
|
+
f"{self.process.name}-aspect-ratio.pdf"
|
|
594
|
+
save_plot(plot_path)
|
|
595
|
+
self.process.callback(constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
596
|
+
f"{self.process.name}: aspect ratio", plot_path)
|
|
597
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
598
|
+
x, y, y_ref = get_coordinates(self._max_angle_dev)
|
|
599
|
+
plt.plot([self.process.ref_idx + 1, self.process.ref_idx + 1],
|
|
600
|
+
[0, y_ref], color='cornflowerblue',
|
|
601
|
+
linestyle='--', label='reference frame')
|
|
602
|
+
plt.plot([x[0], x[-1]], [0, 0], color='cornflowerblue', linestyle='--')
|
|
603
|
+
plt.plot(x, y, color='navy', label='max. dev. ang. (°)')
|
|
604
|
+
y_lim = max(abs(y.min()), abs(y.max())) * 1.1
|
|
605
|
+
plt.ylim(-y_lim, y_lim)
|
|
606
|
+
plt.title(title)
|
|
607
|
+
plt.xlabel('frame')
|
|
608
|
+
plt.ylabel('max deviation angle (degrees)')
|
|
609
|
+
plt.legend()
|
|
610
|
+
plt.xlim(x[0], x[-1])
|
|
611
|
+
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
612
|
+
f"{self.process.name}-rotation.pdf"
|
|
613
|
+
save_plot(plot_path)
|
|
614
|
+
self.process.callback(constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
615
|
+
f"{self.process.name}: rotation", plot_path)
|
|
616
|
+
|
|
617
|
+
def save_transform_result(self, idx, result):
|
|
618
|
+
if result is None:
|
|
619
|
+
return
|
|
620
|
+
transform = self.alignment_config['transform']
|
|
621
|
+
if transform == constants.ALIGN_HOMOGRAPHY:
|
|
622
|
+
area_ratio, aspect_ratio, max_angle_dev = result
|
|
623
|
+
self._area_ratio[idx] = area_ratio
|
|
624
|
+
self._aspect_ratio[idx] = aspect_ratio
|
|
625
|
+
self._max_angle_dev[idx] = max_angle_dev
|
|
626
|
+
elif transform == constants.ALIGN_RIGID:
|
|
627
|
+
scale_x, scale_y, translation_x, translation_y, rotation, shear = result
|
|
628
|
+
self._scale_x[idx] = scale_x
|
|
629
|
+
self._scale_y[idx] = scale_y
|
|
630
|
+
self._translation_x[idx] = translation_x
|
|
631
|
+
self._translation_y[idx] = translation_y
|
|
632
|
+
self._rotation[idx] = rotation
|
|
633
|
+
self._shear[idx] = shear
|
|
634
|
+
else:
|
|
635
|
+
raise InvalidOptionError(
|
|
636
|
+
'transform', transform,
|
|
637
|
+
f". Valid options are: {constants.ALIGN_HOMOGRAPHY}, {constants.ALIGN_RIGID}"
|
|
638
|
+
)
|
|
464
639
|
|
|
465
640
|
|
|
466
641
|
class AlignFrames(AlignFramesBase):
|
|
467
642
|
def align_images(self, idx, img_ref, img_0):
|
|
468
643
|
idx_str = f"{idx:04d}"
|
|
469
644
|
idx_tot_str = self.process.idx_tot_str(idx)
|
|
645
|
+
|
|
470
646
|
callbacks = {
|
|
471
647
|
'message': lambda: self.print_message(f'{idx_tot_str}: find matches'),
|
|
472
648
|
'matches_message': lambda n: self.print_message(f'{idx_tot_str}: good matches: {n}'),
|
|
@@ -476,7 +652,8 @@ class AlignFrames(AlignFramesBase):
|
|
|
476
652
|
f': {msg}', constants.LOG_COLOR_WARNING),
|
|
477
653
|
'save_plot': lambda plot_path: self.process.callback(
|
|
478
654
|
constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
479
|
-
f"{self.process.name}: matches\nframe {idx_str}", plot_path)
|
|
655
|
+
f"{self.process.name}: matches\nframe {idx_str}", plot_path),
|
|
656
|
+
'save_transform_result': lambda result: self.save_transform_result(idx, result)
|
|
480
657
|
}
|
|
481
658
|
if self.plot_matches:
|
|
482
659
|
plot_path = os.path.join(
|
|
@@ -504,5 +681,8 @@ class AlignFrames(AlignFramesBase):
|
|
|
504
681
|
return None
|
|
505
682
|
return img
|
|
506
683
|
|
|
684
|
+
def relative_transformation(self):
|
|
685
|
+
return False
|
|
686
|
+
|
|
507
687
|
def sequential_processing(self):
|
|
508
688
|
return True
|
|
@@ -39,6 +39,7 @@ class AlignFramesParallel(AlignFramesBase):
|
|
|
39
39
|
self.chunk_submit = kwargs.get('chunk_submit', constants.DEFAULT_ALIGN_CHUNK_SUBMIT)
|
|
40
40
|
self.bw_matching = kwargs.get('bw_matching', constants.DEFAULT_ALIGN_BW_MATCHING)
|
|
41
41
|
self._img_cache = None
|
|
42
|
+
self._img_shapes = None
|
|
42
43
|
self._img_locks = None
|
|
43
44
|
self._cache_locks = None
|
|
44
45
|
self._target_indices = None
|
|
@@ -48,6 +49,9 @@ class AlignFramesParallel(AlignFramesBase):
|
|
|
48
49
|
self._kp = None
|
|
49
50
|
self._des = None
|
|
50
51
|
|
|
52
|
+
def relative_transformation(self):
|
|
53
|
+
return True
|
|
54
|
+
|
|
51
55
|
def cache_img(self, idx):
|
|
52
56
|
with self._cache_locks[idx]:
|
|
53
57
|
self._img_locks[idx] += 1
|
|
@@ -56,6 +60,8 @@ class AlignFramesParallel(AlignFramesBase):
|
|
|
56
60
|
if self.bw_matching:
|
|
57
61
|
img = img_bw(img)
|
|
58
62
|
self._img_cache[idx] = img
|
|
63
|
+
if img is not None:
|
|
64
|
+
self._img_shapes[idx] = img.shape
|
|
59
65
|
return self._img_cache[idx]
|
|
60
66
|
|
|
61
67
|
def submit_threads(self, idxs, imgs):
|
|
@@ -112,6 +118,7 @@ class AlignFramesParallel(AlignFramesBase):
|
|
|
112
118
|
self.process.id, self.process.name, 2 * n_frames)
|
|
113
119
|
input_filepaths = self.process.input_filepaths()
|
|
114
120
|
self._img_cache = [None] * n_frames
|
|
121
|
+
self._img_shapes = [None] * n_frames
|
|
115
122
|
self._img_locks = [0] * n_frames
|
|
116
123
|
self._cache_locks = [threading.Lock() for _ in range(n_frames)]
|
|
117
124
|
self._target_indices = [None] * n_frames
|
|
@@ -168,9 +175,17 @@ class AlignFramesParallel(AlignFramesBase):
|
|
|
168
175
|
self._transforms[idx] = None
|
|
169
176
|
gc.collect()
|
|
170
177
|
missing_transforms = 0
|
|
178
|
+
thresholds = self.get_transform_thresholds()
|
|
171
179
|
for i in range(n_frames):
|
|
172
180
|
if self._cumulative_transforms[i] is not None:
|
|
173
181
|
self._cumulative_transforms[i] = self._cumulative_transforms[i].astype(np.float32)
|
|
182
|
+
is_valid, _reason, result = check_transform(
|
|
183
|
+
self._cumulative_transforms[i], self._img_shapes[i],
|
|
184
|
+
transform_type, *thresholds)
|
|
185
|
+
if is_valid:
|
|
186
|
+
self.save_transform_result(i, result)
|
|
187
|
+
else:
|
|
188
|
+
self._cumulative_transforms[i] = None
|
|
174
189
|
else:
|
|
175
190
|
missing_transforms += 1
|
|
176
191
|
msg = "feature extaction completed"
|
|
@@ -277,7 +292,8 @@ class AlignFramesParallel(AlignFramesBase):
|
|
|
277
292
|
return self.extract_features(idx, delta + 1)
|
|
278
293
|
transform_type = self.alignment_config['transform']
|
|
279
294
|
thresholds = self.get_transform_thresholds()
|
|
280
|
-
is_valid, _reason, _result = check_transform(m, img_0, transform_type, *thresholds)
|
|
295
|
+
is_valid, _reason, _result = check_transform(m, img_0.shape, transform_type, *thresholds)
|
|
296
|
+
# self.save_transform_result(idx, result)
|
|
281
297
|
if not is_valid:
|
|
282
298
|
msg = f"invalid transformation for {self.image_str(idx)}"
|
|
283
299
|
do_abort = self.alignment_config['abort_abnormal']
|