shinestacker 0.3.3__py3-none-any.whl → 0.3.4__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/__init__.py +2 -1
- shinestacker/_version.py +1 -1
- shinestacker/algorithms/__init__.py +3 -2
- shinestacker/algorithms/align.py +102 -64
- shinestacker/algorithms/balance.py +89 -42
- shinestacker/algorithms/base_stack_algo.py +42 -0
- shinestacker/algorithms/core_utils.py +6 -6
- shinestacker/algorithms/denoise.py +4 -1
- shinestacker/algorithms/depth_map.py +28 -39
- shinestacker/algorithms/exif.py +43 -38
- shinestacker/algorithms/multilayer.py +48 -28
- shinestacker/algorithms/noise_detection.py +34 -23
- shinestacker/algorithms/pyramid.py +42 -42
- shinestacker/algorithms/sharpen.py +1 -0
- shinestacker/algorithms/stack.py +42 -41
- shinestacker/algorithms/stack_framework.py +111 -65
- shinestacker/algorithms/utils.py +12 -11
- shinestacker/algorithms/vignetting.py +48 -22
- shinestacker/algorithms/white_balance.py +1 -0
- shinestacker/app/about_dialog.py +6 -2
- shinestacker/app/app_config.py +1 -0
- shinestacker/app/gui_utils.py +20 -0
- shinestacker/app/help_menu.py +1 -0
- shinestacker/app/main.py +9 -18
- shinestacker/app/open_frames.py +5 -4
- shinestacker/app/project.py +5 -16
- shinestacker/app/retouch.py +5 -17
- shinestacker/core/colors.py +4 -4
- shinestacker/core/core_utils.py +1 -1
- shinestacker/core/exceptions.py +2 -1
- shinestacker/core/framework.py +46 -33
- shinestacker/core/logging.py +9 -10
- shinestacker/gui/action_config.py +253 -197
- shinestacker/gui/actions_window.py +32 -28
- shinestacker/gui/colors.py +1 -0
- shinestacker/gui/gui_images.py +7 -3
- shinestacker/gui/gui_logging.py +3 -2
- shinestacker/gui/gui_run.py +53 -38
- shinestacker/gui/main_window.py +69 -25
- shinestacker/gui/new_project.py +35 -2
- shinestacker/gui/project_converter.py +21 -20
- shinestacker/gui/project_editor.py +45 -52
- shinestacker/gui/project_model.py +15 -23
- shinestacker/retouch/{filter_base.py → base_filter.py} +7 -4
- shinestacker/retouch/brush.py +1 -0
- shinestacker/retouch/brush_gradient.py +17 -3
- shinestacker/retouch/brush_preview.py +14 -10
- shinestacker/retouch/brush_tool.py +28 -19
- shinestacker/retouch/denoise_filter.py +3 -2
- shinestacker/retouch/display_manager.py +11 -5
- shinestacker/retouch/exif_data.py +1 -0
- shinestacker/retouch/file_loader.py +13 -9
- shinestacker/retouch/filter_manager.py +1 -0
- shinestacker/retouch/image_editor.py +14 -48
- shinestacker/retouch/image_editor_ui.py +10 -5
- shinestacker/retouch/image_filters.py +4 -2
- shinestacker/retouch/image_viewer.py +33 -31
- shinestacker/retouch/io_gui_handler.py +25 -13
- shinestacker/retouch/io_manager.py +3 -2
- shinestacker/retouch/layer_collection.py +79 -23
- shinestacker/retouch/shortcuts_help.py +1 -0
- shinestacker/retouch/undo_manager.py +7 -0
- shinestacker/retouch/unsharp_mask_filter.py +3 -2
- shinestacker/retouch/white_balance_filter.py +11 -6
- {shinestacker-0.3.3.dist-info → shinestacker-0.3.4.dist-info}/METADATA +10 -4
- shinestacker-0.3.4.dist-info/RECORD +86 -0
- shinestacker-0.3.3.dist-info/RECORD +0 -85
- {shinestacker-0.3.3.dist-info → shinestacker-0.3.4.dist-info}/WHEEL +0 -0
- {shinestacker-0.3.3.dist-info → shinestacker-0.3.4.dist-info}/entry_points.txt +0 -0
- {shinestacker-0.3.3.dist-info → shinestacker-0.3.4.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-0.3.3.dist-info → shinestacker-0.3.4.dist-info}/top_level.txt +0 -0
|
@@ -1,9 +1,13 @@
|
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, R0913, R0917, R0915, R0912
|
|
2
|
+
# pylint: disable=E0606, W0718, R1702, W0102, W0221
|
|
3
|
+
import traceback
|
|
1
4
|
from abc import ABC, abstractmethod
|
|
2
5
|
from typing import Dict, Any
|
|
3
6
|
import os.path
|
|
4
7
|
from PySide6.QtWidgets import (QWidget, QPushButton, QHBoxLayout, QFileDialog, QLabel, QComboBox,
|
|
5
8
|
QMessageBox, QSizePolicy, QStackedWidget, QDialog, QFormLayout,
|
|
6
|
-
QLineEdit, QSpinBox, QDoubleSpinBox, QCheckBox, QTreeView,
|
|
9
|
+
QLineEdit, QSpinBox, QDoubleSpinBox, QCheckBox, QTreeView,
|
|
10
|
+
QAbstractItemView, QListView)
|
|
7
11
|
from PySide6.QtCore import Qt, QTimer
|
|
8
12
|
from .. config.constants import constants
|
|
9
13
|
from .project_model import ActionConfig
|
|
@@ -27,11 +31,11 @@ class ActionConfigurator(ABC):
|
|
|
27
31
|
self.current_wd = current_wd
|
|
28
32
|
|
|
29
33
|
@abstractmethod
|
|
30
|
-
def create_form(self, layout: QFormLayout,
|
|
34
|
+
def create_form(self, layout: QFormLayout, action: ActionConfig, tag: str = "Action"):
|
|
31
35
|
pass
|
|
32
36
|
|
|
33
37
|
@abstractmethod
|
|
34
|
-
def update_params(self, params: Dict[str, Any]):
|
|
38
|
+
def update_params(self, params: Dict[str, Any]) -> bool:
|
|
35
39
|
pass
|
|
36
40
|
|
|
37
41
|
|
|
@@ -78,7 +82,9 @@ class FieldBuilder:
|
|
|
78
82
|
elif field_type == FIELD_BOOL:
|
|
79
83
|
default_value = kwargs.get('default', False)
|
|
80
84
|
elif field_type == FIELD_COMBO:
|
|
81
|
-
default_value = kwargs.get(
|
|
85
|
+
default_value = kwargs.get(
|
|
86
|
+
'default',
|
|
87
|
+
kwargs.get('options', [''])[0] if 'options' in kwargs else '')
|
|
82
88
|
self.fields[tag] = {
|
|
83
89
|
'widget': widget,
|
|
84
90
|
'type': field_type,
|
|
@@ -94,7 +100,8 @@ class FieldBuilder:
|
|
|
94
100
|
|
|
95
101
|
def reset_to_defaults(self):
|
|
96
102
|
for tag, field in self.fields.items():
|
|
97
|
-
if tag not in ['name', 'working_path', 'input_path', 'output_path',
|
|
103
|
+
if tag not in ['name', 'working_path', 'input_path', 'output_path',
|
|
104
|
+
'exif_path', 'plot_path']:
|
|
98
105
|
default = field['default_value']
|
|
99
106
|
widget = field['widget']
|
|
100
107
|
if field['type'] == FIELD_TEXT:
|
|
@@ -118,17 +125,16 @@ class FieldBuilder:
|
|
|
118
125
|
return widget.layout().itemAt(0).widget()
|
|
119
126
|
|
|
120
127
|
def get_working_path(self):
|
|
121
|
-
if 'working_path' in self.fields
|
|
128
|
+
if 'working_path' in self.fields:
|
|
122
129
|
working_path = self.get_path_widget(self.fields['working_path']['widget']).text()
|
|
123
130
|
if working_path != '':
|
|
124
131
|
return working_path
|
|
125
132
|
parent = self.action.parent
|
|
126
133
|
while parent is not None:
|
|
127
|
-
if 'working_path' in parent.params
|
|
134
|
+
if 'working_path' in parent.params and parent.params['working_path'] != '':
|
|
128
135
|
return parent.params['working_path']
|
|
129
136
|
parent = parent.parent
|
|
130
|
-
|
|
131
|
-
return ''
|
|
137
|
+
return ''
|
|
132
138
|
|
|
133
139
|
def update_params(self, params: Dict[str, Any]) -> bool:
|
|
134
140
|
for tag, field in self.fields.items():
|
|
@@ -143,13 +149,14 @@ class FieldBuilder:
|
|
|
143
149
|
elif field['type'] == FIELD_INT:
|
|
144
150
|
params[tag] = field['widget'].value()
|
|
145
151
|
elif field['type'] == FIELD_INT_TUPLE:
|
|
146
|
-
params[tag] = [field['widget'].layout().itemAt(1 + i * 2).widget().value()
|
|
152
|
+
params[tag] = [field['widget'].layout().itemAt(1 + i * 2).widget().value()
|
|
153
|
+
for i in range(field['size'])]
|
|
147
154
|
elif field['type'] == FIELD_COMBO:
|
|
148
155
|
values = field.get('values', None)
|
|
149
156
|
options = field.get('options', None)
|
|
150
157
|
text = field['widget'].currentText()
|
|
151
158
|
if values is not None and options is not None:
|
|
152
|
-
text =
|
|
159
|
+
text = dict(zip(options, values))[text]
|
|
153
160
|
params[tag] = text
|
|
154
161
|
if field['required'] and not params[tag]:
|
|
155
162
|
required = True
|
|
@@ -166,8 +173,9 @@ class FieldBuilder:
|
|
|
166
173
|
working_path = os.path.join(self.current_wd, working_path)
|
|
167
174
|
abs_path = os.path.normpath(os.path.join(working_path, params[tag]))
|
|
168
175
|
if not abs_path.startswith(os.path.normpath(working_path)):
|
|
169
|
-
QMessageBox.warning(
|
|
170
|
-
|
|
176
|
+
QMessageBox.warning(
|
|
177
|
+
None, "Invalid Path",
|
|
178
|
+
f"{field['label']} must be a subdirectory of working path")
|
|
171
179
|
return False
|
|
172
180
|
if field.get('must_exist', False):
|
|
173
181
|
paths = [abs_path]
|
|
@@ -180,6 +188,7 @@ class FieldBuilder:
|
|
|
180
188
|
f"{field['label']} {p} does not exist")
|
|
181
189
|
return False
|
|
182
190
|
except Exception as e:
|
|
191
|
+
traceback.print_tb(e.__traceback__)
|
|
183
192
|
QMessageBox.warning(None, "Error", f"Invalid path: {str(e)}")
|
|
184
193
|
return False
|
|
185
194
|
return True
|
|
@@ -256,12 +265,15 @@ class FieldBuilder:
|
|
|
256
265
|
try:
|
|
257
266
|
rel_path = os.path.relpath(path, working_path)
|
|
258
267
|
if rel_path.startswith('..'):
|
|
259
|
-
QMessageBox.warning(
|
|
260
|
-
|
|
268
|
+
QMessageBox.warning(
|
|
269
|
+
None, "Invalid Path",
|
|
270
|
+
f"{label} must be a subdirectory of working path")
|
|
261
271
|
return
|
|
262
272
|
rel_paths.append(rel_path)
|
|
263
|
-
except ValueError:
|
|
264
|
-
|
|
273
|
+
except ValueError as e:
|
|
274
|
+
traceback.print_tb(e.__traceback__)
|
|
275
|
+
QMessageBox.warning(None, "Error",
|
|
276
|
+
"Could not compute relative path")
|
|
265
277
|
return
|
|
266
278
|
|
|
267
279
|
if rel_paths:
|
|
@@ -279,8 +291,10 @@ class FieldBuilder:
|
|
|
279
291
|
f"{label} must be within working path")
|
|
280
292
|
return
|
|
281
293
|
rel_paths.append(rel_path)
|
|
282
|
-
except ValueError:
|
|
283
|
-
|
|
294
|
+
except ValueError as e:
|
|
295
|
+
traceback.print_tb(e.__traceback__)
|
|
296
|
+
QMessageBox.warning(None, "Error",
|
|
297
|
+
"Could not compute relative path")
|
|
284
298
|
return
|
|
285
299
|
edit.setText(constants.PATH_SEPARATOR.join(rel_paths))
|
|
286
300
|
else:
|
|
@@ -309,7 +323,8 @@ class FieldBuilder:
|
|
|
309
323
|
f"{label} must be a subdirectory of working path")
|
|
310
324
|
return
|
|
311
325
|
edit.setText(rel_path)
|
|
312
|
-
except ValueError:
|
|
326
|
+
except ValueError as e:
|
|
327
|
+
traceback.print_tb(e.__traceback__)
|
|
313
328
|
QMessageBox.warning(None, "Error", "Could not compute relative path")
|
|
314
329
|
|
|
315
330
|
button.clicked.connect(browse)
|
|
@@ -323,27 +338,30 @@ class FieldBuilder:
|
|
|
323
338
|
container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
|
324
339
|
return container
|
|
325
340
|
|
|
326
|
-
def create_float_field(self, tag, default=0.0,
|
|
341
|
+
def create_float_field(self, tag, default=0.0, min_val=0.0, max_val=1.0,
|
|
342
|
+
step=0.1, decimals=2):
|
|
327
343
|
spin = QDoubleSpinBox()
|
|
328
344
|
spin.setValue(self.action.params.get(tag, default))
|
|
329
|
-
spin.setRange(
|
|
345
|
+
spin.setRange(min_val, max_val)
|
|
330
346
|
spin.setDecimals(decimals)
|
|
331
347
|
spin.setSingleStep(step)
|
|
332
348
|
return spin
|
|
333
349
|
|
|
334
|
-
def create_int_field(self, tag, default=0,
|
|
350
|
+
def create_int_field(self, tag, default=0, min_val=0, max_val=100):
|
|
335
351
|
spin = QSpinBox()
|
|
336
|
-
spin.setRange(
|
|
352
|
+
spin.setRange(min_val, max_val)
|
|
337
353
|
spin.setValue(self.action.params.get(tag, default))
|
|
338
354
|
return spin
|
|
339
355
|
|
|
340
|
-
def create_int_tuple_field(self, tag, size=1,
|
|
356
|
+
def create_int_tuple_field(self, tag, size=1,
|
|
357
|
+
default=[0] * 100, min_val=[0] * 100, max_val=[100] * 100,
|
|
358
|
+
**kwargs):
|
|
341
359
|
layout = QHBoxLayout()
|
|
342
360
|
spins = [QSpinBox() for i in range(size)]
|
|
343
361
|
labels = kwargs.get('labels', ('') * size)
|
|
344
362
|
value = self.action.params.get(tag, default)
|
|
345
363
|
for i, spin in enumerate(spins):
|
|
346
|
-
spin.setRange(
|
|
364
|
+
spin.setRange(min_val[i], max_val[i])
|
|
347
365
|
spin.setValue(value[i])
|
|
348
366
|
spin.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
|
|
349
367
|
label = QLabel(labels[i] + ":")
|
|
@@ -364,11 +382,11 @@ class FieldBuilder:
|
|
|
364
382
|
combo.addItems(options)
|
|
365
383
|
value = self.action.params.get(tag, default or options[0] if options else '')
|
|
366
384
|
if values is not None and len(options) > 0:
|
|
367
|
-
value =
|
|
385
|
+
value = dict(zip(values, options)).get(value, value)
|
|
368
386
|
combo.setCurrentText(value)
|
|
369
387
|
return combo
|
|
370
388
|
|
|
371
|
-
def create_bool_field(self, tag, default=False
|
|
389
|
+
def create_bool_field(self, tag, default=False):
|
|
372
390
|
checkbox = QCheckBox()
|
|
373
391
|
checkbox.setChecked(self.action.params.get(tag, default))
|
|
374
392
|
return checkbox
|
|
@@ -403,26 +421,27 @@ class ActionConfigDialog(QDialog):
|
|
|
403
421
|
|
|
404
422
|
def get_configurator(self, action_type: str) -> ActionConfigurator:
|
|
405
423
|
configurators = {
|
|
406
|
-
constants.ACTION_JOB: JobConfigurator
|
|
407
|
-
constants.ACTION_COMBO: CombinedActionsConfigurator
|
|
408
|
-
constants.ACTION_NOISEDETECTION: NoiseDetectionConfigurator
|
|
409
|
-
constants.ACTION_FOCUSSTACK: FocusStackConfigurator
|
|
410
|
-
constants.ACTION_FOCUSSTACKBUNCH: FocusStackBunchConfigurator
|
|
411
|
-
constants.ACTION_MULTILAYER: MultiLayerConfigurator
|
|
412
|
-
constants.ACTION_MASKNOISE: MaskNoiseConfigurator
|
|
413
|
-
constants.ACTION_VIGNETTING: VignettingConfigurator
|
|
414
|
-
constants.ACTION_ALIGNFRAMES: AlignFramesConfigurator
|
|
415
|
-
constants.ACTION_BALANCEFRAMES: BalanceFramesConfigurator
|
|
424
|
+
constants.ACTION_JOB: JobConfigurator,
|
|
425
|
+
constants.ACTION_COMBO: CombinedActionsConfigurator,
|
|
426
|
+
constants.ACTION_NOISEDETECTION: NoiseDetectionConfigurator,
|
|
427
|
+
constants.ACTION_FOCUSSTACK: FocusStackConfigurator,
|
|
428
|
+
constants.ACTION_FOCUSSTACKBUNCH: FocusStackBunchConfigurator,
|
|
429
|
+
constants.ACTION_MULTILAYER: MultiLayerConfigurator,
|
|
430
|
+
constants.ACTION_MASKNOISE: MaskNoiseConfigurator,
|
|
431
|
+
constants.ACTION_VIGNETTING: VignettingConfigurator,
|
|
432
|
+
constants.ACTION_ALIGNFRAMES: AlignFramesConfigurator,
|
|
433
|
+
constants.ACTION_BALANCEFRAMES: BalanceFramesConfigurator,
|
|
416
434
|
}
|
|
417
|
-
return configurators.get(
|
|
435
|
+
return configurators.get(
|
|
436
|
+
action_type, DefaultActionConfigurator)(self.expert(), self.current_wd)
|
|
418
437
|
|
|
419
438
|
def accept(self):
|
|
420
|
-
self.parent().
|
|
439
|
+
self.parent().project_buffer.append(self.parent().project.clone())
|
|
421
440
|
if self.configurator.update_params(self.action.params):
|
|
422
441
|
self.parent().mark_as_modified()
|
|
423
442
|
super().accept()
|
|
424
443
|
else:
|
|
425
|
-
self.parent().
|
|
444
|
+
self.parent().project_buffer.pop()
|
|
426
445
|
|
|
427
446
|
def reset_to_defaults(self):
|
|
428
447
|
builder = self.configurator.get_builder()
|
|
@@ -436,11 +455,12 @@ class ActionConfigDialog(QDialog):
|
|
|
436
455
|
class NoNameActionConfigurator(ActionConfigurator):
|
|
437
456
|
def __init__(self, expert, current_wd):
|
|
438
457
|
super().__init__(expert, current_wd)
|
|
458
|
+
self.builder = None
|
|
439
459
|
|
|
440
460
|
def get_builder(self):
|
|
441
461
|
return self.builder
|
|
442
462
|
|
|
443
|
-
def update_params(self, params):
|
|
463
|
+
def update_params(self, params: Dict[str, Any]) -> bool:
|
|
444
464
|
return self.builder.update_params(params)
|
|
445
465
|
|
|
446
466
|
def add_bold_label(self, label):
|
|
@@ -450,18 +470,12 @@ class NoNameActionConfigurator(ActionConfigurator):
|
|
|
450
470
|
|
|
451
471
|
|
|
452
472
|
class DefaultActionConfigurator(NoNameActionConfigurator):
|
|
453
|
-
def __init__(self, expert, current_wd):
|
|
454
|
-
super().__init__(expert, current_wd)
|
|
455
|
-
|
|
456
473
|
def create_form(self, layout, action, tag='Action'):
|
|
457
474
|
self.builder = FieldBuilder(layout, action, self.current_wd)
|
|
458
475
|
self.builder.add_field('name', FIELD_TEXT, f'{tag} name', required=True)
|
|
459
476
|
|
|
460
477
|
|
|
461
478
|
class JobConfigurator(DefaultActionConfigurator):
|
|
462
|
-
def __init__(self, expert, current_wd):
|
|
463
|
-
super().__init__(expert, current_wd)
|
|
464
|
-
|
|
465
479
|
def create_form(self, layout, action):
|
|
466
480
|
super().create_form(layout, action, "Job")
|
|
467
481
|
self.builder.add_field('working_path', FIELD_ABS_PATH, 'Working path', required=True)
|
|
@@ -470,37 +484,39 @@ class JobConfigurator(DefaultActionConfigurator):
|
|
|
470
484
|
|
|
471
485
|
|
|
472
486
|
class NoiseDetectionConfigurator(DefaultActionConfigurator):
|
|
473
|
-
def __init__(self, expert, current_wd):
|
|
474
|
-
super().__init__(expert, current_wd)
|
|
475
|
-
|
|
476
487
|
def create_form(self, layout, action):
|
|
477
488
|
super().create_form(layout, action)
|
|
478
489
|
self.builder.add_field('working_path', FIELD_ABS_PATH, 'Working path', required=True,
|
|
479
490
|
placeholder='inherit from job')
|
|
480
|
-
self.builder.add_field('input_path', FIELD_REL_PATH,
|
|
481
|
-
|
|
491
|
+
self.builder.add_field('input_path', FIELD_REL_PATH,
|
|
492
|
+
f'Input path (separate by {constants.PATH_SEPARATOR})',
|
|
493
|
+
required=False, multiple_entries=True,
|
|
494
|
+
placeholder='relative to working path')
|
|
482
495
|
self.builder.add_field('max_frames', FIELD_INT, 'Max. num. of frames', required=False,
|
|
483
|
-
default=-1,
|
|
484
|
-
self.builder.add_field('channel_thresholds', FIELD_INT_TUPLE, 'Noise threshold',
|
|
485
|
-
|
|
496
|
+
default=-1, min_val=-1, max_val=1000)
|
|
497
|
+
self.builder.add_field('channel_thresholds', FIELD_INT_TUPLE, 'Noise threshold',
|
|
498
|
+
required=False, size=3,
|
|
499
|
+
default=constants.DEFAULT_CHANNEL_THRESHOLDS,
|
|
500
|
+
labels=constants.RGB_LABELS, min_val=[1] * 3,
|
|
501
|
+
max_val=[1000] * 3)
|
|
486
502
|
if self.expert:
|
|
487
503
|
self.builder.add_field('blur_size', FIELD_INT, 'Blur size (px)', required=False,
|
|
488
|
-
default=constants.DEFAULT_BLUR_SIZE,
|
|
504
|
+
default=constants.DEFAULT_BLUR_SIZE, min_val=1, max_val=50)
|
|
489
505
|
self.builder.add_field('file_name', FIELD_TEXT, 'File name', required=False,
|
|
490
506
|
default=constants.DEFAULT_NOISE_MAP_FILENAME,
|
|
491
507
|
placeholder=constants.DEFAULT_NOISE_MAP_FILENAME)
|
|
492
508
|
self.add_bold_label("Miscellanea:")
|
|
493
|
-
self.builder.add_field('plot_histograms', FIELD_BOOL, 'Plot histograms', required=False,
|
|
494
|
-
|
|
509
|
+
self.builder.add_field('plot_histograms', FIELD_BOOL, 'Plot histograms', required=False,
|
|
510
|
+
default=False)
|
|
511
|
+
self.builder.add_field('plot_path', FIELD_REL_PATH, 'Plots path', required=False,
|
|
512
|
+
default=constants.DEFAULT_PLOTS_PATH,
|
|
495
513
|
placeholder='relative to working path')
|
|
496
|
-
self.builder.add_field('plot_range', FIELD_INT_TUPLE, 'Plot range', required=False,
|
|
497
|
-
default=constants.DEFAULT_NOISE_PLOT_RANGE,
|
|
514
|
+
self.builder.add_field('plot_range', FIELD_INT_TUPLE, 'Plot range', required=False,
|
|
515
|
+
size=2, default=constants.DEFAULT_NOISE_PLOT_RANGE,
|
|
516
|
+
labels=['min', 'max'], min_val=[0] * 2, max_val=[1000] * 2)
|
|
498
517
|
|
|
499
518
|
|
|
500
519
|
class FocusStackBaseConfigurator(DefaultActionConfigurator):
|
|
501
|
-
def __init__(self, expert, current_wd):
|
|
502
|
-
super().__init__(expert, current_wd)
|
|
503
|
-
|
|
504
520
|
ENERGY_OPTIONS = ['Laplacian', 'Sobel']
|
|
505
521
|
MAP_TYPE_OPTIONS = ['Average', 'Maximum']
|
|
506
522
|
FLOAT_OPTIONS = ['float 32 bits', 'float 64 bits']
|
|
@@ -516,12 +532,13 @@ class FocusStackBaseConfigurator(DefaultActionConfigurator):
|
|
|
516
532
|
self.builder.add_field('scratch_output_dir', FIELD_BOOL, 'Scratch output dir.',
|
|
517
533
|
required=False, default=True)
|
|
518
534
|
|
|
519
|
-
def common_fields(self, layout
|
|
520
|
-
self.builder.add_field('
|
|
521
|
-
default=0,
|
|
535
|
+
def common_fields(self, layout):
|
|
536
|
+
self.builder.add_field('denoise_amount', FIELD_FLOAT, 'Denoise', required=False,
|
|
537
|
+
default=0, min_val=0, max_val=10)
|
|
522
538
|
self.add_bold_label("Stacking algorithm:")
|
|
523
539
|
combo = self.builder.add_field('stacker', FIELD_COMBO, 'Stacking algorithm', required=True,
|
|
524
|
-
options=constants.STACK_ALGO_OPTIONS,
|
|
540
|
+
options=constants.STACK_ALGO_OPTIONS,
|
|
541
|
+
default=constants.STACK_ALGO_DEFAULT)
|
|
525
542
|
q_pyramid, q_depthmap = QWidget(), QWidget()
|
|
526
543
|
for q in [q_pyramid, q_depthmap]:
|
|
527
544
|
layout = QFormLayout()
|
|
@@ -544,54 +561,57 @@ class FocusStackBaseConfigurator(DefaultActionConfigurator):
|
|
|
544
561
|
if self.expert:
|
|
545
562
|
self.builder.add_field('pyramid_min_size', FIELD_INT, 'Minimum size (px)',
|
|
546
563
|
required=False, add_to_layout=q_pyramid.layout(),
|
|
547
|
-
default=constants.DEFAULT_PY_MIN_SIZE,
|
|
564
|
+
default=constants.DEFAULT_PY_MIN_SIZE, min_val=2, max_val=256)
|
|
548
565
|
self.builder.add_field('pyramid_kernel_size', FIELD_INT, 'Kernel size (px)',
|
|
549
566
|
required=False, add_to_layout=q_pyramid.layout(),
|
|
550
|
-
default=constants.DEFAULT_PY_KERNEL_SIZE,
|
|
567
|
+
default=constants.DEFAULT_PY_KERNEL_SIZE, min_val=3, max_val=21)
|
|
551
568
|
self.builder.add_field('pyramid_gen_kernel', FIELD_FLOAT, 'Gen. kernel',
|
|
552
569
|
required=False, add_to_layout=q_pyramid.layout(),
|
|
553
|
-
default=constants.DEFAULT_PY_GEN_KERNEL,
|
|
570
|
+
default=constants.DEFAULT_PY_GEN_KERNEL,
|
|
571
|
+
min_val=0.0, max_val=2.0)
|
|
554
572
|
self.builder.add_field('pyramid_float_type', FIELD_COMBO, 'Precision', required=False,
|
|
555
|
-
add_to_layout=q_pyramid.layout(),
|
|
556
|
-
|
|
557
|
-
|
|
573
|
+
add_to_layout=q_pyramid.layout(),
|
|
574
|
+
options=self.FLOAT_OPTIONS, values=constants.VALID_FLOATS,
|
|
575
|
+
default=dict(zip(constants.VALID_FLOATS,
|
|
576
|
+
self.FLOAT_OPTIONS))[constants.DEFAULT_PY_FLOAT])
|
|
558
577
|
self.builder.add_field('depthmap_energy', FIELD_COMBO, 'Energy', required=False,
|
|
559
578
|
add_to_layout=q_depthmap.layout(),
|
|
560
579
|
options=self.ENERGY_OPTIONS, values=constants.VALID_DM_ENERGY,
|
|
561
|
-
default=
|
|
562
|
-
|
|
580
|
+
default=dict(zip(constants.VALID_DM_ENERGY,
|
|
581
|
+
self.ENERGY_OPTIONS))[constants.DEFAULT_DM_ENERGY])
|
|
563
582
|
self.builder.add_field('map_type', FIELD_COMBO, 'Map type', required=False,
|
|
564
583
|
add_to_layout=q_depthmap.layout(),
|
|
565
584
|
options=self.MAP_TYPE_OPTIONS, values=constants.VALID_DM_MAP,
|
|
566
|
-
default=
|
|
567
|
-
|
|
585
|
+
default=dict(zip(constants.VALID_DM_MAP,
|
|
586
|
+
self.MAP_TYPE_OPTIONS))[constants.DEFAULT_DM_MAP])
|
|
568
587
|
if self.expert:
|
|
569
588
|
self.builder.add_field('depthmap_kernel_size', FIELD_INT, 'Kernel size (px)',
|
|
570
589
|
required=False, add_to_layout=q_depthmap.layout(),
|
|
571
|
-
default=constants.DEFAULT_DM_KERNEL_SIZE,
|
|
590
|
+
default=constants.DEFAULT_DM_KERNEL_SIZE, min_val=3, max_val=21)
|
|
572
591
|
self.builder.add_field('depthmap_blur_size', FIELD_INT, 'Blurl size (px)',
|
|
573
592
|
required=False, add_to_layout=q_depthmap.layout(),
|
|
574
|
-
default=constants.DEFAULT_DM_BLUR_SIZE,
|
|
593
|
+
default=constants.DEFAULT_DM_BLUR_SIZE, min_val=1, max_val=21)
|
|
575
594
|
self.builder.add_field('depthmap_smooth_size', FIELD_INT, 'Smooth size (px)',
|
|
576
595
|
required=False, add_to_layout=q_depthmap.layout(),
|
|
577
|
-
default=constants.DEFAULT_DM_SMOOTH_SIZE,
|
|
578
|
-
self.builder.add_field('depthmap_temperature', FIELD_FLOAT, 'Temperature',
|
|
579
|
-
|
|
580
|
-
|
|
596
|
+
default=constants.DEFAULT_DM_SMOOTH_SIZE, min_val=0, max_val=256)
|
|
597
|
+
self.builder.add_field('depthmap_temperature', FIELD_FLOAT, 'Temperature',
|
|
598
|
+
required=False,
|
|
599
|
+
add_to_layout=q_depthmap.layout(),
|
|
600
|
+
default=constants.DEFAULT_DM_TEMPERATURE,
|
|
601
|
+
min_val=0, max_val=1, step=0.05)
|
|
581
602
|
self.builder.add_field('depthmap_levels', FIELD_INT, 'Levels', required=False,
|
|
582
|
-
add_to_layout=q_depthmap.layout(),
|
|
603
|
+
add_to_layout=q_depthmap.layout(),
|
|
604
|
+
default=constants.DEFAULT_DM_LEVELS, min_val=2, max_val=6)
|
|
583
605
|
self.builder.add_field('depthmap_float_type', FIELD_COMBO, 'Precision', required=False,
|
|
584
|
-
add_to_layout=q_depthmap.layout(), options=self.FLOAT_OPTIONS,
|
|
585
|
-
|
|
586
|
-
|
|
606
|
+
add_to_layout=q_depthmap.layout(), options=self.FLOAT_OPTIONS,
|
|
607
|
+
values=constants.VALID_FLOATS,
|
|
608
|
+
default=dict(zip(constants.VALID_FLOATS,
|
|
609
|
+
self.FLOAT_OPTIONS))[constants.DEFAULT_DM_FLOAT])
|
|
587
610
|
self.builder.layout.addRow(stacked)
|
|
588
611
|
combo.currentIndexChanged.connect(change)
|
|
589
612
|
|
|
590
613
|
|
|
591
614
|
class FocusStackConfigurator(FocusStackBaseConfigurator):
|
|
592
|
-
def __init__(self, expert, current_wd):
|
|
593
|
-
super().__init__(expert, current_wd)
|
|
594
|
-
|
|
595
615
|
def create_form(self, layout, action):
|
|
596
616
|
super().create_form(layout, action)
|
|
597
617
|
if self.expert:
|
|
@@ -601,35 +621,30 @@ class FocusStackConfigurator(FocusStackBaseConfigurator):
|
|
|
601
621
|
default=constants.DEFAULT_STACK_PREFIX, placeholder="_stack")
|
|
602
622
|
self.builder.add_field('plot_stack', FIELD_BOOL, 'Plot stack', required=False,
|
|
603
623
|
default=constants.DEFAULT_PLOT_STACK)
|
|
604
|
-
super().common_fields(layout
|
|
624
|
+
super().common_fields(layout)
|
|
605
625
|
|
|
606
626
|
|
|
607
627
|
class FocusStackBunchConfigurator(FocusStackBaseConfigurator):
|
|
608
|
-
def __init__(self, expert, current_wd):
|
|
609
|
-
super().__init__(expert, current_wd)
|
|
610
|
-
|
|
611
628
|
def create_form(self, layout, action):
|
|
612
629
|
super().create_form(layout, action)
|
|
613
630
|
self.builder.add_field('frames', FIELD_INT, 'Frames', required=False,
|
|
614
|
-
default=constants.DEFAULT_FRAMES,
|
|
631
|
+
default=constants.DEFAULT_FRAMES, min_val=1, max_val=100)
|
|
615
632
|
self.builder.add_field('overlap', FIELD_INT, 'Overlapping frames', required=False,
|
|
616
|
-
default=constants.DEFAULT_OVERLAP,
|
|
633
|
+
default=constants.DEFAULT_OVERLAP, min_val=0, max_val=100)
|
|
617
634
|
self.builder.add_field('plot_stack', FIELD_BOOL, 'Plot stack', required=False,
|
|
618
635
|
default=constants.DEFAULT_PLOT_STACK_BUNCH)
|
|
619
|
-
super().common_fields(layout
|
|
636
|
+
super().common_fields(layout)
|
|
620
637
|
|
|
621
638
|
|
|
622
639
|
class MultiLayerConfigurator(DefaultActionConfigurator):
|
|
623
|
-
def __init__(self, expert, current_wd):
|
|
624
|
-
super().__init__(expert, current_wd)
|
|
625
|
-
|
|
626
640
|
def create_form(self, layout, action):
|
|
627
641
|
super().create_form(layout, action)
|
|
628
642
|
if self.expert:
|
|
629
643
|
self.builder.add_field('working_path', FIELD_ABS_PATH, 'Working path', required=False)
|
|
630
644
|
self.builder.add_field('input_path', FIELD_REL_PATH,
|
|
631
|
-
f'Input path (separate by {constants.PATH_SEPARATOR})',
|
|
632
|
-
multiple_entries=True,
|
|
645
|
+
f'Input path (separate by {constants.PATH_SEPARATOR})',
|
|
646
|
+
required=False, multiple_entries=True,
|
|
647
|
+
placeholder='relative to working path')
|
|
633
648
|
self.builder.add_field('output_path', FIELD_REL_PATH, 'Output path', required=False,
|
|
634
649
|
placeholder='relative to working path')
|
|
635
650
|
self.builder.add_field('exif_path', FIELD_REL_PATH, 'Exif data path', required=False,
|
|
@@ -637,15 +652,13 @@ class MultiLayerConfigurator(DefaultActionConfigurator):
|
|
|
637
652
|
self.builder.add_field('scratch_output_dir', FIELD_BOOL, 'Scratch output dir.',
|
|
638
653
|
required=False, default=True)
|
|
639
654
|
self.builder.add_field('reverse_order', FIELD_BOOL, 'Reverse file order',
|
|
640
|
-
required=False,
|
|
655
|
+
required=False,
|
|
656
|
+
default=constants.DEFAULT_MULTILAYER_FILE_REVERSE_ORDER)
|
|
641
657
|
|
|
642
658
|
|
|
643
659
|
class CombinedActionsConfigurator(DefaultActionConfigurator):
|
|
644
|
-
def __init__(self, expert, current_wd):
|
|
645
|
-
super().__init__(expert, current_wd)
|
|
646
|
-
|
|
647
660
|
def create_form(self, layout, action):
|
|
648
|
-
|
|
661
|
+
super().create_form(layout, action)
|
|
649
662
|
if self.expert:
|
|
650
663
|
self.builder.add_field('working_path', FIELD_ABS_PATH, 'Working path', required=False)
|
|
651
664
|
self.builder.add_field('input_path', FIELD_REL_PATH, 'Input path', required=False,
|
|
@@ -655,51 +668,52 @@ class CombinedActionsConfigurator(DefaultActionConfigurator):
|
|
|
655
668
|
self.builder.add_field('scratch_output_dir', FIELD_BOOL, 'Scratch output dir.',
|
|
656
669
|
required=False, default=True)
|
|
657
670
|
if self.expert:
|
|
658
|
-
self.builder.add_field('plot_path', FIELD_REL_PATH, 'Plots path', required=False,
|
|
659
|
-
placeholder='relative to working path')
|
|
671
|
+
self.builder.add_field('plot_path', FIELD_REL_PATH, 'Plots path', required=False,
|
|
672
|
+
default="plots", placeholder='relative to working path')
|
|
660
673
|
self.builder.add_field('resample', FIELD_INT, 'Resample frame stack', required=False,
|
|
661
|
-
default=1,
|
|
674
|
+
default=1, min_val=1, max_val=100)
|
|
662
675
|
self.builder.add_field('ref_idx', FIELD_INT, 'Reference frame index', required=False,
|
|
663
|
-
default=-1,
|
|
664
|
-
self.builder.add_field('step_process', FIELD_BOOL, 'Step process', required=False,
|
|
665
|
-
|
|
676
|
+
default=-1, min_val=-1, max_val=1000)
|
|
677
|
+
self.builder.add_field('step_process', FIELD_BOOL, 'Step process', required=False,
|
|
678
|
+
default=True)
|
|
666
679
|
|
|
667
|
-
class MaskNoiseConfigurator(NoNameActionConfigurator):
|
|
668
|
-
def __init__(self, expert, current_wd):
|
|
669
|
-
super().__init__(expert, current_wd)
|
|
670
680
|
|
|
681
|
+
class MaskNoiseConfigurator(DefaultActionConfigurator):
|
|
671
682
|
def create_form(self, layout, action):
|
|
672
|
-
|
|
683
|
+
super().create_form(layout, action)
|
|
673
684
|
self.builder.add_field('noise_mask', FIELD_REL_PATH, 'Noise mask file', required=False,
|
|
674
685
|
path_type='file', must_exist=True,
|
|
675
|
-
default=constants.DEFAULT_NOISE_MAP_FILENAME,
|
|
686
|
+
default=constants.DEFAULT_NOISE_MAP_FILENAME,
|
|
687
|
+
placeholder=constants.DEFAULT_NOISE_MAP_FILENAME)
|
|
676
688
|
if self.expert:
|
|
677
689
|
self.builder.add_field('kernel_size', FIELD_INT, 'Kernel size', required=False,
|
|
678
|
-
default=constants.DEFAULT_MN_KERNEL_SIZE,
|
|
690
|
+
default=constants.DEFAULT_MN_KERNEL_SIZE, min_va=1, max_val=10)
|
|
679
691
|
self.builder.add_field('method', FIELD_COMBO, 'Interpolation method', required=False,
|
|
680
692
|
options=['Mean', 'Median'], default='Mean')
|
|
681
693
|
|
|
682
694
|
|
|
683
|
-
class VignettingConfigurator(
|
|
684
|
-
def __init__(self, expert, current_wd):
|
|
685
|
-
super().__init__(expert, current_wd)
|
|
686
|
-
|
|
695
|
+
class VignettingConfigurator(DefaultActionConfigurator):
|
|
687
696
|
def create_form(self, layout, action):
|
|
688
|
-
|
|
697
|
+
super().create_form(layout, action)
|
|
689
698
|
if self.expert:
|
|
690
699
|
self.builder.add_field('r_steps', FIELD_INT, 'Radial steps', required=False,
|
|
691
|
-
default=constants.DEFAULT_R_STEPS,
|
|
692
|
-
self.builder.add_field('black_threshold', FIELD_INT, 'Black intensity threshold',
|
|
693
|
-
default=constants.DEFAULT_BLACK_THRESHOLD,
|
|
700
|
+
default=constants.DEFAULT_R_STEPS, min_val=1, max_val=1000)
|
|
701
|
+
self.builder.add_field('black_threshold', FIELD_INT, 'Black intensity threshold',
|
|
702
|
+
required=False, default=constants.DEFAULT_BLACK_THRESHOLD,
|
|
703
|
+
min_val=0, max_val=1000)
|
|
694
704
|
self.builder.add_field('max_correction', FIELD_FLOAT, 'Max. correction', required=False,
|
|
695
|
-
default=constants.DEFAULT_MAX_CORRECTION,
|
|
705
|
+
default=constants.DEFAULT_MAX_CORRECTION,
|
|
706
|
+
min_val=0, max_val=1, step=0.05)
|
|
696
707
|
self.add_bold_label("Miscellanea:")
|
|
697
|
-
self.builder.add_field('plot_correction', FIELD_BOOL, 'Plot correction', required=False,
|
|
698
|
-
|
|
699
|
-
self.builder.add_field('
|
|
708
|
+
self.builder.add_field('plot_correction', FIELD_BOOL, 'Plot correction', required=False,
|
|
709
|
+
default=False)
|
|
710
|
+
self.builder.add_field('plot_summary', FIELD_BOOL, 'Plot summary', required=False,
|
|
711
|
+
default=False)
|
|
712
|
+
self.builder.add_field('apply_correction', FIELD_BOOL, 'Apply correction', required=False,
|
|
713
|
+
default=True)
|
|
700
714
|
|
|
701
715
|
|
|
702
|
-
class AlignFramesConfigurator(
|
|
716
|
+
class AlignFramesConfigurator(DefaultActionConfigurator):
|
|
703
717
|
BORDER_MODE_OPTIONS = ['Constant', 'Replicate', 'Replicate and blur']
|
|
704
718
|
TRANSFORM_OPTIONS = ['Rigid', 'Homography']
|
|
705
719
|
METHOD_OPTIONS = ['Random Sample Consensus (RANSAC)', 'Least Median (LMEDS)']
|
|
@@ -707,22 +721,32 @@ class AlignFramesConfigurator(NoNameActionConfigurator):
|
|
|
707
721
|
|
|
708
722
|
def __init__(self, expert, current_wd):
|
|
709
723
|
super().__init__(expert, current_wd)
|
|
724
|
+
self.matching_method_field = None
|
|
725
|
+
self.info_label = None
|
|
726
|
+
self.detector_field = None
|
|
727
|
+
self.descriptor_field = None
|
|
728
|
+
self.matching_method_field = None
|
|
710
729
|
|
|
711
730
|
def show_info(self, message, timeout=3000):
|
|
712
731
|
self.info_label.setText(message)
|
|
713
732
|
self.info_label.setVisible(True)
|
|
714
|
-
QTimer
|
|
733
|
+
timer = QTimer(self.info_label)
|
|
734
|
+
timer.setSingleShot(True)
|
|
735
|
+
timer.timeout.connect(self.info_label.hide)
|
|
736
|
+
timer.start(timeout)
|
|
715
737
|
|
|
716
738
|
def change_match_config(self):
|
|
717
739
|
detector = self.detector_field.currentText()
|
|
718
740
|
descriptor = self.descriptor_field.currentText()
|
|
719
|
-
match_method =
|
|
720
|
-
|
|
741
|
+
match_method = dict(
|
|
742
|
+
zip(self.MATCHING_METHOD_OPTIONS,
|
|
743
|
+
constants.VALID_MATCHING_METHODS))[self.matching_method_field.currentText()]
|
|
721
744
|
try:
|
|
722
745
|
validate_align_config(detector, descriptor, match_method)
|
|
723
746
|
except Exception as e:
|
|
724
747
|
self.show_info(str(e))
|
|
725
|
-
if descriptor == constants.DETECTOR_SIFT and
|
|
748
|
+
if descriptor == constants.DETECTOR_SIFT and \
|
|
749
|
+
match_method == constants.MATCHING_NORM_HAMMING:
|
|
726
750
|
self.matching_method_field.setCurrentText(self.MATCHING_METHOD_OPTIONS[0])
|
|
727
751
|
if detector == constants.DETECTOR_ORB and descriptor == constants.DESCRIPTOR_AKAZE and \
|
|
728
752
|
match_method == constants.MATCHING_NORM_HAMMING:
|
|
@@ -733,14 +757,16 @@ class AlignFramesConfigurator(NoNameActionConfigurator):
|
|
|
733
757
|
self.descriptor_field.setCurrentText('SIFT')
|
|
734
758
|
if detector == constants.DETECTOR_SIFT and descriptor != constants.DESCRIPTOR_SIFT:
|
|
735
759
|
self.descriptor_field.setCurrentText('SIFT')
|
|
736
|
-
if detector in constants.NOKNN_METHODS['detectors'] and
|
|
760
|
+
if detector in constants.NOKNN_METHODS['detectors'] and \
|
|
761
|
+
descriptor in constants.NOKNN_METHODS['descriptors']:
|
|
737
762
|
if match_method == constants.MATCHING_KNN:
|
|
738
763
|
self.matching_method_field.setCurrentText(self.MATCHING_METHOD_OPTIONS[1])
|
|
739
764
|
|
|
740
765
|
def create_form(self, layout, action):
|
|
741
|
-
|
|
742
|
-
self.detector_field =
|
|
743
|
-
self.descriptor_field =
|
|
766
|
+
super().create_form(layout, action)
|
|
767
|
+
self.detector_field = None
|
|
768
|
+
self.descriptor_field = None
|
|
769
|
+
self.matching_method_field = None
|
|
744
770
|
if self.expert:
|
|
745
771
|
self.add_bold_label("Feature identification:")
|
|
746
772
|
|
|
@@ -749,15 +775,18 @@ class AlignFramesConfigurator(NoNameActionConfigurator):
|
|
|
749
775
|
self.info_label.setVisible(False)
|
|
750
776
|
layout.addRow(self.info_label)
|
|
751
777
|
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
778
|
+
self.detector_field = self.builder.add_field(
|
|
779
|
+
'detector', FIELD_COMBO, 'Detector', required=False,
|
|
780
|
+
options=constants.VALID_DETECTORS, default=constants.DEFAULT_DETECTOR)
|
|
781
|
+
self.descriptor_field = self.builder.add_field(
|
|
782
|
+
'descriptor', FIELD_COMBO, 'Descriptor', required=False,
|
|
783
|
+
options=constants.VALID_DESCRIPTORS, default=constants.DEFAULT_DESCRIPTOR)
|
|
756
784
|
|
|
757
785
|
self.add_bold_label("Feature matching:")
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
786
|
+
self.matching_method_field = self.builder.add_field(
|
|
787
|
+
'match_method', FIELD_COMBO, 'Match method', required=False,
|
|
788
|
+
options=self.MATCHING_METHOD_OPTIONS, values=constants.VALID_MATCHING_METHODS,
|
|
789
|
+
default=constants.DEFAULT_MATCHING_METHOD)
|
|
761
790
|
self.detector_field.setToolTip(
|
|
762
791
|
"SIFT: Requires SIFT descriptor and K-NN matching\n"
|
|
763
792
|
"ORB/AKAZE: Work best with Hamming distance"
|
|
@@ -772,27 +801,35 @@ class AlignFramesConfigurator(NoNameActionConfigurator):
|
|
|
772
801
|
"Automatically selected based on detector/descriptor combination"
|
|
773
802
|
)
|
|
774
803
|
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
self.builder.add_field('flann_idx_kdtree', FIELD_INT, 'Flann idx kdtree',
|
|
779
|
-
|
|
804
|
+
self.detector_field.currentIndexChanged.connect(self.change_match_config)
|
|
805
|
+
self.descriptor_field.currentIndexChanged.connect(self.change_match_config)
|
|
806
|
+
self.matching_method_field.currentIndexChanged.connect(self.change_match_config)
|
|
807
|
+
self.builder.add_field('flann_idx_kdtree', FIELD_INT, 'Flann idx kdtree',
|
|
808
|
+
required=False,
|
|
809
|
+
default=constants.DEFAULT_FLANN_IDX_KDTREE,
|
|
810
|
+
min_val=0, max_val=10)
|
|
780
811
|
self.builder.add_field('flann_trees', FIELD_INT, 'Flann trees', required=False,
|
|
781
|
-
default=constants.DEFAULT_FLANN_TREES,
|
|
812
|
+
default=constants.DEFAULT_FLANN_TREES,
|
|
813
|
+
min_val=0, max_val=10)
|
|
782
814
|
self.builder.add_field('flann_checks', FIELD_INT, 'Flann checks', required=False,
|
|
783
|
-
default=constants.DEFAULT_FLANN_CHECKS,
|
|
815
|
+
default=constants.DEFAULT_FLANN_CHECKS,
|
|
816
|
+
min_val=0, max_val=1000)
|
|
784
817
|
self.builder.add_field('threshold', FIELD_FLOAT, 'Threshold', required=False,
|
|
785
|
-
default=constants.DEFAULT_ALIGN_THRESHOLD,
|
|
818
|
+
default=constants.DEFAULT_ALIGN_THRESHOLD,
|
|
819
|
+
min_val=0, max_val=1, step=0.05)
|
|
786
820
|
|
|
787
821
|
self.add_bold_label("Transform:")
|
|
788
|
-
transform = self.builder.add_field(
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
822
|
+
transform = self.builder.add_field(
|
|
823
|
+
'transform', FIELD_COMBO, 'Transform', required=False,
|
|
824
|
+
options=self.TRANSFORM_OPTIONS, values=constants.VALID_TRANSFORMS,
|
|
825
|
+
default=constants.DEFAULT_TRANSFORM)
|
|
826
|
+
method = self.builder.add_field(
|
|
827
|
+
'align_method', FIELD_COMBO, 'Align method', required=False,
|
|
828
|
+
options=self.METHOD_OPTIONS, values=constants.VALID_ALIGN_METHODS,
|
|
829
|
+
default=constants.DEFAULT_ALIGN_METHOD)
|
|
830
|
+
rans_threshold = self.builder.add_field(
|
|
831
|
+
'rans_threshold', FIELD_FLOAT, 'RANSAC threshold (px)', required=False,
|
|
832
|
+
default=constants.DEFAULT_RANS_THRESHOLD, min_val=0, max_val=20, step=0.1)
|
|
796
833
|
|
|
797
834
|
def change_method():
|
|
798
835
|
text = method.currentText()
|
|
@@ -802,13 +839,17 @@ class AlignFramesConfigurator(NoNameActionConfigurator):
|
|
|
802
839
|
rans_threshold.setEnabled(False)
|
|
803
840
|
method.currentIndexChanged.connect(change_method)
|
|
804
841
|
change_method()
|
|
805
|
-
self.builder.add_field('align_confidence', FIELD_FLOAT, 'Confidence (%)',
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
842
|
+
self.builder.add_field('align_confidence', FIELD_FLOAT, 'Confidence (%)',
|
|
843
|
+
required=False, decimals=1,
|
|
844
|
+
default=constants.DEFAULT_ALIGN_CONFIDENCE,
|
|
845
|
+
min_val=70.0, max_val=100.0, step=0.1)
|
|
846
|
+
|
|
847
|
+
refine_iters = self.builder.add_field(
|
|
848
|
+
'refine_iters', FIELD_INT, 'Refinement iterations (Rigid)', required=False,
|
|
849
|
+
default=constants.DEFAULT_REFINE_ITERS, min_val=0, max_val=1000)
|
|
850
|
+
max_iters = self.builder.add_field(
|
|
851
|
+
'max_iters', FIELD_INT, 'Max. iterations (Homography)', required=False,
|
|
852
|
+
default=constants.DEFAULT_ALIGN_MAX_ITERS, min_val=0, max_val=5000)
|
|
812
853
|
|
|
813
854
|
def change_transform():
|
|
814
855
|
text = transform.currentText()
|
|
@@ -820,10 +861,12 @@ class AlignFramesConfigurator(NoNameActionConfigurator):
|
|
|
820
861
|
max_iters.setEnabled(True)
|
|
821
862
|
transform.currentIndexChanged.connect(change_transform)
|
|
822
863
|
change_transform()
|
|
823
|
-
subsample = self.builder.add_field(
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
864
|
+
subsample = self.builder.add_field(
|
|
865
|
+
'subsample', FIELD_INT, 'Subsample factor', required=False,
|
|
866
|
+
default=constants.DEFAULT_ALIGN_SUBSAMPLE, min_val=1, max_val=256)
|
|
867
|
+
fast_subsampling = self.builder.add_field(
|
|
868
|
+
'fast_subsampling', FIELD_BOOL, 'Fast subsampling', required=False,
|
|
869
|
+
default=constants.DEFAULT_ALIGN_FAST_SUBSAMPLING)
|
|
827
870
|
|
|
828
871
|
def change_subsample():
|
|
829
872
|
fast_subsampling.setEnabled(subsample.value() > 1)
|
|
@@ -831,54 +874,67 @@ class AlignFramesConfigurator(NoNameActionConfigurator):
|
|
|
831
874
|
change_subsample()
|
|
832
875
|
self.add_bold_label("Border:")
|
|
833
876
|
self.builder.add_field('border_mode', FIELD_COMBO, 'Border mode', required=False,
|
|
834
|
-
options=self.BORDER_MODE_OPTIONS,
|
|
877
|
+
options=self.BORDER_MODE_OPTIONS,
|
|
878
|
+
values=constants.VALID_BORDER_MODES,
|
|
835
879
|
default=constants.DEFAULT_BORDER_MODE)
|
|
836
|
-
self.builder.add_field('border_value', FIELD_INT_TUPLE,
|
|
837
|
-
|
|
838
|
-
|
|
880
|
+
self.builder.add_field('border_value', FIELD_INT_TUPLE,
|
|
881
|
+
'Border value (if constant)', required=False, size=4,
|
|
882
|
+
default=constants.DEFAULT_BORDER_VALUE,
|
|
883
|
+
labels=constants.RGBA_LABELS,
|
|
884
|
+
min_val=constants.DEFAULT_BORDER_VALUE, max_val=[255] * 4)
|
|
839
885
|
self.builder.add_field('border_blur', FIELD_FLOAT, 'Border blur', required=False,
|
|
840
|
-
default=constants.DEFAULT_BORDER_BLUR,
|
|
886
|
+
default=constants.DEFAULT_BORDER_BLUR,
|
|
887
|
+
min_val=0, max_val=1000, step=1)
|
|
841
888
|
self.add_bold_label("Miscellanea:")
|
|
842
|
-
self.builder.add_field('plot_summary', FIELD_BOOL, 'Plot summary',
|
|
843
|
-
|
|
889
|
+
self.builder.add_field('plot_summary', FIELD_BOOL, 'Plot summary',
|
|
890
|
+
required=False, default=False)
|
|
891
|
+
self.builder.add_field('plot_matches', FIELD_BOOL, 'Plot matches',
|
|
892
|
+
required=False, default=False)
|
|
844
893
|
|
|
845
894
|
def update_params(self, params: Dict[str, Any]) -> bool:
|
|
846
895
|
if self.detector_field and self.descriptor_field and self.matching_method_field:
|
|
847
896
|
try:
|
|
848
897
|
detector = self.detector_field.currentText()
|
|
849
898
|
descriptor = self.descriptor_field.currentText()
|
|
850
|
-
match_method =
|
|
851
|
-
|
|
899
|
+
match_method = dict(
|
|
900
|
+
zip(self.MATCHING_METHOD_OPTIONS,
|
|
901
|
+
constants.VALID_MATCHING_METHODS))[
|
|
902
|
+
self.matching_method_field.currentText()]
|
|
852
903
|
validate_align_config(detector, descriptor, match_method)
|
|
853
904
|
return super().update_params(params)
|
|
854
905
|
except Exception as e:
|
|
906
|
+
traceback.print_tb(e.__traceback__)
|
|
855
907
|
QMessageBox.warning(None, "Error", f"{str(e)}")
|
|
856
908
|
return False
|
|
909
|
+
return super().update_params(params)
|
|
857
910
|
|
|
858
911
|
|
|
859
|
-
class BalanceFramesConfigurator(
|
|
912
|
+
class BalanceFramesConfigurator(DefaultActionConfigurator):
|
|
860
913
|
CORRECTION_MAP_OPTIONS = ['Linear', 'Gamma', 'Match histograms']
|
|
861
914
|
CHANNEL_OPTIONS = ['Luminosity', 'RGB', 'HSV', 'HLS']
|
|
862
915
|
|
|
863
|
-
def __init__(self, expert, current_wd):
|
|
864
|
-
super().__init__(expert, current_wd)
|
|
865
|
-
|
|
866
916
|
def create_form(self, layout, action):
|
|
867
|
-
|
|
917
|
+
super().create_form(layout, action)
|
|
868
918
|
if self.expert:
|
|
869
919
|
self.builder.add_field('mask_size', FIELD_FLOAT, 'Mask size', required=False,
|
|
870
|
-
default=0,
|
|
871
|
-
self.builder.add_field('intensity_interval', FIELD_INT_TUPLE, 'Intensity range',
|
|
872
|
-
|
|
873
|
-
|
|
920
|
+
default=0, min_val=0, max_val=5, step=0.1)
|
|
921
|
+
self.builder.add_field('intensity_interval', FIELD_INT_TUPLE, 'Intensity range',
|
|
922
|
+
required=False, size=2,
|
|
923
|
+
default=[v for k, v in
|
|
924
|
+
constants.DEFAULT_INTENSITY_INTERVAL.items()],
|
|
925
|
+
labels=['min', 'max'], min_val=[-1] * 2, max_val=[65536] * 2)
|
|
874
926
|
self.builder.add_field('subsample', FIELD_INT, 'Subsample factor', required=False,
|
|
875
|
-
default=constants.DEFAULT_BALANCE_SUBSAMPLE,
|
|
927
|
+
default=constants.DEFAULT_BALANCE_SUBSAMPLE,
|
|
928
|
+
min_val=1, max_val=256)
|
|
876
929
|
self.builder.add_field('corr_map', FIELD_COMBO, 'Correction map', required=False,
|
|
877
930
|
options=self.CORRECTION_MAP_OPTIONS, values=constants.VALID_BALANCE,
|
|
878
931
|
default='Linear')
|
|
879
932
|
self.builder.add_field('channel', FIELD_COMBO, 'Channel', required=False,
|
|
880
|
-
options=self.CHANNEL_OPTIONS,
|
|
933
|
+
options=self.CHANNEL_OPTIONS,
|
|
934
|
+
values=constants.VALID_BALANCE_CHANNELS,
|
|
881
935
|
default='Luminosity')
|
|
882
936
|
self.add_bold_label("Miscellanea:")
|
|
883
|
-
self.builder.add_field('plot_summary', FIELD_BOOL, 'Plot summary',
|
|
884
|
-
|
|
937
|
+
self.builder.add_field('plot_summary', FIELD_BOOL, 'Plot summary',
|
|
938
|
+
required=False, default=False)
|
|
939
|
+
self.builder.add_field('plot_histograms', FIELD_BOOL, 'Plot histograms',
|
|
940
|
+
required=False, default=False)
|