shinestacker 1.2.0__tar.gz → 1.2.1__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 (140) hide show
  1. {shinestacker-1.2.0 → shinestacker-1.2.1}/CHANGELOG.md +19 -8
  2. {shinestacker-1.2.0/src/shinestacker.egg-info → shinestacker-1.2.1}/PKG-INFO +1 -1
  3. {shinestacker-1.2.0 → shinestacker-1.2.1}/docs/job.md +1 -1
  4. shinestacker-1.2.1/src/shinestacker/_version.py +1 -0
  5. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/algorithms/align.py +34 -33
  6. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/algorithms/balance.py +11 -12
  7. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/algorithms/multilayer.py +10 -11
  8. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/algorithms/noise_detection.py +6 -7
  9. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/algorithms/stack.py +17 -17
  10. shinestacker-1.2.1/src/shinestacker/algorithms/stack_framework.py +279 -0
  11. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/algorithms/vignetting.py +3 -3
  12. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/config/constants.py +3 -1
  13. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/core/framework.py +12 -12
  14. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/action_config.py +53 -0
  15. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/action_config_dialog.py +4 -5
  16. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/gui_images.py +10 -10
  17. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/gui_run.py +1 -1
  18. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/main_window.py +4 -4
  19. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/menu_manager.py +4 -0
  20. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/new_project.py +0 -1
  21. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/project_controller.py +4 -4
  22. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/project_editor.py +35 -26
  23. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/tab_widget.py +3 -3
  24. {shinestacker-1.2.0 → shinestacker-1.2.1/src/shinestacker.egg-info}/PKG-INFO +1 -1
  25. shinestacker-1.2.0/src/shinestacker/_version.py +0 -1
  26. shinestacker-1.2.0/src/shinestacker/algorithms/stack_framework.py +0 -308
  27. {shinestacker-1.2.0 → shinestacker-1.2.1}/.coveragerc +0 -0
  28. {shinestacker-1.2.0 → shinestacker-1.2.1}/.flake8 +0 -0
  29. {shinestacker-1.2.0 → shinestacker-1.2.1}/.github/workflows/ci-multiplatform.yml +0 -0
  30. {shinestacker-1.2.0 → shinestacker-1.2.1}/.github/workflows/pylint.yml +0 -0
  31. {shinestacker-1.2.0 → shinestacker-1.2.1}/.github/workflows/pypi-publish.yml +0 -0
  32. {shinestacker-1.2.0 → shinestacker-1.2.1}/.github/workflows/release.yml +0 -0
  33. {shinestacker-1.2.0 → shinestacker-1.2.1}/.gitignore +0 -0
  34. {shinestacker-1.2.0 → shinestacker-1.2.1}/.pylintrc +0 -0
  35. {shinestacker-1.2.0 → shinestacker-1.2.1}/.readthedocs.yaml +0 -0
  36. {shinestacker-1.2.0 → shinestacker-1.2.1}/LICENSE +0 -0
  37. {shinestacker-1.2.0 → shinestacker-1.2.1}/MANIFEST.in +0 -0
  38. {shinestacker-1.2.0 → shinestacker-1.2.1}/README.md +0 -0
  39. {shinestacker-1.2.0 → shinestacker-1.2.1}/THIRD_PARTY_LICENSES.txt +0 -0
  40. {shinestacker-1.2.0 → shinestacker-1.2.1}/docs/alignment.md +0 -0
  41. {shinestacker-1.2.0 → shinestacker-1.2.1}/docs/api.md +0 -0
  42. {shinestacker-1.2.0 → shinestacker-1.2.1}/docs/balancing.md +0 -0
  43. {shinestacker-1.2.0 → shinestacker-1.2.1}/docs/conf.py +0 -0
  44. {shinestacker-1.2.0 → shinestacker-1.2.1}/docs/focus_stacking.md +0 -0
  45. {shinestacker-1.2.0 → shinestacker-1.2.1}/docs/gui.md +0 -0
  46. {shinestacker-1.2.0 → shinestacker-1.2.1}/docs/index.md +0 -0
  47. {shinestacker-1.2.0 → shinestacker-1.2.1}/docs/main.md +0 -0
  48. {shinestacker-1.2.0 → shinestacker-1.2.1}/docs/multilayer.md +0 -0
  49. {shinestacker-1.2.0 → shinestacker-1.2.1}/docs/noise.md +0 -0
  50. {shinestacker-1.2.0 → shinestacker-1.2.1}/docs/requirements.txt +0 -0
  51. {shinestacker-1.2.0 → shinestacker-1.2.1}/docs/vignetting.md +0 -0
  52. {shinestacker-1.2.0 → shinestacker-1.2.1}/img/coffee.gif +0 -0
  53. {shinestacker-1.2.0 → shinestacker-1.2.1}/img/coffee_stack.jpg +0 -0
  54. {shinestacker-1.2.0 → shinestacker-1.2.1}/img/extreme-vignetting.jpg +0 -0
  55. {shinestacker-1.2.0 → shinestacker-1.2.1}/img/flies.gif +0 -0
  56. {shinestacker-1.2.0 → shinestacker-1.2.1}/img/flies_stack.jpg +0 -0
  57. {shinestacker-1.2.0 → shinestacker-1.2.1}/img/flow-diagram.png +0 -0
  58. {shinestacker-1.2.0 → shinestacker-1.2.1}/img/gui-finder.png +0 -0
  59. {shinestacker-1.2.0 → shinestacker-1.2.1}/img/gui-project-new.png +0 -0
  60. {shinestacker-1.2.0 → shinestacker-1.2.1}/img/gui-project-run.png +0 -0
  61. {shinestacker-1.2.0 → shinestacker-1.2.1}/img/gui-retouch.png +0 -0
  62. {shinestacker-1.2.0 → shinestacker-1.2.1}/pyproject.toml +0 -0
  63. {shinestacker-1.2.0 → shinestacker-1.2.1}/requirements.txt +0 -0
  64. {shinestacker-1.2.0 → shinestacker-1.2.1}/scripts/build_release.py +0 -0
  65. {shinestacker-1.2.0 → shinestacker-1.2.1}/scripts/git-rev-list.sh +0 -0
  66. {shinestacker-1.2.0 → shinestacker-1.2.1}/scripts/validate-tomli.py +0 -0
  67. {shinestacker-1.2.0 → shinestacker-1.2.1}/setup.cfg +0 -0
  68. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/__init__.py +0 -0
  69. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/algorithms/__init__.py +0 -0
  70. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
  71. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/algorithms/denoise.py +0 -0
  72. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/algorithms/depth_map.py +0 -0
  73. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/algorithms/exif.py +0 -0
  74. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/algorithms/pyramid.py +0 -0
  75. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/algorithms/pyramid_auto.py +0 -0
  76. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/algorithms/pyramid_tiles.py +0 -0
  77. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/algorithms/sharpen.py +0 -0
  78. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/algorithms/utils.py +0 -0
  79. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/algorithms/white_balance.py +0 -0
  80. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/app/__init__.py +0 -0
  81. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/app/about_dialog.py +0 -0
  82. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/app/gui_utils.py +0 -0
  83. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/app/help_menu.py +0 -0
  84. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/app/main.py +0 -0
  85. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/app/open_frames.py +0 -0
  86. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/app/project.py +0 -0
  87. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/app/retouch.py +0 -0
  88. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/config/__init__.py +0 -0
  89. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/config/config.py +0 -0
  90. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/config/gui_constants.py +0 -0
  91. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/core/__init__.py +0 -0
  92. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/core/colors.py +0 -0
  93. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/core/core_utils.py +0 -0
  94. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/core/exceptions.py +0 -0
  95. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/core/logging.py +0 -0
  96. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/__init__.py +0 -0
  97. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/base_form_dialog.py +0 -0
  98. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/colors.py +0 -0
  99. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/flow_layout.py +0 -0
  100. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/gui_logging.py +0 -0
  101. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
  102. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
  103. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
  104. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/ico/shinestacker.png +0 -0
  105. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
  106. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
  107. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
  108. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
  109. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
  110. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/project_converter.py +0 -0
  111. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/project_model.py +0 -0
  112. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/select_path_widget.py +0 -0
  113. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/gui/time_progress_bar.py +0 -0
  114. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/__init__.py +0 -0
  115. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/base_filter.py +0 -0
  116. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/brush.py +0 -0
  117. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/brush_gradient.py +0 -0
  118. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/brush_preview.py +0 -0
  119. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/brush_tool.py +0 -0
  120. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/denoise_filter.py +0 -0
  121. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/display_manager.py +0 -0
  122. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/exif_data.py +0 -0
  123. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/file_loader.py +0 -0
  124. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/filter_manager.py +0 -0
  125. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/icon_container.py +0 -0
  126. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/image_editor_ui.py +0 -0
  127. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/image_viewer.py +0 -0
  128. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/io_gui_handler.py +0 -0
  129. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/io_manager.py +0 -0
  130. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/layer_collection.py +0 -0
  131. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/shortcuts_help.py +0 -0
  132. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/undo_manager.py +0 -0
  133. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/unsharp_mask_filter.py +0 -0
  134. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/vignetting_filter.py +0 -0
  135. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker/retouch/white_balance_filter.py +0 -0
  136. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker.egg-info/SOURCES.txt +0 -0
  137. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker.egg-info/dependency_links.txt +0 -0
  138. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker.egg-info/entry_points.txt +0 -0
  139. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker.egg-info/requires.txt +0 -0
  140. {shinestacker-1.2.0 → shinestacker-1.2.1}/src/shinestacker.egg-info/top_level.txt +0 -0
