shinestacker 1.2.1__py3-none-any.whl → 1.3.1__py3-none-any.whl

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 (46) hide show
  1. shinestacker/_version.py +1 -1
  2. shinestacker/algorithms/align.py +152 -112
  3. shinestacker/algorithms/align_auto.py +76 -0
  4. shinestacker/algorithms/align_parallel.py +336 -0
  5. shinestacker/algorithms/balance.py +3 -1
  6. shinestacker/algorithms/base_stack_algo.py +25 -22
  7. shinestacker/algorithms/depth_map.py +9 -14
  8. shinestacker/algorithms/multilayer.py +8 -8
  9. shinestacker/algorithms/noise_detection.py +10 -10
  10. shinestacker/algorithms/pyramid.py +10 -24
  11. shinestacker/algorithms/pyramid_auto.py +21 -24
  12. shinestacker/algorithms/pyramid_tiles.py +31 -25
  13. shinestacker/algorithms/stack.py +21 -17
  14. shinestacker/algorithms/stack_framework.py +98 -47
  15. shinestacker/algorithms/utils.py +16 -0
  16. shinestacker/algorithms/vignetting.py +13 -10
  17. shinestacker/app/gui_utils.py +10 -0
  18. shinestacker/app/main.py +10 -4
  19. shinestacker/app/project.py +3 -1
  20. shinestacker/app/retouch.py +3 -1
  21. shinestacker/config/constants.py +60 -25
  22. shinestacker/config/gui_constants.py +1 -1
  23. shinestacker/core/core_utils.py +4 -0
  24. shinestacker/core/framework.py +104 -23
  25. shinestacker/gui/action_config.py +4 -5
  26. shinestacker/gui/action_config_dialog.py +409 -239
  27. shinestacker/gui/base_form_dialog.py +2 -2
  28. shinestacker/gui/colors.py +1 -0
  29. shinestacker/gui/folder_file_selection.py +106 -0
  30. shinestacker/gui/gui_run.py +12 -10
  31. shinestacker/gui/main_window.py +10 -5
  32. shinestacker/gui/new_project.py +171 -73
  33. shinestacker/gui/project_controller.py +10 -6
  34. shinestacker/gui/project_converter.py +4 -2
  35. shinestacker/gui/project_editor.py +40 -28
  36. shinestacker/gui/select_path_widget.py +1 -1
  37. shinestacker/gui/sys_mon.py +97 -0
  38. shinestacker/gui/time_progress_bar.py +4 -3
  39. shinestacker/retouch/exif_data.py +1 -1
  40. shinestacker/retouch/image_editor_ui.py +2 -0
  41. {shinestacker-1.2.1.dist-info → shinestacker-1.3.1.dist-info}/METADATA +6 -6
  42. {shinestacker-1.2.1.dist-info → shinestacker-1.3.1.dist-info}/RECORD +46 -42
  43. {shinestacker-1.2.1.dist-info → shinestacker-1.3.1.dist-info}/WHEEL +0 -0
  44. {shinestacker-1.2.1.dist-info → shinestacker-1.3.1.dist-info}/entry_points.txt +0 -0
  45. {shinestacker-1.2.1.dist-info → shinestacker-1.3.1.dist-info}/licenses/LICENSE +0 -0
  46. {shinestacker-1.2.1.dist-info → shinestacker-1.3.1.dist-info}/top_level.txt +0 -0
@@ -53,3 +53,13 @@ def fill_app_menu(app, app_menu):
53
53
  exit_action.setShortcut(quit_short)
54
54
  exit_action.triggered.connect(app.quit)
55
55
  app_menu.addAction(exit_action)
56
+
57
+
58
+ def set_css_style(app):
59
+ css_style = """
60
+ QToolTip {
61
+ color: black;
62
+ border: 1px solid black;
63
+ }
64
+ """
65
+ app.setStyleSheet(css_style)
shinestacker/app/main.py CHANGED
@@ -16,7 +16,8 @@ from shinestacker.config.constants import constants
16
16
  from shinestacker.core.logging import setup_logging
17
17
  from shinestacker.gui.main_window import MainWindow
18
18
  from shinestacker.retouch.image_editor_ui import ImageEditorUI
19
- from shinestacker.app.gui_utils import disable_macos_special_menu_items, fill_app_menu
19
+ from shinestacker.app.gui_utils import (
20
+ disable_macos_special_menu_items, fill_app_menu, set_css_style)
20
21
  from shinestacker.app.help_menu import add_help_action
