shinestacker 1.7.0__tar.gz → 1.8.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.7.0 → shinestacker-1.8.0}/.github/workflows/release.yml +2 -6
- {shinestacker-1.7.0 → shinestacker-1.8.0}/CHANGELOG.md +18 -1
- {shinestacker-1.7.0/src/shinestacker.egg-info → shinestacker-1.8.0}/PKG-INFO +4 -4
- {shinestacker-1.7.0 → shinestacker-1.8.0}/README.md +3 -3
- {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/build_release.py +0 -5
- shinestacker-1.8.0/src/shinestacker/_version.py +1 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/stack.py +9 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/stack_framework.py +35 -16
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/utils.py +5 -1
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/settings_dialog.py +46 -3
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/config/settings.py +4 -1
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/core/core_utils.py +1 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/core/framework.py +7 -2
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/action_config_dialog.py +72 -45
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/gui_run.py +1 -2
- shinestacker-1.8.0/src/shinestacker/gui/img/dark/close-round-line-icon.png +0 -0
- shinestacker-1.8.0/src/shinestacker/gui/img/dark/forward-button-icon.png +0 -0
- shinestacker-1.8.0/src/shinestacker/gui/img/dark/play-button-round-icon.png +0 -0
- shinestacker-1.8.0/src/shinestacker/gui/img/dark/plus-round-line-icon.png +0 -0
- shinestacker-1.8.0/src/shinestacker/gui/img/dark/shinestacker_bkg.png +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/main_window.py +20 -7
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/menu_manager.py +18 -7
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/new_project.py +0 -2
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/tab_widget.py +16 -6
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/adjustments.py +5 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0/src/shinestacker.egg-info}/PKG-INFO +4 -4
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker.egg-info/SOURCES.txt +10 -5
- shinestacker-1.7.0/src/shinestacker/_version.py +0 -1
- {shinestacker-1.7.0 → shinestacker-1.8.0}/.coveragerc +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/.flake8 +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/.github/workflows/ci-multiplatform.yml +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/.github/workflows/pylint.yml +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/.github/workflows/pypi-publish.yml +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/.gitignore +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/.pylintrc +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/.readthedocs.yaml +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/LICENSE +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/MANIFEST.in +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/THIRD_PARTY_LICENSES.txt +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/alignment.md +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/api.md +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/balancing.md +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/conf.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/focus_stacking.md +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/gui.md +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/index.md +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/job.md +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/main.md +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/multilayer.md +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/noise.md +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/requirements.txt +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/vignetting.md +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/img/coffee.gif +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/img/coffee_stack.jpg +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/img/extreme-vignetting.jpg +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/img/flies.gif +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/img/flies_stack.jpg +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/img/flow-diagram.png +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/img/gui-finder.png +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/img/gui-project-new.png +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/img/gui-project-run.png +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/img/gui-retouch.png +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/index.html +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/pyproject.toml +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/requirements.txt +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/create_macos_icon.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/git-rev-list.sh +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/hooks/hook-IPython.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/hooks/hook-PySide6.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/hooks/hook-opencv.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/hooks/hook-tests.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/scan_imports.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/shinestacker-inno-setup.iss +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/validate-tomli.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/setup.cfg +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/shinestacker-inno-setup.iss +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/__init__.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/__init__.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/align.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/align_auto.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/align_parallel.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/balance.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/corrections.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/denoise.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/depth_map.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/exif.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/multilayer.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/noise_detection.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/pyramid.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/pyramid_auto.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/pyramid_tiles.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/sharpen.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/vignetting.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/white_balance.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/__init__.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/about_dialog.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/args_parser_opts.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/gui_utils.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/help_menu.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/main.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/open_frames.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/project.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/retouch.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/config/__init__.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/config/app_config.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/config/config.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/config/constants.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/config/gui_constants.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/core/__init__.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/core/colors.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/core/exceptions.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/core/logging.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/__init__.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/action_config.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/base_form_dialog.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/colors.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/config_dialog.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/flow_layout.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/folder_file_selection.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/gui_images.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/gui_logging.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/ico/shinestacker.png +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
- {shinestacker-1.7.0/src/shinestacker/gui/img → shinestacker-1.8.0/src/shinestacker/gui/img/light}/close-round-line-icon.png +0 -0
- {shinestacker-1.7.0/src/shinestacker/gui/img → shinestacker-1.8.0/src/shinestacker/gui/img/light}/forward-button-icon.png +0 -0
- {shinestacker-1.7.0/src/shinestacker/gui/img → shinestacker-1.8.0/src/shinestacker/gui/img/light}/play-button-round-icon.png +0 -0
- {shinestacker-1.7.0/src/shinestacker/gui/img → shinestacker-1.8.0/src/shinestacker/gui/img/light}/plus-round-line-icon.png +0 -0
- {shinestacker-1.7.0/src/shinestacker/gui/ico → shinestacker-1.8.0/src/shinestacker/gui/img/light}/shinestacker_bkg.png +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/project_controller.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/project_converter.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/project_editor.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/project_model.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/recent_file_manager.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/select_path_widget.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/sys_mon.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/time_progress_bar.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/__init__.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/base_filter.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/brush.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/brush_gradient.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/brush_preview.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/brush_tool.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/denoise_filter.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/display_manager.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/exif_data.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/file_loader.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/filter_manager.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/icon_container.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/image_editor_ui.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/image_view_status.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/image_viewer.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/io_gui_handler.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/io_threads.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/layer_collection.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/overlaid_view.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/paint_area_manager.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/shortcuts_help.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/sidebyside_view.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/transformation_manager.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/undo_manager.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/unsharp_mask_filter.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/view_strategy.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/vignetting_filter.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/white_balance_filter.py +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker.egg-info/dependency_links.txt +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker.egg-info/entry_points.txt +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker.egg-info/requires.txt +0 -0
- {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker.egg-info/top_level.txt +0 -0
|
@@ -40,9 +40,8 @@ jobs:
|
|
|
40
40
|
pip install -e .[dev]
|
|
41
41
|
|
|
42
42
|
- name: Build and package release
|
|
43
|
-
working-directory: scripts
|
|
44
43
|
run: |
|
|
45
|
-
python build_release.py
|
|
44
|
+
python scripts/build_release.py
|
|
46
45
|
|
|
47
46
|
- name: Upload artifacts
|
|
48
47
|
uses: actions/upload-artifact@v4
|
|
@@ -52,7 +51,6 @@ jobs:
|
|
|
52
51
|
${{ matrix.os == 'windows-latest' && 'dist/shinestacker-release.zip' || '' }}
|
|
53
52
|
${{ matrix.os == 'windows-latest' && 'dist/*.exe' || '' }}
|
|
54
53
|
${{ matrix.os == 'ubuntu-latest' && 'dist/shinestacker-release.tar.gz' || '' }}
|
|
55
|
-
${{ matrix.os == 'macos-latest' && 'dist/shinestacker-release.tar.gz' || '' }}
|
|
56
54
|
${{ matrix.os == 'macos-latest' && 'dist/shinestacker-release.dmg' || '' }}
|
|
57
55
|
if-no-files-found: ignore
|
|
58
56
|
|
|
@@ -70,8 +68,7 @@ jobs:
|
|
|
70
68
|
mkdir -p release_assets
|
|
71
69
|
# Linux
|
|
72
70
|
cp artifacts/shinestacker-ubuntu-latest/shinestacker-release.tar.gz release_assets/shinestacker-ubuntu.tar.gz
|
|
73
|
-
# macOS
|
|
74
|
-
cp artifacts/shinestacker-macos-latest/shinestacker-release.tar.gz release_assets/shinestacker-macos.tar.gz
|
|
71
|
+
# macOS
|
|
75
72
|
cp artifacts/shinestacker-macos-latest/shinestacker-release.dmg release_assets/shinestacker-macos.dmg
|
|
76
73
|
# Windows
|
|
77
74
|
cp artifacts/shinestacker-windows-latest/shinestacker-release.zip release_assets/shinestacker-windows.zip
|
|
@@ -86,7 +83,6 @@ jobs:
|
|
|
86
83
|
draft: true
|
|
87
84
|
files: |
|
|
88
85
|
release_assets/shinestacker-ubuntu.tar.gz
|
|
89
|
-
release_assets/shinestacker-macos.tar.gz
|
|
90
86
|
release_assets/shinestacker-macos.dmg
|
|
91
87
|
release_assets/shinestacker-windows.zip
|
|
92
88
|
release_assets/shinestacker-setup.exe
|
|
@@ -2,8 +2,25 @@
|
|
|
2
2
|
|
|
3
3
|
This page reports the main releases only and the main changes therein.
|
|
4
4
|
|
|
5
|
+
## [v1.8.0] - 2025-10-08
|
|
6
|
+
** Minor improvements and accessibility fix **
|
|
7
|
+
|
|
8
|
+
### Added
|
|
9
|
+
- temporary disk space can be cleaned up with a new option to scratch output files at the end of a job
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- new project dialog displays well also with dark background settings
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- icons now adapt automatically to light or dark desktop theme
|
|
16
|
+
- additional alignment parameters added to default settings
|
|
17
|
+
- minor GUI stability fix
|
|
18
|
+
- redundant macOS .tar.gz installer removed, replaced by .dmg image
|
|
19
|
+
|
|
20
|
+
-----
|
|
21
|
+
|
|
5
22
|
## [v1.7.0] - 2025-10-04
|
|
6
|
-
** New image adjustment actions and macOS dmg installer **
|
|
23
|
+
** New image adjustment actions and macOS dmg image installer **
|
|
7
24
|
|
|
8
25
|
### Added
|
|
9
26
|
- luminosity and contrast adjustment action
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: shinestacker
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.8.0
|
|
4
4
|
Summary: ShineStacker
|
|
5
5
|
Author-email: Luca Lista <luka.lista@gmail.com>
|
|
6
6
|
License-Expression: LGPL-3.0
|
|
@@ -85,11 +85,11 @@ In order to prevent this, follow the instructions below:
|
|
|
85
85
|
1. Download the compressed archive ```shinestacker-macos.tar.gz``` in your ```Download``` folder.
|
|
86
86
|
2. Double-click the archive to uncompress it. You will find a new folder ```shinestacker```.
|
|
87
87
|
3. Open a terminal (*Applications > Utilities > Terminal*)
|
|
88
|
-
4. Type the folliwng command on the terminal (assuming you
|
|
88
|
+
4. Type the folliwng command on the terminal (assuming you installed the app from the ```dmg``` image under ```Applications```):
|
|
89
89
|
```bash
|
|
90
|
-
xattr -cr
|
|
90
|
+
xattr -cr /Applications/shinestacker/shinestacker.app
|
|
91
91
|
```
|
|
92
|
-
5. Now you can double-click the Sine Stacker icon app
|
|
92
|
+
5. Now you can double-click the Sine Stacker icon app and it should run.
|
|
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
|
|
|
@@ -53,11 +53,11 @@ In order to prevent this, follow the instructions below:
|
|
|
53
53
|
1. Download the compressed archive ```shinestacker-macos.tar.gz``` in your ```Download``` folder.
|
|
54
54
|
2. Double-click the archive to uncompress it. You will find a new folder ```shinestacker```.
|
|
55
55
|
3. Open a terminal (*Applications > Utilities > Terminal*)
|
|
56
|
-
4. Type the folliwng command on the terminal (assuming you
|
|
56
|
+
4. Type the folliwng command on the terminal (assuming you installed the app from the ```dmg``` image under ```Applications```):
|
|
57
57
|
```bash
|
|
58
|
-
xattr -cr
|
|
58
|
+
xattr -cr /Applications/shinestacker/shinestacker.app
|
|
59
59
|
```
|
|
60
|
-
5. Now you can double-click the Sine Stacker icon app
|
|
60
|
+
5. Now you can double-click the Sine Stacker icon app and it should run.
|
|
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
|
|
|
@@ -7,7 +7,6 @@ import platform
|
|
|
7
7
|
|
|
8
8
|
|
|
9
9
|
def setup_environment():
|
|
10
|
-
os.chdir("../")
|
|
11
10
|
project_root = Path(__file__).resolve().parent.parent
|
|
12
11
|
dist_dir = project_root / "dist"
|
|
13
12
|
project_name = "shinestacker"
|
|
@@ -119,10 +118,6 @@ def package_macos(dist_dir, app_name, project_root):
|
|
|
119
118
|
except subprocess.CalledProcessError as e:
|
|
120
119
|
print(f"Could not set custom icon: {e}")
|
|
121
120
|
shutil.rmtree(dmg_temp_dir)
|
|
122
|
-
archive_path = dist_dir / "shinestacker-release.tar.gz"
|
|
123
|
-
with tarfile.open(archive_path, "w:gz") as tar:
|
|
124
|
-
tar.add(app_bundle, arcname=app_bundle.name, recursive=True)
|
|
125
|
-
print(f"Created tar.gz: {archive_path.name}")
|
|
126
121
|
|
|
127
122
|
|
|
128
123
|
def package_linux(dist_dir, app_name):
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.8.0'
|
|
@@ -65,6 +65,9 @@ class FocusStackBase(TaskBase, ImageSequenceManager):
|
|
|
65
65
|
if self.exif_path != '':
|
|
66
66
|
self.exif_path = os.path.join(working_path, self.exif_path)
|
|
67
67
|
|
|
68
|
+
def end_job(self):
|
|
69
|
+
ImageSequenceManager.end_job(self)
|
|
70
|
+
|
|
68
71
|
|
|
69
72
|
def get_bunches(collection, n_frames, n_overlap):
|
|
70
73
|
bunches = [collection[x:x + n_frames]
|
|
@@ -100,6 +103,9 @@ class FocusStackBunch(SequentialTask, FocusStackBase):
|
|
|
100
103
|
def end(self):
|
|
101
104
|
SequentialTask.end(self)
|
|
102
105
|
|
|
106
|
+
def end_job(self):
|
|
107
|
+
FocusStackBase.end_job(self)
|
|
108
|
+
|
|
103
109
|
def run_step(self, action_count=-1):
|
|
104
110
|
self.print_message(
|
|
105
111
|
color_str(f"fusing bunch: {action_count + 1}/{self.total_action_counts}",
|
|
@@ -126,3 +132,6 @@ class FocusStack(FocusStackBase):
|
|
|
126
132
|
|
|
127
133
|
def init(self, job, _working_path=''):
|
|
128
134
|
FocusStackBase.init(self, job, self.working_path)
|
|
135
|
+
|
|
136
|
+
def end_job(self):
|
|
137
|
+
FocusStackBase.end_job(self)
|
|
@@ -46,7 +46,8 @@ class StackJob(Job):
|
|
|
46
46
|
class ImageSequenceManager:
|
|
47
47
|
def __init__(self, name, input_path='', output_path='', working_path='',
|
|
48
48
|
plot_path=constants.DEFAULT_PLOTS_PATH,
|
|
49
|
-
scratch_output_dir=True,
|
|
49
|
+
scratch_output_dir=True, delete_output_at_end=False,
|
|
50
|
+
resample=1,
|
|
50
51
|
reverse_order=constants.DEFAULT_FILE_REVERSE_ORDER, **_kwargs):
|
|
51
52
|
self.name = name
|
|
52
53
|
self.working_path = working_path
|
|
@@ -56,6 +57,7 @@ class ImageSequenceManager:
|
|
|
56
57
|
self._resample = resample
|
|
57
58
|
self.reverse_order = reverse_order
|
|
58
59
|
self.scratch_output_dir = scratch_output_dir
|
|
60
|
+
self.delete_output_at_end = delete_output_at_end
|
|
59
61
|
self.enabled = None
|
|
60
62
|
self.base_message = ''
|
|
61
63
|
self._input_full_path = None
|
|
@@ -122,6 +124,24 @@ class ImageSequenceManager:
|
|
|
122
124
|
constants.LOG_COLOR_LEVEL_2))
|
|
123
125
|
self.base_message = color_str(self.name, constants.LOG_COLOR_LEVEL_1, "bold")
|
|
124
126
|
|
|
127
|
+
def scratch_outout_folder(self):
|
|
128
|
+
if self.enabled:
|
|
129
|
+
output_dir = self.output_full_path()
|
|
130
|
+
list_dir = os.listdir(output_dir)
|
|
131
|
+
n_files = len(list_dir)
|
|
132
|
+
if n_files > 0:
|
|
133
|
+
for filename in list_dir:
|
|
134
|
+
file_path = os.path.join(output_dir, filename)
|
|
135
|
+
if os.path.isfile(file_path):
|
|
136
|
+
os.remove(file_path)
|
|
137
|
+
self.print_message(
|
|
138
|
+
color_str(f"output directory {self.output_path} content erased",
|
|
139
|
+
'yellow'))
|
|
140
|
+
else:
|
|
141
|
+
self.print_message(
|
|
142
|
+
color_str(f"module disabled, output directory {self.output_path}"
|
|
143
|
+
" not scratched", 'yellow'))
|
|
144
|
+
|
|
125
145
|
def init(self, job):
|
|
126
146
|
if self.working_path == '':
|
|
127
147
|
self.working_path = job.working_path
|
|
@@ -130,22 +150,10 @@ class ImageSequenceManager:
|
|
|
130
150
|
if not os.path.exists(output_dir):
|
|
131
151
|
os.makedirs(output_dir)
|
|
132
152
|
else:
|
|
133
|
-
|
|
134
|
-
if len(list_dir) > 0:
|
|
153
|
+
if len(os.listdir(output_dir)):
|
|
135
154
|
if self.scratch_output_dir:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
file_path = os.path.join(output_dir, filename)
|
|
139
|
-
if os.path.isfile(file_path):
|
|
140
|
-
os.remove(file_path)
|
|
141
|
-
self.print_message(
|
|
142
|
-
color_str(f": output directory {self.output_path} content erased",
|
|
143
|
-
'yellow'))
|
|
144
|
-
else:
|
|
145
|
-
self.print_message(
|
|
146
|
-
color_str(f": module disabled, output directory {self.output_path}"
|
|
147
|
-
" not scratched", 'yellow'))
|
|
148
|
-
else:
|
|
155
|
+
self.scratch_outout_folder()
|
|
156
|
+
elif self.enabled:
|
|
149
157
|
self.print_message(
|
|
150
158
|
color_str(
|
|
151
159
|
f": output directory {self.output_path} not empty, "
|
|
@@ -168,6 +176,11 @@ class ImageSequenceManager:
|
|
|
168
176
|
self._input_filepaths.append(filepath)
|
|
169
177
|
job.add_action_path(self.output_path)
|
|
170
178
|
|
|
179
|
+
def end_job(self):
|
|
180
|
+
if self.delete_output_at_end:
|
|
181
|
+
self.scratch_outout_folder()
|
|
182
|
+
os.rmdir(self.output_full_path())
|
|
183
|
+
|
|
171
184
|
def folder_list_str(self):
|
|
172
185
|
if isinstance(self.input_full_path(), list):
|
|
173
186
|
file_list = ", ".join(
|
|
@@ -206,6 +219,9 @@ class ReferenceFrameTask(SequentialTask, ImageSequenceManager):
|
|
|
206
219
|
def end(self):
|
|
207
220
|
SequentialTask.end(self)
|
|
208
221
|
|
|
222
|
+
def end_job(self):
|
|
223
|
+
ImageSequenceManager.end_job(self)
|
|
224
|
+
|
|
209
225
|
def run_frame(self, _idx, _ref_idx):
|
|
210
226
|
return None
|
|
211
227
|
|
|
@@ -323,6 +339,9 @@ class CombinedActions(ReferenceFrameTask):
|
|
|
323
339
|
if a.enabled:
|
|
324
340
|
a.end()
|
|
325
341
|
|
|
342
|
+
def end_job(self):
|
|
343
|
+
ReferenceFrameTask.end_job(self)
|
|
344
|
+
|
|
326
345
|
def sequential_processing(self):
|
|
327
346
|
for a in self._actions:
|
|
328
347
|
if a.sequential_processing():
|
|
@@ -140,8 +140,12 @@ def save_plot(filename, fig=None):
|
|
|
140
140
|
if not os.path.isdir(dir_path):
|
|
141
141
|
os.makedirs(dir_path)
|
|
142
142
|
if fig is None:
|
|
143
|
+
logging_level = logging.getLogger().level
|
|
144
|
+
logger = logging.getLogger()
|
|
145
|
+
logger.setLevel(logging.WARNING)
|
|
143
146
|
fig = plt.gcf()
|
|
144
|
-
|
|
147
|
+
fig.savefig(filename, dpi=150)
|
|
148
|
+
logger.setLevel(logging_level)
|
|
145
149
|
if config.JUPYTER_NOTEBOOK:
|
|
146
150
|
plt.show()
|
|
147
151
|
plt.close(fig)
|
|
@@ -1,23 +1,28 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, E0611, W0718, R0903, E0611, R0902
|
|
2
2
|
from PySide6.QtCore import Signal
|
|
3
3
|
from PySide6.QtWidgets import QFrame, QLabel, QCheckBox, QComboBox, QDoubleSpinBox, QSpinBox
|
|
4
|
-
from .. gui.config_dialog import ConfigDialog
|
|
5
4
|
from .. config.settings import Settings
|
|
6
5
|
from .. config.constants import constants
|
|
7
6
|
from .. config.gui_constants import gui_constants
|
|
7
|
+
from .. gui.config_dialog import ConfigDialog
|
|
8
|
+
from .. gui.action_config_dialog import AlignFramesConfigBase
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
class SettingsDialog(ConfigDialog):
|
|
11
|
+
class SettingsDialog(ConfigDialog, AlignFramesConfigBase):
|
|
11
12
|
update_project_config_requested = Signal()
|
|
12
13
|
update_retouch_config_requested = Signal()
|
|
13
14
|
|
|
14
15
|
def __init__(self, parent=None, project_settings=True, retouch_settings=True):
|
|
16
|
+
AlignFramesConfigBase.__init__(self)
|
|
15
17
|
self.project_settings = project_settings
|
|
16
18
|
self.retouch_settings = retouch_settings
|
|
17
19
|
self.settings = Settings.instance()
|
|
18
20
|
self.expert_options = None
|
|
19
21
|
self.combined_actions_max_threads = None
|
|
20
22
|
self.align_frames_max_threads = None
|
|
23
|
+
self.detector = None
|
|
24
|
+
self.descriptor = None
|
|
25
|
+
self.matching_method = None
|
|
21
26
|
self.focus_stack_max_threads = None
|
|
22
27
|
self.view_strategy = None
|
|
23
28
|
self.min_mouse_step_brush_fraction = None
|
|
@@ -58,6 +63,32 @@ class SettingsDialog(ConfigDialog):
|
|
|
58
63
|
self.container_layout.addRow("Max num. of cores, align frames:",
|
|
59
64
|
self.align_frames_max_threads)
|
|
60
65
|
|
|
66
|
+
def change_match_config():
|
|
67
|
+
self.change_match_config(
|
|
68
|
+
self.detector, self.descriptor,
|
|
69
|
+
self. matching_method, self.show_info)
|
|
70
|
+
|
|
71
|
+
self.detector = QComboBox()
|
|
72
|
+
self.detector.addItems(constants.VALID_DETECTORS)
|
|
73
|
+
self.descriptor = QComboBox()
|
|
74
|
+
self.descriptor.addItems(constants.VALID_DESCRIPTORS)
|
|
75
|
+
self.matching_method = QComboBox()
|
|
76
|
+
self.info_label = QLabel()
|
|
77
|
+
self.info_label.setStyleSheet("color: orange; font-style: italic;")
|
|
78
|
+
self.matching_method = QComboBox()
|
|
79
|
+
for k, v in zip(self.MATCHING_METHOD_OPTIONS, constants.VALID_MATCHING_METHODS):
|
|
80
|
+
self.matching_method.addItem(k, v)
|
|
81
|
+
self.detector.setToolTip(self.DETECTOR_DESCRIPTOR_TOOLTIPS['detector'])
|
|
82
|
+
self.descriptor.setToolTip(self.DETECTOR_DESCRIPTOR_TOOLTIPS['descriptor'])
|
|
83
|
+
self.matching_method.setToolTip(self.DETECTOR_DESCRIPTOR_TOOLTIPS['match_method'])
|
|
84
|
+
self.detector.currentIndexChanged.connect(change_match_config)
|
|
85
|
+
self.descriptor.currentIndexChanged.connect(change_match_config)
|
|
86
|
+
self.matching_method.currentIndexChanged.connect(change_match_config)
|
|
87
|
+
self.container_layout.addRow('Detector:', self.detector)
|
|
88
|
+
self.container_layout.addRow('Descriptor:', self.descriptor)
|
|
89
|
+
self.container_layout.addRow(self.info_label)
|
|
90
|
+
self.container_layout.addRow('Match method:', self.matching_method)
|
|
91
|
+
|
|
61
92
|
self.focus_stack_max_threads = QSpinBox()
|
|
62
93
|
self.focus_stack_max_threads.setRange(0, 64)
|
|
63
94
|
self.focus_stack_max_threads.setValue(
|
|
@@ -115,7 +146,14 @@ class SettingsDialog(ConfigDialog):
|
|
|
115
146
|
})
|
|
116
147
|
self.settings.set(
|
|
117
148
|
'align_frames_params', {
|
|
118
|
-
'max_threads':
|
|
149
|
+
'max_threads':
|
|
150
|
+
self.align_frames_max_threads.value(),
|
|
151
|
+
'detector':
|
|
152
|
+
self.descriptor.currentText(),
|
|
153
|
+
'descriptor':
|
|
154
|
+
self.descriptor.currentText(),
|
|
155
|
+
'match_method':
|
|
156
|
+
self.matching_method.itemData(self.matching_method.currentIndex())
|
|
119
157
|
})
|
|
120
158
|
self.settings.set(
|
|
121
159
|
'focus_stack_params', {
|
|
@@ -148,6 +186,11 @@ class SettingsDialog(ConfigDialog):
|
|
|
148
186
|
self.expert_options.setChecked(constants.DEFAULT_EXPERT_OPTIONS)
|
|
149
187
|
self.combined_actions_max_threads.setValue(constants.DEFAULT_MAX_FWK_THREADS)
|
|
150
188
|
self.align_frames_max_threads.setValue(constants.DEFAULT_ALIGN_MAX_THREADS)
|
|
189
|
+
self.detector.setCurrentText(constants.DEFAULT_DETECTOR)
|
|
190
|
+
self.descriptor.setCurrentText(constants.DEFAULT_DESCRIPTOR)
|
|
191
|
+
idx = self.matching_method.findData(constants.DEFAULT_MATCHING_METHOD)
|
|
192
|
+
if idx >= 0:
|
|
193
|
+
self.matching_method.setCurrentIndex(idx)
|
|
151
194
|
self.focus_stack_max_threads.setValue(constants.DEFAULT_PY_MAX_THREADS)
|
|
152
195
|
if self.retouch_settings:
|
|
153
196
|
idx = self.view_strategy.findData(constants.DEFAULT_VIEW_STRATEGY)
|
|
@@ -42,7 +42,10 @@ DEFAULT_SETTINGS = {
|
|
|
42
42
|
'max_threads': constants.DEFAULT_MAX_FWK_THREADS
|
|
43
43
|
},
|
|
44
44
|
'align_frames_params': {
|
|
45
|
-
'max_threads': constants.DEFAULT_ALIGN_MAX_THREADS
|
|
45
|
+
'max_threads': constants.DEFAULT_ALIGN_MAX_THREADS,
|
|
46
|
+
'detector': constants.DEFAULT_DETECTOR,
|
|
47
|
+
'descriptor': constants.DEFAULT_DESCRIPTOR,
|
|
48
|
+
'match_method': constants.DEFAULT_MATCHING_METHOD
|
|
46
49
|
},
|
|
47
50
|
'focus_stack_params': {
|
|
48
51
|
'max_threads': constants.DEFAULT_PY_MAX_THREADS
|
|
@@ -146,6 +146,9 @@ class TaskBase:
|
|
|
146
146
|
def sub_message_r(self, msg='', level=logging.INFO):
|
|
147
147
|
self.sub_message(msg, level, self.end_r, self.begin_r, False)
|
|
148
148
|
|
|
149
|
+
def end_job(self):
|
|
150
|
+
pass
|
|
151
|
+
|
|
149
152
|
|
|
150
153
|
class Job(TaskBase):
|
|
151
154
|
def __init__(self, name, logger_name=None, log_file='', callbacks=None, **kwargs):
|
|
@@ -188,6 +191,8 @@ class Job(TaskBase):
|
|
|
188
191
|
self.id, self.name) is False:
|
|
189
192
|
raise RunStopException(self.name)
|
|
190
193
|
a.run()
|
|
194
|
+
for a in self.__actions:
|
|
195
|
+
a.end_job()
|
|
191
196
|
|
|
192
197
|
|
|
193
198
|
class SequentialTask(TaskBase):
|
|
@@ -261,11 +266,11 @@ class SequentialTask(TaskBase):
|
|
|
261
266
|
try:
|
|
262
267
|
result = future.result()
|
|
263
268
|
if result:
|
|
264
|
-
self.
|
|
269
|
+
self.print_message_r(color_str(
|
|
265
270
|
f"completed processing step: {self.idx_tot_str(idx)}",
|
|
266
271
|
constants.LOG_COLOR_LEVEL_1))
|
|
267
272
|
else:
|
|
268
|
-
self.
|
|
273
|
+
self.print_message_r(color_str(
|
|
269
274
|
f"failed processing step: {self.idx_tot_str(idx)}",
|
|
270
275
|
constants.LOG_COLOR_WARNING))
|
|
271
276
|
self.current_action_count += 1
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, E0611, R0913, R0917, R0915, R0912
|
|
2
|
-
# pylint: disable=E0606, W0718, R1702, W0102, W0221, R0914, C0302
|
|
2
|
+
# pylint: disable=E0606, W0718, R1702, W0102, W0221, R0914, C0302, R0903
|
|
3
3
|
import os
|
|
4
4
|
import traceback
|
|
5
5
|
from PySide6.QtCore import QTimer
|
|
@@ -214,7 +214,7 @@ class FocusStackBaseConfigurator(DefaultActionConfigurator):
|
|
|
214
214
|
expert=True,
|
|
215
215
|
placeholder='relative to working path')
|
|
216
216
|
self.add_field_to_layout(
|
|
217
|
-
layout, 'scratch_output_dir', FIELD_BOOL, 'Scratch output
|
|
217
|
+
layout, 'scratch_output_dir', FIELD_BOOL, 'Scratch output folder before run',
|
|
218
218
|
required=False, default=True)
|
|
219
219
|
|
|
220
220
|
def create_algorithm_tab(self, layout):
|
|
@@ -366,6 +366,14 @@ class FocusStackBunchConfigurator(FocusStackBaseConfigurator):
|
|
|
366
366
|
self.add_field_to_layout(
|
|
367
367
|
self.general_tab_layout, 'overlap', FIELD_INT, 'Overlapping frames', required=False,
|
|
368
368
|
default=constants.DEFAULT_OVERLAP, min_val=0, max_val=100)
|
|
369
|
+
self.add_field_to_layout(
|
|
370
|
+
self.general_tab_layout, 'scratch_output_dir', FIELD_BOOL,
|
|
371
|
+
'Scratch output folder before run',
|
|
372
|
+
required=False, default=True)
|
|
373
|
+
self.add_field_to_layout(
|
|
374
|
+
self.general_tab_layout, 'delete_output_at_end', FIELD_BOOL,
|
|
375
|
+
'Delete output at end of job',
|
|
376
|
+
required=False, default=False)
|
|
369
377
|
self.add_field_to_layout(
|
|
370
378
|
self.general_tab_layout, 'plot_stack', FIELD_BOOL, 'Plot stack', required=False,
|
|
371
379
|
default=constants.DEFAULT_PLOT_STACK_BUNCH)
|
|
@@ -391,7 +399,7 @@ class MultiLayerConfigurator(DefaultActionConfigurator):
|
|
|
391
399
|
expert=True,
|
|
392
400
|
placeholder='relative to working path')
|
|
393
401
|
self.add_field(
|
|
394
|
-
'scratch_output_dir', FIELD_BOOL, 'Scratch output
|
|
402
|
+
'scratch_output_dir', FIELD_BOOL, 'Scratch output folder before run',
|
|
395
403
|
required=False, default=True)
|
|
396
404
|
self.add_field(
|
|
397
405
|
'reverse_order', FIELD_BOOL, 'Reverse file order', required=False,
|
|
@@ -413,8 +421,11 @@ class CombinedActionsConfigurator(DefaultActionConfigurator):
|
|
|
413
421
|
expert=True,
|
|
414
422
|
placeholder='relative to working path')
|
|
415
423
|
self.add_field(
|
|
416
|
-
'scratch_output_dir', FIELD_BOOL, 'Scratch output
|
|
424
|
+
'scratch_output_dir', FIELD_BOOL, 'Scratch output folder before run',
|
|
417
425
|
required=False, default=True)
|
|
426
|
+
self.add_field(
|
|
427
|
+
'delete_output_at_end', FIELD_BOOL, 'Delete output at end of job',
|
|
428
|
+
required=False, default=False)
|
|
418
429
|
self.add_field(
|
|
419
430
|
'plot_path', FIELD_REL_PATH, 'Plots path', required=False,
|
|
420
431
|
expert=True,
|
|
@@ -490,22 +501,22 @@ class SubsampleActionConfigurator(DefaultActionConfigurator):
|
|
|
490
501
|
self.subsample_field.currentText() not in constants.FIELD_SUBSAMPLE_OPTIONS[:2])
|
|
491
502
|
|
|
492
503
|
|
|
493
|
-
class
|
|
494
|
-
BORDER_MODE_OPTIONS = ['Constant', 'Replicate', 'Replicate and blur']
|
|
495
|
-
TRANSFORM_OPTIONS = ['Rigid', 'Homography']
|
|
496
|
-
METHOD_OPTIONS = ['Random Sample Consensus (RANSAC)', 'Least Median (LMEDS)']
|
|
504
|
+
class AlignFramesConfigBase:
|
|
497
505
|
MATCHING_METHOD_OPTIONS = ['K-nearest neighbors', 'Hamming distance']
|
|
498
|
-
|
|
506
|
+
DETECTOR_DESCRIPTOR_TOOLTIPS = {
|
|
507
|
+
'detector':
|
|
508
|
+
"SIFT: Requires SIFT descriptor and K-NN matching\n"
|
|
509
|
+
"ORB/AKAZE: Work best with Hamming distance",
|
|
510
|
+
'descriptor':
|
|
511
|
+
"SIFT: Requires K-NN matching\n"
|
|
512
|
+
"ORB/AKAZE: Require Hamming distance with ORB/AKAZE detectors",
|
|
513
|
+
'match_method':
|
|
514
|
+
"Automatically selected based on detector/descriptor combination"
|
|
499
515
|
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
def __init__(self):
|
|
503
519
|
self.info_label = None
|
|
504
|
-
self.detector_field = None
|
|
505
|
-
self.descriptor_field = None
|
|
506
|
-
self.matching_method_field = None
|
|
507
|
-
self.tab_widget = None
|
|
508
|
-
self.current_tab_layout = None
|
|
509
520
|
|
|
510
521
|
def show_info(self, message, timeout=3000):
|
|
511
522
|
self.info_label.setText(message)
|
|
@@ -514,32 +525,50 @@ class AlignFramesConfigurator(SubsampleActionConfigurator):
|
|
|
514
525
|
timer.timeout.connect(lambda: self.info_label.setText(''))
|
|
515
526
|
timer.start(timeout)
|
|
516
527
|
|
|
517
|
-
def change_match_config(
|
|
518
|
-
|
|
519
|
-
|
|
528
|
+
def change_match_config(
|
|
529
|
+
self, detector_field, descriptor_field, matching_method_field, show_info):
|
|
530
|
+
detector = detector_field.currentText()
|
|
531
|
+
descriptor = descriptor_field.currentText()
|
|
520
532
|
match_method = dict(
|
|
521
533
|
zip(self.MATCHING_METHOD_OPTIONS,
|
|
522
|
-
constants.VALID_MATCHING_METHODS))[
|
|
534
|
+
constants.VALID_MATCHING_METHODS))[matching_method_field.currentText()]
|
|
523
535
|
try:
|
|
524
536
|
validate_align_config(detector, descriptor, match_method)
|
|
525
537
|
except Exception as e:
|
|
526
|
-
|
|
538
|
+
show_info(str(e))
|
|
527
539
|
if descriptor == constants.DETECTOR_SIFT and \
|
|
528
540
|
match_method == constants.MATCHING_NORM_HAMMING:
|
|
529
|
-
|
|
541
|
+
matching_method_field.setCurrentText(self.MATCHING_METHOD_OPTIONS[0])
|
|
530
542
|
if detector == constants.DETECTOR_ORB and descriptor == constants.DESCRIPTOR_AKAZE and \
|
|
531
543
|
match_method == constants.MATCHING_NORM_HAMMING:
|
|
532
|
-
|
|
544
|
+
matching_method_field.setCurrentText(constants.MATCHING_NORM_HAMMING)
|
|
533
545
|
if detector == constants.DETECTOR_BRISK and descriptor == constants.DESCRIPTOR_AKAZE:
|
|
534
|
-
|
|
546
|
+
descriptor_field.setCurrentText('BRISK')
|
|
535
547
|
if detector == constants.DETECTOR_SURF and descriptor == constants.DESCRIPTOR_AKAZE:
|
|
536
|
-
|
|
548
|
+
descriptor_field.setCurrentText('SIFT')
|
|
537
549
|
if detector == constants.DETECTOR_SIFT and descriptor != constants.DESCRIPTOR_SIFT:
|
|
538
|
-
|
|
550
|
+
descriptor_field.setCurrentText('SIFT')
|
|
539
551
|
if detector in constants.NOKNN_METHODS['detectors'] and \
|
|
540
552
|
descriptor in constants.NOKNN_METHODS['descriptors']:
|
|
541
553
|
if match_method == constants.MATCHING_KNN:
|
|
542
|
-
|
|
554
|
+
matching_method_field.setCurrentText(self.MATCHING_METHOD_OPTIONS[1])
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
class AlignFramesConfigurator(SubsampleActionConfigurator, AlignFramesConfigBase):
|
|
558
|
+
BORDER_MODE_OPTIONS = ['Constant', 'Replicate', 'Replicate and blur']
|
|
559
|
+
TRANSFORM_OPTIONS = ['Rigid', 'Homography']
|
|
560
|
+
METHOD_OPTIONS = ['Random Sample Consensus (RANSAC)', 'Least Median (LMEDS)']
|
|
561
|
+
MODE_OPTIONS = ['Auto', 'Sequential', 'Parallel']
|
|
562
|
+
|
|
563
|
+
def __init__(self, expert, current_wd):
|
|
564
|
+
SubsampleActionConfigurator.__init__(self, expert, current_wd)
|
|
565
|
+
AlignFramesConfigBase.__init__(self)
|
|
566
|
+
self.matching_method_field = None
|
|
567
|
+
self.detector_field = None
|
|
568
|
+
self.descriptor_field = None
|
|
569
|
+
self.matching_method_field = None
|
|
570
|
+
self.tab_widget = None
|
|
571
|
+
self.current_tab_layout = None
|
|
543
572
|
|
|
544
573
|
def create_form(self, layout, action):
|
|
545
574
|
super().create_form(layout, action)
|
|
@@ -557,23 +586,23 @@ class AlignFramesConfigurator(SubsampleActionConfigurator):
|
|
|
557
586
|
self.create_miscellanea_tab(misc_layout)
|
|
558
587
|
|
|
559
588
|
def create_feature_tab(self, layout):
|
|
589
|
+
|
|
590
|
+
def change_match_config():
|
|
591
|
+
self.change_match_config(
|
|
592
|
+
self.detector_field, self.descriptor_field,
|
|
593
|
+
self. matching_method_field, self.show_info)
|
|
594
|
+
|
|
560
595
|
self.add_bold_label_to_layout(layout, "Feature identification:")
|
|
561
596
|
self.detector_field = self.add_field_to_layout(
|
|
562
597
|
layout, 'detector', FIELD_COMBO, 'Detector', required=False,
|
|
563
|
-
options=constants.VALID_DETECTORS, default=
|
|
598
|
+
options=constants.VALID_DETECTORS, default=AppConfig.get('detector'))
|
|
564
599
|
self.descriptor_field = self.add_field_to_layout(
|
|
565
600
|
layout, 'descriptor', FIELD_COMBO, 'Descriptor', required=False,
|
|
566
|
-
options=constants.VALID_DESCRIPTORS, default=
|
|
567
|
-
self.detector_field.setToolTip(
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
)
|
|
571
|
-
self.descriptor_field.setToolTip(
|
|
572
|
-
"SIFT: Requires K-NN matching\n"
|
|
573
|
-
"ORB/AKAZE: Require Hamming distance with ORB/AKAZE detectors"
|
|
574
|
-
)
|
|
575
|
-
self.detector_field.currentIndexChanged.connect(self.change_match_config)
|
|
576
|
-
self.descriptor_field.currentIndexChanged.connect(self.change_match_config)
|
|
601
|
+
options=constants.VALID_DESCRIPTORS, default=AppConfig.get('descriptor'))
|
|
602
|
+
self.detector_field.setToolTip(self.DETECTOR_DESCRIPTOR_TOOLTIPS['detector'])
|
|
603
|
+
self.descriptor_field.setToolTip(self.DETECTOR_DESCRIPTOR_TOOLTIPS['descriptor'])
|
|
604
|
+
self.detector_field.currentIndexChanged.connect(change_match_config)
|
|
605
|
+
self.descriptor_field.currentIndexChanged.connect(change_match_config)
|
|
577
606
|
self.info_label = QLabel()
|
|
578
607
|
self.info_label.setStyleSheet("color: orange; font-style: italic;")
|
|
579
608
|
layout.addRow(self.info_label)
|
|
@@ -581,11 +610,9 @@ class AlignFramesConfigurator(SubsampleActionConfigurator):
|
|
|
581
610
|
self.matching_method_field = self.add_field_to_layout(
|
|
582
611
|
layout, 'match_method', FIELD_COMBO, 'Match method', required=False,
|
|
583
612
|
options=self.MATCHING_METHOD_OPTIONS, values=constants.VALID_MATCHING_METHODS,
|
|
584
|
-
default=
|
|
585
|
-
self.matching_method_field.setToolTip(
|
|
586
|
-
|
|
587
|
-
)
|
|
588
|
-
self.matching_method_field.currentIndexChanged.connect(self.change_match_config)
|
|
613
|
+
default=AppConfig.get('match_method'))
|
|
614
|
+
self.matching_method_field.setToolTip(self.DETECTOR_DESCRIPTOR_TOOLTIPS['match_method'])
|
|
615
|
+
self.matching_method_field.currentIndexChanged.connect(change_match_config)
|
|
589
616
|
self.add_field_to_layout(
|
|
590
617
|
layout, 'flann_idx_kdtree', FIELD_INT, 'Flann idx kdtree', required=False,
|
|
591
618
|
expert=True,
|
|
@@ -215,8 +215,7 @@ class RunWindow(QTextEditLogger):
|
|
|
215
215
|
raise RuntimeError(f"Can't visualize file type {os.path.splitext(path)[1]}.")
|
|
216
216
|
self.image_views.append(image_view)
|
|
217
217
|
self.image_layout.addWidget(image_view)
|
|
218
|
-
|
|
219
|
-
needed_width = max_width + 20
|
|
218
|
+
needed_width = gui_constants.GUI_IMG_WIDTH + 20
|
|
220
219
|
self.right_area.setFixedWidth(needed_width)
|
|
221
220
|
self.image_area_widget.setFixedWidth(needed_width)
|
|
222
221
|
self.right_area.updateGeometry()
|
|
Binary file
|
|
Binary file
|