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.
- shinestacker/_version.py +1 -1
- shinestacker/algorithms/align.py +152 -112
- shinestacker/algorithms/align_auto.py +76 -0
- shinestacker/algorithms/align_parallel.py +336 -0
- shinestacker/algorithms/balance.py +3 -1
- shinestacker/algorithms/base_stack_algo.py +25 -22
- shinestacker/algorithms/depth_map.py +9 -14
- shinestacker/algorithms/multilayer.py +8 -8
- shinestacker/algorithms/noise_detection.py +10 -10
- shinestacker/algorithms/pyramid.py +10 -24
- shinestacker/algorithms/pyramid_auto.py +21 -24
- shinestacker/algorithms/pyramid_tiles.py +31 -25
- shinestacker/algorithms/stack.py +21 -17
- shinestacker/algorithms/stack_framework.py +98 -47
- shinestacker/algorithms/utils.py +16 -0
- shinestacker/algorithms/vignetting.py +13 -10
- shinestacker/app/gui_utils.py +10 -0
- shinestacker/app/main.py +10 -4
- shinestacker/app/project.py +3 -1
- shinestacker/app/retouch.py +3 -1
- shinestacker/config/constants.py +60 -25
- shinestacker/config/gui_constants.py +1 -1
- shinestacker/core/core_utils.py +4 -0
- shinestacker/core/framework.py +104 -23
- shinestacker/gui/action_config.py +4 -5
- shinestacker/gui/action_config_dialog.py +409 -239
- shinestacker/gui/base_form_dialog.py +2 -2
- shinestacker/gui/colors.py +1 -0
- shinestacker/gui/folder_file_selection.py +106 -0
- shinestacker/gui/gui_run.py +12 -10
- shinestacker/gui/main_window.py +10 -5
- shinestacker/gui/new_project.py +171 -73
- shinestacker/gui/project_controller.py +10 -6
- shinestacker/gui/project_converter.py +4 -2
- shinestacker/gui/project_editor.py +40 -28
- shinestacker/gui/select_path_widget.py +1 -1
- shinestacker/gui/sys_mon.py +97 -0
- shinestacker/gui/time_progress_bar.py +4 -3
- shinestacker/retouch/exif_data.py +1 -1
- shinestacker/retouch/image_editor_ui.py +2 -0
- {shinestacker-1.2.1.dist-info → shinestacker-1.3.1.dist-info}/METADATA +6 -6
- {shinestacker-1.2.1.dist-info → shinestacker-1.3.1.dist-info}/RECORD +46 -42
- {shinestacker-1.2.1.dist-info → shinestacker-1.3.1.dist-info}/WHEEL +0 -0
- {shinestacker-1.2.1.dist-info → shinestacker-1.3.1.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.2.1.dist-info → shinestacker-1.3.1.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.2.1.dist-info → shinestacker-1.3.1.dist-info}/top_level.txt +0 -0
shinestacker/app/gui_utils.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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.
|
|
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']:
|
shinestacker/app/project.py
CHANGED
|
@@ -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
|
|
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()
|
shinestacker/app/retouch.py
CHANGED
|
@@ -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
|
|
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()
|
shinestacker/config/constants.py
CHANGED
|
@@ -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 = [
|
|
41
|
-
|
|
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
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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 =
|
|
108
|
-
DEFAULT_DESCRIPTOR =
|
|
109
|
-
DEFAULT_MATCHING_METHOD =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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':
|
|
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
|
shinestacker/core/core_utils.py
CHANGED
shinestacker/core/framework.py
CHANGED
|
@@ -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 = " " *
|
|
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
|
|
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
|
-
|
|
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(
|
|
96
|
+
self.callback(constants.CALLBACK_BEFORE_ACTION, self.id, self.name)
|
|
96
97
|
self.run_core()
|
|
97
|
-
self.callback(
|
|
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(
|
|
150
|
+
class Job(TaskBase):
|
|
150
151
|
def __init__(self, name, logger_name=None, log_file='', callbacks=None, **kwargs):
|
|
151
|
-
|
|
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:
|
|
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(
|
|
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
|
|
193
|
+
class SequentialTask(TaskBase):
|
|
192
194
|
def __init__(self, name, enabled=True, **kwargs):
|
|
193
|
-
|
|
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(
|
|
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(
|
|
211
|
+
self.callback(constants.CALLBACK_BEGIN_STEPS, self.id, self.name)
|
|
203
212
|
|
|
204
213
|
def end(self):
|
|
205
|
-
self.callback(
|
|
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
|
-
|
|
226
|
-
self.
|
|
227
|
-
|
|
228
|
-
|
|
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
|
|
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
|
-
)
|
|
226
|
+
)
|
|
228
227
|
|
|
229
228
|
def create_rel_path_field(self, tag, **kwargs):
|
|
230
229
|
value = self.action.params.get(tag, kwargs.get('default', ''))
|