21
22
  from shinestacker.app.open_frames import open_frames
22
23
 
@@ -142,9 +143,12 @@ class MainApp(QMainWindow):
142
143
  return app_menu
143
144
 
144
145
  def quit(self):
145
- self.retouch_window.quit()
146
- self.project_window.quit()
146
+ if not self.retouch_window.quit():
147
+ return False
148
+ if not self.project_window.quit():
149
+ return False
147
150
  self.close()
151
+ return True
148
152
 
149
153
  def switch_app(self, index):
150
154
  self.stacked_widget.setCurrentIndex(index)
@@ -192,7 +196,8 @@ class MainApp(QMainWindow):
192
196
  class Application(QApplication):
193
197
  def event(self, event):
194
198
  if event.type() == QEvent.Quit and event.spontaneous():
195
- self.main_app.quit()
199
+ if not self.quit():
200
+ return True
196
201
  return super().event(event)
197
202
 
198
203
 
@@ -229,6 +234,7 @@ expert options are visible by default.
229
234
  app.setWindowIcon(QIcon(icon_path))
230
235
  main_app = MainApp()
231
236
  app.main_app = main_app
237
+ set_css_style(app)
232
238
  main_app.show()
233
239
  main_app.activateWindow()
234
240
  if args['expert']:
@@ -14,7 +14,8 @@ config.init(DISABLE_TQDM=True, DONT_USE_NATIVE_MENU=True)
14
14
  from shinestacker.config.constants import constants
15
15
  from shinestacker.core.logging import setup_logging
16
16
  from shinestacker.gui.main_window import MainWindow
17
- from shinestacker.app.gui_utils import disable_macos_special_menu_items, fill_app_menu
17
+ from shinestacker.app.gui_utils import (
18
+ disable_macos_special_menu_items, fill_app_menu, set_css_style)
18
19
  from shinestacker.app.help_menu import add_help_action
19
20
 
20
21
 
@@ -63,6 +64,7 @@ expert options are visible by default.
63
64
  disable_macos_special_menu_items()
64
65
  icon_path = f"{os.path.dirname(__file__)}/../gui/ico/shinestacker.png"
65
66
  app.setWindowIcon(QIcon(icon_path))
67
+ set_css_style(app)
66
68
  window = ProjectApp()
67
69
  if args['expert']:
68
70
  window.set_expert_options()
@@ -9,7 +9,8 @@ from shinestacker.config.config import config
9
9
  config.init(DISABLE_TQDM=True, DONT_USE_NATIVE_MENU=True)
10
10
  from shinestacker.config.constants import constants
11
11
  from shinestacker.retouch.image_editor_ui import ImageEditorUI
12
- from shinestacker.app.gui_utils import disable_macos_special_menu_items, fill_app_menu
12
+ from shinestacker.app.gui_utils import (
13
+ disable_macos_special_menu_items, fill_app_menu, set_css_style)
13
14
  from shinestacker.app.help_menu import add_help_action
14
15
  from shinestacker.app.open_frames import open_frames
15
16
 
@@ -60,6 +61,7 @@ Multiple directories can be specified separated by ';'.
60
61
  disable_macos_special_menu_items()
61
62
  icon_path = f"{os.path.dirname(__file__)}/../gui/ico/shinestacker.png"
62
63
  app.setWindowIcon(QIcon(icon_path))
64
+ set_css_style(app)
63
65
  editor = RetouchApp()
64
66
  app.editor = editor
65
67
  editor.show()
@@ -37,19 +37,25 @@ class _Constants:
37
37
  ACTION_VIGNETTING = "Vignetting"
38
38
  ACTION_ALIGNFRAMES = "AlignFrames"
39
39
  ACTION_BALANCEFRAMES = "BalanceFrames"
40
- SUB_ACTION_TYPES = [ACTION_MASKNOISE, ACTION_VIGNETTING, ACTION_ALIGNFRAMES,
41
- ACTION_BALANCEFRAMES]
40
+ SUB_ACTION_TYPES = [ACTION_ALIGNFRAMES, ACTION_BALANCEFRAMES,
41
+ ACTION_VIGNETTING, ACTION_MASKNOISE]
42
42
  STACK_ALGO_PYRAMID = 'Pyramid'
43
43
  STACK_ALGO_DEPTH_MAP = 'Depth map'
