shinestacker 1.6.1__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.6.1 → shinestacker-1.8.0}/.github/workflows/release.yml +10 -5
- {shinestacker-1.6.1 → shinestacker-1.8.0}/CHANGELOG.md +38 -3
- {shinestacker-1.6.1/src/shinestacker.egg-info → shinestacker-1.8.0}/PKG-INFO +4 -4
- {shinestacker-1.6.1 → shinestacker-1.8.0}/README.md +3 -3
- shinestacker-1.8.0/scripts/build_release.py +210 -0
- shinestacker-1.8.0/scripts/create_macos_icon.py +58 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0/scripts}/shinestacker-inno-setup.iss +10 -6
- shinestacker-1.8.0/src/shinestacker/_version.py +1 -0
- shinestacker-1.8.0/src/shinestacker/algorithms/corrections.py +26 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/stack.py +9 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/stack_framework.py +35 -16
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/utils.py +5 -1
- shinestacker-1.8.0/src/shinestacker/app/args_parser_opts.py +66 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/app/gui_utils.py +19 -2
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/app/main.py +16 -27
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/app/project.py +12 -23
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/app/retouch.py +12 -25
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/app/settings_dialog.py +46 -3
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/config/settings.py +4 -1
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/core/core_utils.py +2 -2
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/core/framework.py +7 -2
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/core/logging.py +2 -2
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/action_config_dialog.py +72 -45
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/gui_run.py +1 -2
- shinestacker-1.8.0/src/shinestacker/gui/ico/shinestacker.icns +0 -0
- 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.8.0/src/shinestacker/gui/img/light/shinestacker_bkg.png +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/main_window.py +20 -7
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/menu_manager.py +18 -7
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/new_project.py +0 -2
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/tab_widget.py +16 -10
- shinestacker-1.8.0/src/shinestacker/retouch/adjustments.py +98 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/base_filter.py +62 -7
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/denoise_filter.py +1 -1
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/image_editor_ui.py +26 -4
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/unsharp_mask_filter.py +13 -28
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/vignetting_filter.py +1 -1
- {shinestacker-1.6.1 → shinestacker-1.8.0/src/shinestacker.egg-info}/PKG-INFO +4 -4
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker.egg-info/SOURCES.txt +13 -5
- shinestacker-1.6.1/scripts/build_release.py +0 -112
- shinestacker-1.6.1/src/shinestacker/_version.py +0 -1
- shinestacker-1.6.1/src/shinestacker/app/args_parser_opts.py +0 -27
- shinestacker-1.6.1/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
- shinestacker-1.6.1/src/shinestacker/gui/ico/shinestacker.icns +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/.coveragerc +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/.flake8 +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/.github/workflows/ci-multiplatform.yml +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/.github/workflows/pylint.yml +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/.github/workflows/pypi-publish.yml +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/.gitignore +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/.pylintrc +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/.readthedocs.yaml +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/LICENSE +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/MANIFEST.in +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/THIRD_PARTY_LICENSES.txt +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/docs/alignment.md +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/docs/api.md +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/docs/balancing.md +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/docs/conf.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/docs/focus_stacking.md +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/docs/gui.md +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/docs/index.md +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/docs/job.md +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/docs/main.md +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/docs/multilayer.md +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/docs/noise.md +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/docs/requirements.txt +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/docs/vignetting.md +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/img/coffee.gif +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/img/coffee_stack.jpg +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/img/extreme-vignetting.jpg +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/img/flies.gif +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/img/flies_stack.jpg +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/img/flow-diagram.png +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/img/gui-finder.png +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/img/gui-project-new.png +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/img/gui-project-run.png +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/img/gui-retouch.png +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/index.html +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/pyproject.toml +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/requirements.txt +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/scripts/git-rev-list.sh +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/scripts/hooks/hook-IPython.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/scripts/hooks/hook-PySide6.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/scripts/hooks/hook-opencv.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/scripts/hooks/hook-tests.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/scripts/scan_imports.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/scripts/validate-tomli.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/setup.cfg +0 -0
- {shinestacker-1.6.1/scripts → shinestacker-1.8.0}/shinestacker-inno-setup.iss +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/__init__.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/__init__.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/align.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/align_auto.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/align_parallel.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/balance.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/denoise.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/depth_map.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/exif.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/multilayer.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/noise_detection.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/pyramid.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/pyramid_auto.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/pyramid_tiles.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/sharpen.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/vignetting.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/algorithms/white_balance.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/app/__init__.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/app/about_dialog.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/app/help_menu.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/app/open_frames.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/config/__init__.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/config/app_config.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/config/config.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/config/constants.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/config/gui_constants.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/core/__init__.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/core/colors.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/core/exceptions.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/__init__.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/action_config.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/base_form_dialog.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/colors.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/config_dialog.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/flow_layout.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/folder_file_selection.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/gui_images.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/gui_logging.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/ico/shinestacker.png +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
- {shinestacker-1.6.1/src/shinestacker/gui/img → shinestacker-1.8.0/src/shinestacker/gui/img/light}/close-round-line-icon.png +0 -0
- {shinestacker-1.6.1/src/shinestacker/gui/img → shinestacker-1.8.0/src/shinestacker/gui/img/light}/forward-button-icon.png +0 -0
- {shinestacker-1.6.1/src/shinestacker/gui/img → shinestacker-1.8.0/src/shinestacker/gui/img/light}/play-button-round-icon.png +0 -0
- {shinestacker-1.6.1/src/shinestacker/gui/img → shinestacker-1.8.0/src/shinestacker/gui/img/light}/plus-round-line-icon.png +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/project_controller.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/project_converter.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/project_editor.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/project_model.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/recent_file_manager.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/select_path_widget.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/sys_mon.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/gui/time_progress_bar.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/__init__.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/brush.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/brush_gradient.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/brush_preview.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/brush_tool.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/display_manager.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/exif_data.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/file_loader.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/filter_manager.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/icon_container.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/image_view_status.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/image_viewer.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/io_gui_handler.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/io_threads.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/layer_collection.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/overlaid_view.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/paint_area_manager.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/shortcuts_help.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/sidebyside_view.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/transformation_manager.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/undo_manager.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/view_strategy.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker/retouch/white_balance_filter.py +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker.egg-info/dependency_links.txt +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker.egg-info/entry_points.txt +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker.egg-info/requires.txt +0 -0
- {shinestacker-1.6.1 → shinestacker-1.8.0}/src/shinestacker.egg-info/top_level.txt +0 -0
|
@@ -40,18 +40,20 @@ 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
|
|
49
48
|
with:
|
|
50
49
|
name: shinestacker-${{ matrix.os }}
|
|
51
50
|
path: |
|
|
52
|
-
${{ matrix.os == 'windows-latest' && 'dist/shinestacker-release.zip' || '
|
|
51
|
+
${{ matrix.os == 'windows-latest' && 'dist/shinestacker-release.zip' || '' }}
|
|
53
52
|
${{ matrix.os == 'windows-latest' && 'dist/*.exe' || '' }}
|
|
53
|
+
${{ matrix.os == 'ubuntu-latest' && 'dist/shinestacker-release.tar.gz' || '' }}
|
|
54
|
+
${{ matrix.os == 'macos-latest' && 'dist/shinestacker-release.dmg' || '' }}
|
|
54
55
|
if-no-files-found: ignore
|
|
56
|
+
|
|
55
57
|
create-release:
|
|
56
58
|
needs: publish-release
|
|
57
59
|
runs-on: ubuntu-latest
|
|
@@ -64,8 +66,11 @@ jobs:
|
|
|
64
66
|
- name: Prepare release assets
|
|
65
67
|
run: |
|
|
66
68
|
mkdir -p release_assets
|
|
69
|
+
# Linux
|
|
67
70
|
cp artifacts/shinestacker-ubuntu-latest/shinestacker-release.tar.gz release_assets/shinestacker-ubuntu.tar.gz
|
|
68
|
-
|
|
71
|
+
# macOS
|
|
72
|
+
cp artifacts/shinestacker-macos-latest/shinestacker-release.dmg release_assets/shinestacker-macos.dmg
|
|
73
|
+
# Windows
|
|
69
74
|
cp artifacts/shinestacker-windows-latest/shinestacker-release.zip release_assets/shinestacker-windows.zip
|
|
70
75
|
if ls artifacts/shinestacker-windows-latest/shinestacker-setup.exe 1> /dev/null 2>&1; then
|
|
71
76
|
cp artifacts/shinestacker-windows-latest/shinestacker-setup.exe release_assets/
|
|
@@ -78,6 +83,6 @@ jobs:
|
|
|
78
83
|
draft: true
|
|
79
84
|
files: |
|
|
80
85
|
release_assets/shinestacker-ubuntu.tar.gz
|
|
81
|
-
release_assets/shinestacker-macos.
|
|
86
|
+
release_assets/shinestacker-macos.dmg
|
|
82
87
|
release_assets/shinestacker-windows.zip
|
|
83
88
|
release_assets/shinestacker-setup.exe
|
|
@@ -2,15 +2,50 @@
|
|
|
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
|
+
|
|
22
|
+
## [v1.7.0] - 2025-10-04
|
|
23
|
+
** New image adjustment actions and macOS dmg image installer **
|
|
24
|
+
|
|
25
|
+
### Added
|
|
26
|
+
- luminosity and contrast adjustment action
|
|
27
|
+
- saturation and vibrance adjustment action
|
|
28
|
+
- macOS dmg installer
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
- improved windows installer
|
|
32
|
+
- white balance moved from filters to edit > adjust menu
|
|
33
|
+
- minor GUI cosmetic improvements
|
|
34
|
+
- code refactoring
|
|
35
|
+
|
|
36
|
+
-----
|
|
37
|
+
|
|
5
38
|
## [v1.6.1] - 2025-10-01
|
|
6
|
-
**
|
|
39
|
+
** Performance improvements **
|
|
40
|
+
|
|
41
|
+
### Added
|
|
42
|
+
- windows installer
|
|
7
43
|
|
|
8
44
|
### Changed
|
|
9
45
|
- improved display update performance by refreshing only the painted area
|
|
10
46
|
- multiple frame import now runs in a separate thread, avoiding UI freezes
|
|
11
47
|
- reduced dependencies and code refactored for more robust architecture
|
|
12
|
-
-
|
|
13
|
-
- dropped examples and test images reduces distribution file size
|
|
48
|
+
- dropped examples and test images to reduce distribution file size
|
|
14
49
|
|
|
15
50
|
-----
|
|
16
51
|
|
|
@@ -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:
|
|
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:
|
|
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
|
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import tarfile
|
|
4
|
+
import subprocess
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
import platform
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def setup_environment():
|
|
10
|
+
project_root = Path(__file__).resolve().parent.parent
|
|
11
|
+
dist_dir = project_root / "dist"
|
|
12
|
+
project_name = "shinestacker"
|
|
13
|
+
app_name = "shinestacker"
|
|
14
|
+
hooks_dir = "scripts/hooks"
|
|
15
|
+
hook_files = list(Path(hooks_dir).glob("hook-*.py"))
|
|
16
|
+
for hook in hook_files:
|
|
17
|
+
print(f" - {hook.name}")
|
|
18
|
+
return project_root, dist_dir, project_name, app_name
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def build_pyinstaller_command(sys_name, dist_dir, project_name, app_name, hooks_dir):
|
|
22
|
+
if sys_name == 'darwin':
|
|
23
|
+
return [
|
|
24
|
+
"pyinstaller", "--windowed",
|
|
25
|
+
f"--name={app_name}",
|
|
26
|
+
f"--distpath={dist_dir}",
|
|
27
|
+
"--paths=src",
|
|
28
|
+
"--icon=src/shinestacker/gui/ico/shinestacker.icns",
|
|
29
|
+
"--argv-emulation",
|
|
30
|
+
f"--additional-hooks-dir={hooks_dir}",
|
|
31
|
+
f"--collect-all={project_name}",
|
|
32
|
+
"--collect-data=imagecodecs",
|
|
33
|
+
"--collect-submodules=imagecodecs",
|
|
34
|
+
"--copy-metadata=imagecodecs",
|
|
35
|
+
"src/shinestacker/app/main.py"
|
|
36
|
+
]
|
|
37
|
+
elif sys_name == 'windows':
|
|
38
|
+
return [
|
|
39
|
+
"pyinstaller", "--onedir", "--windowed",
|
|
40
|
+
f"--name={app_name}",
|
|
41
|
+
f"--distpath={dist_dir}",
|
|
42
|
+
"--paths=src",
|
|
43
|
+
"--icon=src/shinestacker/gui/ico/shinestacker.ico",
|
|
44
|
+
f"--collect-all={project_name}",
|
|
45
|
+
"--collect-data=imagecodecs", "--collect-submodules=imagecodecs",
|
|
46
|
+
"--copy-metadata=imagecodecs", f"--additional-hooks-dir={hooks_dir}",
|
|
47
|
+
"src/shinestacker/app/main.py"
|
|
48
|
+
]
|
|
49
|
+
else:
|
|
50
|
+
return [
|
|
51
|
+
"pyinstaller", "--onedir",
|
|
52
|
+
f"--name={app_name}",
|
|
53
|
+
f"--distpath={dist_dir}",
|
|
54
|
+
"--paths=src",
|
|
55
|
+
f"--collect-all={project_name}",
|
|
56
|
+
"--collect-data=imagecodecs", "--collect-submodules=imagecodecs",
|
|
57
|
+
"--copy-metadata=imagecodecs", f"--additional-hooks-dir={hooks_dir}",
|
|
58
|
+
"src/shinestacker/app/main.py"
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def package_windows(dist_dir, app_name):
|
|
63
|
+
shutil.make_archive(
|
|
64
|
+
base_name=str(dist_dir / "shinestacker-release"),
|
|
65
|
+
format="zip",
|
|
66
|
+
root_dir=dist_dir,
|
|
67
|
+
base_dir=app_name
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def package_macos(dist_dir, app_name, project_root):
|
|
72
|
+
app_bundle = dist_dir / f"{app_name}.app"
|
|
73
|
+
if not app_bundle.exists():
|
|
74
|
+
print(f"ERROR: .app bundle not found at {app_bundle}")
|
|
75
|
+
return
|
|
76
|
+
version = get_version(project_root)
|
|
77
|
+
build_number = version.replace('.', '') + '0' # Convert x.y.z -> xyz0
|
|
78
|
+
info_plist_template = project_root / "scripts" / "Info.plist"
|
|
79
|
+
info_plist_target = app_bundle / "Contents" / "Info.plist"
|
|
80
|
+
if info_plist_template.exists():
|
|
81
|
+
print("Processing Info.plist...")
|
|
82
|
+
with open(info_plist_template, 'r') as f:
|
|
83
|
+
plist_content = f.read()
|
|
84
|
+
plist_content = plist_content.replace('{{VERSION}}', version)
|
|
85
|
+
plist_content = plist_content.replace('{{BUILD_NUMBER}}', build_number)
|
|
86
|
+
info_plist_target.parent.mkdir(parents=True, exist_ok=True)
|
|
87
|
+
with open(info_plist_target, 'w') as f:
|
|
88
|
+
f.write(plist_content)
|
|
89
|
+
print(f"Info.plist created at: {info_plist_target}")
|
|
90
|
+
else:
|
|
91
|
+
print(f"WARNING: Info.plist template not found at {info_plist_template}")
|
|
92
|
+
icon_source = project_root / "src" / "shinestacker" / "gui" / "ico" / "shinestacker.icns"
|
|
93
|
+
dmg_temp_dir = dist_dir / "dmg_temp"
|
|
94
|
+
if dmg_temp_dir.exists():
|
|
95
|
+
shutil.rmtree(dmg_temp_dir)
|
|
96
|
+
shutil.copytree(app_bundle, dmg_temp_dir / app_bundle.name, symlinks=True)
|
|
97
|
+
os.symlink("/Applications", dmg_temp_dir / "Applications")
|
|
98
|
+
dmg_path = dist_dir / f"{app_name}-release.dmg"
|
|
99
|
+
dmg_cmd = [
|
|
100
|
+
"hdiutil", "create",
|
|
101
|
+
"-volname", app_name,
|
|
102
|
+
"-srcfolder", str(dmg_temp_dir),
|
|
103
|
+
"-ov", str(dmg_path),
|
|
104
|
+
"-format", "UDBZ",
|
|
105
|
+
"-fs", "HFS+"
|
|
106
|
+
]
|
|
107
|
+
subprocess.run(dmg_cmd, check=True)
|
|
108
|
+
print(f"Created DMG: {dmg_path.name}")
|
|
109
|
+
if icon_source.exists():
|
|
110
|
+
print("Setting custom icon...")
|
|
111
|
+
try:
|
|
112
|
+
subprocess.run(["sips", "-i", str(icon_source)], check=True)
|
|
113
|
+
subprocess.run(["DeRez", "-only", "icns", str(icon_source)],
|
|
114
|
+
stdout=open("/tmp/icon.r", "w"), check=True)
|
|
115
|
+
subprocess.run(["Rez", "-append", "/tmp/icon.r", "-o", str(dmg_path)], check=True)
|
|
116
|
+
subprocess.run(["SetFile", "-a", "C", str(dmg_path)], check=True)
|
|
117
|
+
print("Custom icon set successfully!")
|
|
118
|
+
except subprocess.CalledProcessError as e:
|
|
119
|
+
print(f"Could not set custom icon: {e}")
|
|
120
|
+
shutil.rmtree(dmg_temp_dir)
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def package_linux(dist_dir, app_name):
|
|
124
|
+
archive_path = dist_dir / "shinestacker-release.tar.gz"
|
|
125
|
+
linux_app_dir = dist_dir / app_name
|
|
126
|
+
if linux_app_dir.exists():
|
|
127
|
+
with tarfile.open(archive_path, "w:gz") as tar:
|
|
128
|
+
tar.add(linux_app_dir, arcname=app_name, recursive=True)
|
|
129
|
+
print(f"Packaged Linux application: {app_name}")
|
|
130
|
+
else:
|
|
131
|
+
print(f"ERROR: Linux app directory not found at {linux_app_dir}")
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def get_version(project_root):
|
|
135
|
+
version_file = project_root / "src" / "shinestacker" / "_version.py"
|
|
136
|
+
version = "0.0.0"
|
|
137
|
+
if version_file.exists():
|
|
138
|
+
with open(version_file, 'r') as f:
|
|
139
|
+
content = f.read()
|
|
140
|
+
import re
|
|
141
|
+
match = re.search(r"__version__\s*=\s*['\"]([^'\"]+)['\"]", content)
|
|
142
|
+
if match:
|
|
143
|
+
version = match.group(1)
|
|
144
|
+
return version
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def create_windows_installer(project_root, dist_dir):
|
|
148
|
+
inno_paths = [
|
|
149
|
+
r"C:\Program Files (x86)\Inno Setup 6\ISCC.exe",
|
|
150
|
+
r"C:\Program Files (x86)\Inno Setup 5\ISCC.exe",
|
|
151
|
+
r"C:\Program Files\Inno Setup 6\ISCC.exe",
|
|
152
|
+
r"C:\Program Files\Inno Setup 5\ISCC.exe"
|
|
153
|
+
]
|
|
154
|
+
iscc_exe = None
|
|
155
|
+
for path in inno_paths:
|
|
156
|
+
if os.path.exists(path):
|
|
157
|
+
iscc_exe = path
|
|
158
|
+
break
|
|
159
|
+
if not iscc_exe:
|
|
160
|
+
try:
|
|
161
|
+
subprocess.run(["choco", "--version"], check=True, capture_output=True)
|
|
162
|
+
subprocess.run(["choco", "install", "innosetup", "-y",
|
|
163
|
+
"--no-progress", "--accept-license"], check=True)
|
|
164
|
+
for path in inno_paths:
|
|
165
|
+
if os.path.exists(path):
|
|
166
|
+
iscc_exe = path
|
|
167
|
+
break
|
|
168
|
+
except (subprocess.CalledProcessError, FileNotFoundError):
|
|
169
|
+
pass
|
|
170
|
+
if iscc_exe:
|
|
171
|
+
iss_script_source = project_root / "scripts" / "shinestacker-inno-setup.iss"
|
|
172
|
+
iss_script_temp = project_root / "shinestacker-inno-setup.iss"
|
|
173
|
+
if iss_script_source.exists():
|
|
174
|
+
version = get_version(project_root)
|
|
175
|
+
with open(iss_script_source, 'r') as f:
|
|
176
|
+
iss_content = f.read()
|
|
177
|
+
old_version_line = f'#define MyAppVersion "{"x.x.x"}"'
|
|
178
|
+
new_version_line = f'#define MyAppVersion "{version}"'
|
|
179
|
+
iss_content = iss_content.replace(old_version_line, new_version_line)
|
|
180
|
+
with open(iss_script_temp, 'w') as f:
|
|
181
|
+
f.write(iss_content)
|
|
182
|
+
subprocess.run([iscc_exe, str(iss_script_temp)], check=True)
|
|
183
|
+
iss_script_temp.unlink()
|
|
184
|
+
if dist_dir.exists():
|
|
185
|
+
installer_files = list(dist_dir.glob("*.exe"))
|
|
186
|
+
if installer_files:
|
|
187
|
+
print(f"Installer created: {installer_files[0].name}")
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
def main():
|
|
191
|
+
project_root, dist_dir, project_name, app_name = setup_environment()
|
|
192
|
+
sys_name = platform.system().lower()
|
|
193
|
+
hooks_dir = "scripts/hooks"
|
|
194
|
+
pyinstaller_cmd = build_pyinstaller_command(
|
|
195
|
+
sys_name, dist_dir, project_name, app_name, hooks_dir)
|
|
196
|
+
print(" ".join(pyinstaller_cmd))
|
|
197
|
+
subprocess.run(pyinstaller_cmd, check=True)
|
|
198
|
+
if sys_name == 'windows':
|
|
199
|
+
package_windows(dist_dir, app_name)
|
|
200
|
+
elif sys_name == 'darwin':
|
|
201
|
+
package_macos(dist_dir, app_name, project_root)
|
|
202
|
+
else:
|
|
203
|
+
package_linux(dist_dir, app_name)
|
|
204
|
+
if sys_name == 'windows':
|
|
205
|
+
print("=== CREATING WINDOWS INSTALLER ===")
|
|
206
|
+
create_windows_installer(project_root, dist_dir)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
if __name__ == "__main__":
|
|
210
|
+
main()
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import shutil
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
def create_macos_icon():
|
|
7
|
+
script_dir = Path(__file__).parent
|
|
8
|
+
project_root = script_dir.parent
|
|
9
|
+
png_source = project_root / "src" / "shinestacker" / "gui" / "ico" / "shinestacker.png"
|
|
10
|
+
icns_output = project_root / "src" / "shinestacker" / "gui" / "ico" / "shinestacker.icns"
|
|
11
|
+
|
|
12
|
+
if not png_source.exists():
|
|
13
|
+
print(f"ERROR: Source PNG not found at {png_source}")
|
|
14
|
+
return False
|
|
15
|
+
|
|
16
|
+
print(f"Creating macOS icon from {png_source}")
|
|
17
|
+
|
|
18
|
+
iconset_dir = project_root / "src" / "shinestacker" / "gui" / "ico" / "shinestacker.iconset"
|
|
19
|
+
if iconset_dir.exists():
|
|
20
|
+
shutil.rmtree(iconset_dir)
|
|
21
|
+
iconset_dir.mkdir()
|
|
22
|
+
|
|
23
|
+
sizes = [
|
|
24
|
+
("16x16", 16),
|
|
25
|
+
("16x16@2x", 32),
|
|
26
|
+
("32x32", 32),
|
|
27
|
+
("32x32@2x", 64),
|
|
28
|
+
("128x128", 128),
|
|
29
|
+
("128x128@2x", 256),
|
|
30
|
+
("256x256", 256),
|
|
31
|
+
("256x256@2x", 512),
|
|
32
|
+
("512x512", 512),
|
|
33
|
+
("512x512@2x", 1024)
|
|
34
|
+
]
|
|
35
|
+
|
|
36
|
+
for name, size in sizes:
|
|
37
|
+
output_file = iconset_dir / f"icon_{name}.png"
|
|
38
|
+
print(f" Creating {name}...")
|
|
39
|
+
subprocess.run([
|
|
40
|
+
"sips", "-z", str(size), str(size),
|
|
41
|
+
str(png_source),
|
|
42
|
+
"--out", str(output_file)
|
|
43
|
+
], check=True)
|
|
44
|
+
|
|
45
|
+
print("Converting to .icns format...")
|
|
46
|
+
subprocess.run([
|
|
47
|
+
"iconutil", "-c", "icns",
|
|
48
|
+
str(iconset_dir),
|
|
49
|
+
"-o", str(icns_output)
|
|
50
|
+
], check=True)
|
|
51
|
+
|
|
52
|
+
shutil.rmtree(iconset_dir)
|
|
53
|
+
print(f"SUCCESS: Created {icns_output}")
|
|
54
|
+
return True
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
if __name__ == "__main__":
|
|
58
|
+
create_macos_icon()
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#define MyAppName "ShineStacker"
|
|
2
|
-
#define MyAppVersion "
|
|
2
|
+
#define MyAppVersion "x.x.x"
|
|
3
3
|
#define MyAppPublisher "Luca Lista"
|
|
4
4
|
#define MyAppURL "https://shinestacker.wordpress.com/"
|
|
5
5
|
#define MyAppExeName "shinestacker.exe"
|
|
@@ -34,30 +34,34 @@ LicenseFile=.\LICENSE
|
|
|
34
34
|
; Uncomment the following line to run in non administrative install mode (install for current user only).
|
|
35
35
|
;PrivilegesRequired=lowest
|
|
36
36
|
OutputBaseFilename=shinestacker-setup
|
|
37
|
-
OutputDir
|
|
37
|
+
OutputDir=dist
|
|
38
38
|
VersionInfoVersion={#MyAppVersion}
|
|
39
39
|
VersionInfoCompany={#MyAppPublisher}
|
|
40
|
-
SetupIconFile
|
|
40
|
+
SetupIconFile=src\shinestacker\gui\ico\shinestacker.ico
|
|
41
41
|
SolidCompression=yes
|
|
42
42
|
WizardStyle=modern
|
|
43
43
|
|
|
44
44
|
[Languages]
|
|
45
45
|
Name: "english"; MessagesFile: "compiler:Default.isl"
|
|
46
46
|
|
|
47
|
+
[Messages]
|
|
48
|
+
WelcomeLabel1=Welcome to the {#MyAppName} Setup Wizard
|
|
49
|
+
WelcomeLabel2=This wizard will install {#MyAppName} {#MyAppVersion}, an open-source focus stacking application developed by Luca Lista.%n%nThe source code is available on GitHub.
|
|
50
|
+
FinishedLabel=Setup has finished installing {#MyAppName} on your computer. The application may be launched by selecting the installed shortcuts.%n%nClick Finish to exit Setup.
|
|
51
|
+
|
|
47
52
|
[Tasks]
|
|
48
53
|
Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked
|
|
49
54
|
|
|
50
55
|
[Files]
|
|
51
56
|
; Copy the entire shinestacker folder structure that contains both the exe and _internal
|
|
52
|
-
Source: "
|
|
53
|
-
Source: ".\examples\*"; DestDir: "{app}\examples"; Flags: ignoreversion recursesubdirs
|
|
57
|
+
Source: "dist\shinestacker\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs
|
|
54
58
|
; NOTE: Don't use "Flags: ignoreversion" on any shared system files
|
|
55
59
|
|
|
56
60
|
[Registry]
|
|
57
61
|
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocExt}\OpenWithProgids"; ValueType: string; ValueName: "{#MyAppAssocKey}"; ValueData: ""; Flags: uninsdeletevalue
|
|
58
62
|
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}"; ValueType: string; ValueName: ""; ValueData: "{#MyAppAssocName}"; Flags: uninsdeletekey
|
|
59
63
|
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\{#MyAppExeName},0"
|
|
60
|
-
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1"""
|
|
64
|
+
Root: HKA; Subkey: "Software\Classes\{#MyAppAssocKey}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" -f ""%1"""
|
|
61
65
|
|
|
62
66
|
[Icons]
|
|
63
67
|
Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = '1.8.0'
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E1101
|
|
2
|
+
import numpy as np
|
|
3
|
+
import cv2
|
|
4
|
+
from ..config.constants import constants
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def gamma_correction(img, gamma):
|
|
8
|
+
max_px_val = constants.MAX_UINT8 if img.dtype == np.uint8 else constants.MAX_UINT16
|
|
9
|
+
ar = np.arange(0, max_px_val + 1, dtype=np.float64)
|
|
10
|
+
lut = (((ar / max_px_val) ** (1.0 / gamma)) * max_px_val).astype(img.dtype)
|
|
11
|
+
return cv2.LUT(img, lut) if img.dtype == np.uint8 else np.take(lut, img)
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def contrast_correction(img, k):
|
|
15
|
+
max_px_val = constants.MAX_UINT8 if img.dtype == np.uint8 else constants.MAX_UINT16
|
|
16
|
+
ar = np.arange(0, max_px_val + 1, dtype=np.float64)
|
|
17
|
+
x = 2.0 * (ar / max_px_val) - 1.0
|
|
18
|
+
# f(x) = x * exp(k) / (1 + (exp(k) - 1)|x|), -1 < x < +1
|
|
19
|
+
# note that: f(f(x, k), -k) = x
|
|
20
|
+
exp_k = np.exp(k)
|
|
21
|
+
numerator = x * exp_k
|
|
22
|
+
denominator = 1 + (exp_k - 1) * np.abs(x)
|
|
23
|
+
corrected = numerator / denominator
|
|
24
|
+
corrected = (corrected + 1.0) * 0.5 * max_px_val
|
|
25
|
+
lut = np.clip(corrected, 0, max_px_val).astype(img.dtype)
|
|
26
|
+
return cv2.LUT(img, lut) if img.dtype == np.uint8 else np.take(lut, img)
|
|
@@ -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)
|