shinestacker 1.3.0__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 +35 -27
- shinestacker/algorithms/align_auto.py +15 -3
- shinestacker/algorithms/align_parallel.py +65 -25
- shinestacker/algorithms/base_stack_algo.py +14 -20
- shinestacker/algorithms/depth_map.py +9 -14
- shinestacker/algorithms/pyramid.py +8 -22
- shinestacker/algorithms/pyramid_auto.py +5 -14
- shinestacker/algorithms/pyramid_tiles.py +18 -20
- shinestacker/algorithms/stack_framework.py +1 -1
- shinestacker/algorithms/utils.py +16 -0
- shinestacker/app/gui_utils.py +10 -0
- shinestacker/app/main.py +3 -1
- shinestacker/app/project.py +3 -1
- shinestacker/app/retouch.py +3 -1
- shinestacker/gui/action_config_dialog.py +302 -272
- shinestacker/gui/colors.py +1 -0
- shinestacker/gui/folder_file_selection.py +5 -0
- shinestacker/gui/main_window.py +4 -4
- shinestacker/gui/new_project.py +5 -5
- shinestacker/gui/project_editor.py +6 -4
- shinestacker/gui/sys_mon.py +24 -23
- {shinestacker-1.3.0.dist-info → shinestacker-1.3.1.dist-info}/METADATA +1 -1
- {shinestacker-1.3.0.dist-info → shinestacker-1.3.1.dist-info}/RECORD +28 -28
- {shinestacker-1.3.0.dist-info → shinestacker-1.3.1.dist-info}/WHEEL +0 -0
- {shinestacker-1.3.0.dist-info → shinestacker-1.3.1.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.3.0.dist-info → shinestacker-1.3.1.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.3.0.dist-info → shinestacker-1.3.1.dist-info}/top_level.txt +0 -0
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
import os
|
|
4
4
|
import traceback
|
|
5
5
|
from PySide6.QtWidgets import (QWidget, QPushButton, QHBoxLayout, QLabel, QScrollArea, QSizePolicy,
|
|
6
|
-
QMessageBox, QStackedWidget, QFormLayout, QDialog)
|
|
6
|
+
QMessageBox, QStackedWidget, QFormLayout, QDialog, QTabWidget)
|
|
7
7
|
from PySide6.QtCore import Qt, QTimer
|
|
8
8
|
from .. config.constants import constants
|
|
9
9
|
from .. algorithms.align import validate_align_config
|
|
@@ -156,6 +156,31 @@ class NoNameActionConfigurator(ActionConfigurator):
|
|
|
156
156
|
def add_labelled_row(self, label, widget):
|
|
157
157
|
self.add_row(self.labelled_widget(label, widget))
|
|
158
158
|
|
|
159
|
+
def create_tab_widget(self, layout):
|
|
160
|
+
tab_widget = QTabWidget()
|
|
161
|
+
layout.addRow(tab_widget)
|
|
162
|
+
return tab_widget
|
|
163
|
+
|
|
164
|
+
def add_tab(self, tab_widget, title):
|
|
165
|
+
tab = QWidget()
|
|
166
|
+
tab_layout = QFormLayout()
|
|
167
|
+
tab_layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
|
|
168
|
+
tab_layout.setRowWrapPolicy(QFormLayout.DontWrapRows)
|
|
169
|
+
tab_layout.setFormAlignment(Qt.AlignLeft | Qt.AlignTop)
|
|
170
|
+
tab_layout.setLabelAlignment(Qt.AlignLeft)
|
|
171
|
+
tab.setLayout(tab_layout)
|
|
172
|
+
tab_widget.addTab(tab, title)
|
|
173
|
+
return tab_layout
|
|
174
|
+
|
|
175
|
+
def add_field_to_layout(self, layout, tag, field_type, label, required=False, **kwargs):
|
|
176
|
+
return self.add_field(tag, field_type, label, required, add_to_layout=layout, **kwargs)
|
|
177
|
+
|
|
178
|
+
def add_bold_label_to_layout(self, layout, label):
|
|
179
|
+
label_widget = QLabel(label)
|
|
180
|
+
label_widget.setStyleSheet("font-weight: bold")
|
|
181
|
+
layout.addRow(label_widget)
|
|
182
|
+
return label_widget
|
|
183
|
+
|
|
159
184
|
|
|
160
185
|
class DefaultActionConfigurator(NoNameActionConfigurator):
|
|
161
186
|
def create_form(self, layout, action, tag='Action'):
|
|
@@ -183,20 +208,11 @@ class JobConfigurator(DefaultActionConfigurator):
|
|
|
183
208
|
input_filepaths = input_filepaths.split(constants.PATH_SEPARATOR)
|
|
184
209
|
self.working_path_label = QLabel(working_path or "Not set")
|
|
185
210
|
self.input_path_label = QLabel(input_path or "Not set")
|
|
186
|
-
|
|
211
|
+
self.input_widget.path_edit.setText('')
|
|
187
212
|
if input_filepaths:
|
|
188
213
|
self.input_widget.files_mode_radio.setChecked(True)
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
parent_dir = os.path.dirname(input_filepaths[0])
|
|
192
|
-
self.input_widget.path_edit.setText(parent_dir)
|
|
193
|
-
elif input_path and working_path:
|
|
194
|
-
self.input_widget.folder_mode_radio.setChecked(True)
|
|
195
|
-
input_fullpath = os.path.join(working_path, input_path)
|
|
196
|
-
self.input_widget.path_edit.setText(input_fullpath)
|
|
197
|
-
elif input_path:
|
|
198
|
-
self.input_widget.folder_mode_radio.setChecked(True)
|
|
199
|
-
self.input_widget.path_edit.setText(input_path)
|
|
214
|
+
else:
|
|
215
|
+
self.input_widget.folder_mode_radio.setChecked(False)
|
|
200
216
|
self.input_widget.text_changed_connect(self.update_paths_and_frames)
|
|
201
217
|
self.input_widget.folder_mode_radio.toggled.connect(self.update_paths_and_frames)
|
|
202
218
|
self.input_widget.files_mode_radio.toggled.connect(self.update_paths_and_frames)
|
|
@@ -206,29 +222,27 @@ class JobConfigurator(DefaultActionConfigurator):
|
|
|
206
222
|
self.add_bold_label("Derived Paths:")
|
|
207
223
|
self.add_labelled_row("Working path: ", self.working_path_label)
|
|
208
224
|
self.add_labelled_row("Input path:", self.input_path_label)
|
|
209
|
-
|
|
210
|
-
self.update_paths_and_frames()
|
|
211
|
-
else:
|
|
212
|
-
self.update_frames_count()
|
|
225
|
+
self.set_paths_and_frames(working_path, input_path, input_filepaths)
|
|
213
226
|
|
|
214
227
|
def update_frames_count(self):
|
|
215
228
|
if self.input_widget.get_selection_mode() == 'files':
|
|
216
|
-
count =
|
|
229
|
+
count = self.input_widget.num_selected_files()
|
|
217
230
|
else:
|
|
218
231
|
count = self.count_image_files(self.input_widget.get_path())
|
|
219
232
|
self.frames_label.setText(str(count))
|
|
220
233
|
|
|
221
|
-
def
|
|
234
|
+
def set_paths_and_frames(self, working_path, input_path, input_filepaths):
|
|
235
|
+
self.input_path_label.setText(input_path or "Not set")
|
|
236
|
+
self.working_path_label.setText(working_path or "Not set")
|
|
237
|
+
self.frames_label.setText(str(len(input_filepaths)))
|
|
238
|
+
|
|
239
|
+
def update_paths_and_frames(self, ):
|
|
240
|
+
input_fullpath = self.input_widget.get_path()
|
|
241
|
+
input_path = os.path.basename(os.path.normpath(input_fullpath)) if input_fullpath else ""
|
|
242
|
+
working_path = os.path.dirname(input_fullpath) if input_fullpath else ""
|
|
243
|
+
self.input_path_label.setText(input_path or "Not set")
|
|
244
|
+
self.working_path_label.setText(working_path or "Not set")
|
|
222
245
|
self.update_frames_count()
|
|
223
|
-
selection_mode = self.input_widget.get_selection_mode()
|
|
224
|
-
selected_files = self.input_widget.get_selected_files()
|
|
225
|
-
input_path = self.input_widget.get_path()
|
|
226
|
-
if selection_mode == 'files' and selected_files:
|
|
227
|
-
input_path = os.path.dirname(selected_files[0])
|
|
228
|
-
input_path_value = os.path.basename(os.path.normpath(input_path)) if input_path else ""
|
|
229
|
-
working_path_value = os.path.dirname(input_path) if input_path else ""
|
|
230
|
-
self.input_path_label.setText(input_path_value or "Not set")
|
|
231
|
-
self.working_path_label.setText(working_path_value or "Not set")
|
|
232
246
|
|
|
233
247
|
def count_image_files(self, path):
|
|
234
248
|
if not path or not os.path.isdir(path):
|
|
@@ -244,18 +258,16 @@ class JobConfigurator(DefaultActionConfigurator):
|
|
|
244
258
|
return False
|
|
245
259
|
selection_mode = self.input_widget.get_selection_mode()
|
|
246
260
|
selected_files = self.input_widget.get_selected_files()
|
|
247
|
-
input_path = self.input_widget.get_path()
|
|
248
261
|
if selection_mode == 'files' and selected_files:
|
|
262
|
+
input_full_path = os.path.dirname(selected_files[0])
|
|
249
263
|
params['input_filepaths'] = self.input_widget.get_selected_filenames()
|
|
250
|
-
params['input_path'] = os.path.dirname(selected_files[0])
|
|
251
264
|
else:
|
|
265
|
+
input_full_path = self.input_widget.get_path()
|
|
252
266
|
params['input_filepaths'] = []
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
else:
|
|
258
|
-
params['working_path'] = ''
|
|
267
|
+
input_path = os.path.basename(os.path.normpath(input_full_path)) if input_full_path else ""
|
|
268
|
+
working_path = os.path.dirname(input_full_path) if input_full_path else ""
|
|
269
|
+
params['input_path'] = input_path
|
|
270
|
+
params['working_path'] = working_path
|
|
259
271
|
return True
|
|
260
272
|
|
|
261
273
|
|
|
@@ -307,38 +319,48 @@ class FocusStackBaseConfigurator(DefaultActionConfigurator):
|
|
|
307
319
|
FLOAT_OPTIONS = ['float 32 bits', 'float 64 bits']
|
|
308
320
|
MODE_OPTIONS = ['Auto', 'All in memory', 'Tiled I/O buffered']
|
|
309
321
|
|
|
322
|
+
def __init__(self, expert, current_wd):
|
|
323
|
+
super().__init__(expert, current_wd)
|
|
324
|
+
self.tab_widget = None
|
|
325
|
+
self.general_tab_layout = None
|
|
326
|
+
self.algorithm_tab_layout = None
|
|
327
|
+
|
|
310
328
|
def create_form(self, layout, action):
|
|
311
329
|
super().create_form(layout, action)
|
|
330
|
+
self.tab_widget = self.create_tab_widget(layout)
|
|
331
|
+
self.general_tab_layout = self.add_tab(self.tab_widget, "General Parameters")
|
|
332
|
+
self.create_general_tab(self.general_tab_layout)
|
|
333
|
+
self.algorithm_tab_layout = self.add_tab(self.tab_widget, "Stacking Algorithm")
|
|
334
|
+
self.create_algorithm_tab(self.algorithm_tab_layout)
|
|
335
|
+
|
|
336
|
+
def create_general_tab(self, layout):
|
|
312
337
|
if self.expert:
|
|
313
|
-
self.
|
|
314
|
-
'working_path', FIELD_ABS_PATH, 'Working path', required=False)
|
|
315
|
-
self.
|
|
316
|
-
'input_path', FIELD_REL_PATH, 'Input path', required=False,
|
|
338
|
+
self.add_field_to_layout(
|
|
339
|
+
layout, 'working_path', FIELD_ABS_PATH, 'Working path', required=False)
|
|
340
|
+
self.add_field_to_layout(
|
|
341
|
+
layout, 'input_path', FIELD_REL_PATH, 'Input path', required=False,
|
|
317
342
|
placeholder='relative to working path')
|
|
318
|
-
self.
|
|
319
|
-
'output_path', FIELD_REL_PATH, 'Output path', required=False,
|
|
343
|
+
self.add_field_to_layout(
|
|
344
|
+
layout, 'output_path', FIELD_REL_PATH, 'Output path', required=False,
|
|
320
345
|
placeholder='relative to working path')
|
|
321
|
-
self.
|
|
322
|
-
'scratch_output_dir', FIELD_BOOL, 'Scratch output dir.',
|
|
346
|
+
self.add_field_to_layout(
|
|
347
|
+
layout, 'scratch_output_dir', FIELD_BOOL, 'Scratch output dir.',
|
|
323
348
|
required=False, default=True)
|
|
324
349
|
|
|
325
|
-
def
|
|
326
|
-
self.
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
self.add_bold_label("Stacking algorithm:")
|
|
330
|
-
combo = self.add_field(
|
|
331
|
-
'stacker', FIELD_COMBO, 'Stacking algorithm', required=True,
|
|
350
|
+
def create_algorithm_tab(self, layout):
|
|
351
|
+
self.add_bold_label_to_layout(layout, "Stacking algorithm:")
|
|
352
|
+
combo = self.add_field_to_layout(
|
|
353
|
+
layout, 'stacker', FIELD_COMBO, 'Stacking algorithm', required=True,
|
|
332
354
|
options=constants.STACK_ALGO_OPTIONS,
|
|
333
355
|
default=constants.STACK_ALGO_DEFAULT)
|
|
334
356
|
q_pyramid, q_depthmap = QWidget(), QWidget()
|
|
335
357
|
for q in [q_pyramid, q_depthmap]:
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
q.setLayout(
|
|
358
|
+
tab_layout = QFormLayout()
|
|
359
|
+
tab_layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
|
|
360
|
+
tab_layout.setRowWrapPolicy(QFormLayout.DontWrapRows)
|
|
361
|
+
tab_layout.setFormAlignment(Qt.AlignLeft | Qt.AlignTop)
|
|
362
|
+
tab_layout.setLabelAlignment(Qt.AlignLeft)
|
|
363
|
+
q.setLayout(tab_layout)
|
|
342
364
|
stacked = QStackedWidget()
|
|
343
365
|
stacked.addWidget(q_pyramid)
|
|
344
366
|
stacked.addWidget(q_depthmap)
|
|
@@ -349,52 +371,45 @@ class FocusStackBaseConfigurator(DefaultActionConfigurator):
|
|
|
349
371
|
stacked.setCurrentWidget(q_pyramid)
|
|
350
372
|
elif text == constants.STACK_ALGO_DEPTH_MAP:
|
|
351
373
|
stacked.setCurrentWidget(q_depthmap)
|
|
374
|
+
|
|
352
375
|
change()
|
|
353
376
|
if self.expert:
|
|
354
|
-
self.
|
|
355
|
-
'pyramid_min_size', FIELD_INT, 'Minimum size (px)',
|
|
356
|
-
required=False,
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
'pyramid_gen_kernel', FIELD_FLOAT, 'Gen. kernel',
|
|
364
|
-
required=False, add_to_layout=q_pyramid.layout(),
|
|
365
|
-
default=constants.DEFAULT_PY_GEN_KERNEL,
|
|
377
|
+
self.add_field_to_layout(
|
|
378
|
+
q_pyramid.layout(), 'pyramid_min_size', FIELD_INT, 'Minimum size (px)',
|
|
379
|
+
required=False, default=constants.DEFAULT_PY_MIN_SIZE, min_val=2, max_val=256)
|
|
380
|
+
self.add_field_to_layout(
|
|
381
|
+
q_pyramid.layout(), 'pyramid_kernel_size', FIELD_INT, 'Kernel size (px)',
|
|
382
|
+
required=False, default=constants.DEFAULT_PY_KERNEL_SIZE, min_val=3, max_val=21)
|
|
383
|
+
self.add_field_to_layout(
|
|
384
|
+
q_pyramid.layout(), 'pyramid_gen_kernel', FIELD_FLOAT, 'Gen. kernel',
|
|
385
|
+
required=False, default=constants.DEFAULT_PY_GEN_KERNEL,
|
|
366
386
|
min_val=0.0, max_val=2.0)
|
|
367
|
-
self.
|
|
368
|
-
'pyramid_float_type', FIELD_COMBO, 'Precision', required=False,
|
|
369
|
-
add_to_layout=q_pyramid.layout(),
|
|
387
|
+
self.add_field_to_layout(
|
|
388
|
+
q_pyramid.layout(), 'pyramid_float_type', FIELD_COMBO, 'Precision', required=False,
|
|
370
389
|
options=self.FLOAT_OPTIONS, values=constants.VALID_FLOATS,
|
|
371
390
|
default=dict(zip(constants.VALID_FLOATS,
|
|
372
391
|
self.FLOAT_OPTIONS))[constants.DEFAULT_PY_FLOAT])
|
|
373
|
-
mode = self.
|
|
374
|
-
'pyramid_mode', FIELD_COMBO, 'Mode',
|
|
375
|
-
required=False,
|
|
376
|
-
options=self.MODE_OPTIONS, values=constants.PY_VALID_MODES,
|
|
392
|
+
mode = self.add_field_to_layout(
|
|
393
|
+
q_pyramid.layout(), 'pyramid_mode', FIELD_COMBO, 'Mode',
|
|
394
|
+
required=False, options=self.MODE_OPTIONS, values=constants.PY_VALID_MODES,
|
|
377
395
|
default=dict(zip(constants.PY_VALID_MODES,
|
|
378
396
|
self.MODE_OPTIONS))[constants.DEFAULT_PY_MODE])
|
|
379
|
-
memory_limit = self.
|
|
380
|
-
'pyramid_memory_limit', FIELD_FLOAT,
|
|
381
|
-
|
|
382
|
-
default=constants.DEFAULT_PY_MEMORY_LIMIT_GB,
|
|
397
|
+
memory_limit = self.add_field_to_layout(
|
|
398
|
+
q_pyramid.layout(), 'pyramid_memory_limit', FIELD_FLOAT,
|
|
399
|
+
'Memory limit (approx., GBytes)',
|
|
400
|
+
required=False, default=constants.DEFAULT_PY_MEMORY_LIMIT_GB,
|
|
383
401
|
min_val=1.0, max_val=64.0)
|
|
384
|
-
max_threads = self.
|
|
385
|
-
'pyramid_max_threads', FIELD_INT, 'Max num. of cores',
|
|
386
|
-
required=False,
|
|
387
|
-
default=constants.DEFAULT_PY_MAX_THREADS,
|
|
402
|
+
max_threads = self.add_field_to_layout(
|
|
403
|
+
q_pyramid.layout(), 'pyramid_max_threads', FIELD_INT, 'Max num. of cores',
|
|
404
|
+
required=False, default=constants.DEFAULT_PY_MAX_THREADS,
|
|
388
405
|
min_val=1, max_val=64)
|
|
389
|
-
tile_size = self.
|
|
390
|
-
'pyramid_tile_size', FIELD_INT, 'Tile size (px)',
|
|
391
|
-
required=False,
|
|
392
|
-
default=constants.DEFAULT_PY_TILE_SIZE,
|
|
406
|
+
tile_size = self.add_field_to_layout(
|
|
407
|
+
q_pyramid.layout(), 'pyramid_tile_size', FIELD_INT, 'Tile size (px)',
|
|
408
|
+
required=False, default=constants.DEFAULT_PY_TILE_SIZE,
|
|
393
409
|
min_val=128, max_val=2048)
|
|
394
|
-
n_tiled_layers = self.
|
|
395
|
-
'pyramid_n_tiled_layers', FIELD_INT, 'Num. tiled layers',
|
|
396
|
-
required=False,
|
|
397
|
-
default=constants.DEFAULT_PY_N_TILED_LAYERS,
|
|
410
|
+
n_tiled_layers = self.add_field_to_layout(
|
|
411
|
+
q_pyramid.layout(), 'pyramid_n_tiled_layers', FIELD_INT, 'Num. tiled layers',
|
|
412
|
+
required=False, default=constants.DEFAULT_PY_N_TILED_LAYERS,
|
|
398
413
|
min_val=0, max_val=6)
|
|
399
414
|
|
|
400
415
|
def change_mode():
|
|
@@ -407,47 +422,41 @@ class FocusStackBaseConfigurator(DefaultActionConfigurator):
|
|
|
407
422
|
|
|
408
423
|
mode.currentIndexChanged.connect(change_mode)
|
|
409
424
|
change_mode()
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
425
|
+
|
|
426
|
+
self.add_field_to_layout(
|
|
427
|
+
q_depthmap.layout(), 'depthmap_energy', FIELD_COMBO, 'Energy', required=False,
|
|
413
428
|
options=self.ENERGY_OPTIONS, values=constants.VALID_DM_ENERGY,
|
|
414
429
|
default=dict(zip(constants.VALID_DM_ENERGY,
|
|
415
430
|
self.ENERGY_OPTIONS))[constants.DEFAULT_DM_ENERGY])
|
|
416
|
-
self.
|
|
417
|
-
'map_type', FIELD_COMBO, 'Map type', required=False,
|
|
418
|
-
add_to_layout=q_depthmap.layout(),
|
|
431
|
+
self.add_field_to_layout(
|
|
432
|
+
q_depthmap.layout(), 'map_type', FIELD_COMBO, 'Map type', required=False,
|
|
419
433
|
options=self.MAP_TYPE_OPTIONS, values=constants.VALID_DM_MAP,
|
|
420
434
|
default=dict(zip(constants.VALID_DM_MAP,
|
|
421
435
|
self.MAP_TYPE_OPTIONS))[constants.DEFAULT_DM_MAP])
|
|
422
436
|
if self.expert:
|
|
423
|
-
self.
|
|
424
|
-
'depthmap_kernel_size', FIELD_INT, 'Kernel size (px)',
|
|
425
|
-
required=False,
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
default=constants.
|
|
435
|
-
self.add_field(
|
|
436
|
-
'depthmap_temperature', FIELD_FLOAT, 'Temperature',
|
|
437
|
-
required=False, add_to_layout=q_depthmap.layout(),
|
|
438
|
-
default=constants.DEFAULT_DM_TEMPERATURE,
|
|
437
|
+
self.add_field_to_layout(
|
|
438
|
+
q_depthmap.layout(), 'depthmap_kernel_size', FIELD_INT, 'Kernel size (px)',
|
|
439
|
+
required=False, default=constants.DEFAULT_DM_KERNEL_SIZE, min_val=3, max_val=21)
|
|
440
|
+
self.add_field_to_layout(
|
|
441
|
+
q_depthmap.layout(), 'depthmap_blur_size', FIELD_INT, 'Blurl size (px)',
|
|
442
|
+
required=False, default=constants.DEFAULT_DM_BLUR_SIZE, min_val=1, max_val=21)
|
|
443
|
+
self.add_field_to_layout(
|
|
444
|
+
q_depthmap.layout(), 'depthmap_smooth_size', FIELD_INT, 'Smooth size (px)',
|
|
445
|
+
required=False, default=constants.DEFAULT_DM_SMOOTH_SIZE, min_val=0, max_val=256)
|
|
446
|
+
self.add_field_to_layout(
|
|
447
|
+
q_depthmap.layout(), 'depthmap_temperature', FIELD_FLOAT, 'Temperature',
|
|
448
|
+
required=False, default=constants.DEFAULT_DM_TEMPERATURE,
|
|
439
449
|
min_val=0, max_val=1, step=0.05)
|
|
440
|
-
self.
|
|
441
|
-
'depthmap_levels', FIELD_INT, 'Levels', required=False,
|
|
442
|
-
add_to_layout=q_depthmap.layout(),
|
|
450
|
+
self.add_field_to_layout(
|
|
451
|
+
q_depthmap.layout(), 'depthmap_levels', FIELD_INT, 'Levels', required=False,
|
|
443
452
|
default=constants.DEFAULT_DM_LEVELS, min_val=2, max_val=6)
|
|
444
|
-
self.
|
|
445
|
-
'depthmap_float_type', FIELD_COMBO,
|
|
446
|
-
|
|
447
|
-
values=constants.VALID_FLOATS,
|
|
453
|
+
self.add_field_to_layout(
|
|
454
|
+
q_depthmap.layout(), 'depthmap_float_type', FIELD_COMBO,
|
|
455
|
+
'Precision', required=False,
|
|
456
|
+
options=self.FLOAT_OPTIONS, values=constants.VALID_FLOATS,
|
|
448
457
|
default=dict(zip(constants.VALID_FLOATS,
|
|
449
458
|
self.FLOAT_OPTIONS))[constants.DEFAULT_DM_FLOAT])
|
|
450
|
-
|
|
459
|
+
layout.addRow(stacked)
|
|
451
460
|
combo.currentIndexChanged.connect(change)
|
|
452
461
|
|
|
453
462
|
|
|
@@ -455,32 +464,32 @@ class FocusStackConfigurator(FocusStackBaseConfigurator):
|
|
|
455
464
|
def create_form(self, layout, action):
|
|
456
465
|
super().create_form(layout, action)
|
|
457
466
|
if self.expert:
|
|
458
|
-
self.
|
|
459
|
-
'exif_path', FIELD_REL_PATH,
|
|
467
|
+
self.add_field_to_layout(
|
|
468
|
+
self.general_tab_layout, 'exif_path', FIELD_REL_PATH,
|
|
469
|
+
'Exif data path', required=False,
|
|
460
470
|
placeholder='relative to working path')
|
|
461
|
-
self.
|
|
462
|
-
|
|
471
|
+
self.add_field_to_layout(
|
|
472
|
+
self.general_tab_layout, 'prefix', FIELD_TEXT,
|
|
473
|
+
'Output filename prefix', required=False,
|
|
463
474
|
default=constants.DEFAULT_STACK_PREFIX,
|
|
464
475
|
placeholder=constants.DEFAULT_STACK_PREFIX)
|
|
465
|
-
self.
|
|
466
|
-
'plot_stack', FIELD_BOOL, 'Plot stack', required=False,
|
|
476
|
+
self.add_field_to_layout(
|
|
477
|
+
self.general_tab_layout, 'plot_stack', FIELD_BOOL, 'Plot stack', required=False,
|
|
467
478
|
default=constants.DEFAULT_PLOT_STACK)
|
|
468
|
-
super().common_fields(layout)
|
|
469
479
|
|
|
470
480
|
|
|
471
481
|
class FocusStackBunchConfigurator(FocusStackBaseConfigurator):
|
|
472
482
|
def create_form(self, layout, action):
|
|
473
483
|
super().create_form(layout, action)
|
|
474
|
-
self.
|
|
475
|
-
'frames', FIELD_INT, 'Frames', required=False,
|
|
484
|
+
self.add_field_to_layout(
|
|
485
|
+
self.general_tab_layout, 'frames', FIELD_INT, 'Frames', required=False,
|
|
476
486
|
default=constants.DEFAULT_FRAMES, min_val=1, max_val=100)
|
|
477
|
-
self.
|
|
478
|
-
'overlap', FIELD_INT, 'Overlapping frames', required=False,
|
|
487
|
+
self.add_field_to_layout(
|
|
488
|
+
self.general_tab_layout, 'overlap', FIELD_INT, 'Overlapping frames', required=False,
|
|
479
489
|
default=constants.DEFAULT_OVERLAP, min_val=0, max_val=100)
|
|
480
|
-
self.
|
|
481
|
-
'plot_stack', FIELD_BOOL, 'Plot stack', required=False,
|
|
490
|
+
self.add_field_to_layout(
|
|
491
|
+
self.general_tab_layout, 'plot_stack', FIELD_BOOL, 'Plot stack', required=False,
|
|
482
492
|
default=constants.DEFAULT_PLOT_STACK_BUNCH)
|
|
483
|
-
super().common_fields(layout)
|
|
484
493
|
|
|
485
494
|
|
|
486
495
|
class MultiLayerConfigurator(DefaultActionConfigurator):
|
|
@@ -569,18 +578,21 @@ class SubsampleActionConfigurator(DefaultActionConfigurator):
|
|
|
569
578
|
self.subsample_field = None
|
|
570
579
|
self.fast_subsampling_field = None
|
|
571
580
|
|
|
572
|
-
def add_subsample_fields(self):
|
|
581
|
+
def add_subsample_fields(self, add_to_layout=None):
|
|
582
|
+
if add_to_layout is None:
|
|
583
|
+
add_to_layout = self.builder.main_layout
|
|
573
584
|
self.subsample_field = self.add_field(
|
|
574
585
|
'subsample', FIELD_COMBO, 'Subsample', required=False,
|
|
575
586
|
options=constants.FIELD_SUBSAMPLE_OPTIONS,
|
|
576
587
|
values=constants.FIELD_SUBSAMPLE_VALUES,
|
|
577
|
-
default=constants.FIELD_SUBSAMPLE_DEFAULT
|
|
588
|
+
default=constants.FIELD_SUBSAMPLE_DEFAULT,
|
|
589
|
+
add_to_layout=add_to_layout)
|
|
578
590
|
self.fast_subsampling_field = self.add_field(
|
|
579
591
|
'fast_subsampling', FIELD_BOOL, 'Fast subsampling', required=False,
|
|
580
|
-
default=constants.DEFAULT_ALIGN_FAST_SUBSAMPLING
|
|
592
|
+
default=constants.DEFAULT_ALIGN_FAST_SUBSAMPLING,
|
|
593
|
+
add_to_layout=add_to_layout)
|
|
581
594
|
|
|
582
595
|
self.subsample_field.currentTextChanged.connect(self.change_subsample)
|
|
583
|
-
|
|
584
596
|
self.change_subsample()
|
|
585
597
|
|
|
586
598
|
def change_subsample(self):
|
|
@@ -602,13 +614,14 @@ class AlignFramesConfigurator(SubsampleActionConfigurator):
|
|
|
602
614
|
self.detector_field = None
|
|
603
615
|
self.descriptor_field = None
|
|
604
616
|
self.matching_method_field = None
|
|
617
|
+
self.tab_widget = None
|
|
618
|
+
self.current_tab_layout = None
|
|
605
619
|
|
|
606
620
|
def show_info(self, message, timeout=3000):
|
|
607
621
|
self.info_label.setText(message)
|
|
608
|
-
self.info_label.setVisible(True)
|
|
609
622
|
timer = QTimer(self.info_label)
|
|
610
623
|
timer.setSingleShot(True)
|
|
611
|
-
timer.timeout.connect(self.info_label.
|
|
624
|
+
timer.timeout.connect(lambda: self.info_label.setText(''))
|
|
612
625
|
timer.start(timeout)
|
|
613
626
|
|
|
614
627
|
def change_match_config(self):
|
|
@@ -643,142 +656,157 @@ class AlignFramesConfigurator(SubsampleActionConfigurator):
|
|
|
643
656
|
self.detector_field = None
|
|
644
657
|
self.descriptor_field = None
|
|
645
658
|
self.matching_method_field = None
|
|
659
|
+
self.tab_widget = self.create_tab_widget(layout)
|
|
646
660
|
if self.expert:
|
|
647
|
-
self.
|
|
648
|
-
self.
|
|
649
|
-
self.
|
|
650
|
-
self.
|
|
651
|
-
|
|
652
|
-
self.
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
self.
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
661
|
+
feature_layout = self.add_tab(self.tab_widget, "Feature extraction")
|
|
662
|
+
self.create_feature_tab(feature_layout)
|
|
663
|
+
transform_layout = self.add_tab(self.tab_widget, "Transform")
|
|
664
|
+
self.create_transform_tab(transform_layout)
|
|
665
|
+
border_layout = self.add_tab(self.tab_widget, "Border")
|
|
666
|
+
self.create_border_tab(border_layout)
|
|
667
|
+
misc_layout = self.add_tab(self.tab_widget, "Miscellanea")
|
|
668
|
+
self.create_miscellanea_tab(misc_layout)
|
|
669
|
+
|
|
670
|
+
def create_feature_tab(self, layout):
|
|
671
|
+
self.add_bold_label_to_layout(layout, "Feature identification:")
|
|
672
|
+
self.detector_field = self.add_field_to_layout(
|
|
673
|
+
layout, 'detector', FIELD_COMBO, 'Detector', required=False,
|
|
674
|
+
options=constants.VALID_DETECTORS, default=constants.DEFAULT_DETECTOR)
|
|
675
|
+
self.descriptor_field = self.add_field_to_layout(
|
|
676
|
+
layout, 'descriptor', FIELD_COMBO, 'Descriptor', required=False,
|
|
677
|
+
options=constants.VALID_DESCRIPTORS, default=constants.DEFAULT_DESCRIPTOR)
|
|
678
|
+
self.detector_field.setToolTip(
|
|
679
|
+
"SIFT: Requires SIFT descriptor and K-NN matching\n"
|
|
680
|
+
"ORB/AKAZE: Work best with Hamming distance"
|
|
681
|
+
)
|
|
682
|
+
self.descriptor_field.setToolTip(
|
|
683
|
+
"SIFT: Requires K-NN matching\n"
|
|
684
|
+
"ORB/AKAZE: Require Hamming distance with ORB/AKAZE detectors"
|
|
685
|
+
)
|
|
686
|
+
self.detector_field.currentIndexChanged.connect(self.change_match_config)
|
|
687
|
+
self.descriptor_field.currentIndexChanged.connect(self.change_match_config)
|
|
688
|
+
self.info_label = QLabel()
|
|
689
|
+
self.info_label.setStyleSheet("color: orange; font-style: italic;")
|
|
690
|
+
layout.addRow(self.info_label)
|
|
691
|
+
self.add_bold_label_to_layout(layout, "Feature matching:")
|
|
692
|
+
self.matching_method_field = self.add_field_to_layout(
|
|
693
|
+
layout, 'match_method', FIELD_COMBO, 'Match method', required=False,
|
|
694
|
+
options=self.MATCHING_METHOD_OPTIONS, values=constants.VALID_MATCHING_METHODS,
|
|
695
|
+
default=constants.DEFAULT_MATCHING_METHOD)
|
|
696
|
+
self.matching_method_field.setToolTip(
|
|
697
|
+
"Automatically selected based on detector/descriptor combination"
|
|
698
|
+
)
|
|
699
|
+
self.matching_method_field.currentIndexChanged.connect(self.change_match_config)
|
|
700
|
+
self.add_field_to_layout(
|
|
701
|
+
layout, 'flann_idx_kdtree', FIELD_INT, 'Flann idx kdtree', required=False,
|
|
702
|
+
default=constants.DEFAULT_FLANN_IDX_KDTREE,
|
|
703
|
+
min_val=0, max_val=10)
|
|
704
|
+
self.add_field_to_layout(
|
|
705
|
+
layout, 'flann_trees', FIELD_INT, 'Flann trees', required=False,
|
|
706
|
+
default=constants.DEFAULT_FLANN_TREES,
|
|
707
|
+
min_val=0, max_val=10)
|
|
708
|
+
self.add_field_to_layout(
|
|
709
|
+
layout, 'flann_checks', FIELD_INT, 'Flann checks', required=False,
|
|
710
|
+
default=constants.DEFAULT_FLANN_CHECKS,
|
|
711
|
+
min_val=0, max_val=1000)
|
|
712
|
+
self.add_field_to_layout(
|
|
713
|
+
layout, 'threshold', FIELD_FLOAT, 'Threshold', required=False,
|
|
714
|
+
default=constants.DEFAULT_ALIGN_THRESHOLD,
|
|
715
|
+
min_val=0, max_val=1, step=0.05)
|
|
716
|
+
self.add_subsample_fields(add_to_layout=layout)
|
|
717
|
+
|
|
718
|
+
def create_transform_tab(self, layout):
|
|
719
|
+
self.add_bold_label_to_layout(layout, "Transform:")
|
|
720
|
+
transform = self.add_field_to_layout(
|
|
721
|
+
layout, 'transform', FIELD_COMBO, 'Transform', required=False,
|
|
722
|
+
options=self.TRANSFORM_OPTIONS, values=constants.VALID_TRANSFORMS,
|
|
723
|
+
default=constants.DEFAULT_TRANSFORM)
|
|
724
|
+
method = self.add_field_to_layout(
|
|
725
|
+
layout, 'align_method', FIELD_COMBO, 'Align method', required=False,
|
|
726
|
+
options=self.METHOD_OPTIONS, values=constants.VALID_ALIGN_METHODS,
|
|
727
|
+
default=constants.DEFAULT_ALIGN_METHOD)
|
|
728
|
+
rans_threshold = self.add_field_to_layout(
|
|
729
|
+
layout, 'rans_threshold', FIELD_FLOAT, 'RANSAC threshold (px)', required=False,
|
|
730
|
+
default=constants.DEFAULT_RANS_THRESHOLD, min_val=0, max_val=20, step=0.1)
|
|
731
|
+
self.add_field_to_layout(
|
|
732
|
+
layout, 'min_good_matches', FIELD_INT, "Min. good matches", required=False,
|
|
733
|
+
default=constants.DEFAULT_ALIGN_MIN_GOOD_MATCHES, min_val=0, max_val=500)
|
|
734
|
+
|
|
735
|
+
def change_method():
|
|
736
|
+
text = method.currentText()
|
|
737
|
+
if text == self.METHOD_OPTIONS[0]:
|
|
738
|
+
rans_threshold.setEnabled(True)
|
|
739
|
+
elif text == self.METHOD_OPTIONS[1]:
|
|
740
|
+
rans_threshold.setEnabled(False)
|
|
741
|
+
|
|
742
|
+
method.currentIndexChanged.connect(change_method)
|
|
743
|
+
change_method()
|
|
744
|
+
self.add_field_to_layout(
|
|
745
|
+
layout, 'align_confidence', FIELD_FLOAT, 'Confidence (%)',
|
|
746
|
+
required=False, decimals=1,
|
|
747
|
+
default=constants.DEFAULT_ALIGN_CONFIDENCE,
|
|
748
|
+
min_val=70.0, max_val=100.0, step=0.1)
|
|
749
|
+
refine_iters = self.add_field_to_layout(
|
|
750
|
+
layout, 'refine_iters', FIELD_INT, 'Refinement iterations (Rigid)', required=False,
|
|
751
|
+
default=constants.DEFAULT_REFINE_ITERS, min_val=0, max_val=1000)
|
|
752
|
+
max_iters = self.add_field_to_layout(
|
|
753
|
+
layout, 'max_iters', FIELD_INT, 'Max. iterations (Homography)', required=False,
|
|
754
|
+
default=constants.DEFAULT_ALIGN_MAX_ITERS, min_val=0, max_val=5000)
|
|
755
|
+
|
|
756
|
+
def change_transform():
|
|
757
|
+
text = transform.currentText()
|
|
758
|
+
if text == self.TRANSFORM_OPTIONS[0]:
|
|
759
|
+
refine_iters.setEnabled(True)
|
|
760
|
+
max_iters.setEnabled(False)
|
|
761
|
+
elif text == self.TRANSFORM_OPTIONS[1]:
|
|
762
|
+
refine_iters.setEnabled(False)
|
|
763
|
+
max_iters.setEnabled(True)
|
|
764
|
+
|
|
765
|
+
transform.currentIndexChanged.connect(change_transform)
|
|
766
|
+
change_transform()
|
|
767
|
+
self.add_field_to_layout(
|
|
768
|
+
layout, 'abort_abnormal', FIELD_BOOL, 'Abort on abnormal transf.',
|
|
769
|
+
required=False, default=constants.DEFAULT_ALIGN_ABORT_ABNORMAL)
|
|
770
|
+
|
|
771
|
+
def create_border_tab(self, layout):
|
|
772
|
+
self.add_bold_label_to_layout(layout, "Border:")
|
|
773
|
+
self.add_field_to_layout(
|
|
774
|
+
layout, 'border_mode', FIELD_COMBO, 'Border mode', required=False,
|
|
775
|
+
options=self.BORDER_MODE_OPTIONS,
|
|
776
|
+
values=constants.VALID_BORDER_MODES,
|
|
777
|
+
default=constants.DEFAULT_BORDER_MODE)
|
|
778
|
+
self.add_field_to_layout(
|
|
779
|
+
layout, 'border_value', FIELD_INT_TUPLE,
|
|
780
|
+
'Border value (if constant)', required=False, size=4,
|
|
781
|
+
default=constants.DEFAULT_BORDER_VALUE,
|
|
782
|
+
labels=constants.RGBA_LABELS,
|
|
783
|
+
min_val=constants.DEFAULT_BORDER_VALUE, max_val=[255] * 4)
|
|
784
|
+
self.add_field_to_layout(
|
|
785
|
+
layout, 'border_blur', FIELD_FLOAT, 'Border blur', required=False,
|
|
786
|
+
default=constants.DEFAULT_BORDER_BLUR,
|
|
787
|
+
min_val=0, max_val=1000, step=1)
|
|
788
|
+
|
|
789
|
+
def create_miscellanea_tab(self, layout):
|
|
790
|
+
self.add_bold_label_to_layout(layout, "Miscellanea:")
|
|
763
791
|
if self.expert:
|
|
764
|
-
mode = self.
|
|
765
|
-
'mode', FIELD_COMBO, 'Mode',
|
|
792
|
+
mode = self.add_field_to_layout(
|
|
793
|
+
layout, 'mode', FIELD_COMBO, 'Mode',
|
|
766
794
|
required=False, options=self.MODE_OPTIONS, values=constants.ALIGN_VALID_MODES,
|
|
767
795
|
default=dict(zip(constants.ALIGN_VALID_MODES,
|
|
768
796
|
self.MODE_OPTIONS))[constants.DEFAULT_ALIGN_MODE])
|
|
769
|
-
memory_limit = self.
|
|
770
|
-
'memory_limit', FIELD_FLOAT, 'Memory limit (approx., GBytes)',
|
|
797
|
+
memory_limit = self.add_field_to_layout(
|
|
798
|
+
layout, 'memory_limit', FIELD_FLOAT, 'Memory limit (approx., GBytes)',
|
|
771
799
|
required=False, default=constants.DEFAULT_ALIGN_MEMORY_LIMIT_GB,
|
|
772
800
|
min_val=1.0, max_val=64.0)
|
|
773
|
-
max_threads = self.
|
|
774
|
-
'max_threads', FIELD_INT, 'Max num. of cores',
|
|
801
|
+
max_threads = self.add_field_to_layout(
|
|
802
|
+
layout, 'max_threads', FIELD_INT, 'Max num. of cores',
|
|
775
803
|
required=False, default=constants.DEFAULT_ALIGN_MAX_THREADS,
|
|
776
804
|
min_val=1, max_val=64)
|
|
777
|
-
chunk_submit = self.
|
|
778
|
-
'chunk_submit', FIELD_BOOL, 'Submit in chunks',
|
|
805
|
+
chunk_submit = self.add_field_to_layout(
|
|
806
|
+
layout, 'chunk_submit', FIELD_BOOL, 'Submit in chunks',
|
|
779
807
|
required=False, default=constants.DEFAULT_ALIGN_CHUNK_SUBMIT)
|
|
780
|
-
bw_matching = self.
|
|
781
|
-
'bw_matching', FIELD_BOOL, 'Match using black & white',
|
|
808
|
+
bw_matching = self.add_field_to_layout(
|
|
809
|
+
layout, 'bw_matching', FIELD_BOOL, 'Match using black & white',
|
|
782
810
|
required=False, default=constants.DEFAULT_ALIGN_BW_MATCHING)
|
|
783
811
|
|
|
784
812
|
def change_mode():
|
|
@@ -790,11 +818,13 @@ class AlignFramesConfigurator(SubsampleActionConfigurator):
|
|
|
790
818
|
bw_matching.setEnabled(enabled)
|
|
791
819
|
|
|
792
820
|
mode.currentIndexChanged.connect(change_mode)
|
|
793
|
-
|
|
794
|
-
|
|
821
|
+
|
|
822
|
+
self.add_field_to_layout(
|
|
823
|
+
layout, 'plot_summary', FIELD_BOOL, 'Plot summary',
|
|
795
824
|
required=False, default=False)
|
|
796
|
-
|
|
797
|
-
|
|
825
|
+
|
|
826
|
+
self.add_field_to_layout(
|
|
827
|
+
layout, 'plot_matches', FIELD_BOOL, 'Plot matches',
|
|
798
828
|
required=False, default=False)
|
|
799
829
|
|
|
800
830
|
def update_params(self, params):
|