44
44
  STACK_ALGO_OPTIONS = [STACK_ALGO_PYRAMID, STACK_ALGO_DEPTH_MAP]
45
45
  STACK_ALGO_DEFAULT = STACK_ALGO_PYRAMID
46
- DEFAULT_PLOTS_PATH = 'plots'
47
46
 
48
- FIELD_SUBSAMPLE_VALUES_1 = [2, 3, 4, 6, 8, 12, 16, 24, 32]
49
- FIELD_SUBSAMPLE_OPTIONS_1 = [f"1/{n} × 1/{n}" for n in FIELD_SUBSAMPLE_VALUES_1]
50
- FIELD_SUBSAMPLE_VALUES = [0, 1] + FIELD_SUBSAMPLE_VALUES_1
51
- FIELD_SUBSAMPLE_OPTIONS = ['Auto', 'Full resolution'] + FIELD_SUBSAMPLE_OPTIONS_1
52
- FIELD_SUBSAMPLE_DEFAULT = FIELD_SUBSAMPLE_VALUES[0]
47
+ ACTION_ICONS = {
48
+ ACTION_JOB: '',
49
+ ACTION_COMBO: '',
50
+ ACTION_NOISEDETECTION: '',
51
+ ACTION_FOCUSSTACK: '',
52
+ ACTION_FOCUSSTACKBUNCH: '',
53
+ ACTION_MULTILAYER: '',
54
+ ACTION_MASKNOISE: '',
55
+ ACTION_VIGNETTING: '',
56
+ ACTION_ALIGNFRAMES: '',
57
+ ACTION_BALANCEFRAMES: ''
58
+ }
53
59
 
54
60
  PATH_SEPARATOR = ';'
55
61
 
@@ -60,10 +66,39 @@ class _Constants:
60
66
  LOG_COLOR_LEVEL_2 = 'magenta'
61
67
  LOG_COLOR_LEVEL_3 = 'cyan'
62
68
 
69
+ STATUS_RUNNING = 1
70
+ STATUS_PAUSED = 2
71
+ STATUS_STOPPED = 3
72
+
73
+ RUN_COMPLETED = 0
74
+ RUN_ONGOING = 1
75
+ RUN_FAILED = 2
76
+ RUN_STOPPED = 3
77
+
78
+ CALLBACK_BEFORE_ACTION = 'before_action'
79
+ CALLBACK_AFTER_ACTION = 'after_action'
80
+ CALLBACK_STEP_COUNTS = 'step_counts'
81
+ CALLBACK_BEGIN_STEPS = 'begin_steps'
82
+ CALLBACK_END_STEPS = 'end_steps'
83
+ CALLBACK_AFTER_STEP = 'after_step'
84
+ CALLBACK_CHECK_RUNNING = 'check_running'
85
+ CALLBACK_SAVE_PLOT = 'save_plot'
86
+ CALLBACK_OPEN_APP = 'open_app'
87
+
63
88
  DEFAULT_FILE_REVERSE_ORDER = False
64
89
  DEFAULT_MULTILAYER_FILE_REVERSE_ORDER = True
65
90
  MULTILAYER_WARNING_MEM_GB = 1
66
91
 
92
+ DEFAULT_PLOTS_PATH = 'plots'
93
+ DEFAULT_MAX_FWK_THREADS = 8
94
+ DEFAULT_MAX_FWK_CHUNK_SUBMIT = True
95
+
96
+ FIELD_SUBSAMPLE_VALUES_1 = [2, 3, 4, 6, 8, 12, 16, 24, 32]
97
+ FIELD_SUBSAMPLE_OPTIONS_1 = [f"1/{n} × 1/{n}" for n in FIELD_SUBSAMPLE_VALUES_1]
98
+ FIELD_SUBSAMPLE_VALUES = [0, 1] + FIELD_SUBSAMPLE_VALUES_1
99
+ FIELD_SUBSAMPLE_OPTIONS = ['Auto', 'Full resolution'] + FIELD_SUBSAMPLE_OPTIONS_1
100
+ FIELD_SUBSAMPLE_DEFAULT = FIELD_SUBSAMPLE_VALUES[0]
101
+
67
102
  DEFAULT_NOISE_MAP_FILENAME = "noise-map/hot_pixels.png"
68
103
  DEFAULT_NOISE_MAX_FRAMES = 10
69
104
  DEFAULT_MN_KERNEL_SIZE = 3
