shinestacker 0.3.2__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.

Files changed (71) hide show
  1. shinestacker/__init__.py +2 -1
  2. shinestacker/_version.py +1 -1
  3. shinestacker/algorithms/__init__.py +3 -2
  4. shinestacker/algorithms/align.py +102 -64
  5. shinestacker/algorithms/balance.py +89 -42
  6. shinestacker/algorithms/base_stack_algo.py +42 -0
  7. shinestacker/algorithms/core_utils.py +6 -6
  8. shinestacker/algorithms/denoise.py +4 -1
  9. shinestacker/algorithms/depth_map.py +28 -39
  10. shinestacker/algorithms/exif.py +43 -38
  11. shinestacker/algorithms/multilayer.py +48 -28
  12. shinestacker/algorithms/noise_detection.py +34 -23
  13. shinestacker/algorithms/pyramid.py +42 -42
  14. shinestacker/algorithms/sharpen.py +1 -0
  15. shinestacker/algorithms/stack.py +42 -41
  16. shinestacker/algorithms/stack_framework.py +111 -65
  17. shinestacker/algorithms/utils.py +12 -11
  18. shinestacker/algorithms/vignetting.py +48 -22
  19. shinestacker/algorithms/white_balance.py +1 -0
  20. shinestacker/app/about_dialog.py +6 -2
  21. shinestacker/app/app_config.py +1 -0
  22. shinestacker/app/gui_utils.py +20 -0
  23. shinestacker/app/help_menu.py +1 -0
  24. shinestacker/app/main.py +9 -18
  25. shinestacker/app/open_frames.py +5 -4
  26. shinestacker/app/project.py +5 -16
  27. shinestacker/app/retouch.py +5 -17
  28. shinestacker/core/colors.py +4 -4
  29. shinestacker/core/core_utils.py +1 -1
  30. shinestacker/core/exceptions.py +2 -1
  31. shinestacker/core/framework.py +46 -33
  32. shinestacker/core/logging.py +9 -10
  33. shinestacker/gui/action_config.py +253 -197
  34. shinestacker/gui/actions_window.py +32 -28
  35. shinestacker/gui/colors.py +1 -0
  36. shinestacker/gui/gui_images.py +7 -3
  37. shinestacker/gui/gui_logging.py +3 -2
  38. shinestacker/gui/gui_run.py +53 -38
  39. shinestacker/gui/main_window.py +69 -25
  40. shinestacker/gui/new_project.py +35 -2
  41. shinestacker/gui/project_converter.py +21 -20
  42. shinestacker/gui/project_editor.py +45 -52
  43. shinestacker/gui/project_model.py +15 -23
  44. shinestacker/retouch/{filter_base.py → base_filter.py} +7 -4
  45. shinestacker/retouch/brush.py +1 -0
  46. shinestacker/retouch/brush_gradient.py +17 -3
  47. shinestacker/retouch/brush_preview.py +14 -10
  48. shinestacker/retouch/brush_tool.py +28 -19
  49. shinestacker/retouch/denoise_filter.py +3 -2
  50. shinestacker/retouch/display_manager.py +11 -5
  51. shinestacker/retouch/exif_data.py +1 -0
  52. shinestacker/retouch/file_loader.py +13 -9
  53. shinestacker/retouch/filter_manager.py +1 -0
  54. shinestacker/retouch/image_editor.py +14 -48
  55. shinestacker/retouch/image_editor_ui.py +10 -5
  56. shinestacker/retouch/image_filters.py +4 -2
  57. shinestacker/retouch/image_viewer.py +33 -31
  58. shinestacker/retouch/io_gui_handler.py +25 -13
  59. shinestacker/retouch/io_manager.py +3 -2
  60. shinestacker/retouch/layer_collection.py +79 -23
  61. shinestacker/retouch/shortcuts_help.py +1 -0
  62. shinestacker/retouch/undo_manager.py +7 -0
  63. shinestacker/retouch/unsharp_mask_filter.py +3 -2
  64. shinestacker/retouch/white_balance_filter.py +11 -6
  65. {shinestacker-0.3.2.dist-info → shinestacker-0.3.4.dist-info}/METADATA +10 -4
  66. shinestacker-0.3.4.dist-info/RECORD +86 -0
  67. shinestacker-0.3.2.dist-info/RECORD +0 -85
  68. {shinestacker-0.3.2.dist-info → shinestacker-0.3.4.dist-info}/WHEEL +0 -0
  69. {shinestacker-0.3.2.dist-info → shinestacker-0.3.4.dist-info}/entry_points.txt +0 -0
  70. {shinestacker-0.3.2.dist-info → shinestacker-0.3.4.dist-info}/licenses/LICENSE +0 -0
  71. {shinestacker-0.3.2.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, QAbstractItemView, QListView)
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, params: Dict[str, Any]):
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('default', kwargs.get('options', [''])[0] if 'options' in kwargs else '')
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', 'exif_path', 'plot_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.keys():
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.keys() and parent.params['working_path'] != '':
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
- else:
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() for i in range(field['size'])]
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 = {k: v for k, v in zip(options, values)}[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(None, "Invalid Path",
170
- f"{field['label']} must be a subdirectory of working path")
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(None, "Invalid Path",
260
- f"{label} must be a subdirectory of working path")
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
- QMessageBox.warning(None, "Error", "Could not compute relative path")
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
- QMessageBox.warning(None, "Error", "Could not compute relative path")
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, min=0.0, max=1.0, step=0.1, decimals=2, **kwargs):
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(min, max)
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, min=0, max=100, **kwargs):
350
+ def create_int_field(self, tag, default=0, min_val=0, max_val=100):
335
351
  spin = QSpinBox()