@@ -2,20 +2,31 @@
2
2
 
3
3
  This page reports the main releases only and the main changes therein.
4
4
 
5
+ ## [v1.2.1] - 2025-09-01
6
+ **Bug fixes and minor improvements**
7
+ * alignment is more tolerant in case of failures: frames are skipped and the running job is not stopped
8
+ * fixed the -x (--expert) option
9
+ * more safety checks prevent crashes for abnormal conditions
10
+ * reference frame index improved with a more consistent treatment, a better numbering scheme and GUI widget
11
+ * improved project undo action description text
12
+ * some bug fixes and code cleanup
13
+
14
+ ---
15
+
5
16
  ## [v1.2.0] - 2025-08-31
6
- **Improved pyramid algorithm**
17
+ **Parallel processing and more improvements**
7
18
 
8
19
  * Implemented parallel processing for pyramid stacking algorithm
9
- * optimized Pyramid algorithm: now a single algorithm selects automatically the best strategy ensuring that all the processing stays approximately within a given memory budget; This avoids memory issues in case many pictures are selected. Explicit configuration is also possible for specific needs.
10
- * Implemented automatic subsample option for alignment, balancing and vignetting, now default.
20
+ * optimized pyramid algorithm: selects automatically the best within the given memory budget to avoid memory issues in case many pictures are selected. Explicit configuration is also possible for specific needs.
21
+ * Implemented automatic subsample option for alignment, balancing and vignetting, now default
11
22
  * HLS and HSV corrections now supported for 16 bit images