@@ -104,9 +139,9 @@ class _Constants:
104
139
  NOKNN_METHODS = {'detectors': [DETECTOR_ORB, DETECTOR_SURF, DETECTOR_AKAZE, DETECTOR_BRISK],
105
140
  'descriptors': [DESCRIPTOR_ORB, DESCRIPTOR_AKAZE, DESCRIPTOR_BRISK]}
106
141
 
107
- DEFAULT_DETECTOR = DETECTOR_SIFT
108
- DEFAULT_DESCRIPTOR = DESCRIPTOR_SIFT
109
- DEFAULT_MATCHING_METHOD = MATCHING_KNN
142
+ DEFAULT_DETECTOR = DETECTOR_ORB
143
+ DEFAULT_DESCRIPTOR = DESCRIPTOR_ORB
144
+ DEFAULT_MATCHING_METHOD = MATCHING_NORM_HAMMING
110
145
  DEFAULT_FLANN_IDX_KDTREE = 2
111
146
  DEFAULT_FLANN_TREES = 5
112
147
  DEFAULT_FLANN_CHECKS = 50
@@ -124,7 +159,13 @@ class _Constants:
124
159
  DEFAULT_ALIGN_SUBSAMPLE = 0
125
160
  DEFAULT_ALIGN_RES_TARGET_MPX = 2
126
161
  DEFAULT_ALIGN_FAST_SUBSAMPLING = False
127
- DEFAULT_ALIGN_MIN_GOOD_MATCHES = 50
162
+ DEFAULT_ALIGN_MIN_GOOD_MATCHES = 20
163
+ ALIGN_VALID_MODES = ['auto', 'sequential', 'parallel']
164
+ DEFAULT_ALIGN_MODE = 'auto'
165
+ DEFAULT_ALIGN_MEMORY_LIMIT_GB = 8
166
+ DEFAULT_ALIGN_MAX_THREADS = min(os.cpu_count() or 4, 8)
167
+ DEFAULT_ALIGN_CHUNK_SUBMIT = True
168
+ DEFAULT_ALIGN_BW_MATCHING = False
128
169
 
129
170
  BALANCE_LINEAR = "LINEAR"
130
171
  BALANCE_GAMMA = "GAMMA"
@@ -162,13 +203,13 @@ class _Constants:
162
203
  DEFAULT_STACK_PREFIX = "stack_"
163
204
  DEFAULT_BUNCH_PREFIX = "bunch_"
164
205
 
165
- DEFAULT_DM_FLOAT = FLOAT_32
166
206
  DM_ENERGY_LAPLACIAN = "laplacian"
167
207
  DM_ENERGY_SOBEL = "sobel"
168
208
  DM_MAP_AVERAGE = "average"
169
209
  DM_MAP_MAX = "max"
170
210
  VALID_DM_MAP = [DM_MAP_AVERAGE, DM_MAP_MAX]
171
211
  VALID_DM_ENERGY = [DM_ENERGY_LAPLACIAN, DM_ENERGY_SOBEL]
212
+ DEFAULT_DM_FLOAT = FLOAT_32
172
213
  DEFAULT_DM_MAP = DM_MAP_AVERAGE
173
214
  DEFAULT_DM_ENERGY = DM_ENERGY_LAPLACIAN
174
215
  DEFAULT_DM_KERNEL_SIZE = 5
@@ -185,22 +226,16 @@ class _Constants:
185
226
  DEFAULT_PY_N_TILED_LAYERS = 2
186
227
  DEFAULT_PY_MEMORY_LIMIT_GB = 8
187
228
  DEFAULT_PY_MAX_THREADS = min(os.cpu_count() or 4, 8)
188
- DEFAULT_PY_MODE = 'auto'
189
229
  PY_VALID_MODES = ['auto', 'memory', 'tiled']
190
- MIN_PY_TILE_SIZE = 256
230
+ DEFAULT_PY_MODE = 'auto'
231
+ DEFAULT_PY_MAX_TILE_SIZE = 4096
232
+ DEFAULT_PY_MIN_TILE_SIZE = 128
233
+ DEFAULT_PY_MIN_N_TILED_LAYERS = 1
234
+ PY_MEMORY_OVERHEAD = 2.5
191
235
 
192
- DEFAULT_PLOT_STACK_BUNCH = False
236
+ DEFAULT_PLOT_STACK_BUNCH = True
193
237
  DEFAULT_PLOT_STACK = True
