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.

Files changed (171) hide show
  1. {shinestacker-1.7.0 → shinestacker-1.8.0}/.github/workflows/release.yml +2 -6
  2. {shinestacker-1.7.0 → shinestacker-1.8.0}/CHANGELOG.md +18 -1
  3. {shinestacker-1.7.0/src/shinestacker.egg-info → shinestacker-1.8.0}/PKG-INFO +4 -4
  4. {shinestacker-1.7.0 → shinestacker-1.8.0}/README.md +3 -3
  5. {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/build_release.py +0 -5
  6. shinestacker-1.8.0/src/shinestacker/_version.py +1 -0
  7. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/stack.py +9 -0
  8. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/stack_framework.py +35 -16
  9. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/utils.py +5 -1
  10. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/settings_dialog.py +46 -3
  11. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/config/settings.py +4 -1
  12. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/core/core_utils.py +1 -0
  13. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/core/framework.py +7 -2
  14. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/action_config_dialog.py +72 -45
  15. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/gui_run.py +1 -2
  16. shinestacker-1.8.0/src/shinestacker/gui/img/dark/close-round-line-icon.png +0 -0
  17. shinestacker-1.8.0/src/shinestacker/gui/img/dark/forward-button-icon.png +0 -0
  18. shinestacker-1.8.0/src/shinestacker/gui/img/dark/play-button-round-icon.png +0 -0
  19. shinestacker-1.8.0/src/shinestacker/gui/img/dark/plus-round-line-icon.png +0 -0
  20. shinestacker-1.8.0/src/shinestacker/gui/img/dark/shinestacker_bkg.png +0 -0
  21. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/main_window.py +20 -7
  22. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/menu_manager.py +18 -7
  23. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/new_project.py +0 -2
  24. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/tab_widget.py +16 -6
  25. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/adjustments.py +5 -0
  26. {shinestacker-1.7.0 → shinestacker-1.8.0/src/shinestacker.egg-info}/PKG-INFO +4 -4
  27. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker.egg-info/SOURCES.txt +10 -5
  28. shinestacker-1.7.0/src/shinestacker/_version.py +0 -1
  29. {shinestacker-1.7.0 → shinestacker-1.8.0}/.coveragerc +0 -0
  30. {shinestacker-1.7.0 → shinestacker-1.8.0}/.flake8 +0 -0
  31. {shinestacker-1.7.0 → shinestacker-1.8.0}/.github/workflows/ci-multiplatform.yml +0 -0
  32. {shinestacker-1.7.0 → shinestacker-1.8.0}/.github/workflows/pylint.yml +0 -0
  33. {shinestacker-1.7.0 → shinestacker-1.8.0}/.github/workflows/pypi-publish.yml +0 -0
  34. {shinestacker-1.7.0 → shinestacker-1.8.0}/.gitignore +0 -0
  35. {shinestacker-1.7.0 → shinestacker-1.8.0}/.pylintrc +0 -0
  36. {shinestacker-1.7.0 → shinestacker-1.8.0}/.readthedocs.yaml +0 -0
  37. {shinestacker-1.7.0 → shinestacker-1.8.0}/LICENSE +0 -0
  38. {shinestacker-1.7.0 → shinestacker-1.8.0}/MANIFEST.in +0 -0
  39. {shinestacker-1.7.0 → shinestacker-1.8.0}/THIRD_PARTY_LICENSES.txt +0 -0
  40. {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/alignment.md +0 -0
  41. {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/api.md +0 -0
  42. {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/balancing.md +0 -0
  43. {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/conf.py +0 -0
  44. {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/focus_stacking.md +0 -0
  45. {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/gui.md +0 -0
  46. {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/index.md +0 -0
  47. {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/job.md +0 -0
  48. {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/main.md +0 -0
  49. {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/multilayer.md +0 -0
  50. {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/noise.md +0 -0
  51. {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/requirements.txt +0 -0
  52. {shinestacker-1.7.0 → shinestacker-1.8.0}/docs/vignetting.md +0 -0
  53. {shinestacker-1.7.0 → shinestacker-1.8.0}/img/coffee.gif +0 -0
  54. {shinestacker-1.7.0 → shinestacker-1.8.0}/img/coffee_stack.jpg +0 -0
  55. {shinestacker-1.7.0 → shinestacker-1.8.0}/img/extreme-vignetting.jpg +0 -0
  56. {shinestacker-1.7.0 → shinestacker-1.8.0}/img/flies.gif +0 -0
  57. {shinestacker-1.7.0 → shinestacker-1.8.0}/img/flies_stack.jpg +0 -0
  58. {shinestacker-1.7.0 → shinestacker-1.8.0}/img/flow-diagram.png +0 -0
  59. {shinestacker-1.7.0 → shinestacker-1.8.0}/img/gui-finder.png +0 -0
  60. {shinestacker-1.7.0 → shinestacker-1.8.0}/img/gui-project-new.png +0 -0
  61. {shinestacker-1.7.0 → shinestacker-1.8.0}/img/gui-project-run.png +0 -0
  62. {shinestacker-1.7.0 → shinestacker-1.8.0}/img/gui-retouch.png +0 -0
  63. {shinestacker-1.7.0 → shinestacker-1.8.0}/index.html +0 -0
  64. {shinestacker-1.7.0 → shinestacker-1.8.0}/pyproject.toml +0 -0
  65. {shinestacker-1.7.0 → shinestacker-1.8.0}/requirements.txt +0 -0
  66. {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/create_macos_icon.py +0 -0
  67. {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/git-rev-list.sh +0 -0
  68. {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/hooks/hook-IPython.py +0 -0
  69. {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/hooks/hook-PySide6.py +0 -0
  70. {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/hooks/hook-opencv.py +0 -0
  71. {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/hooks/hook-tests.py +0 -0
  72. {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/scan_imports.py +0 -0
  73. {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/shinestacker-inno-setup.iss +0 -0
  74. {shinestacker-1.7.0 → shinestacker-1.8.0}/scripts/validate-tomli.py +0 -0
  75. {shinestacker-1.7.0 → shinestacker-1.8.0}/setup.cfg +0 -0
  76. {shinestacker-1.7.0 → shinestacker-1.8.0}/shinestacker-inno-setup.iss +0 -0
  77. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/__init__.py +0 -0
  78. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/__init__.py +0 -0
  79. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/align.py +0 -0
  80. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/align_auto.py +0 -0
  81. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/align_parallel.py +0 -0
  82. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/balance.py +0 -0
  83. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
  84. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/corrections.py +0 -0
  85. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/denoise.py +0 -0
  86. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/depth_map.py +0 -0
  87. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/exif.py +0 -0
  88. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/multilayer.py +0 -0
  89. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/noise_detection.py +0 -0
  90. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/pyramid.py +0 -0
  91. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/pyramid_auto.py +0 -0
  92. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/pyramid_tiles.py +0 -0
  93. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/sharpen.py +0 -0
  94. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/vignetting.py +0 -0
  95. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/algorithms/white_balance.py +0 -0
  96. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/__init__.py +0 -0
  97. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/about_dialog.py +0 -0
  98. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/args_parser_opts.py +0 -0
  99. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/gui_utils.py +0 -0
  100. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/help_menu.py +0 -0
  101. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/main.py +0 -0
  102. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/open_frames.py +0 -0
  103. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/project.py +0 -0
  104. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/app/retouch.py +0 -0
  105. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/config/__init__.py +0 -0
  106. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/config/app_config.py +0 -0
  107. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/config/config.py +0 -0
  108. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/config/constants.py +0 -0
  109. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/config/gui_constants.py +0 -0
  110. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/core/__init__.py +0 -0
  111. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/core/colors.py +0 -0
  112. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/core/exceptions.py +0 -0
  113. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/core/logging.py +0 -0
  114. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/__init__.py +0 -0
  115. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/action_config.py +0 -0
  116. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/base_form_dialog.py +0 -0
  117. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/colors.py +0 -0
  118. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/config_dialog.py +0 -0
  119. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/flow_layout.py +0 -0
  120. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/folder_file_selection.py +0 -0
  121. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/gui_images.py +0 -0
  122. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/gui_logging.py +0 -0
  123. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
  124. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
  125. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/ico/shinestacker.png +0 -0
  126. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
  127. {shinestacker-1.7.0/src/shinestacker/gui/img → shinestacker-1.8.0/src/shinestacker/gui/img/light}/close-round-line-icon.png +0 -0
  128. {shinestacker-1.7.0/src/shinestacker/gui/img → shinestacker-1.8.0/src/shinestacker/gui/img/light}/forward-button-icon.png +0 -0
  129. {shinestacker-1.7.0/src/shinestacker/gui/img → shinestacker-1.8.0/src/shinestacker/gui/img/light}/play-button-round-icon.png +0 -0
  130. {shinestacker-1.7.0/src/shinestacker/gui/img → shinestacker-1.8.0/src/shinestacker/gui/img/light}/plus-round-line-icon.png +0 -0
  131. {shinestacker-1.7.0/src/shinestacker/gui/ico → shinestacker-1.8.0/src/shinestacker/gui/img/light}/shinestacker_bkg.png +0 -0
  132. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/project_controller.py +0 -0
  133. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/project_converter.py +0 -0
  134. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/project_editor.py +0 -0
  135. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/project_model.py +0 -0
  136. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/recent_file_manager.py +0 -0
  137. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/select_path_widget.py +0 -0
  138. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/sys_mon.py +0 -0
  139. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/gui/time_progress_bar.py +0 -0
  140. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/__init__.py +0 -0
  141. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/base_filter.py +0 -0
  142. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/brush.py +0 -0
  143. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/brush_gradient.py +0 -0
  144. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/brush_preview.py +0 -0
  145. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/brush_tool.py +0 -0
  146. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/denoise_filter.py +0 -0
  147. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/display_manager.py +0 -0
  148. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/exif_data.py +0 -0
  149. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/file_loader.py +0 -0
  150. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/filter_manager.py +0 -0
  151. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/icon_container.py +0 -0
  152. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/image_editor_ui.py +0 -0
  153. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/image_view_status.py +0 -0
  154. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/image_viewer.py +0 -0
  155. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/io_gui_handler.py +0 -0
  156. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/io_threads.py +0 -0
  157. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/layer_collection.py +0 -0
  158. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/overlaid_view.py +0 -0
  159. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/paint_area_manager.py +0 -0
  160. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/shortcuts_help.py +0 -0
  161. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/sidebyside_view.py +0 -0
  162. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/transformation_manager.py +0 -0
  163. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/undo_manager.py +0 -0
  164. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/unsharp_mask_filter.py +0 -0
  165. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/view_strategy.py +0 -0
  166. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/vignetting_filter.py +0 -0
  167. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker/retouch/white_balance_filter.py +0 -0
  168. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker.egg-info/dependency_links.txt +0 -0
  169. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker.egg-info/entry_points.txt +0 -0
  170. {shinestacker-1.7.0 → shinestacker-1.8.0}/src/shinestacker.egg-info/requires.txt +0 -0
  171. {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 - include both formats
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.7.0
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 have expanded the ```tar.gz``` under ```Downloads```):
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 ~/Downloads/shinestacker/shinestacker.app
90
+ xattr -cr /Applications/shinestacker/shinestacker.app
91
91
  ```
92
- 5. Now you can double-click the Sine Stacker icon app in the ```shiestacker``` folder and it should run.
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 have expanded the ```tar.gz``` under ```Downloads```):
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 ~/Downloads/shinestacker/shinestacker.app
58
+ xattr -cr /Applications/shinestacker/shinestacker.app
59
59
  ```
60
- 5. Now you can double-click the Sine Stacker icon app in the ```shiestacker``` folder and it should run.
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, resample=1,
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
- list_dir = os.listdir(output_dir)
134
- if len(list_dir) > 0:
153
+ if len(os.listdir(output_dir)):
135
154
  if self.scratch_output_dir:
136
- if self.enabled:
137
- for filename in list_dir:
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
- fig.savefig(filename, dpi=150)
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': self.align_frames_max_threads.value()
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
@@ -52,4 +52,5 @@ def setup_matplotlib_mode():
52
52
  __IPYTHON__ # noqa
53
53
  except Exception:
54
54
  matplotlib.use('agg')
55
+ matplotlib.pyplot.set_loglevel("warning")
55
56
  matplotlib.rcParams['pdf.fonttype'] = 42
@@ -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.print_message(color_str(
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.print_message(color_str(
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 dir.',
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 dir.',
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 dir.',
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 AlignFramesConfigurator(SubsampleActionConfigurator):
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
- MODE_OPTIONS = ['Auto', 'Sequential', 'Parallel']
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
- def __init__(self, expert, current_wd):
501
- super().__init__(expert, current_wd)
502
- self.matching_method_field = None
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(self):
518
- detector = self.detector_field.currentText()
519
- descriptor = self.descriptor_field.currentText()
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))[self.matching_method_field.currentText()]
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
- self.show_info(str(e))
538
+ show_info(str(e))
527
539
  if descriptor == constants.DETECTOR_SIFT and \
528
540
  match_method == constants.MATCHING_NORM_HAMMING:
529
- self.matching_method_field.setCurrentText(self.MATCHING_METHOD_OPTIONS[0])
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
- self.matching_method_field.setCurrentText(constants.MATCHING_NORM_HAMMING)
544
+ matching_method_field.setCurrentText(constants.MATCHING_NORM_HAMMING)
533
545
  if detector == constants.DETECTOR_BRISK and descriptor == constants.DESCRIPTOR_AKAZE:
534
- self.descriptor_field.setCurrentText('BRISK')
546
+ descriptor_field.setCurrentText('BRISK')
535
547
  if detector == constants.DETECTOR_SURF and descriptor == constants.DESCRIPTOR_AKAZE:
536
- self.descriptor_field.setCurrentText('SIFT')
548
+ descriptor_field.setCurrentText('SIFT')
537
549
  if detector == constants.DETECTOR_SIFT and descriptor != constants.DESCRIPTOR_SIFT:
538
- self.descriptor_field.setCurrentText('SIFT')
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
- self.matching_method_field.setCurrentText(self.MATCHING_METHOD_OPTIONS[1])
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=constants.DEFAULT_DETECTOR)
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=constants.DEFAULT_DESCRIPTOR)
567
- self.detector_field.setToolTip(
568
- "SIFT: Requires SIFT descriptor and K-NN matching\n"
569
- "ORB/AKAZE: Work best with Hamming distance"
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=constants.DEFAULT_MATCHING_METHOD)
585
- self.matching_method_field.setToolTip(
586
- "Automatically selected based on detector/descriptor combination"
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
- max_width = max(pv.size().width() for pv in self.image_views) if self.image_views else 0
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()