12
23
  * Added luminosity correction in the LAB color space
13
24
  * Alignment module skips frames if transformation parameters are out of a reasonable ranges
14
- * Multilayer modules sends a warning if the estimated file size is > 1GB
15
- * "Run all jobs" action is enabled only if more than one job are present.
16
- * Updated default module names for "new project" dialog.
17
- * Code refactoring.
18
- * Some GUI fixes.
25
+ * Multilayer modules sends a warning if the estimated output file size is > 1GB
26
+ * "Run all jobs" action is enabled only if more than one job are present
27
+ * Updated default module names in project genereated by "new project" dialog
28
+ * Code refactoring
29
+ * Some GUI fixes
19
30
 
20
31
  ---
21
32
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shinestacker
3
- Version: 1.2.0
3
+ Version: 1.2.1
4
4
  Summary: ShineStacker
5
5
  Author-email: Luca Lista <luka.lista@gmail.com>
6
6
  License-Expression: LGPL-3.0
@@ -41,6 +41,6 @@ Arguments for the constructor of ```CombinedActions``` are for the :
41
41
  * ```working_path``` (optional): the directory that contains input and output image subdirectories. If not specified, it is the same as ```job.working_path```.
42
42
  * ```plot_path``` (optional, default: ```plots```): the directory within ```working_path``` that contains plots produced by the different actions.
43
43
  * ```resample``` (optional, default: 1): take every *n*<sup>th</sup> frame in the selected directory. Default: take all frames.
44
- * ```ref_idx``` (optional): the index of the image used as reference. Images are numbered starting from zero. If not specified, it is the index of the middle image.
44
+ * ```reference_index``` (optional, default: 0): the index of the image used as reference. Images are numbered starting from one to N. -1 is interpreted as the last image, 0 as the median index.
45
45
  * ```step_process``` (optional): if equal to ```True``` (default), each image is processed with respect to the previous or next image, depending if its file is placed in alphabetic order after or befor the reference image.
46
46
  * ```enabled``` (optional, default: ```True```): allows to switch on and off this module.
@@ -0,0 +1 @@
1
+ __version__ = '1.2.1'
@@ -5,7 +5,7 @@ import numpy as np
5
5
  import matplotlib.pyplot as plt
6
6
  import cv2
7
7
  from .. config.constants import constants
8
- from .. core.exceptions import AlignmentError, InvalidOptionError
8
+ from .. core.exceptions import InvalidOptionError
9
9
  from .. core.colors import color_str
10
10
  from .utils import img_8bit, img_bw_8bit, save_plot, img_subsample
11
11
  from .stack_framework import SubAction
@@ -130,7 +130,7 @@ def check_homography_distortion(m, img_shape, homography_thresholds=_HOMOGRAPHY_
130
130
  return True, "Transformation within acceptable limits"
131
131
 
132
132
 
133
- def get_good_matches(des_0, des_1, matching_config=None):
133
+ def get_good_matches(des_0, des_ref, matching_config=None):
134
134
  matching_config = {**_DEFAULT_MATCHING_CONFIG, **(matching_config or {})}
135
135
  match_method = matching_config['match_method']
136
136
  good_matches = []
@@ -139,12 +139,12 @@ def get_good_matches(des_0, des_1, matching_config=None):
139
139
  {'algorithm': matching_config['flann_idx_kdtree'],
140
140
  'trees': matching_config['flann_trees']},
141
141
  {'checks': matching_config['flann_checks']})
142
- matches = flann.knnMatch(des_0, des_1, k=2)
142
+ matches = flann.knnMatch(des_0, des_ref, k=2)
143
143
  good_matches = [m for m, n in matches
144
144
  if m.distance < matching_config['threshold'] * n.distance]
145
145
  elif match_method == constants.MATCHING_NORM_HAMMING:
146
146
  bf = cv2.BFMatcher(cv2.NORM_HAMMING, crossCheck=True)
147
- good_matches = sorted(bf.match(des_0, des_1), key=lambda x: x.distance)
147
+ good_matches = sorted(bf.match(des_0, des_ref), key=lambda x: x.distance)
148
148
  else:
149
149
  raise InvalidOptionError(
150
150
  'match_method', match_method,
@@ -172,14 +172,14 @@ def validate_align_config(detector, descriptor, match_method):
172
172
  " require matching method Hamming distance")
173
173
 
174
174
 
175
- def detect_and_compute(img_0, img_1, feature_config=None, matching_config=None):
175
+ def detect_and_compute(img_0, img_ref, feature_config=None, matching_config=None):
176
176
  feature_config = {**_DEFAULT_FEATURE_CONFIG, **(feature_config or {})}
177
177
  matching_config = {**_DEFAULT_MATCHING_CONFIG, **(matching_config or {})}
178
178
  feature_config_detector = feature_config['detector']
179
179
  feature_config_descriptor = feature_config['descriptor']
180
180
  match_method = matching_config['match_method']
181
181
  validate_align_config(feature_config_detector, feature_config_descriptor, match_method)