194
238
 
195
- STATUS_RUNNING = 1
196
- STATUS_PAUSED = 2
197
- STATUS_STOPPED = 3
198
-
199
- RUN_COMPLETED = 0
200
- RUN_ONGOING = 1
201
- RUN_FAILED = 2
202
- RUN_STOPPED = 3
203
-
204
239
  def __setattr__aux(self, name, value):
205
240
  raise AttributeError(f"Can't reassign constant '{name}'")
206
241
 
@@ -16,7 +16,7 @@ class _GuiConstants:
16
16
  NEW_PROJECT_ALIGN_FRAMES = True
17
17
  NEW_PROJECT_BALANCE_FRAMES = True
18
18
  NEW_PROJECT_BUNCH_STACK = False
19
- NEW_PROJECT_BUNCH_FRAMES = {'min': 2, 'max': 20}
19
+ NEW_PROJECT_BUNCH_FRAMES = {'min': 1, 'max': 20}
20
20
  NEW_PROJECT_BUNCH_OVERLAP = {'min': 0, 'max': 10}
21
21
  NEW_PROJECT_FOCUS_STACK_PYRAMID = True
22
22
  NEW_PROJECT_FOCUS_STACK_DEPTH_MAP = False
@@ -50,3 +50,7 @@ def running_under_macos() -> bool:
50
50
 
51
51
  def running_under_linux() -> bool:
52
52
  return platform.system().lower() == 'linux'
53
+
54
+
55
+ def make_chunks(ll, max_size):
56
+ return [ll[i:i + max_size] for i in range(0, len(ll), max_size)]
@@ -1,15 +1,17 @@
1
- # pylint: disable=C0114, C0115, C0116, R0917, R0913, R0902
1
+ # pylint: disable=C0114, C0115, C0116, R0917, R0913, R0902, W0718
2
2
  import time
3
3
  import logging
4
+ import traceback
5
+ from concurrent.futures import ThreadPoolExecutor, as_completed
4
6
  from .. config.constants import constants
5
7
  from .. config.config import config
6
8
  from .colors import color_str
7
9
  from .logging import setup_logging
8
- from .core_utils import make_tqdm_bar
10
+ from .core_utils import make_tqdm_bar, make_chunks
9
11
  from .exceptions import RunStopException
10
12
 
11
13
  LINE_UP = "\r\033[A"
12
- TRAILING_SPACES = " " * 30
14
+ TRAILING_SPACES = " " * 50
13
15
 
14
16
 
15
17
  class TqdmCallbacks:
@@ -62,7 +64,7 @@ def elapsed_time_str(start):
62
64
  return f"{hh:02d}:{mm:02d}:{ss:05.2f}s"
63
65
 
64
66
 
65
- class JobBase:
67
+ class TaskBase:
66
68
  def __init__(self, name, enabled=True):
67
69
  self.id = -1
68
70
  self.name = name
@@ -77,8 +79,7 @@ class JobBase:
77
79
  self.begin_r, self.end_r = LINE_UP, None
78
80
 
79
81
  def callback(self, key, *args):
80
- has_callbacks = hasattr(self, 'callbacks')
81
- if has_callbacks and self.callbacks is not None:
82
+ if self.callbacks is not None:
82
83
  callback = self.callbacks.get(key, None)
83
84
  if callback:
84
85
  return callback(*args)
@@ -92,9 +93,9 @@ class JobBase:
92
93
  if not self.enabled:
93
94
  self.get_logger().warning(color_str(self.name + ": entire job disabled",
94
95
  constants.LOG_COLOR_ALERT))
95
- self.callback('before_action', self.id, self.name)
96
+ self.callback(constants.CALLBACK_BEFORE_ACTION, self.id, self.name)
96
97
  self.run_core()
97
- self.callback('after_action', self.id, self.name)
98
+ self.callback(constants.CALLBACK_AFTER_ACTION, self.id, self.name)
98
99
  msg_name = color_str(self.name + ":", constants.LOG_COLOR_LEVEL_JOB, "bold")
99
100
  msg_time = color_str(f"elapsed time: {elapsed_time_str(self._t0)}",
100
101
  constants.LOG_COLOR_LEVEL_JOB)
@@ -146,9 +147,9 @@ class JobBase:
146
147
  self.sub_message(msg, level, self.end_r, self.begin_r, False)
147
148
 
148
149
 