336
- spin.setRange(min, max)
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, default=[0] * 100, min=[0] * 100, max=[100] * 100, **kwargs):
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(min[i], max[i])
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 = {k: v for k, v in zip(values, options)}.get(value, 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, **kwargs):
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(self.expert(), self.current_wd),
407
- constants.ACTION_COMBO: CombinedActionsConfigurator(self.expert(), self.current_wd),
408
- constants.ACTION_NOISEDETECTION: NoiseDetectionConfigurator(self.expert(), self.current_wd),
409
- constants.ACTION_FOCUSSTACK: FocusStackConfigurator(self.expert(), self.current_wd),
410
- constants.ACTION_FOCUSSTACKBUNCH: FocusStackBunchConfigurator(self.expert(), self.current_wd),
411
- constants.ACTION_MULTILAYER: MultiLayerConfigurator(self.expert(), self.current_wd),
412
- constants.ACTION_MASKNOISE: MaskNoiseConfigurator(self.expert(), self.current_wd),
413
- constants.ACTION_VIGNETTING: VignettingConfigurator(self.expert(), self.current_wd),
414
- constants.ACTION_ALIGNFRAMES: AlignFramesConfigurator(self.expert(), self.current_wd),
415
- constants.ACTION_BALANCEFRAMES: BalanceFramesConfigurator(self.expert(), self.current_wd),
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(action_type, DefaultActionConfigurator(self.expert(), self.current_wd))
435
+ return configurators.get(
436
+ action_type, DefaultActionConfigurator)(self.expert(), self.current_wd)
418
437
 
419
438
  def accept(self):
