shinestacker 1.2.1__py3-none-any.whl → 1.3.0__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 (40) hide show
  1. shinestacker/_version.py +1 -1
  2. shinestacker/algorithms/align.py +126 -94
  3. shinestacker/algorithms/align_auto.py +64 -0
  4. shinestacker/algorithms/align_parallel.py +296 -0
  5. shinestacker/algorithms/balance.py +3 -1
  6. shinestacker/algorithms/base_stack_algo.py +11 -2
  7. shinestacker/algorithms/multilayer.py +8 -8
  8. shinestacker/algorithms/noise_detection.py +10 -10
  9. shinestacker/algorithms/pyramid.py +4 -4
  10. shinestacker/algorithms/pyramid_auto.py +16 -10
  11. shinestacker/algorithms/pyramid_tiles.py +19 -11
  12. shinestacker/algorithms/stack.py +21 -17
  13. shinestacker/algorithms/stack_framework.py +97 -46
  14. shinestacker/algorithms/vignetting.py +13 -10
  15. shinestacker/app/main.py +7 -3
  16. shinestacker/config/constants.py +60 -25
  17. shinestacker/config/gui_constants.py +1 -1
  18. shinestacker/core/core_utils.py +4 -0
  19. shinestacker/core/framework.py +104 -23
  20. shinestacker/gui/action_config.py +4 -5
  21. shinestacker/gui/action_config_dialog.py +152 -12
  22. shinestacker/gui/base_form_dialog.py +2 -2
  23. shinestacker/gui/folder_file_selection.py +101 -0
  24. shinestacker/gui/gui_run.py +12 -10
  25. shinestacker/gui/main_window.py +6 -1
  26. shinestacker/gui/new_project.py +171 -73
  27. shinestacker/gui/project_controller.py +10 -6
  28. shinestacker/gui/project_converter.py +4 -2
  29. shinestacker/gui/project_editor.py +37 -27
  30. shinestacker/gui/select_path_widget.py +1 -1
  31. shinestacker/gui/sys_mon.py +96 -0
  32. shinestacker/gui/time_progress_bar.py +4 -3
  33. shinestacker/retouch/exif_data.py +1 -1
  34. shinestacker/retouch/image_editor_ui.py +2 -0
  35. {shinestacker-1.2.1.dist-info → shinestacker-1.3.0.dist-info}/METADATA +6 -6
  36. {shinestacker-1.2.1.dist-info → shinestacker-1.3.0.dist-info}/RECORD +40 -36
  37. {shinestacker-1.2.1.dist-info → shinestacker-1.3.0.dist-info}/WHEEL +0 -0
  38. {shinestacker-1.2.1.dist-info → shinestacker-1.3.0.dist-info}/entry_points.txt +0 -0
  39. {shinestacker-1.2.1.dist-info → shinestacker-1.3.0.dist-info}/licenses/LICENSE +0 -0
  40. {shinestacker-1.2.1.dist-info → shinestacker-1.3.0.dist-info}/top_level.txt +0 -0
@@ -134,7 +134,8 @@ class Vignetting(SubAction):
134
134
  self.corrections = None
135
135
 
136
136
  def run_frame(self, idx, _ref_idx, img_0):
137
- self.process.sub_message_r(color_str(": compute vignetting", "cyan"))
137
+ self.process.print_message(
138
+ color_str(f"{self.process.idx_tot_str(idx)}: compute vignetting", "cyan"))
138
139
  h, w = img_0.shape[:2]
139
140
  self.w_2, self.h_2 = w / 2, h / 2
140
141
  self.r_max = np.sqrt((w / 2)**2 + (h / 2)**2)
@@ -153,12 +154,13 @@ class Vignetting(SubAction):
153
154
  return img_0
154
155
  self.v0 = sigmoid_model(0, *params)
155
156
  i0_fit, k_fit, r0_fit = params
156
- self.process.sub_message(color_str(": vignetting model parameters: ", "cyan") +
157
- color_str(f"i0={i0_fit / 2:.4f}, "
158
- f"k={k_fit * self.r_max:.4f}, "
159
- f"r0={r0_fit / self.r_max:.4f}",
160
- "light_blue"),
161
- level=logging.DEBUG)
157
+ self.process.print_message(
158
+ color_str(f"{self.process.idx_tot_str(idx)}: vignetting model parameters: ", "cyan") +
159
+ color_str(f"i0={i0_fit / 2:.4f}, "
160
+ f"k={k_fit * self.r_max:.4f}, "
161
+ f"r0={r0_fit / self.r_max:.4f}",
162
+ "light_blue"),
163
+ level=logging.DEBUG)
162
164
  if self.plot_correction:
163
165
  plt.figure(figsize=constants.PLT_FIG_SIZE)
164
166
  plt.plot(radii, intensities, label="image mean intensity")
@@ -175,12 +177,13 @@ class Vignetting(SubAction):
175
177
  save_plot(plot_path)
176
178
  plt.close('all')
177
179
  self.process.callback(
178
- 'save_plot', self.process.id,
180
+ constants.CALLBACK_SAVE_PLOT, self.process.id,
179
181
  f"{self.process.name}: intensity\nframe {idx_str}", plot_path)
180
182
  for i, p in enumerate(self.percentiles):
181
183
  self.corrections[i][idx] = fsolve(lambda x: sigmoid_model(x, *params) /
182
184
  self.v0 - p, r0_fit)[0]
183
- self.process.sub_message_r(color_str(": correct vignetting", "cyan"))
185
+ self.process.print_message(
186
+ color_str(f"{self.process.idx_tot_str(idx)}: correct vignetting", "cyan"))
184
187
  return correct_vignetting(
185
188
  img_0, self.max_correction, self.black_threshold, None, params, self.v0,
186
189
  subsample, self.fast_subsampling)
@@ -224,5 +227,5 @@ class Vignetting(SubAction):
224
227
  f"{self.process.name}-r0.pdf"
225
228
  save_plot(plot_path)
226
229
  plt.close('all')
227
- self.process.callback('save_plot', self.process.id,
230
+ self.process.callback(constants.CALLBACK_SAVE_PLOT, self.process.id,
228
231
  f"{self.process.name}: vignetting", plot_path)
shinestacker/app/main.py CHANGED
@@ -142,9 +142,12 @@ class MainApp(QMainWindow):
142
142
  return app_menu
143
143
 
144
144
  def quit(self):
145
- self.retouch_window.quit()
146
- self.project_window.quit()
145
+ if not self.retouch_window.quit():
146
+ return False
147
+ if not self.project_window.quit():
148
+ return False
147
149
  self.close()
150
+ return True
148
151
 
149
152
  def switch_app(self, index):
150
153
  self.stacked_widget.setCurrentIndex(index)
@@ -192,7 +195,8 @@ class MainApp(QMainWindow):
192
195
  class Application(QApplication):
193
196
  def event(self, event):
194
197
  if event.type() == QEvent.Quit and event.spontaneous():
195
- self.main_app.quit()
198
+ if not self.quit():
199
+ return True
196
200
  return super().event(event)
197
201
 
198
202
 
@@ -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', ''))