182
- img_bw_0, img_bw_1 = img_bw_8bit(img_0), img_bw_8bit(img_1)
182
+ img_bw_0, img_bw_ref = img_bw_8bit(img_0), img_bw_8bit(img_ref)
183
183
  detector_map = {
184
184
  constants.DETECTOR_SIFT: cv2.SIFT_create,
185
185
  constants.DETECTOR_ORB: cv2.ORB_create,
@@ -199,12 +199,12 @@ def detect_and_compute(img_0, img_1, feature_config=None, matching_config=None):
199
199
  constants.DETECTOR_AKAZE,
200
200
  constants.DETECTOR_BRISK):
201
201
  kp_0, des_0 = detector.detectAndCompute(img_bw_0, None)
202
- kp_1, des_1 = detector.detectAndCompute(img_bw_1, None)
202
+ kp_ref, des_ref = detector.detectAndCompute(img_bw_ref, None)
203
203
  else:
204
204
  descriptor = descriptor_map[feature_config_descriptor]()
205
205
  kp_0, des_0 = descriptor.compute(img_bw_0, detector.detect(img_bw_0, None))
206
- kp_1, des_1 = descriptor.compute(img_bw_1, detector.detect(img_bw_1, None))
207
- return kp_0, kp_1, get_good_matches(des_0, des_1, matching_config)
206
+ kp_ref, des_ref = descriptor.compute(img_bw_ref, detector.detect(img_bw_ref, None))
207
+ return kp_0, kp_ref, get_good_matches(des_0, des_ref, matching_config)
208
208
 
209
209
 