420
- self.parent()._project_buffer.append(self.parent().project.clone())
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()._project_buffer.pop()
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, f'Input path (separate by {constants.PATH_SEPARATOR})', required=False,
481
- multiple_entries=True, placeholder='relative to working path')
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, min=-1, max=1000)
484
- self.builder.add_field('channel_thresholds', FIELD_INT_TUPLE, 'Noise threshold', required=False, size=3,
485
- default=constants.DEFAULT_CHANNEL_THRESHOLDS, labels=constants.RGB_LABELS, min=[1] * 3, max=[1000] * 3)
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, min=1, max=50)
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, default=False)
494
- self.builder.add_field('plot_path', FIELD_REL_PATH, 'Plots path', required=False, default=constants.DEFAULT_PLOTS_PATH,
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, size=2,
497
- default=constants.DEFAULT_NOISE_PLOT_RANGE, labels=['min', 'max'], min=[0] * 2, max=[1000] * 2)
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, action):
520
- self.builder.add_field('denoise', FIELD_FLOAT, 'Denoise', required=False,
521
- default=0, min=0, max=10)
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, default=constants.STACK_ALGO_DEFAULT)
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, min=2, max=256)
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, min=3, max=21)
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, min=0.0, max=2.0)
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(), options=self.FLOAT_OPTIONS, values=constants.VALID_FLOATS,
556
- default={k: v for k, v in
557
- zip(constants.VALID_FLOATS, self.FLOAT_OPTIONS)}[constants.DEFAULT_PY_FLOAT])
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={k: v for k, v in
562
- zip(constants.VALID_DM_ENERGY, self.ENERGY_OPTIONS)}[constants.DEFAULT_DM_ENERGY])
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={k: v for k, v in
567
- zip(constants.VALID_DM_MAP, self.MAP_TYPE_OPTIONS)}[constants.DEFAULT_DM_MAP])
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, min=3, max=21)
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, min=1, max=21)
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, min=0, max=256)
578
- self.builder.add_field('depthmap_temperature', FIELD_FLOAT, 'Temperature', required=False,
579
- add_to_layout=q_depthmap.layout(), default=constants.DEFAULT_DM_TEMPERATURE,
580
- min=0, max=1, step=0.05)
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(), default=constants.DEFAULT_DM_LEVELS, min=2, max=6)
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, values=constants.VALID_FLOATS,
585
- default={k: v for k, v in
586
- zip(constants.VALID_FLOATS, self.FLOAT_OPTIONS)}[constants.DEFAULT_DM_FLOAT])
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, action)
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, min=1, max=100)
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, min=0, max=100)
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, action)
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})', required=False,
632
- multiple_entries=True, placeholder='relative to working path')
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, default=constants.DEFAULT_MULTILAYER_FILE_REVERSE_ORDER)
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
- DefaultActionConfigurator.create_form(self, layout, action)
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, default="plots",
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, min=1, max=100)
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, min=-1, max=1000)
664
- self.builder.add_field('step_process', FIELD_BOOL, 'Step process', required=False, default=True)
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
- DefaultActionConfigurator.create_form(self, layout, action)
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, placeholder=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, min=1, max=10)
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(NoNameActionConfigurator):
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
- DefaultActionConfigurator.create_form(self, layout, action)
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, min=1, max=1000)
692
- self.builder.add_field('black_threshold', FIELD_INT, 'Black intensity threshold', required=False,
693
- default=constants.DEFAULT_BLACK_THRESHOLD, min=0, max=1000)
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, min=0, max=1, step=0.05)
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, default=False)
698
- self.builder.add_field('plot_summary', FIELD_BOOL, 'Plot summary', required=False, default=False)
699
- self.builder.add_field('apply_correction', FIELD_BOOL, 'Apply correction', required=False, default=True)
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(NoNameActionConfigurator):
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.singleShot(timeout, lambda: self.info_label.setVisible(False))
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 = {k: v for k, v in zip(self.MATCHING_METHOD_OPTIONS,
720
- constants.VALID_MATCHING_METHODS)}[self.matching_method_field.currentText()]
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 match_method == constants.MATCHING_NORM_HAMMING:
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 descriptor in constants.NOKNN_METHODS['descriptors']:
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
- DefaultActionConfigurator.create_form(self, layout, action)
742
- self.detector_field = 0
743
- self.descriptor_field = 0
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
- detector = self.detector_field = self.builder.add_field('detector', FIELD_COMBO, 'Detector', required=False,
753
- options=constants.VALID_DETECTORS, default=constants.DEFAULT_DETECTOR)
754
- descriptor = self.descriptor_field = self.builder.add_field('descriptor', FIELD_COMBO, 'Descriptor', required=False,
755
- options=constants.VALID_DESCRIPTORS, default=constants.DEFAULT_DESCRIPTOR)
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
- match_method = self.matching_method_field = self.builder.add_field('match_method', FIELD_COMBO, 'Match method', required=False,
759
- options=self.MATCHING_METHOD_OPTIONS, values=constants.VALID_MATCHING_METHODS,
760
- default=constants.DEFAULT_MATCHING_METHOD)
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
- detector.currentIndexChanged.connect(self.change_match_config)
776
- descriptor.currentIndexChanged.connect(self.change_match_config)
777
- match_method.currentIndexChanged.connect(self.change_match_config)
778
- self.builder.add_field('flann_idx_kdtree', FIELD_INT, 'Flann idx kdtree', required=False,
779
- default=constants.DEFAULT_FLANN_IDX_KDTREE, min=0, max=10)
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, min=0, max=10)
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, min=0, max=1000)
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, min=0, max=1, step=0.05)
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('transform', FIELD_COMBO, 'Transform', required=False,
789
- options=self.TRANSFORM_OPTIONS, values=constants.VALID_TRANSFORMS,
790
- default=constants.DEFAULT_TRANSFORM)
791
- method = self.builder.add_field('align_method', FIELD_COMBO, 'Align method', required=False,
792
- options=self.METHOD_OPTIONS, values=constants.VALID_ALIGN_METHODS,
793
- default=constants.DEFAULT_ALIGN_METHOD)
794
- rans_threshold = self.builder.add_field('rans_threshold', FIELD_FLOAT, 'RANSAC threshold (px)', required=False,
795
- default=constants.DEFAULT_RANS_THRESHOLD, min=0, max=20, step=0.1)
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 (%)', required=False, decimals=1,
806
- default=constants.DEFAULT_ALIGN_CONFIDENCE, min=70.0, max=100.0, step=0.1)
807
-
808
- refine_iters = self.builder.add_field('refine_iters', FIELD_INT, 'Refinement iterations (Rigid)', required=False,
809
- default=constants.DEFAULT_REFINE_ITERS, min=0, max=1000)
810
- max_iters = self.builder.add_field('max_iters', FIELD_INT, 'Max. iterations (Homography)', required=False,
811
- default=constants.DEFAULT_ALIGN_MAX_ITERS, min=0, max=5000)
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('subsample', FIELD_INT, 'Subsample factor', required=False,
824
- default=constants.DEFAULT_ALIGN_SUBSAMPLE, min=0, max=256)
825
- fast_subsampling = self.builder.add_field('fast_subsampling', FIELD_BOOL, 'Fast subsampling', required=False,
826
- default=constants.DEFAULT_ALIGN_FAST_SUBSAMPLING)
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, values=constants.VALID_BORDER_MODES,
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, 'Border value (if constant)', required=False, size=4,
837
- default=constants.DEFAULT_BORDER_VALUE, labels=constants.RGBA_LABELS,
838
- min=constants.DEFAULT_BORDER_VALUE, max=[255] * 4)
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, min=0, max=1000, step=1)
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', required=False, default=False)
843
- self.builder.add_field('plot_matches', FIELD_BOOL, 'Plot matches', required=False, default=False)
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 = {k: v for k, v in zip(self.MATCHING_METHOD_OPTIONS,
851
- constants.VALID_MATCHING_METHODS)}[self.matching_method_field.currentText()]
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(NoNameActionConfigurator):
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
- DefaultActionConfigurator.create_form(self, layout, action)
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, min=0, max=5, step=0.1)
871
- self.builder.add_field('intensity_interval', FIELD_INT_TUPLE, 'Intensity range', required=False, size=2,
872
- default=[v for k, v in constants.DEFAULT_INTENSITY_INTERVAL.items()],
873
- labels=['min', 'max'], min=[-1] * 2, max=[65536] * 2)
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, min=1, max=256)
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, values=constants.VALID_BALANCE_CHANNELS,
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', required=False, default=False)
884
- self.builder.add_field('plot_histograms', FIELD_BOOL, 'Plot histograms', required=False, default=False)
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)