shinestacker 1.0.4__tar.gz → 1.1.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.0.4 → shinestacker-1.1.0}/.github/workflows/release.yml +6 -6
- {shinestacker-1.0.4 → shinestacker-1.1.0}/CHANGELOG.md +23 -1
- {shinestacker-1.0.4/src/shinestacker.egg-info → shinestacker-1.1.0}/PKG-INFO +20 -1
- {shinestacker-1.0.4 → shinestacker-1.1.0}/README.md +19 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/docs/focus_stacking.md +5 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/scripts/build_release.py +17 -6
- shinestacker-1.1.0/src/shinestacker/_version.py +1 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/algorithms/align.py +11 -11
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/algorithms/base_stack_algo.py +27 -4
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/algorithms/depth_map.py +8 -11
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/algorithms/noise_detection.py +7 -2
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/algorithms/pyramid.py +55 -41
- shinestacker-1.1.0/src/shinestacker/algorithms/pyramid_tiles.py +109 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/algorithms/stack.py +11 -8
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/algorithms/stack_framework.py +5 -8
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/algorithms/utils.py +5 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/config/constants.py +5 -2
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/action_config_dialog.py +26 -4
- shinestacker-1.1.0/src/shinestacker/gui/flow_layout.py +105 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/gui_run.py +24 -19
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/main_window.py +2 -2
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/new_project.py +1 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/project_controller.py +1 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/project_converter.py +10 -4
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/time_progress_bar.py +2 -2
- {shinestacker-1.0.4 → shinestacker-1.1.0/src/shinestacker.egg-info}/PKG-INFO +20 -1
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker.egg-info/SOURCES.txt +2 -0
- shinestacker-1.0.4/src/shinestacker/_version.py +0 -1
- {shinestacker-1.0.4 → shinestacker-1.1.0}/.coveragerc +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/.flake8 +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/.github/workflows/ci-multiplatform.yml +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/.github/workflows/pylint.yml +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/.github/workflows/pypi-publish.yml +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/.gitignore +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/.pylintrc +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/.readthedocs.yaml +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/LICENSE +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/MANIFEST.in +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/THIRD_PARTY_LICENSES.txt +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/docs/alignment.md +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/docs/api.md +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/docs/balancing.md +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/docs/conf.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/docs/gui.md +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/docs/index.md +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/docs/job.md +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/docs/main.md +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/docs/multilayer.md +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/docs/noise.md +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/docs/requirements.txt +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/docs/vignetting.md +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/img/coffee.gif +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/img/coffee_stack.jpg +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/img/extreme-vignetting.jpg +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/img/flies.gif +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/img/flies_stack.jpg +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/img/flow-diagram.png +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/img/gui-finder.png +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/img/gui-project-new.png +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/img/gui-project-run.png +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/img/gui-retouch.png +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/pyproject.toml +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/requirements.txt +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/scripts/git-rev-list.sh +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/scripts/validate-tomli.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/setup.cfg +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/__init__.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/algorithms/__init__.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/algorithms/balance.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/algorithms/denoise.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/algorithms/exif.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/algorithms/multilayer.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/algorithms/sharpen.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/algorithms/vignetting.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/algorithms/white_balance.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/app/__init__.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/app/about_dialog.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/app/gui_utils.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/app/help_menu.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/app/main.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/app/open_frames.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/app/project.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/app/retouch.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/config/__init__.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/config/config.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/config/gui_constants.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/core/__init__.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/core/colors.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/core/core_utils.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/core/exceptions.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/core/framework.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/core/logging.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/__init__.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/action_config.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/base_form_dialog.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/colors.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/gui_images.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/gui_logging.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/ico/shinestacker.png +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/menu_manager.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/project_editor.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/project_model.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/select_path_widget.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/gui/tab_widget.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/__init__.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/base_filter.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/brush.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/brush_gradient.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/brush_preview.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/brush_tool.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/denoise_filter.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/display_manager.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/exif_data.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/file_loader.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/filter_manager.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/icon_container.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/image_editor_ui.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/image_viewer.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/io_gui_handler.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/io_manager.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/layer_collection.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/shortcuts_help.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/undo_manager.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/unsharp_mask_filter.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/vignetting_filter.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker/retouch/white_balance_filter.py +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker.egg-info/dependency_links.txt +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker.egg-info/entry_points.txt +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker.egg-info/requires.txt +0 -0
- {shinestacker-1.0.4 → shinestacker-1.1.0}/src/shinestacker.egg-info/top_level.txt +0 -0
|
@@ -44,8 +44,8 @@ jobs:
|
|
|
44
44
|
uses: actions/upload-artifact@v4
|
|
45
45
|
with:
|
|
46
46
|
name: shinestacker-${{ matrix.os }}
|
|
47
|
-
path:
|
|
48
|
-
|
|
47
|
+
path: |
|
|
48
|
+
${{ matrix.os == 'windows-latest' && 'dist/shinestacker-release.zip' || 'dist/shinestacker-release.tar.gz' }}
|
|
49
49
|
create-release:
|
|
50
50
|
needs: publish-release
|
|
51
51
|
runs-on: ubuntu-latest
|
|
@@ -58,9 +58,9 @@ jobs:
|
|
|
58
58
|
- name: Prepare release assets
|
|
59
59
|
run: |
|
|
60
60
|
mkdir -p release_assets
|
|
61
|
-
cp artifacts/shinestacker-ubuntu-latest/shinestacker-release.
|
|
61
|
+
cp artifacts/shinestacker-ubuntu-latest/shinestacker-release.tar.gz release_assets/shinestacker-ubuntu.tar.gz
|
|
62
|
+
cp artifacts/shinestacker-macos-latest/shinestacker-release.tar.gz release_assets/shinestacker-macos.tar.gz
|
|
62
63
|
cp artifacts/shinestacker-windows-latest/shinestacker-release.zip release_assets/shinestacker-windows.zip
|
|
63
|
-
cp artifacts/shinestacker-macos-latest/shinestacker-release.zip release_assets/shinestacker-macos.zip
|
|
64
64
|
ls -la release_assets/
|
|
65
65
|
|
|
66
66
|
- name: Create release
|
|
@@ -68,6 +68,6 @@ jobs:
|
|
|
68
68
|
with:
|
|
69
69
|
draft: true
|
|
70
70
|
files: |
|
|
71
|
-
release_assets/shinestacker-ubuntu.
|
|
71
|
+
release_assets/shinestacker-ubuntu.tar.gz
|
|
72
|
+
release_assets/shinestacker-macos.tar.gz
|
|
72
73
|
release_assets/shinestacker-windows.zip
|
|
73
|
-
release_assets/shinestacker-macos.zip
|
|
@@ -2,13 +2,35 @@
|
|
|
2
2
|
|
|
3
3
|
This page reports the main releases only and the main changes therein.
|
|
4
4
|
|
|
5
|
+
## [v1.1.0] - 2025-08-28
|
|
6
|
+
**New Pyramids algorith, some improvements and more fixes**
|
|
7
|
+
|
|
8
|
+
### Changes
|
|
9
|
+
|
|
10
|
+
* added Pyramids Tiles, that requires less RAM by fusing the image in tiles
|
|
11
|
+
* the alignment module now tolerates images of different shapes
|
|
12
|
+
* noisy pixel mask verifies that the mask has the same shape as input images
|
|
13
|
+
* minor changes to default alignment parameters
|
|
14
|
+
* some improvements to the GUI
|
|
15
|
+
* some bug fixes
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## [v1.0.4.pre2] - 2025-08-26
|
|
19
|
+
**Bug fixes**
|
|
20
|
+
|
|
21
|
+
### Changes
|
|
22
|
+
|
|
23
|
+
* fixed release build script changing format from zip to tar.gz for macOs and Linux
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
5
27
|
## [v1.0.4] - 2025-08-26
|
|
6
28
|
**Bug fixes**
|
|
7
29
|
|
|
8
30
|
### Changes
|
|
9
31
|
|
|
10
32
|
* extensions are treated in lower case (e.g.: both jpg and JPG)
|
|
11
|
-
* added retouch menu action: import frames from current project
|
|
33
|
+
* added missing retouch menu action: import frames from current project
|
|
12
34
|
|
|
13
35
|
---
|
|
14
36
|
## [v1.0.3] - 2025-08-26
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shinestacker
|
|
3
|
-
Version: 1.0
|
|
3
|
+
Version: 1.1.0
|
|
4
4
|
Summary: ShineStacker
|
|
5
5
|
Author-email: Luca Lista <luka.lista@gmail.com>
|
|
6
6
|
License-Expression: LGPL-3.0
|
|
@@ -69,6 +69,25 @@ The GUI has two main working areas:
|
|
|
69
69
|
|
|
70
70
|
<img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/gui-retouch.png' width="600" referrerpolicy="no-referrer">
|
|
71
71
|
|
|
72
|
+
# Note for macOS users
|
|
73
|
+
|
|
74
|
+
**The following note is only relevant if you download the application as compressed archive from the [release page](https://github.com/lucalista/shinestacker/releases).**
|
|
75
|
+
|
|
76
|
+
The macOS system security protection prevent to run applications downloaded from the web that come from developers that don't hold an Apple Developer Certificate.
|
|
77
|
+
|
|
78
|
+
In order to prevent this, follow the instructions below:
|
|
79
|
+
|
|
80
|
+
1. Download the compressed archive ```shinestacker-macos.tar.gz``` in your ```Download``` folder.
|
|
81
|
+
2. Double-click the archive to uncompress it. You will find a new folder ```shinestacker```.
|
|
82
|
+
3. Open a terminal (*Applications > Utilities > Terminal*)
|
|
83
|
+
4. Type the folliwng command on the terminal:
|
|
84
|
+
```bash
|
|
85
|
+
xattr -cr ~/Downloads/shinestacker/shinestacker.app
|
|
86
|
+
```
|
|
87
|
+
5. Now you can double-click the Sine Stacker icon app in the ```shiestacker``` folder and it should run.
|
|
88
|
+
|
|
89
|
+
macOS adds a quarantine flag to all files downloaded from the internet. The above command removes that flag while preserving all other application functionality.
|
|
90
|
+
|
|
72
91
|
# Resources
|
|
73
92
|
|
|
74
93
|
🌍 [Website on WordPress](https://shinestacker.wordpress.com) • 📖 [Main documentation](https://shinestacker.readthedocs.io) • 📝 [Changelog](https://github.com/lucalista/shinestacker/blob/main/CHANGELOG.md)
|
|
@@ -38,6 +38,25 @@ 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
|
+
# Note for macOS users
|
|
42
|
+
|
|
43
|
+
**The following note is only relevant if you download the application as compressed archive from the [release page](https://github.com/lucalista/shinestacker/releases).**
|
|
44
|
+
|
|
45
|
+
The macOS system security protection prevent to run applications downloaded from the web that come from developers that don't hold an Apple Developer Certificate.
|
|
46
|
+
|
|
47
|
+
In order to prevent this, follow the instructions below:
|
|
48
|
+
|
|
49
|
+
1. Download the compressed archive ```shinestacker-macos.tar.gz``` in your ```Download``` folder.
|
|
50
|
+
2. Double-click the archive to uncompress it. You will find a new folder ```shinestacker```.
|
|
51
|
+
3. Open a terminal (*Applications > Utilities > Terminal*)
|
|
52
|
+
4. Type the folliwng command on the terminal:
|
|
53
|
+
```bash
|
|
54
|
+
xattr -cr ~/Downloads/shinestacker/shinestacker.app
|
|
55
|
+
```
|
|
56
|
+
5. Now you can double-click the Sine Stacker icon app in the ```shiestacker``` folder and it should run.
|
|
57
|
+
|
|
58
|
+
macOS adds a quarantine flag to all files downloaded from the internet. The above command removes that flag while preserving all other application functionality.
|
|
59
|
+
|
|
41
60
|
# Resources
|
|
42
61
|
|
|
43
62
|
🌍 [Website on WordPress](https://shinestacker.wordpress.com) • 📖 [Main documentation](https://shinestacker.readthedocs.io) • 📝 [Changelog](https://github.com/lucalista/shinestacker/blob/main/CHANGELOG.md)
|
|
@@ -42,6 +42,11 @@ Arguments for the constructor are:
|
|
|
42
42
|
* ```gen_kernel``` (optional, default: 0.4)
|
|
43
43
|
* ```float_type``` (optional, default: ```FLOAT_32```, possible values: ```FLOAT_32```, ```FLOAT_64```): precision for internal image representation
|
|
44
44
|
|
|
45
|
+
```PyramidTilesStack```, Laplacian pyramid with tile pyramid merging to optimize memory usage for large files
|
|
46
|
+
|
|
47
|
+
Arguments for the constructor are the same ad for ```PyramidStack``` plus:
|
|
48
|
+
* ```tile_size``` (optional, default: 512): size of the tile used for partial image merging
|
|
49
|
+
|
|
45
50
|
```DepthMapStack```, Depth map focus stacking algorithm
|
|
46
51
|
|
|
47
52
|
Arguments for the constructor are:
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import shutil
|
|
3
|
+
import tarfile
|
|
3
4
|
import subprocess
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
import platform
|
|
@@ -35,9 +36,19 @@ for project_file in ["complete-project.fsp", "stack-from-frames.fsp"]:
|
|
|
35
36
|
shutil.copy(examples_dir / project_file, target_examples)
|
|
36
37
|
shutil.copytree(examples_dir / 'input', target_examples / 'input', dirs_exist_ok=True)
|
|
37
38
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
39
|
+
if sys_name == 'windows':
|
|
40
|
+
shutil.make_archive(
|
|
41
|
+
base_name=str(dist_dir / "shinestacker-release"),
|
|
42
|
+
format="zip",
|
|
43
|
+
root_dir=dist_dir,
|
|
44
|
+
base_dir=package_dir
|
|
45
|
+
)
|
|
46
|
+
else:
|
|
47
|
+
archive_path = dist_dir / "shinestacker-release.tar.gz"
|
|
48
|
+
with tarfile.open(archive_path, "w:gz") as tar:
|
|
49
|
+
tar.add(
|
|
50
|
+
dist_dir / package_dir,
|
|
51
|
+
arcname=package_dir,
|
|
52
|
+
recursive=True,
|
|
53
|
+
filter=lambda info: info
|
|
54
|
+
)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.1.0'
|
|
@@ -6,7 +6,7 @@ import cv2
|
|
|
6
6
|
from .. config.constants import constants
|
|
7
7
|
from .. core.exceptions import AlignmentError, InvalidOptionError
|
|
8
8
|
from .. core.colors import color_str
|
|
9
|
-
from .utils import img_8bit, img_bw_8bit, save_plot,
|
|
9
|
+
from .utils import img_8bit, img_bw_8bit, save_plot, img_subsample
|
|
10
10
|
from .stack_framework import SubAction
|
|
11
11
|
|
|
12
12
|
_DEFAULT_FEATURE_CONFIG = {
|
|
@@ -161,9 +161,10 @@ def align_images(img_1, img_0, feature_config=None, matching_config=None, alignm
|
|
|
161
161
|
except KeyError as e:
|
|
162
162
|
raise InvalidOptionError("border_mode", alignment_config['border_mode']) from e
|
|
163
163
|
min_matches = 4 if alignment_config['transform'] == constants.ALIGN_HOMOGRAPHY else 3
|
|
164
|
-
validate_image(img_0, *get_img_metadata(img_1))
|
|
165
164
|
if callbacks and 'message' in callbacks:
|
|
166
165
|
callbacks['message']()
|
|
166
|
+
h_ref, w_ref = img_1.shape[:2]
|
|
167
|
+
h0, w0 = img_0.shape[:2]
|
|
167
168
|
subsample = alignment_config['subsample']
|
|
168
169
|
fast_subsampling = alignment_config['fast_subsampling']
|
|
169
170
|
min_good_matches = alignment_config['min_good_matches']
|
|
@@ -204,15 +205,14 @@ def align_images(img_1, img_0, feature_config=None, matching_config=None, alignm
|
|
|
204
205
|
flags=2), cv2.COLOR_BGR2RGB)
|
|
205
206
|
plt.figure(figsize=(10, 5))
|
|
206
207
|
plt.imshow(img_match, 'gray')
|
|
207
|
-
|
|
208
|
+
save_plot(plot_path)
|
|
208
209
|
if callbacks and 'save_plot' in callbacks:
|
|
209
210
|
callbacks['save_plot'](plot_path)
|
|
210
|
-
h, w = img_0.shape[:2]
|
|
211
211
|
h_sub, w_sub = img_0_sub.shape[:2]
|
|
212
212
|
if subsample > 1:
|
|
213
213
|
if transform == constants.ALIGN_HOMOGRAPHY:
|
|
214
214
|
low_size = np.float32([[0, 0], [0, h_sub], [w_sub, h_sub], [w_sub, 0]])
|
|
215
|
-
high_size = np.float32([[0, 0], [0,
|
|
215
|
+
high_size = np.float32([[0, 0], [0, h0], [w0, h0], [w0, 0]])
|
|
216
216
|
scale_up = cv2.getPerspectiveTransform(low_size, high_size)
|
|
217
217
|
scale_down = cv2.getPerspectiveTransform(high_size, low_size)
|
|
218
218
|
m = scale_up @ m @ scale_down
|
|
@@ -230,17 +230,17 @@ def align_images(img_1, img_0, feature_config=None, matching_config=None, alignm
|
|
|
230
230
|
img_mask = np.ones_like(img_0, dtype=np.uint8)
|
|
231
231
|
if alignment_config['transform'] == constants.ALIGN_HOMOGRAPHY:
|
|
232
232
|
img_warp = cv2.warpPerspective(
|
|
233
|
-
img_0, m, (
|
|
233
|
+
img_0, m, (w_ref, h_ref),
|
|
234
234
|
borderMode=cv2_border_mode, borderValue=alignment_config['border_value'])
|
|
235
235
|
if alignment_config['border_mode'] == constants.BORDER_REPLICATE_BLUR:
|
|
236
|
-
mask = cv2.warpPerspective(img_mask, m, (
|
|
236
|
+
mask = cv2.warpPerspective(img_mask, m, (w_ref, h_ref),
|
|
237
237
|
borderMode=cv2.BORDER_CONSTANT, borderValue=0)
|
|
238
238
|
elif alignment_config['transform'] == constants.ALIGN_RIGID:
|
|
239
239
|
img_warp = cv2.warpAffine(
|
|
240
|
-
img_0, m, (
|
|
240
|
+
img_0, m, (w_ref, h_ref),
|
|
241
241
|
borderMode=cv2_border_mode, borderValue=alignment_config['border_value'])
|
|
242
242
|
if alignment_config['border_mode'] == constants.BORDER_REPLICATE_BLUR:
|
|
243
|
-
mask = cv2.warpAffine(img_mask, m, (
|
|
243
|
+
mask = cv2.warpAffine(img_mask, m, (w_ref, h_ref),
|
|
244
244
|
borderMode=cv2.BORDER_CONSTANT, borderValue=0)
|
|
245
245
|
if alignment_config['border_mode'] == constants.BORDER_REPLICATE_BLUR:
|
|
246
246
|
if callbacks and 'blur_message' in callbacks:
|
|
@@ -293,7 +293,7 @@ class AlignFrames(SubAction):
|
|
|
293
293
|
'ecc_message': lambda: self.sub_msg(": ecc refinement"),
|
|
294
294
|
'blur_message': lambda: self.sub_msg(': blur borders'),
|
|
295
295
|
'warning': lambda msg: self.sub_msg(
|
|
296
|
-
f': {msg}', constants.
|
|
296
|
+
f': {msg}', constants.LOG_COLOR_WARNING),
|
|
297
297
|
'save_plot': lambda plot_path: self.process.callback(
|
|
298
298
|
'save_plot', self.process.id,
|
|
299
299
|
f"{self.process.name}: matches\nframe {idx_str}", plot_path)
|
|
@@ -315,7 +315,7 @@ class AlignFrames(SubAction):
|
|
|
315
315
|
if n_good_matches < self.min_matches:
|
|
316
316
|
self.process.sub_message(f": image not aligned, too few matches found: "
|
|
317
317
|
f"{n_good_matches}", level=logging.CRITICAL)
|
|
318
|
-
raise AlignmentError(idx, f"too few matches found: "
|
|
318
|
+
raise AlignmentError(idx, f"Image not aligned, too few matches found: "
|
|
319
319
|
f"{n_good_matches} < {self.min_matches}")
|
|
320
320
|
return img
|
|
321
321
|
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, E0602, R0903
|
|
2
|
+
import os
|
|
2
3
|
import numpy as np
|
|
3
|
-
from .. core.exceptions import InvalidOptionError, ImageLoadError
|
|
4
|
+
from .. core.exceptions import InvalidOptionError, ImageLoadError, RunStopException
|
|
4
5
|
from .. config.constants import constants
|
|
5
6
|
from .. core.colors import color_str
|
|
6
|
-
from .utils import read_img, get_img_metadata, validate_image
|
|
7
|
+
from .utils import read_img, get_img_metadata, validate_image, get_img_file_shape, extension_tif_jpg
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class BaseStackAlgo:
|
|
@@ -11,6 +12,9 @@ class BaseStackAlgo:
|
|
|
11
12
|
self._name = name
|
|
12
13
|
self._steps_per_frame = steps_per_frame
|
|
13
14
|
self.process = None
|
|
15
|
+
self.filenames = None
|
|
16
|
+
self.shape = None
|
|
17
|
+
self.do_step_callback = False
|
|
14
18
|
if float_type == constants.FLOAT_32:
|
|
15
19
|
self.float_type = np.float32
|
|
16
20
|
elif float_type == constants.FLOAT_64:
|
|
@@ -24,8 +28,17 @@ class BaseStackAlgo:
|
|
|
24
28
|
def name(self):
|
|
25
29
|
return self._name
|
|
26
30
|
|
|
27
|
-
def
|
|
28
|
-
|
|
31
|
+
def init(self, filenames):
|
|
32
|
+
self.filenames = filenames
|
|
33
|
+
first_img_file = ''
|
|
34
|
+
for filename in filenames:
|
|
35
|
+
if os.path.isfile(filename) and extension_tif_jpg(filename):
|
|
36
|
+
first_img_file = filename
|
|
37
|
+
break
|
|
38
|
+
self.shape = get_img_file_shape(first_img_file)
|
|
39
|
+
|
|
40
|
+
def total_steps(self, n_frames):
|
|
41
|
+
return self._steps_per_frame * n_frames
|
|
29
42
|
|
|
30
43
|
def print_message(self, msg):
|
|
31
44
|
self.process.sub_message_r(color_str(msg, constants.LOG_COLOR_LEVEL_3))
|
|
@@ -40,3 +53,13 @@ class BaseStackAlgo:
|
|
|
40
53
|
else:
|
|
41
54
|
validate_image(img, *metadata)
|
|
42
55
|
return img, metadata, updated
|
|
56
|
+
|
|
57
|
+
def check_running(self, cleanup_callback=None):
|
|
58
|
+
if self.process.callback('check_running', self.process.id, self.process.name) is False:
|
|
59
|
+
if cleanup_callback is not None:
|
|
60
|
+
cleanup_callback()
|
|
61
|
+
raise RunStopException(self.name)
|
|
62
|
+
|
|
63
|
+
def after_step(self, step):
|
|
64
|
+
if self.do_step_callback:
|
|
65
|
+
self.process.callback('after_step', self.process.id, self.process.name, step)
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import numpy as np
|
|
3
3
|
import cv2
|
|
4
4
|
from .. config.constants import constants
|
|
5
|
-
from .. core.exceptions import InvalidOptionError
|
|
5
|
+
from .. core.exceptions import InvalidOptionError
|
|
6
6
|
from .utils import read_img, img_bw
|
|
7
7
|
from .base_stack_algo import BaseStackAlgo
|
|
8
8
|
|
|
@@ -61,19 +61,18 @@ class DepthMapStack(BaseStackAlgo):
|
|
|
61
61
|
raise InvalidOptionError("map_type", self.map_type, details=f" valid values are "
|
|
62
62
|
f"{constants.DM_MAP_AVERAGE} and {constants.DM_MAP_MAX}.")
|
|
63
63
|
|
|
64
|
-
def focus_stack(self
|
|
64
|
+
def focus_stack(self):
|
|
65
65
|
gray_images = []
|
|
66
66
|
metadata = None
|
|
67
|
-
for i, img_path in enumerate(filenames):
|
|
67
|
+
for i, img_path in enumerate(self.filenames):
|
|
68
68
|
self.print_message(f": reading file (1/2) {img_path.split('/')[-1]}")
|
|
69
69
|
|
|
70
70
|
img, metadata, _updated = self.read_image_and_update_metadata(img_path, metadata)
|
|
71
71
|
|
|
72
72
|
gray = img_bw(img)
|
|
73
73
|
gray_images.append(gray)
|
|
74
|
-
self.
|
|
75
|
-
|
|
76
|
-
raise RunStopException(self.name)
|
|
74
|
+
self.after_step(i)
|
|
75
|
+
self.check_running()
|
|
77
76
|
dtype = metadata[1]
|
|
78
77
|
gray_images = np.array(gray_images, dtype=self.float_type)
|
|
79
78
|
if self.energy == constants.DM_ENERGY_SOBEL:
|
|
@@ -92,7 +91,7 @@ class DepthMapStack(BaseStackAlgo):
|
|
|
92
91
|
energies = self.smooth_energy(energies)
|
|
93
92
|
weights = self.get_focus_map(energies)
|
|
94
93
|
blended_pyramid = None
|
|
95
|
-
for i, img_path in enumerate(filenames):
|
|
94
|
+
for i, img_path in enumerate(self.filenames):
|
|
96
95
|
self.print_message(f": reading file (2/2) {img_path.split('/')[-1]}")
|
|
97
96
|
img = read_img(img_path).astype(self.float_type)
|
|
98
97
|
weight = weights[i]
|
|
@@ -110,10 +109,8 @@ class DepthMapStack(BaseStackAlgo):
|
|
|
110
109
|
for j in range(self.levels)]
|
|
111
110
|
blended_pyramid = current_blend if blended_pyramid is None \
|
|
112
111
|
else [np.add(bp, cb) for bp, cb in zip(blended_pyramid, current_blend)]
|
|
113
|
-
self.
|
|
114
|
-
|
|
115
|
-
if self.process.callback('check_running', self.process.id, self.process.name) is False:
|
|
116
|
-
raise RunStopException(self.name)
|
|
112
|
+
self.after_step(i + len(self.filenames))
|
|
113
|
+
self.check_running()
|
|
117
114
|
result = blended_pyramid[0]
|
|
118
115
|
self.print_message(': blend levels')
|
|
119
116
|
for j in range(1, self.levels):
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, E1101, W0718, R0914, R0915
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E1101, W0718, R0914, R0915, R0902
|
|
2
2
|
import os
|
|
3
3
|
import errno
|
|
4
4
|
import logging
|
|
@@ -11,7 +11,7 @@ from .. core.colors import color_str
|
|
|
11
11
|
from .. core.exceptions import ImageLoadError
|
|
12
12
|
from .. core.framework import JobBase
|
|
13
13
|
from .. core.core_utils import make_tqdm_bar
|
|
14
|
-
from .. core.exceptions import RunStopException
|
|
14
|
+
from .. core.exceptions import RunStopException, ShapeError
|
|
15
15
|
from .stack_framework import FrameMultiDirectory, SubAction
|
|
16
16
|
from .utils import read_img, save_plot, get_img_metadata, validate_image
|
|
17
17
|
|
|
@@ -154,6 +154,7 @@ class MaskNoise(SubAction):
|
|
|
154
154
|
self.method = method
|
|
155
155
|
self.process = None
|
|
156
156
|
self.noise_mask_img = None
|
|
157
|
+
self.expected_shape = None
|
|
157
158
|
|
|
158
159
|
def begin(self, process):
|
|
159
160
|
self.process = process
|
|
@@ -163,6 +164,7 @@ class MaskNoise(SubAction):
|
|
|
163
164
|
f': reading noisy pixel mask file: {self.noise_mask}',
|
|
164
165
|
constants.LOG_COLOR_LEVEL_3))
|
|
165
166
|
self.noise_mask_img = cv2.imread(path, cv2.IMREAD_GRAYSCALE)
|
|
167
|
+
self.expected_shape = self.noise_mask_img.shape[:2]
|
|
166
168
|
if self.noise_mask_img is None:
|
|
167
169
|
raise ImageLoadError(path, f"failed to load image file {self.noise_mask}.")
|
|
168
170
|
else:
|
|
@@ -170,6 +172,9 @@ class MaskNoise(SubAction):
|
|
|
170
172
|
|
|
171
173
|
def run_frame(self, _idx, _ref_idx, image):
|
|
172
174
|
self.process.sub_message_r(color_str(': mask noisy pixels', constants.LOG_COLOR_LEVEL_3))
|
|
175
|
+
shape = image.shape[:2]
|
|
176
|
+
if shape != self.expected_shape:
|
|
177
|
+
raise ShapeError(self.expected_shape, shape)
|
|
173
178
|
if len(image.shape) == 3:
|
|
174
179
|
corrected = image.copy()
|
|
175
180
|
for c in range(3):
|
|
@@ -1,25 +1,36 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, E1101
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E1101, R0913, R0917, R0902
|
|
2
2
|
import numpy as np
|
|
3
3
|
import cv2
|
|
4
4
|
from .. config.constants import constants
|
|
5
|
-
from .. core.exceptions import RunStopException
|
|
6
5
|
from .utils import read_img
|
|
7
6
|
from .base_stack_algo import BaseStackAlgo
|
|
8
7
|
|
|
9
8
|
|
|
10
9
|
class PyramidBase(BaseStackAlgo):
|
|
11
|
-
def __init__(self, min_size=constants.DEFAULT_PY_MIN_SIZE,
|
|
10
|
+
def __init__(self, name, min_size=constants.DEFAULT_PY_MIN_SIZE,
|
|
12
11
|
kernel_size=constants.DEFAULT_PY_KERNEL_SIZE,
|
|
13
12
|
gen_kernel=constants.DEFAULT_PY_GEN_KERNEL,
|
|
14
13
|
float_type=constants.DEFAULT_PY_FLOAT):
|
|
15
|
-
super().__init__(
|
|
14
|
+
super().__init__(name, 2, float_type)
|
|
16
15
|
self.min_size = min_size
|
|
17
16
|
self.kernel_size = kernel_size
|
|
18
17
|
self.pad_amount = (kernel_size - 1) // 2
|
|
19
|
-
self.do_step_callback = False
|
|
20
18
|
kernel = np.array([0.25 - gen_kernel / 2.0, 0.25,
|
|
21
19
|
gen_kernel, 0.25, 0.25 - gen_kernel / 2.0])
|
|
22
20
|
self.gen_kernel = np.outer(kernel, kernel)
|
|
21
|
+
self.dtype = None
|
|
22
|
+
self.num_pixel_values = None
|
|
23
|
+
self.max_pixel_value = None
|
|
24
|
+
self.n_levels = 0
|
|
25
|
+
self.n_frames = 0
|
|
26
|
+
|
|
27
|
+
def init(self, filenames):
|
|
28
|
+
super().init(filenames)
|
|
29
|
+
self.n_levels = int(np.log2(min(self.shape) / self.min_size))
|
|
30
|
+
|
|
31
|
+
def total_steps(self, n_frames):
|
|
32
|
+
self.n_frames = n_frames
|
|
33
|
+
return self._steps_per_frame * n_frames + self.n_levels
|
|
23
34
|
|
|
24
35
|
def convolve(self, image):
|
|
25
36
|
return cv2.filter2D(image, -1, self.gen_kernel, borderType=cv2.BORDER_REFLECT101)
|
|
@@ -55,6 +66,7 @@ class PyramidBase(BaseStackAlgo):
|
|
|
55
66
|
return fused
|
|
56
67
|
|
|
57
68
|
def collapse(self, pyramid):
|
|
69
|
+
self.print_message(': collapsing pyramid')
|
|
58
70
|
img = pyramid[-1]
|
|
59
71
|
for layer in pyramid[-2::-1]:
|
|
60
72
|
expanded = self.expand_layer(img)
|
|
@@ -110,19 +122,22 @@ class PyramidBase(BaseStackAlgo):
|
|
|
110
122
|
fused += np.where(best_d[:, :, np.newaxis] == layer, img, 0)
|
|
111
123
|
return (fused / 2).astype(images.dtype)
|
|
112
124
|
|
|
125
|
+
def focus_stack_validate(self, cleanup_callback=None):
|
|
126
|
+
metadata = None
|
|
127
|
+
for i, img_path in enumerate(self.filenames):
|
|
128
|
+
self.print_message(f": validating file {img_path.split('/')[-1]}")
|
|
113
129
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
self.max_pixel_value = None
|
|
130
|
+
_img, metadata, updated = self.read_image_and_update_metadata(img_path, metadata)
|
|
131
|
+
if updated:
|
|
132
|
+
self.dtype = metadata[1]
|
|
133
|
+
self.num_pixel_values = constants.NUM_UINT8 \
|
|
134
|
+
if self.dtype == np.uint8 else constants.NUM_UINT16
|
|
135
|
+
self.max_pixel_value = constants.MAX_UINT8 \
|
|
136
|
+
if self.dtype == np.uint8 else constants.MAX_UINT16
|
|
137
|
+
self.after_step(i + 1)
|
|
138
|
+
self.check_running(cleanup_callback)
|
|
124
139
|
|
|
125
|
-
def
|
|
140
|
+
def single_image_laplacian(self, img, levels):
|
|
126
141
|
pyramid = [img.astype(self.float_type)]
|
|
127
142
|
for _ in range(levels):
|
|
128
143
|
next_layer = self.reduce_layer(pyramid[-1])
|
|
@@ -136,44 +151,43 @@ class PyramidStack(PyramidBase):
|
|
|
136
151
|
h, w = pyr.shape[:2]
|
|
137
152
|
expanded = expanded[:h, :w]
|
|
138
153
|
laplacian.append(pyr - expanded)
|
|
154
|
+
return laplacian
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
class PyramidStack(PyramidBase):
|
|
158
|
+
def __init__(self, min_size=constants.DEFAULT_PY_MIN_SIZE,
|
|
159
|
+
kernel_size=constants.DEFAULT_PY_KERNEL_SIZE,
|
|
160
|
+
gen_kernel=constants.DEFAULT_PY_GEN_KERNEL,
|
|
161
|
+
float_type=constants.DEFAULT_PY_FLOAT):
|
|
162
|
+
super().__init__("pyramid", min_size, kernel_size, gen_kernel, float_type)
|
|
163
|
+
self.offset = np.arange(-self.pad_amount, self.pad_amount + 1)
|
|
164
|
+
|
|
165
|
+
def process_single_image(self, img, levels):
|
|
166
|
+
laplacian = self.single_image_laplacian(img, levels)
|
|
139
167
|
return laplacian[::-1]
|
|
140
168
|
|
|
141
169
|
def fuse_pyramids(self, all_laplacians):
|
|
142
170
|
fused = [self.get_fused_base(np.stack([p[-1] for p in all_laplacians], axis=0))]
|
|
171
|
+
count = 0
|
|
143
172
|
for layer in range(len(all_laplacians[0]) - 2, -1, -1):
|
|
144
173
|
self.print_message(f': fusing pyramids, layer: {layer + 1}')
|
|
145
174
|
laplacians = np.stack([p[layer] for p in all_laplacians], axis=0)
|
|
146
175
|
fused.append(self.fuse_laplacian(laplacians))
|
|
176
|
+
count += 1
|
|
177
|
+
self.after_step(self._steps_per_frame * self.n_frames + count)
|
|
178
|
+
self.check_running()
|
|
147
179
|
self.print_message(': pyramids fusion completed')
|
|
148
180
|
return fused[::-1]
|
|
149
181
|
|
|
150
|
-
def focus_stack(self
|
|
151
|
-
|
|
182
|
+
def focus_stack(self):
|
|
183
|
+
n = len(self.filenames)
|
|
184
|
+
self.focus_stack_validate()
|
|
152
185
|
all_laplacians = []
|
|
153
|
-
|
|
154
|
-
n = len(filenames)
|
|
155
|
-
for i, img_path in enumerate(filenames):
|
|
156
|
-
self.print_message(f": validating file {img_path.split('/')[-1]}")
|
|
157
|
-
|
|
158
|
-
img, metadata, updated = self.read_image_and_update_metadata(img_path, metadata)
|
|
159
|
-
if updated:
|
|
160
|
-
self.dtype = metadata[1]
|
|
161
|
-
self.num_pixel_values = constants.NUM_UINT8 \
|
|
162
|
-
if self.dtype == np.uint8 else constants.NUM_UINT16
|
|
163
|
-
self.max_pixel_value = constants.MAX_UINT8 \
|
|
164
|
-
if self.dtype == np.uint8 else constants.MAX_UINT16
|
|
165
|
-
levels = int(np.log2(min(img.shape[:2]) / self.min_size))
|
|
166
|
-
if self.do_step_callback:
|
|
167
|
-
self.process.callback('after_step', self.process.id, self.process.name, i)
|
|
168
|
-
if self.process.callback('check_running', self.process.id, self.process.name) is False:
|
|
169
|
-
raise RunStopException(self.name)
|
|
170
|
-
for i, img_path in enumerate(filenames):
|
|
186
|
+
for i, img_path in enumerate(self.filenames):
|
|
171
187
|
self.print_message(f": processing file {img_path.split('/')[-1]}")
|
|
172
188
|
img = read_img(img_path)
|
|
173
|
-
all_laplacians.append(self.process_single_image(img,
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if self.process.callback('check_running', self.process.id, self.process.name) is False:
|
|
177
|
-
raise RunStopException(self.name)
|
|
189
|
+
all_laplacians.append(self.process_single_image(img, self.n_levels))
|
|
190
|
+
self.after_step(i + n + 1)
|
|
191
|
+
self.check_running()
|
|
178
192
|
stacked_image = self.collapse(self.fuse_pyramids(all_laplacians))
|
|
179
193
|
return stacked_image.astype(self.dtype)
|