210
210
  def find_transform(src_pts, dst_pts, transform=constants.DEFAULT_TRANSFORM,
@@ -236,7 +236,7 @@ def find_transform(src_pts, dst_pts, transform=constants.DEFAULT_TRANSFORM,
236
236
  return result
237
237
 
238
238
 
239
- def align_images(img_1, img_0, feature_config=None, matching_config=None, alignment_config=None,
239
+ def align_images(img_ref, img_0, feature_config=None, matching_config=None, alignment_config=None,
240
240
  plot_path=None, callbacks=None,
241
241
  affine_thresholds=_AFFINE_THRESHOLDS,
242
242
  homography_thresholds=_HOMOGRAPHY_THRESHOLDS):
@@ -250,11 +250,11 @@ def align_images(img_1, img_0, feature_config=None, matching_config=None, alignm
250
250
  min_matches = 4 if alignment_config['transform'] == constants.ALIGN_HOMOGRAPHY else 3
251
251
  if callbacks and 'message' in callbacks:
252
252
  callbacks['message']()
253
- h_ref, w_ref = img_1.shape[:2]
253
+ h_ref, w_ref = img_ref.shape[:2]
254
254
  h0, w0 = img_0.shape[:2]
255
255
  subsample = alignment_config['subsample']
256
256
  if subsample == 0:
257
- img_res = (float(h0) / 1000) * (float(w0) / 1000)
257
+ img_res = (float(h0) / constants.ONE_KILO) * (float(w0) / constants.ONE_KILO)
258
258
  target_res = constants.DEFAULT_ALIGN_RES_TARGET_MPX
259
259
  subsample = int(1 + math.floor(img_res / target_res))
260
260
  fast_subsampling = alignment_config['fast_subsampling']
@@ -262,11 +262,11 @@ def align_images(img_1, img_0, feature_config=None, matching_config=None, alignm
262
262
  while True:
263
263
  if subsample > 1:
264
264
  img_0_sub = img_subsample(img_0, subsample, fast_subsampling)
265
- img_1_sub = img_subsample(img_1, subsample, fast_subsampling)
265
+ img_ref_sub = img_subsample(img_ref, subsample, fast_subsampling)
266
266
  else:
267
- img_0_sub, img_1_sub = img_0, img_1
268
- kp_0, kp_1, good_matches = detect_and_compute(img_0_sub, img_1_sub,
269
- feature_config, matching_config)
267
+ img_0_sub, img_ref_sub = img_0, img_ref
268
+ kp_0, kp_ref, good_matches = detect_and_compute(img_0_sub, img_ref_sub,
269
+ feature_config, matching_config)
270
270
  n_good_matches = len(good_matches)
271
271
  if n_good_matches > min_good_matches or subsample == 1:
272
272
  break
@@ -282,7 +282,7 @@ def align_images(img_1, img_0, feature_config=None, matching_config=None, alignm
282
282
  if n_good_matches >= min_matches:
283
283
  transform = alignment_config['transform']
284
284
  src_pts = np.float32([kp_0[m.queryIdx].pt for m in good_matches]).reshape(-1, 1, 2)
285
- dst_pts = np.float32([kp_1[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)
285
+ dst_pts = np.float32([kp_ref[m.trainIdx].pt for m in good_matches]).reshape(-1, 1, 2)
286
286
  m, msk = find_transform(src_pts, dst_pts, transform, alignment_config['align_method'],
287
287
  *(alignment_config[k]
288
288
  for k in ['rans_threshold', 'max_iters',
@@ -290,11 +290,11 @@ def align_images(img_1, img_0, feature_config=None, matching_config=None, alignm
290
290
  if plot_path is not None:
291
291
  matches_mask = msk.ravel().tolist()
292
292
  img_match = cv2.cvtColor(cv2.drawMatches(
293
- img_8bit(img_0_sub), kp_0, img_8bit(img_1_sub),
294
- kp_1, good_matches, None, matchColor=(0, 255, 0),
293
+ img_8bit(img_0_sub), kp_0, img_8bit(img_ref_sub),
294
+ kp_ref, good_matches, None, matchColor=(0, 255, 0),
295
295
  singlePointColor=None, matchesMask=matches_mask,
296
296
  flags=2), cv2.COLOR_BGR2RGB)
297
- plt.figure(figsize=(10, 5))
297
+ plt.figure(figsize=constants.PLT_FIG_SIZE)
298
298
  plt.imshow(img_match, 'gray')
299
299
  save_plot(plot_path)
300
300
  if callbacks and 'save_plot' in callbacks:
@@ -330,7 +330,6 @@ def align_images(img_1, img_0, feature_config=None, matching_config=None, alignm
330
330
  if callbacks and 'warning' in callbacks:
331
331
  callbacks['warning'](f"invalid transformation: {reason}")
332
332
  return n_good_matches, None, None
333
-
334
333
  if callbacks and 'align_message' in callbacks:
335
334
  callbacks['align_message']()
336
335
  img_mask = np.ones_like(img_0, dtype=np.uint8)
@@ -390,7 +389,7 @@ class AlignFrames(SubAction):
390
389
  def sub_msg(self, msg, color=constants.LOG_COLOR_LEVEL_3):
391
390
  self.process.sub_message_r(color_str(msg, color))
392
391
 
393
- def align_images(self, idx, img_1, img_0):
392
+ def align_images(self, idx, img_ref, img_0):
394
393
  idx_str = f"{idx:04d}"
395
394
  callbacks = {
396
395
  'message': lambda: self.sub_msg(': find matches'),
@@ -416,7 +415,7 @@ class AlignFrames(SubAction):
416
415
  affine_thresholds = None
417
416
  homography_thresholds = None
418
417
  n_good_matches, _m, img = align_images(
419
- img_1, img_0,
418
+ img_ref, img_0,
420
419
  feature_config=self.feature_config,
421
420
  matching_config=self.matching_config,
422
421
  alignment_config=self.alignment_config,
@@ -427,27 +426,29 @@ class AlignFrames(SubAction):
427
426
  )
428
427
  self.n_matches[idx] = n_good_matches
429
428
  if n_good_matches < self.min_matches:
430
- self.process.sub_message(f": image not aligned, too few matches found: "
431
- f"{n_good_matches}", level=logging.CRITICAL)
432
- raise AlignmentError(idx, f"Image not aligned, too few matches found: "
433
- f"{n_good_matches} < {self.min_matches}")
429
+ self.process.sub_message(color_str(f": image not aligned, too few matches found: "
430
+ f"{n_good_matches}", constants.LOG_COLOR_WARNING),
431
+ level=logging.WARNING)
432
+ return None
434
433
  return img
435
434
 
436
435
  def begin(self, process):
437
436
  self.process = process
438
- self.n_matches = np.zeros(process.counts)
437
+ self.n_matches = np.zeros(process.total_action_counts)
439
438
 
440
439
  def end(self):
441
440
  if self.plot_summary:
442
- plt.figure(figsize=(10, 5))
441
+ plt.figure(figsize=constants.PLT_FIG_SIZE)
443
442
  x = np.arange(1, len(self.n_matches) + 1, dtype=int)
444
443
  no_ref = x != self.process.ref_idx + 1
445
444
  x = x[no_ref]
446
445
  y = self.n_matches[no_ref]
447
- y_max = y[1] \
448
- if self.process.ref_idx == 0 \
449
- else y[-1] if self.process.ref_idx == len(y) - 1 \
450
- else (y[self.process.ref_idx - 1] + y[self.process.ref_idx]) / 2
446
+ if self.process.ref_idx == 0:
447
+ y_max = y[1]
448
+ elif self.process.ref_idx >= len(y):
449
+ y_max = y[-1]
450
+ else:
451
+ y_max = (y[self.process.ref_idx - 1] + y[self.process.ref_idx]) / 2
451
452
 
452
453
  plt.plot([self.process.ref_idx + 1, self.process.ref_idx + 1],
453
454
  [0, y_max], color='cornflowerblue', linestyle='--', label='reference frame')
@@ -24,7 +24,6 @@ class BaseHistogrammer:
24
24
  self.plot_summary = plot_summary
25
25
  self.process = process
26
26
  self.corrections = None
27
- self.figsize = (10, 5)
28
27
 
29
28
  def begin(self, size):
30
29
  self.corrections = np.ones((size, self.channels))
@@ -70,7 +69,7 @@ class LumiHistogrammer(BaseHistogrammer):
70
69
  self.colors = ("r", "g", "b")
71
70
 
72
71
  def generate_frame_plot(self, idx, hist, chans, calc_hist_func):
73
- _fig, axs = plt.subplots(1, 2, figsize=self.figsize, sharey=True)
72
+ _fig, axs = plt.subplots(1, 2, figsize=constants.PLT_FIG_SIZE, sharey=True)
74
73
  self.histo_plot(axs[0], hist, "pixel luminosity", 'black')
75
74
  for (chan, color) in zip(chans, self.colors):
76
75
  hist_col = calc_hist_func(chan)
@@ -79,7 +78,7 @@ class LumiHistogrammer(BaseHistogrammer):
79
78
  self.save_plot(idx)
80
79
 
81
80
  def generate_summary_plot(self, ref_idx):
82
- plt.figure(figsize=self.figsize)
81
+ plt.figure(figsize=constants.PLT_FIG_SIZE)
83
82
  x = np.arange(0, len(self.corrections), dtype=int)
84
83
  y = self.corrections
85
84
  plt.plot([ref_idx, ref_idx], [0, np.max(y)], color='cornflowerblue',
@@ -101,14 +100,14 @@ class RGBHistogrammer(BaseHistogrammer):
101
100
  self.colors = ("r", "g", "b")
102
101
 
103
102
  def generate_frame_plot(self, idx, hists):
104
- _fig, axs = plt.subplots(1, 3, figsize=self.figsize, sharey=True)
103
+ _fig, axs = plt.subplots(1, 3, figsize=constants.PLT_FIG_SIZE, sharey=True)
105
104
  for c in [2, 1, 0]:
106
105
  self.histo_plot(axs[c], hists[c], self.colors[c] + " luminosity", self.colors[c])
107
106
  plt.xlim(0, self.max_pixel_value)
108
107
  self.save_plot(idx)
109
108
 
110
109
  def generate_summary_plot(self, ref_idx):
111
- plt.figure(figsize=self.figsize)
110
+ plt.figure(figsize=constants.PLT_FIG_SIZE)
112
111
  x = np.arange(0, len(self.corrections), dtype=int)
113
112
  y = self.corrections
114
113
  max_val = np.max(y) if np.any(y) else 1.0
@@ -134,14 +133,14 @@ class Ch1Histogrammer(BaseHistogrammer):
134
133
  self.colors = colors
135
134
 
136
135
  def generate_frame_plot(self, idx, hists):
137
- _fig, axs = plt.subplots(1, 3, figsize=self.figsize, sharey=True)
136
+ _fig, axs = plt.subplots(1, 3, figsize=constants.PLT_FIG_SIZE, sharey=True)
138
137
  for c in range(3):
139
138
  self.histo_plot(axs[c], hists[c], self.labels[c], self.colors[c])
140
139
  plt.xlim(0, self.max_pixel_value)
141
140
  self.save_plot(idx)
142
141
 
143
142
  def generate_summary_plot(self, ref_idx):
144
- plt.figure(figsize=self.figsize)
143
+ plt.figure(figsize=constants.PLT_FIG_SIZE)
145
144
  x = np.arange(0, len(self.corrections), dtype=int)
146
145
  y = self.corrections
147
146
  max_val = np.max(y) if np.any(y) else 1.0
@@ -165,14 +164,14 @@ class Ch2Histogrammer(BaseHistogrammer):
165
164
  self.colors = colors
166
165
 
167
166
  def generate_frame_plot(self, idx, hists):
168
- _fig, axs = plt.subplots(1, 3, figsize=self.figsize, sharey=True)
167
+ _fig, axs = plt.subplots(1, 3, figsize=constants.PLT_FIG_SIZE, sharey=True)
169
168
  for c in range(3):
170
169
  self.histo_plot(axs[c], hists[c], self.labels[c], self.colors[c])
171
170
  plt.xlim(0, self.max_pixel_value)
172
171
  self.save_plot(idx)
173
172
 
174
173
  def generate_summary_plot(self, ref_idx):
175
- plt.figure(figsize=self.figsize)
174
+ plt.figure(figsize=constants.PLT_FIG_SIZE)
176
175
  x = np.arange(0, len(self.corrections), dtype=int)
177
176
  y = self.corrections
178
177
  max_val = np.max(y) if np.any(y) else 1.0
@@ -591,9 +590,9 @@ class BalanceFrames(SubAction):
591
590
  def begin(self, process):
592
591
  self.process = process
593
592
  self.correction.process = process
594
- img = read_img(self.process.input_full_path + "/" + self.process.filenames[process.ref_idx])
593
+ img = read_img(self.process.input_filepath(process.ref_idx))
595
594
  self.shape = img.shape
596
- self.correction.begin(img, self.process.counts, process.ref_idx)
595
+ self.correction.begin(img, self.process.total_action_counts, process.ref_idx)
597
596
 
598
597
  def end(self):
599
598
  self.process.print_message(' ' * 60)
@@ -603,7 +602,7 @@ class BalanceFrames(SubAction):
603
602
  img = np.zeros(shape)
604
603
  mask_radius = int(min(*shape) * self.mask_size / 2)
605
604
  cv2.circle(img, (shape[1] // 2, shape[0] // 2), mask_radius, 255, -1)
606
- plt.figure(figsize=(10, 5))
605
+ plt.figure(figsize=constants.PLT_FIG_SIZE)
607
606
  plt.title('Mask')
608
607
  plt.imshow(img, 'gray')
609
608
  self.correction.histogrammer.save_summary_plot("mask")
@@ -14,7 +14,7 @@ from .. config.config import config
14
14
  from .. core.colors import color_str
15
15
  from .. core.framework import JobBase
16
16
  from .utils import EXTENSIONS_TIF, EXTENSIONS_JPG, EXTENSIONS_PNG
17
- from .stack_framework import FrameMultiDirectory
17
+ from .stack_framework import FramePaths
18
18
  from .exif import exif_extra_tags_for_tif, get_exif
19
19
 
20
20
 
@@ -159,9 +159,9 @@ def write_multilayer_tiff_from_images(image_dict, output_file, exif_path='', cal
159
159
  compression=compression, metadata=None, **tiff_tags)
160
160
 
161
161
 
162
- class MultiLayer(JobBase, FrameMultiDirectory):
162
+ class MultiLayer(JobBase, FramePaths):
163
163
  def __init__(self, name, enabled=True, **kwargs):
164
- FrameMultiDirectory.__init__(self, name, **kwargs)
164
+ FramePaths.__init__(self, name, **kwargs)
165
165
  JobBase.__init__(self, name, enabled)
166
166
  self.exif_path = kwargs.get('exif_path', '')
167
167
  self.reverse_order = kwargs.get(
@@ -170,16 +170,16 @@ class MultiLayer(JobBase, FrameMultiDirectory):
170
170
  )
171
171
 
172
172
  def init(self, job):
173
- FrameMultiDirectory.init(self, job)
173
+ FramePaths.init(self, job)
174
174
  if self.exif_path == '':
175
175
  self.exif_path = job.paths[0]
176
176
  if self.exif_path != '':
177
177
  self.exif_path = self.working_path + "/" + self.exif_path
178
178
 
179
179
  def run_core(self):
180
- if isinstance(self.input_full_path, str):
180
+ if isinstance(self.input_full_path(), str):
181
181
  paths = [self.input_path]
182
- elif hasattr(self.input_full_path, "__len__"):
182
+ elif hasattr(self.input_full_path(), "__len__"):
183
183
  paths = self.input_path
184
184
  else:
185
185
  raise RuntimeError("input_path option must contain a path or an array of paths")
@@ -188,8 +188,8 @@ class MultiLayer(JobBase, FrameMultiDirectory):
188
188
  constants.LOG_COLOR_ALERT),
189
189
  level=logging.WARNING)
190
190
  return
191
- files = self.folder_filelist()
192
- if len(files) == 0:
191
+ input_files = self.input_filepaths()
192
+ if len(input_files) == 0:
193
193
  self.print_message(
194
194
  color_str(f"no input in {len(paths)} specified path" +
195
195
  ('s' if len(paths) > 1 else '') + ": "
@@ -199,12 +199,11 @@ class MultiLayer(JobBase, FrameMultiDirectory):
199
199
  return
200
200
  self.print_message(color_str("merging frames in " + self.folder_list_str(),
201
201
  constants.LOG_COLOR_LEVEL_2))
202
- input_files = [f"{self.working_path}/{f}" for f in files]
203
202
  self.print_message(
204
- color_str("frames: " + ", ".join([i.split("/")[-1] for i in files]),
203
+ color_str("frames: " + ", ".join([os.path.basename(i) for i in input_files]),
205
204
  constants.LOG_COLOR_LEVEL_2))
206
205
  self.print_message(color_str("reading files", constants.LOG_COLOR_LEVEL_2))
207
- filename = ".".join(files[0].split("/")[-1].split(".")[:-1])
206
+ filename = ".".join(os.path.basename(input_files[0]).split(".")[:-1])
208
207
  output_file = f"{self.working_path}/{self.output_path}/{filename}.tif"
209
208
  callbacks = {
210
209
  'exif_msg': lambda path: self.print_message(
@@ -12,7 +12,7 @@ from .. core.exceptions import ImageLoadError
12
12
  from .. core.framework import JobBase
13
13
  from .. core.core_utils import make_tqdm_bar
14
14
  from .. core.exceptions import RunStopException, ShapeError
15
- from .stack_framework import FrameMultiDirectory, SubAction
15
+ from .stack_framework import FramePaths, SubAction
16
16
  from .utils import read_img, save_plot, get_img_metadata, validate_image
17
17
 
18
18
  MAX_NOISY_PIXELS = 1000
@@ -45,9 +45,9 @@ def mean_image(file_paths, max_frames=-1, message_callback=None, progress_callba
45
45
  return None if mean_img is None else (mean_img / counter).astype(np.uint8)
46
46
 
47
47
 
48
- class NoiseDetection(JobBase, FrameMultiDirectory):
48
+ class NoiseDetection(JobBase, FramePaths):
49
49
  def __init__(self, name="noise-map", enabled=True, **kwargs):
50
- FrameMultiDirectory.__init__(self, name, **kwargs)
50
+ FramePaths.__init__(self, name, **kwargs)
51
51
  JobBase.__init__(self, name, enabled)
52
52
  self.max_frames = kwargs.get('max_frames', constants.DEFAULT_NOISE_MAX_FRAMES)
53
53
  self.blur_size = kwargs.get('blur_size', constants.DEFAULT_BLUR_SIZE)
@@ -76,8 +76,7 @@ class NoiseDetection(JobBase, FrameMultiDirectory):
76
76
  f"map noisy pixels from frames in {self.folder_list_str()}",
77
77
  constants.LOG_COLOR_LEVEL_2
78
78
  ))
79
- files = self.folder_filelist()
80
- in_paths = [self.working_path + "/" + f for f in files]
79
+ in_paths = self.input_filepaths()
81
80
  n_frames = min(len(in_paths), self.max_frames) if self.max_frames > 0 else len(in_paths)
82
81
  self.callback('step_counts', self.id, self.name, n_frames)
83
82
  if not config.DISABLE_TQDM:
@@ -90,7 +89,7 @@ class NoiseDetection(JobBase, FrameMultiDirectory):
90
89
  mean_img = mean_image(
91
90
  file_paths=in_paths, max_frames=self.max_frames,
92
91
  message_callback=lambda path: self.print_message_r(
93
- color_str(f"reading frame: {path.split('/')[-1]}", constants.LOG_COLOR_LEVEL_2)
92
+ color_str(f"reading frame: {os.path.basename(path)}", constants.LOG_COLOR_LEVEL_2)
94
93
  ),
95
94
  progress_callback=progress_callback)
96
95
  if not config.DISABLE_TQDM:
@@ -123,7 +122,7 @@ class NoiseDetection(JobBase, FrameMultiDirectory):
123
122
  plot_range[1] = max_th + 1
124
123
  th_range = np.arange(self.plot_range[0], self.plot_range[1] + 1)
125
124
  if self.plot_histograms:
126
- plt.figure(figsize=(10, 5))
125
+ plt.figure(figsize=constants.PLT_FIG_SIZE)
127
126
  x = np.array(list(th_range))
128
127
  ys = [[np.count_nonzero(self.hot_map(ch, th) > 0)
129
128
  for th in th_range] for ch in channels]
@@ -6,14 +6,14 @@ from .. core.framework import JobBase
6
6
  from .. core.colors import color_str
7
7
  from .. core.exceptions import InvalidOptionError
8
8
  from .utils import write_img, extension_tif_jpg
9
- from .stack_framework import FrameDirectory, ActionList
9
+ from .stack_framework import FramePaths, ActionList
10
10
  from .exif import copy_exif_from_file_to_file
11
11
  from .denoise import denoise
12
12
 
13
13
 
14
- class FocusStackBase(JobBase, FrameDirectory):
14
+ class FocusStackBase(JobBase, FramePaths):
15
15
  def __init__(self, name, stack_algo, enabled=True, **kwargs):
16
- FrameDirectory.__init__(self, name, **kwargs)
16
+ FramePaths.__init__(self, name, **kwargs)
17
17
  JobBase.__init__(self, name, enabled)
18
18
  self.stack_algo = stack_algo
19
19
  self.exif_path = kwargs.pop('exif_path', '')
@@ -26,9 +26,10 @@ class FocusStackBase(JobBase, FrameDirectory):
26
26
  def focus_stack(self, filenames):
27
27
  self.sub_message_r(color_str(': reading input files', constants.LOG_COLOR_LEVEL_3))
28
28
  stacked_img = self.stack_algo.focus_stack()
29
- in_filename = filenames[0].split(".")
30
- out_filename = f"{self.output_dir}/{self.prefix}{in_filename[0]}." + \
31
- '.'.join(in_filename[1:])
29
+ in_filename = os.path.basename(filenames[0]).split(".")
30
+ out_filename = os.path.join(
31
+ self.output_full_path(),
32
+ f"{self.prefix}{in_filename[0]}." + '.'.join(in_filename[1:]))
32
33
  if self.denoise_amount > 0:
33
34
  self.sub_message_r(': denoise image')
34
35
  stacked_img = denoise(stacked_img, self.denoise_amount, self.denoise_amount)
@@ -50,7 +51,7 @@ class FocusStackBase(JobBase, FrameDirectory):
50
51
  self.frame_count += 1
51
52
 
52
53
  def init(self, job, working_path=''):
53
- FrameDirectory.init(self, job)
54
+ FramePaths.init(self, job)
54
55
  if self.exif_path is None:
55
56
  self.exif_path = job.paths[0]
56
57
  if self.exif_path != '':
@@ -82,20 +83,19 @@ class FocusStackBunch(ActionList, FocusStackBase):
82
83
 
83
84
  def begin(self):
84
85
  ActionList.begin(self)
85
- fnames = self.folder_filelist()
86
- self._chunks = get_bunches(fnames, self.frames, self.overlap)
86
+ self._chunks = get_bunches(self.input_filepaths(), self.frames, self.overlap)
87
87
  self.set_counts(len(self._chunks))
88
88
 
89
89
  def end(self):
90
90
  ActionList.end(self)
91
91
 
92
92
  def run_step(self):
93
- self.print_message_r(color_str(f"fusing bunch: {self.count + 1}/{self.counts}",
94
- constants.LOG_COLOR_LEVEL_2))
95
- img_files = [os.path.join(self.input_full_path, name)
96
- for name in self._chunks[self.count - 1]]
93
+ self.print_message_r(
94
+ color_str(f"fusing bunch: {self.current_action_count + 1}/{self.total_action_counts}",
95
+ constants.LOG_COLOR_LEVEL_2))
96
+ img_files = self._chunks[self.current_action_count - 1]
97
97
  self.stack_algo.init(img_files)
98
- self.focus_stack(self._chunks[self.count - 1])
98
+ self.focus_stack(self._chunks[self.current_action_count - 1])
99
99
 
100
100
 
101
101
  class FocusStack(FocusStackBase):
@@ -106,11 +106,11 @@ class FocusStack(FocusStackBase):
106
106
 
107
107
  def run_core(self):
108
108
  self.set_filelist()
109
- img_files = sorted([os.path.join(self.input_full_path, name) for name in self.filenames])
109
+ img_files = sorted(self.input_filepaths())
110
110
  self.stack_algo.init(img_files)
111
111
  self.callback('step_counts', self.id, self.name,
112
- self.stack_algo.total_steps(len(self.filenames)))
113
- self.focus_stack(self.filenames)
112
+ self.stack_algo.total_steps(self.num_input_filepaths()))
113
+ self.focus_stack(img_files)
114
114
 
115
115
  def init(self, job, _working_path=''):
116
116
  FocusStackBase.init(self, job, self.working_path)