shinestacker 1.2.0__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.
- shinestacker/_version.py +1 -1
- shinestacker/algorithms/align.py +148 -115
- shinestacker/algorithms/align_auto.py +64 -0
- shinestacker/algorithms/align_parallel.py +296 -0
- shinestacker/algorithms/balance.py +14 -13
- shinestacker/algorithms/base_stack_algo.py +11 -2
- shinestacker/algorithms/multilayer.py +14 -15
- shinestacker/algorithms/noise_detection.py +13 -14
- shinestacker/algorithms/pyramid.py +4 -4
- shinestacker/algorithms/pyramid_auto.py +16 -10
- shinestacker/algorithms/pyramid_tiles.py +19 -11
- shinestacker/algorithms/stack.py +30 -26
- shinestacker/algorithms/stack_framework.py +200 -178
- shinestacker/algorithms/vignetting.py +16 -13
- shinestacker/app/main.py +7 -3
- shinestacker/config/constants.py +63 -26
- shinestacker/config/gui_constants.py +1 -1
- shinestacker/core/core_utils.py +4 -0
- shinestacker/core/framework.py +114 -33
- shinestacker/gui/action_config.py +57 -5
- shinestacker/gui/action_config_dialog.py +156 -17
- shinestacker/gui/base_form_dialog.py +2 -2
- shinestacker/gui/folder_file_selection.py +101 -0
- shinestacker/gui/gui_images.py +10 -10
- shinestacker/gui/gui_run.py +13 -11
- shinestacker/gui/main_window.py +10 -5
- shinestacker/gui/menu_manager.py +4 -0
- shinestacker/gui/new_project.py +171 -74
- shinestacker/gui/project_controller.py +13 -9
- shinestacker/gui/project_converter.py +4 -2
- shinestacker/gui/project_editor.py +72 -53
- shinestacker/gui/select_path_widget.py +1 -1
- shinestacker/gui/sys_mon.py +96 -0
- shinestacker/gui/tab_widget.py +3 -3
- 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.0.dist-info → shinestacker-1.3.0.dist-info}/METADATA +6 -6
- {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/RECORD +43 -39
- {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/WHEEL +0 -0
- {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/top_level.txt +0 -0
shinestacker/config/constants.py
CHANGED
|
@@ -22,6 +22,8 @@ class _Constants:
|
|
|
22
22
|
|
|
23
23
|
ANSI_ESCAPE = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
|
24
24
|
|
|
25
|
+
PLT_FIG_SIZE = (10, 5)
|
|
26
|
+
|
|
25
27
|
ACTION_JOB = "Job"
|
|
26
28
|
ACTION_COMBO = "CombinedActions"
|
|
27
29
|
ACTION_NOISEDETECTION = "NoiseDetection"
|
|
@@ -35,19 +37,25 @@ class _Constants:
|
|
|
35
37
|
ACTION_VIGNETTING = "Vignetting"
|
|
36
38
|
ACTION_ALIGNFRAMES = "AlignFrames"
|
|
37
39
|
ACTION_BALANCEFRAMES = "BalanceFrames"
|
|
38
|
-
SUB_ACTION_TYPES = [
|
|
39
|
-
|
|
40
|
+
SUB_ACTION_TYPES = [ACTION_ALIGNFRAMES, ACTION_BALANCEFRAMES,
|
|
41
|
+
ACTION_VIGNETTING, ACTION_MASKNOISE]
|
|
40
42
|
STACK_ALGO_PYRAMID = 'Pyramid'
|
|
41
43
|
STACK_ALGO_DEPTH_MAP = 'Depth map'
|
|
42
44
|
STACK_ALGO_OPTIONS = [STACK_ALGO_PYRAMID, STACK_ALGO_DEPTH_MAP]
|
|
43
45
|
STACK_ALGO_DEFAULT = STACK_ALGO_PYRAMID
|
|
44
|
-
DEFAULT_PLOTS_PATH = 'plots'
|
|
45
46
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
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
|
+
}
|
|
51
59
|
|
|
52
60
|
PATH_SEPARATOR = ';'
|
|
53
61
|
|
|
@@ -58,10 +66,39 @@ class _Constants:
|
|
|
58
66
|
LOG_COLOR_LEVEL_2 = 'magenta'
|
|
59
67
|
LOG_COLOR_LEVEL_3 = 'cyan'
|
|
60
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
|
+
|
|
61
88
|
DEFAULT_FILE_REVERSE_ORDER = False
|
|
62
89
|
DEFAULT_MULTILAYER_FILE_REVERSE_ORDER = True
|
|
63
90
|
MULTILAYER_WARNING_MEM_GB = 1
|
|
64
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
|
+
|
|
65
102
|
DEFAULT_NOISE_MAP_FILENAME = "noise-map/hot_pixels.png"
|
|
66
103
|
DEFAULT_NOISE_MAX_FRAMES = 10
|
|
67
104
|
DEFAULT_MN_KERNEL_SIZE = 3
|
|
@@ -102,9 +139,9 @@ class _Constants:
|
|
|
102
139
|
NOKNN_METHODS = {'detectors': [DETECTOR_ORB, DETECTOR_SURF, DETECTOR_AKAZE, DETECTOR_BRISK],
|
|
103
140
|
'descriptors': [DESCRIPTOR_ORB, DESCRIPTOR_AKAZE, DESCRIPTOR_BRISK]}
|
|
104
141
|
|
|
105
|
-
DEFAULT_DETECTOR =
|
|
106
|
-
DEFAULT_DESCRIPTOR =
|
|
107
|
-
DEFAULT_MATCHING_METHOD =
|
|
142
|
+
DEFAULT_DETECTOR = DETECTOR_ORB
|
|
143
|
+
DEFAULT_DESCRIPTOR = DESCRIPTOR_ORB
|
|
144
|
+
DEFAULT_MATCHING_METHOD = MATCHING_NORM_HAMMING
|
|
108
145
|
DEFAULT_FLANN_IDX_KDTREE = 2
|
|
109
146
|
DEFAULT_FLANN_TREES = 5
|
|
110
147
|
DEFAULT_FLANN_CHECKS = 50
|
|
@@ -116,13 +153,19 @@ class _Constants:
|
|
|
116
153
|
DEFAULT_REFINE_ITERS = 100
|
|
117
154
|
DEFAULT_ALIGN_CONFIDENCE = 99.9
|
|
118
155
|
DEFAULT_ALIGN_MAX_ITERS = 2000
|
|
119
|
-
DEFAULT_ALIGN_ABORT_ABNORMAL =
|
|
156
|
+
DEFAULT_ALIGN_ABORT_ABNORMAL = False
|
|
120
157
|
DEFAULT_BORDER_VALUE = [0] * 4
|
|
121
158
|
DEFAULT_BORDER_BLUR = 50
|
|
122
159
|
DEFAULT_ALIGN_SUBSAMPLE = 0
|
|
123
160
|
DEFAULT_ALIGN_RES_TARGET_MPX = 2
|
|
124
161
|
DEFAULT_ALIGN_FAST_SUBSAMPLING = False
|
|
125
|
-
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
|
|
126
169
|
|
|
127
170
|
BALANCE_LINEAR = "LINEAR"
|
|
128
171
|
BALANCE_GAMMA = "GAMMA"
|
|
@@ -160,13 +203,13 @@ class _Constants:
|
|
|
160
203
|
DEFAULT_STACK_PREFIX = "stack_"
|
|
161
204
|
DEFAULT_BUNCH_PREFIX = "bunch_"
|
|
162
205
|
|
|
163
|
-
DEFAULT_DM_FLOAT = FLOAT_32
|
|
164
206
|
DM_ENERGY_LAPLACIAN = "laplacian"
|
|
165
207
|
DM_ENERGY_SOBEL = "sobel"
|
|
166
208
|
DM_MAP_AVERAGE = "average"
|
|
167
209
|
DM_MAP_MAX = "max"
|
|
168
210
|
VALID_DM_MAP = [DM_MAP_AVERAGE, DM_MAP_MAX]
|
|
169
211
|
VALID_DM_ENERGY = [DM_ENERGY_LAPLACIAN, DM_ENERGY_SOBEL]
|
|
212
|
+
DEFAULT_DM_FLOAT = FLOAT_32
|
|
170
213
|
DEFAULT_DM_MAP = DM_MAP_AVERAGE
|
|
171
214
|
DEFAULT_DM_ENERGY = DM_ENERGY_LAPLACIAN
|
|
172
215
|
DEFAULT_DM_KERNEL_SIZE = 5
|
|
@@ -183,22 +226,16 @@ class _Constants:
|
|
|
183
226
|
DEFAULT_PY_N_TILED_LAYERS = 2
|
|
184
227
|
DEFAULT_PY_MEMORY_LIMIT_GB = 8
|
|
185
228
|
DEFAULT_PY_MAX_THREADS = min(os.cpu_count() or 4, 8)
|
|
186
|
-
DEFAULT_PY_MODE = 'auto'
|
|
187
229
|
PY_VALID_MODES = ['auto', 'memory', 'tiled']
|
|
188
|
-
|
|
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
|
|
189
235
|
|
|
190
|
-
DEFAULT_PLOT_STACK_BUNCH =
|
|
236
|
+
DEFAULT_PLOT_STACK_BUNCH = True
|
|
191
237
|
DEFAULT_PLOT_STACK = True
|
|
192
238
|
|
|
193
|
-
STATUS_RUNNING = 1
|
|
194
|
-
STATUS_PAUSED = 2
|
|
195
|
-
STATUS_STOPPED = 3
|
|
196
|
-
|
|
197
|
-
RUN_COMPLETED = 0
|
|
198
|
-
RUN_ONGOING = 1
|
|
199
|
-
RUN_FAILED = 2
|
|
200
|
-
RUN_STOPPED = 3
|
|
201
|
-
|
|
202
239
|
def __setattr__aux(self, name, value):
|
|
203
240
|
raise AttributeError(f"Can't reassign constant '{name}'")
|
|
204
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:
|
|
@@ -24,7 +26,7 @@ class TqdmCallbacks:
|
|
|
24
26
|
|
|
25
27
|
def __init__(self):
|
|
26
28
|
self.tbar = None
|
|
27
|
-
self.
|
|
29
|
+
self.total_action_counts = -1
|
|
28
30
|
|
|
29
31
|
@classmethod
|
|
30
32
|
def instance(cls):
|
|
@@ -33,8 +35,8 @@ class TqdmCallbacks:
|
|
|
33
35
|
return cls._instance
|
|
34
36
|
|
|
35
37
|
def step_counts(self, name, counts):
|
|
36
|
-
self.
|
|
37
|
-
self.tbar = make_tqdm_bar(name, self.
|
|
38
|
+
self.total_action_counts = counts
|
|
39
|
+
self.tbar = make_tqdm_bar(name, self.total_action_counts)
|
|
38
40
|
|
|
39
41
|
def begin_steps(self, name):
|
|
40
42
|
pass
|
|
@@ -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
|
-
|
|
194
|
-
self.
|
|
195
|
-
self
|
|
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)
|
|
198
|
+
self.total_action_counts = None
|
|
199
|
+
self.current_action_count = None
|
|
200
|
+
self.begin_steps = 0
|
|
196
201
|
|
|
197
202
|
def set_counts(self, counts):
|
|
198
|
-
self.
|
|
199
|
-
self.callback(
|
|
203
|
+
self.total_action_counts = 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(
|
|
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
|
-
self.
|
|
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
|
-
if self.
|
|
216
|
-
self.run_step()
|
|
217
|
-
x = self.
|
|
218
|
-
self.
|
|
224
|
+
if self.current_action_count < self.total_action_counts:
|
|
225
|
+
self.run_step(self.current_action_count)
|
|
226
|
+
x = self.current_action_count
|
|
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,
|
|
@@ -16,12 +15,16 @@ FIELD_ABS_PATH = 'abs_path'
|
|
|
16
15
|
FIELD_REL_PATH = 'rel_path'
|
|
17
16
|
FIELD_FLOAT = 'float'
|
|
18
17
|
FIELD_INT = 'int'
|
|
18
|
+
FIELD_REF_IDX = 'ref_idx'
|
|
19
19
|
FIELD_INT_TUPLE = 'int_tuple'
|
|
20
20
|
FIELD_BOOL = 'bool'
|
|
21
21
|
FIELD_COMBO = 'combo'
|
|
22
22
|
FIELD_TYPES = [FIELD_TEXT, FIELD_ABS_PATH, FIELD_REL_PATH, FIELD_FLOAT,
|
|
23
23
|
FIELD_INT, FIELD_INT_TUPLE, FIELD_BOOL, FIELD_COMBO]
|
|
24
24
|
|
|
25
|
+
FIELD_REF_IDX_OPTIONS = ['Median frame', 'First frame', 'Last frame', 'Specify index']
|
|
26
|
+
FIELD_REF_IDX_MAX = 1000
|
|
27
|
+
|
|
25
28
|
|
|
26
29
|
class ActionConfigurator(ABC):
|
|
27
30
|
def __init__(self, expert, current_wd):
|
|
@@ -56,6 +59,8 @@ class FieldBuilder:
|
|
|
56
59
|
widget = self.create_float_field(tag, **kwargs)
|
|
57
60
|
elif field_type == FIELD_INT:
|
|
58
61
|
widget = self.create_int_field(tag, **kwargs)
|
|
62
|
+
elif field_type == FIELD_REF_IDX:
|
|
63
|
+
widget = self.create_ref_idx_field(tag, **kwargs)
|
|
59
64
|
elif field_type == FIELD_INT_TUPLE:
|
|
60
65
|
widget = self.create_int_tuple_field(tag, **kwargs)
|
|
61
66
|
elif field_type == FIELD_BOOL:
|
|
@@ -75,6 +80,8 @@ class FieldBuilder:
|
|
|
75
80
|
default_value = kwargs.get('default', 0.0)
|
|
76
81
|
elif field_type == FIELD_INT:
|
|
77
82
|
default_value = kwargs.get('default', 0)
|
|
83
|
+
elif field_type == FIELD_REF_IDX:
|
|
84
|
+
default_value = kwargs.get('default', 0)
|
|
78
85
|
elif field_type == FIELD_INT_TUPLE:
|
|
79
86
|
default_value = kwargs.get('default', [0] * kwargs.get('size', 1))
|
|
80
87
|
elif field_type == FIELD_BOOL:
|
|
@@ -112,12 +119,15 @@ class FieldBuilder:
|
|
|
112
119
|
widget.setChecked(default)
|
|
113
120
|
elif field['type'] == FIELD_INT:
|
|
114
121
|
widget.setValue(default)
|
|
122
|
+
elif field['type'] == FIELD_REF_IDX:
|
|
123
|
+
widget.layout().itemAt(2).widget().setValue(default)
|
|
124
|
+
widget.layout().itemAt(0).widget().setCurrentText(FIELD_REF_IDX_OPTIONS[0])
|
|
115
125
|
elif field['type'] == FIELD_INT_TUPLE:
|
|
116
126
|
for i in range(field['size']):
|
|
117
127
|
spinbox = widget.layout().itemAt(1 + i * 2).widget()
|
|
118
128
|
spinbox.setValue(default[i])
|
|
119
129
|
elif field['type'] == FIELD_COMBO:
|
|
120
|
-
widget.setCurrentText(default)
|
|
130
|
+
widget.setCurrentText(str(default))
|
|
121
131
|
|
|
122
132
|
def get_path_widget(self, widget):
|
|
123
133
|
return widget.layout().itemAt(0).widget()
|
|
@@ -134,7 +144,7 @@ class FieldBuilder:
|
|
|
134
144
|
parent = parent.parent
|
|
135
145
|
return ''
|
|
136
146
|
|
|
137
|
-
def update_params(self, params
|
|
147
|
+
def update_params(self, params):
|
|
138
148
|
for tag, field in self.fields.items():
|
|
139
149
|
if field['type'] == FIELD_TEXT:
|
|
140
150
|
params[tag] = field['widget'].text()
|
|
@@ -146,6 +156,17 @@ class FieldBuilder:
|
|
|
146
156
|
params[tag] = field['widget'].isChecked()
|
|
147
157
|
elif field['type'] == FIELD_INT:
|
|
148
158
|
params[tag] = field['widget'].value()
|
|
159
|
+
elif field['type'] == FIELD_REF_IDX:
|
|
160
|
+
wl = field['widget'].layout()
|
|
161
|
+
txt = wl.itemAt(0).widget().currentText()
|
|
162
|
+
if txt == FIELD_REF_IDX_OPTIONS[0]:
|
|
163
|
+
params[tag] = 0
|
|
164
|
+
elif txt == FIELD_REF_IDX_OPTIONS[1]:
|
|
165
|
+
params[tag] = 1
|
|
166
|
+
elif txt == FIELD_REF_IDX_OPTIONS[2]:
|
|
167
|
+
params[tag] = -1
|
|
168
|
+
else:
|
|
169
|
+
params[tag] = wl.itemAt(2).widget().value()
|
|
149
170
|
elif field['type'] == FIELD_INT_TUPLE:
|
|
150
171
|
params[tag] = [field['widget'].layout().itemAt(1 + i * 2).widget().value()
|
|
151
172
|
for i in range(field['size'])]
|
|
@@ -202,7 +223,7 @@ class FieldBuilder:
|
|
|
202
223
|
self.action.params.get(tag, ''),
|
|
203
224
|
kwargs.get('placeholder', ''),
|
|
204
225
|
tag.replace('_', ' ')
|
|
205
|
-
)
|
|
226
|
+
)
|
|
206
227
|
|
|
207
228
|
def create_rel_path_field(self, tag, **kwargs):
|
|
208
229
|
value = self.action.params.get(tag, kwargs.get('default', ''))
|
|
@@ -327,6 +348,37 @@ class FieldBuilder:
|
|
|
327
348
|
spin.setValue(self.action.params.get(tag, default))
|
|
328
349
|
return spin
|
|
329
350
|
|
|
351
|
+
def create_ref_idx_field(self, tag, default=0):
|
|
352
|
+
layout = QHBoxLayout()
|
|
353
|
+
combo = QComboBox()
|
|
354
|
+
combo.addItems(FIELD_REF_IDX_OPTIONS)
|
|
355
|
+
label = QLabel("index [1, ..., N]: ")
|
|
356
|
+
spin = QSpinBox()
|
|
357
|
+
spin.setRange(1, FIELD_REF_IDX_MAX)
|
|
358
|
+
value = self.action.params.get(tag, default)
|
|
359
|
+
if value == 0:
|
|
360
|
+
combo.setCurrentText(FIELD_REF_IDX_OPTIONS[0])
|
|
361
|
+
spin.setValue(1)
|
|
362
|
+
elif value == 1:
|
|
363
|
+
combo.setCurrentText(FIELD_REF_IDX_OPTIONS[1])
|
|
364
|
+
spin.setValue(1)
|
|
365
|
+
elif value == -1:
|
|
366
|
+
combo.setCurrentText(FIELD_REF_IDX_OPTIONS[2])
|
|
367
|
+
spin.setValue(1)
|
|
368
|
+
else:
|
|
369
|
+
combo.setCurrentText(FIELD_REF_IDX_OPTIONS[3])
|
|
370
|
+
spin.setValue(value)
|
|
371
|
+
|
|
372
|
+
def set_enabled():
|
|
373
|
+
spin.setEnabled(combo.currentText() == FIELD_REF_IDX_OPTIONS[-1])
|
|
374
|
+
|
|
375
|
+
combo.currentTextChanged.connect(set_enabled)
|
|
376
|
+
set_enabled()
|
|
377
|
+
layout.addWidget(combo)
|
|
378
|
+
layout.addWidget(label)
|
|
379
|
+
layout.addWidget(spin)
|
|
380
|
+
return create_layout_widget_no_margins(layout)
|
|
381
|
+
|
|
330
382
|
def create_int_tuple_field(self, tag, size=1,
|
|
331
383
|
default=[0] * 100, min_val=[0] * 100, max_val=[100] * 100,
|
|
332
384
|
**kwargs):
|