149
- class Job(JobBase):
150
+ class Job(TaskBase):
150
151
  def __init__(self, name, logger_name=None, log_file='', callbacks=None, **kwargs):
151
- JobBase.__init__(self, name, **kwargs)
152
+ TaskBase.__init__(self, name, **kwargs)
152
153
  self.action_counter = 0
153
154
  self.__actions = []
154
155
  if logger_name is None:
@@ -163,7 +164,7 @@ class Job(JobBase):
163
164
  def init(self, a):
164
165
  pass
165
166
 
166
- def add_action(self, a: JobBase):
167
+ def add_action(self, a: TaskBase):
167
168
  a.id = self.action_counter
168
169
  self.action_counter += 1
169
170
  a.logger = self.logger
@@ -183,47 +184,127 @@ class Job(JobBase):
183
184
  self.get_logger().warning(color_str(a.name + f": {msg} disabled",
184
185
  constants.LOG_COLOR_ALERT))
185
186
  else:
186
- if self.callback('check_running', self.id, self.name) is False:
187
+ if self.callback(constants.CALLBACK_CHECK_RUNNING,
188
+ self.id, self.name) is False:
187
189
  raise RunStopException(self.name)
188
190
  a.run()
189
191
 
190
192
 
191
- class ActionList(JobBase):
193
+ class SequentialTask(TaskBase):
192
194
  def __init__(self, name, enabled=True, **kwargs):
193
- JobBase.__init__(self, name, enabled, **kwargs)
195
+ self.max_threads = kwargs.pop('max_threads', constants.DEFAULT_MAX_FWK_THREADS)
196
+ self.chunk_submit = kwargs.pop('chunk_submit', constants.DEFAULT_MAX_FWK_CHUNK_SUBMIT)
197
+ TaskBase.__init__(self, name, enabled, **kwargs)
194
198
  self.total_action_counts = None
195
199
  self.current_action_count = None
200
+ self.begin_steps = 0
196
201
 
197
202
  def set_counts(self, counts):
198
203
  self.total_action_counts = counts
199
- self.callback('step_counts', self.id, self.name, self.total_action_counts)
204
+ self.callback(constants.CALLBACK_STEP_COUNTS,
205
+ self.id, self.name, self.total_action_counts)
206
+
207
+ def add_begin_steps(self, steps):
208
+ self.begin_steps += steps
200
209
 
201
210
  def begin(self):
202
- self.callback('begin_steps', self.id, self.name)
211
+ self.callback(constants.CALLBACK_BEGIN_STEPS, self.id, self.name)
203
212
 
204
213
  def end(self):
205
- self.callback('end_steps', self.id, self.name)
214
+ self.callback(constants.CALLBACK_END_STEPS, self.id, self.name)
206
215
 
207
216
  def __iter__(self):
208
217
  self.current_action_count = 0
209
218
  return self
210
219
 
211
- def run_step(self):
220
+ def run_step(self, action_count=-1):
212
221
  pass
213
222
 
214
223
  def __next__(self):
215
224
  if self.current_action_count < self.total_action_counts:
216
- self.run_step()
225
+ self.run_step(self.current_action_count)
217
226
  x = self.current_action_count
218
227
  self.current_action_count += 1
219
228
  return x
220
229
  raise StopIteration
221
230
 
231
+ def check_running(self):
232
+ if self.callback(constants.CALLBACK_CHECK_RUNNING,
233
+ self.id, self.name) is False:
234
+ raise RunStopException(self.name)
235
+
236
+ def after_step(self, step=-1):
237
+ if step == -1:
238
+ step = self.current_action_count + self.begin_steps
239
+ self.callback(constants.CALLBACK_AFTER_STEP, self.id, self.name, step)
240
+
241
+ def run_core_serial(self):
242
+ for _ in iter(self):
243
+ self.after_step()
244
+ self.check_running()
245
+
246
+ def idx_tot_str(self, idx):
247
+ return f"{idx + 1}/{self.total_action_counts}"
248
+
249
+ def run_core_parallel_single_chunk(self, idx_chunk):
250
+ with ThreadPoolExecutor(max_workers=self.max_threads) as executor:
251
+ future_to_index = {}
252
+ for idx in idx_chunk:
253
+ self.print_message(color_str(
254
+ f"submit processing step: {self.idx_tot_str(idx)}",
255
+ constants.LOG_COLOR_LEVEL_1))
256
+ future = executor.submit(self.run_step, idx)
257
+ future_to_index[future] = idx
258
+ self.check_running()
259
+ for future in as_completed(future_to_index):
260
+ idx = future_to_index[future]
261
+ try:
262
+ result = future.result()
263
+ if result:
264
+ self.print_message(color_str(
265
+ f"completed processing step: {self.idx_tot_str(idx)}",
266
+ constants.LOG_COLOR_LEVEL_1))
267
+ else:
268
+ self.print_message(color_str(
269
+ f"failed processing step: {self.idx_tot_str(idx)}",
270
+ constants.LOG_COLOR_WARNING))
271
+ self.current_action_count += 1
272
+ self.after_step()
273
+ self.check_running()
274
+ except RunStopException as e:
275
+ raise e
276
+ except Exception as e:
277
+ traceback.print_tb(e.__traceback__)
278
+ self.print_message(color_str(
279
+ f"failed processing step: {idx + 1}: {str(e)}",
280
+ constants.LOG_COLOR_ALERT))
281
+
282
+ def run_core_parallel(self):
283
+ self.current_action_count = 0
284
+ self.run_core_parallel_single_chunk(list(range(self.total_action_counts)))
285
+
286
+ def run_core_parallel_chunks(self):
287
+ self.current_action_count = 0
288
+ action_idx_list = list(range(self.total_action_counts))
289
+ max_chunck_size = self.max_threads
290
+ action_idx_chunks = make_chunks(action_idx_list, max_chunck_size)
291
+ for idx_chunk in action_idx_chunks:
292
+ self.run_core_parallel_single_chunk(idx_chunk)
293
+
222
294
  def run_core(self):
223
295
  self.print_message(color_str('begin run', constants.LOG_COLOR_LEVEL_2), end='\n')
224
296
  self.begin()
225
- for _ in iter(self):
226
- self.callback('after_step', self.id, self.name, self.current_action_count)
227
- if self.callback('check_running', self.id, self.name) is False:
228
- raise RunStopException(self.name)
297
+ if self.run_sequential():
298
+ self.run_core_serial()
299
+ else:
300
+ if self.chunk_submit:
301
+ self.run_core_parallel_chunks()
302
+ else:
303
+ self.run_core_parallel()
229
304
  self.end()
305
+
306
+ def sequential_processing(self):
307
+ return False
308
+
309
+ def run_sequential(self):
310
+ return self.sequential_processing() or self.max_threads == 1
@@ -1,8 +1,7 @@
1
1
  # pylint: disable=C0114, C0115, C0116, E0611, R0913, R0917, R0915, R0912
2
- # pylint: disable=E0606, W0718, R1702, W0102, W0221
2
+ # pylint: disable=E0606, W0718, R1702, W0102, W0221, R0914, E1121
3
3
  import traceback
4
4
  from abc import ABC, abstractmethod
5
- from typing import Dict, Any
6
5
  import os.path
7
6
  from PySide6.QtWidgets import (QPushButton, QHBoxLayout, QFileDialog, QLabel, QComboBox,
8
7
  QMessageBox, QSizePolicy, QLineEdit, QSpinBox,
@@ -128,7 +127,7 @@ class FieldBuilder:
128
127
  spinbox = widget.layout().itemAt(1 + i * 2).widget()
129
128
  spinbox.setValue(default[i])
130
129
  elif field['type'] == FIELD_COMBO:
131
- widget.setCurrentText(default)
130
+ widget.setCurrentText(str(default))
132
131
 
133
132
  def get_path_widget(self, widget):
134
133
  return widget.layout().itemAt(0).widget()
@@ -145,7 +144,7 @@ class FieldBuilder:
145
144
  parent = parent.parent
146
145
  return ''
147
146
 
148
- def update_params(self, params: Dict[str, Any]) -> bool:
147
+ def update_params(self, params):
149
148
  for tag, field in self.fields.items():
150
149
  if field['type'] == FIELD_TEXT:
151
150
  params[tag] = field['widget'].text()
@@ -224,7 +223,7 @@ class FieldBuilder:
224
223
  self.action.params.get(tag, ''),
225
224
  kwargs.get('placeholder', ''),
226
225
  tag.replace('_', ' ')
227
- )[1]
226
+ )
228
227
 
229
228
  def create_rel_path_field(self, tag, **kwargs):
230
229
  value = self.action.params.get(tag, kwargs.get('default', ''))