shinestacker 1.1.0__py3-none-any.whl → 1.2.1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of shinestacker might be problematic. Click here for more details.

Files changed (38) hide show
  1. shinestacker/_version.py +1 -1
  2. shinestacker/algorithms/__init__.py +4 -1
  3. shinestacker/algorithms/align.py +149 -34
  4. shinestacker/algorithms/balance.py +364 -166
  5. shinestacker/algorithms/base_stack_algo.py +6 -0
  6. shinestacker/algorithms/depth_map.py +1 -1
  7. shinestacker/algorithms/multilayer.py +22 -13
  8. shinestacker/algorithms/noise_detection.py +7 -8
  9. shinestacker/algorithms/pyramid.py +3 -2
  10. shinestacker/algorithms/pyramid_auto.py +141 -0
  11. shinestacker/algorithms/pyramid_tiles.py +199 -44
  12. shinestacker/algorithms/stack.py +20 -20
  13. shinestacker/algorithms/stack_framework.py +136 -156
  14. shinestacker/algorithms/utils.py +175 -1
  15. shinestacker/algorithms/vignetting.py +26 -8
  16. shinestacker/config/constants.py +31 -6
  17. shinestacker/core/framework.py +12 -12
  18. shinestacker/gui/action_config.py +59 -7
  19. shinestacker/gui/action_config_dialog.py +427 -283
  20. shinestacker/gui/base_form_dialog.py +11 -6
  21. shinestacker/gui/gui_images.py +10 -10
  22. shinestacker/gui/gui_run.py +1 -1
  23. shinestacker/gui/main_window.py +6 -5
  24. shinestacker/gui/menu_manager.py +16 -2
  25. shinestacker/gui/new_project.py +26 -22
  26. shinestacker/gui/project_controller.py +43 -27
  27. shinestacker/gui/project_converter.py +2 -8
  28. shinestacker/gui/project_editor.py +50 -27
  29. shinestacker/gui/tab_widget.py +3 -3
  30. shinestacker/retouch/exif_data.py +5 -5
  31. shinestacker/retouch/shortcuts_help.py +4 -4
  32. shinestacker/retouch/vignetting_filter.py +12 -8
  33. {shinestacker-1.1.0.dist-info → shinestacker-1.2.1.dist-info}/METADATA +1 -1
  34. {shinestacker-1.1.0.dist-info → shinestacker-1.2.1.dist-info}/RECORD +38 -37
  35. {shinestacker-1.1.0.dist-info → shinestacker-1.2.1.dist-info}/WHEEL +0 -0
  36. {shinestacker-1.1.0.dist-info → shinestacker-1.2.1.dist-info}/entry_points.txt +0 -0
  37. {shinestacker-1.1.0.dist-info → shinestacker-1.2.1.dist-info}/licenses/LICENSE +0 -0
  38. {shinestacker-1.1.0.dist-info → shinestacker-1.2.1.dist-info}/top_level.txt +0 -0
@@ -2,27 +2,38 @@
2
2
  # pylint: disable=E0606, W0718, R1702, W0102, W0221
3
3
  import traceback
4
4
  from typing import Dict, Any
5
- from PySide6.QtWidgets import (QWidget, QPushButton, QHBoxLayout, QLabel,
6
- QMessageBox, QStackedWidget, QFormLayout)
5
+ from PySide6.QtWidgets import (QWidget, QPushButton, QHBoxLayout, QLabel, QScrollArea,
6
+ QMessageBox, QStackedWidget, QFormLayout, QDialog)
7
7
  from PySide6.QtCore import Qt, QTimer
8
8
  from .. config.constants import constants
9
9
  from .. algorithms.align import validate_align_config
10
10
  from .project_model import ActionConfig
11
- from .base_form_dialog import BaseFormDialog
11
+ from .base_form_dialog import create_form_layout
12
12
  from . action_config import (
13
13
  FieldBuilder, ActionConfigurator,
14
14
  FIELD_TEXT, FIELD_ABS_PATH, FIELD_REL_PATH, FIELD_FLOAT,
15
- FIELD_INT, FIELD_INT_TUPLE, FIELD_BOOL, FIELD_COMBO
15
+ FIELD_INT, FIELD_INT_TUPLE, FIELD_BOOL, FIELD_COMBO, FIELD_REF_IDX
16
16
  )
17
17
 
18
18
 
19
- class ActionConfigDialog(BaseFormDialog):
19
+ class ActionConfigDialog(QDialog):
20
20
  def __init__(self, action: ActionConfig, current_wd, parent=None):
21
- super().__init__(f"Configure {action.type_name}", parent)
21
+ super().__init__(parent)
22
+ self.setWindowTitle(f"Configure {action.type_name}")
23
+ self.form_layout = create_form_layout(self)
22
24
  self.current_wd = current_wd
23
25
  self.action = action
26
+ scroll_area = QScrollArea()
27
+ scroll_area.setWidgetResizable(True)
28
+ container_widget = QWidget()
29
+ self.container_layout = QFormLayout(container_widget)
30
+ self.container_layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
31
+ self.container_layout.setRowWrapPolicy(QFormLayout.DontWrapRows)
32
+ self.container_layout.setFormAlignment(Qt.AlignLeft | Qt.AlignTop)
33
+ self.container_layout.setLabelAlignment(Qt.AlignLeft)
24
34
  self.configurator = self.get_configurator(action.type_name)
25
- self.configurator.create_form(self.layout, action)
35
+ self.configurator.create_form(self.container_layout, action)
36
+ scroll_area.setWidget(container_widget)
26
37
  button_box = QHBoxLayout()
27
38
  ok_button = QPushButton("OK")
28
39
  ok_button.setFocus()
@@ -32,10 +43,47 @@ class ActionConfigDialog(BaseFormDialog):
32
43
  button_box.addWidget(cancel_button)
33
44
  button_box.addWidget(reset_button)
34
45
  reset_button.clicked.connect(self.reset_to_defaults)
35
- self.add_row_to_layout(button_box)
46
+ self.form_layout.addRow(scroll_area)
47
+ self.form_layout.addRow(button_box)
48
+ QTimer.singleShot(0, self.adjust_dialog_size)
36
49
  ok_button.clicked.connect(self.accept)
37
50
  cancel_button.clicked.connect(self.reject)
38
51
 
52
+ def adjust_dialog_size(self):
53
+ screen_geometry = self.screen().availableGeometry()
54
+ screen_height = screen_geometry.height()
55
+ screen_width = screen_geometry.width()
56
+ scroll_area = self.findChild(QScrollArea)
57
+ container_widget = scroll_area.widget()
58
+ container_size = container_widget.sizeHint()
59
+ container_height = container_size.height()
60
+ container_width = container_size.width()
61
+ button_row_height = 50 # Approx height of button row
62
+ margins_height = 40 # Approx. height of margins
63
+ total_height_needed = container_height + button_row_height + margins_height
64
+ if total_height_needed < screen_height * 0.8:
65
+ width = max(container_width + 40, 600)
66
+ height = total_height_needed
67
+ self.resize(width, height)
68
+ scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
69
+ else:
70
+ max_height = int(screen_height * 0.9)
71
+ width = max(container_width + 40, 600)
72
+ width = min(width, int(screen_width * 0.9))
73
+ self.resize(width, max_height)
74
+ self.setMaximumHeight(max_height)
75
+ scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
76
+ self.setMinimumHeight(min(max_height, 500))
77
+ self.setMinimumWidth(width)
78
+ self.center_on_screen()
79
+
80
+ def center_on_screen(self):
81
+ screen_geometry = self.screen().availableGeometry()
82
+ center_point = screen_geometry.center()
83
+ frame_geometry = self.frameGeometry()
84
+ frame_geometry.moveCenter(center_point)
85
+ self.move(frame_geometry.topLeft())
86
+
39
87
  def get_configurator(self, action_type: str) -> ActionConfigurator:
40
88
  configurators = {
41
89
  constants.ACTION_JOB: JobConfigurator,
@@ -53,9 +101,8 @@ class ActionConfigDialog(BaseFormDialog):
53
101
  action_type, DefaultActionConfigurator)(self.expert(), self.current_wd)
54
102
 
55
103
  def accept(self):
56
- self.parent().project_editor.add_undo(self.parent().project().clone())
57
104
  if self.configurator.update_params(self.action.params):
58
- self.parent().mark_as_modified()
105
+ self.parent().mark_as_modified(True, "Modify Configuration")
59
106
  super().accept()
60
107
  else:
61
108
  self.parent().project_editor.pop_undo()
@@ -83,81 +130,107 @@ class NoNameActionConfigurator(ActionConfigurator):
83
130
  def add_bold_label(self, label):
84
131
  label = QLabel(label)
85
132
  label.setStyleSheet("font-weight: bold")
86
- self.builder.layout.addRow(label)
133
+ self.add_row(label)
134
+
135
+ def add_row(self, row):
136
+ self.builder.main_layout.addRow(row)
137
+
138
+ def add_field(self, tag, field_type, label,
139
+ required=False, add_to_layout=None, **kwargs):
140
+ return self.builder.add_field(tag, field_type, label, required, add_to_layout, **kwargs)
87
141
 
88
142
 
89
143
  class DefaultActionConfigurator(NoNameActionConfigurator):
90
144
  def create_form(self, layout, action, tag='Action'):
91
145
  self.builder = FieldBuilder(layout, action, self.current_wd)
92
- self.builder.add_field('name', FIELD_TEXT, f'{tag} name', required=True)
146
+ self.add_field(
147
+ 'name', FIELD_TEXT, f'{tag} name', required=True)
93
148
 
94
149
 
95
150
  class JobConfigurator(DefaultActionConfigurator):
96
151
  def create_form(self, layout, action):
97
152
  super().create_form(layout, action, "Job")
98
- self.builder.add_field('working_path', FIELD_ABS_PATH, 'Working path', required=True)
99
- self.builder.add_field('input_path', FIELD_REL_PATH, 'Input path', required=False,
100
- must_exist=True, placeholder='relative to working path')
153
+ self.add_field(
154
+ 'working_path', FIELD_ABS_PATH, 'Working path', required=True)
155
+ self.add_field(
156
+ 'input_path', FIELD_REL_PATH, 'Input path', required=False,
157
+ must_exist=True, placeholder='relative to working path')
101
158
 
102
159
 
103
160
  class NoiseDetectionConfigurator(DefaultActionConfigurator):
104
161
  def create_form(self, layout, action):
105
162
  super().create_form(layout, action)
106
- self.builder.add_field('working_path', FIELD_ABS_PATH, 'Working path', required=True,
107
- placeholder='inherit from job')
108
- self.builder.add_field('input_path', FIELD_REL_PATH,
109
- f'Input path (separate by {constants.PATH_SEPARATOR})',
110
- required=False, multiple_entries=True,
111
- placeholder='relative to working path')
112
- self.builder.add_field('max_frames', FIELD_INT, 'Max. num. of frames', required=False,
113
- default=-1, min_val=-1, max_val=1000)
114
- self.builder.add_field('channel_thresholds', FIELD_INT_TUPLE, 'Noise threshold',
115
- required=False, size=3,
116
- default=constants.DEFAULT_CHANNEL_THRESHOLDS,
117
- labels=constants.RGB_LABELS, min_val=[1] * 3,
118
- max_val=[1000] * 3)
163
+ self.add_field(
164
+ 'working_path', FIELD_ABS_PATH, 'Working path', required=True,
165
+ placeholder='inherit from job')
166
+ self.add_field(
167
+ 'input_path', FIELD_REL_PATH,
168
+ f'Input path (separate by {constants.PATH_SEPARATOR})',
169
+ required=False, multiple_entries=True,
170
+ placeholder='relative to working path')
171
+ self.add_field(
172
+ 'max_frames', FIELD_INT, 'Max. num. of frames (0 = All)',
173
+ required=False,
174
+ default=constants.DEFAULT_NOISE_MAX_FRAMES, min_val=0, max_val=1000)
175
+ self.add_field(
176
+ 'channel_thresholds', FIELD_INT_TUPLE, 'Noise threshold',
177
+ required=False, size=3,
178
+ default=constants.DEFAULT_CHANNEL_THRESHOLDS,
179
+ labels=constants.RGB_LABELS, min_val=[1] * 3, max_val=[1000] * 3)
119
180
  if self.expert:
120
- self.builder.add_field('blur_size', FIELD_INT, 'Blur size (px)', required=False,
121
- default=constants.DEFAULT_BLUR_SIZE, min_val=1, max_val=50)
122
- self.builder.add_field('file_name', FIELD_TEXT, 'File name', required=False,
123
- default=constants.DEFAULT_NOISE_MAP_FILENAME,
124
- placeholder=constants.DEFAULT_NOISE_MAP_FILENAME)
181
+ self.add_field(
182
+ 'blur_size', FIELD_INT, 'Blur size (px)', required=False,
183
+ default=constants.DEFAULT_BLUR_SIZE, min_val=1, max_val=50)
184
+ self.add_field(
185
+ 'file_name', FIELD_TEXT, 'File name', required=False,
186
+ default=constants.DEFAULT_NOISE_MAP_FILENAME,
187
+ placeholder=constants.DEFAULT_NOISE_MAP_FILENAME)
125
188
  self.add_bold_label("Miscellanea:")
126
- self.builder.add_field('plot_histograms', FIELD_BOOL, 'Plot histograms', required=False,
127
- default=False)
128
- self.builder.add_field('plot_path', FIELD_REL_PATH, 'Plots path', required=False,
129
- default=constants.DEFAULT_PLOTS_PATH,
130
- placeholder='relative to working path')
131
- self.builder.add_field('plot_range', FIELD_INT_TUPLE, 'Plot range', required=False,
132
- size=2, default=constants.DEFAULT_NOISE_PLOT_RANGE,
133
- labels=['min', 'max'], min_val=[0] * 2, max_val=[1000] * 2)
189
+ self.add_field(
190
+ 'plot_histograms', FIELD_BOOL, 'Plot histograms', required=False,
191
+ default=False)
192
+ self.add_field(
193
+ 'plot_path', FIELD_REL_PATH, 'Plots path', required=False,
194
+ default=constants.DEFAULT_PLOTS_PATH,
195
+ placeholder='relative to working path')
196
+ self.add_field(
197
+ 'plot_range', FIELD_INT_TUPLE, 'Plot range', required=False,
198
+ size=2, default=constants.DEFAULT_NOISE_PLOT_RANGE,
199
+ labels=['min', 'max'], min_val=[0] * 2, max_val=[1000] * 2)
134
200
 
135
201
 
136
202
  class FocusStackBaseConfigurator(DefaultActionConfigurator):
137
203
  ENERGY_OPTIONS = ['Laplacian', 'Sobel']
138
204
  MAP_TYPE_OPTIONS = ['Average', 'Maximum']
139
205
  FLOAT_OPTIONS = ['float 32 bits', 'float 64 bits']
206
+ MODE_OPTIONS = ['Auto', 'All in memory', 'Tiled I/O buffered']
140
207
 
141
208
  def create_form(self, layout, action):
142
209
  super().create_form(layout, action)
143
210
  if self.expert:
144
- self.builder.add_field('working_path', FIELD_ABS_PATH, 'Working path', required=False)
145
- self.builder.add_field('input_path', FIELD_REL_PATH, 'Input path', required=False,
146
- placeholder='relative to working path')
147
- self.builder.add_field('output_path', FIELD_REL_PATH, 'Output path', required=False,
148
- placeholder='relative to working path')
149
- self.builder.add_field('scratch_output_dir', FIELD_BOOL, 'Scratch output dir.',
150
- required=False, default=True)
211
+ self.add_field(
212
+ 'working_path', FIELD_ABS_PATH, 'Working path', required=False)
213
+ self.add_field(
214
+ 'input_path', FIELD_REL_PATH, 'Input path', required=False,
215
+ placeholder='relative to working path')
216
+ self.add_field(
217
+ 'output_path', FIELD_REL_PATH, 'Output path', required=False,
218
+ placeholder='relative to working path')
219
+ self.add_field(
220
+ 'scratch_output_dir', FIELD_BOOL, 'Scratch output dir.',
221
+ required=False, default=True)
151
222
 
152
223
  def common_fields(self, layout):
153
- self.builder.add_field('denoise_amount', FIELD_FLOAT, 'Denoise', required=False,
154
- default=0, min_val=0, max_val=10)
224
+ self.add_field(
225
+ 'denoise_amount', FIELD_FLOAT, 'Denoise', required=False,
226
+ default=0, min_val=0, max_val=10)
155
227
  self.add_bold_label("Stacking algorithm:")
156
- combo = self.builder.add_field('stacker', FIELD_COMBO, 'Stacking algorithm', required=True,
157
- options=constants.STACK_ALGO_OPTIONS,
158
- default=constants.STACK_ALGO_DEFAULT)
159
- q_pyramid, q_pyramid_tiles, q_depthmap = QWidget(), QWidget(), QWidget()
160
- for q in [q_pyramid, q_pyramid_tiles, q_depthmap]:
228
+ combo = self.add_field(
229
+ 'stacker', FIELD_COMBO, 'Stacking algorithm', required=True,
230
+ options=constants.STACK_ALGO_OPTIONS,
231
+ default=constants.STACK_ALGO_DEFAULT)
232
+ q_pyramid, q_depthmap = QWidget(), QWidget()
233
+ for q in [q_pyramid, q_depthmap]:
161
234
  layout = QFormLayout()
162
235
  layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
163
236
  layout.setRowWrapPolicy(QFormLayout.DontWrapRows)
@@ -166,87 +239,113 @@ class FocusStackBaseConfigurator(DefaultActionConfigurator):
166
239
  q.setLayout(layout)
167
240
  stacked = QStackedWidget()
168
241
  stacked.addWidget(q_pyramid)
169
- stacked.addWidget(q_pyramid_tiles)
170
242
  stacked.addWidget(q_depthmap)
171
243
 
172
244
  def change():
173
245
  text = combo.currentText()
174
246
  if text == constants.STACK_ALGO_PYRAMID:
175
247
  stacked.setCurrentWidget(q_pyramid)
176
- if text == constants.STACK_ALGO_PYRAMID_TILES:
177
- stacked.setCurrentWidget(q_pyramid_tiles)
178
248
  elif text == constants.STACK_ALGO_DEPTH_MAP:
179
249
  stacked.setCurrentWidget(q_depthmap)
180
250
  change()
181
251
  if self.expert:
182
- self.builder.add_field('pyramid_min_size', FIELD_INT, 'Minimum size (px)',
183
- required=False, add_to_layout=q_pyramid.layout(),
184
- default=constants.DEFAULT_PY_MIN_SIZE, min_val=2, max_val=256)
185
- self.builder.add_field('pyramid_kernel_size', FIELD_INT, 'Kernel size (px)',
186
- required=False, add_to_layout=q_pyramid.layout(),
187
- default=constants.DEFAULT_PY_KERNEL_SIZE, min_val=3, max_val=21)
188
- self.builder.add_field('pyramid_gen_kernel', FIELD_FLOAT, 'Gen. kernel',
189
- required=False, add_to_layout=q_pyramid.layout(),
190
- default=constants.DEFAULT_PY_GEN_KERNEL,
191
- min_val=0.0, max_val=2.0)
192
- self.builder.add_field('pyramid_float_type', FIELD_COMBO, 'Precision', required=False,
193
- add_to_layout=q_pyramid.layout(),
194
- options=self.FLOAT_OPTIONS, values=constants.VALID_FLOATS,
195
- default=dict(zip(constants.VALID_FLOATS,
196
- self.FLOAT_OPTIONS))[constants.DEFAULT_PY_FLOAT])
197
- self.builder.add_field('tiles_pyramid_min_size', FIELD_INT, 'Minimum size (px)',
198
- required=False, add_to_layout=q_pyramid_tiles.layout(),
199
- default=constants.DEFAULT_PY_MIN_SIZE, min_val=2, max_val=256)
200
- self.builder.add_field('tiles_pyramid_kernel_size', FIELD_INT, 'Kernel size (px)',
201
- required=False, add_to_layout=q_pyramid_tiles.layout(),
202
- default=constants.DEFAULT_PY_KERNEL_SIZE, min_val=3, max_val=21)
203
- self.builder.add_field('tiles_pyramid_gen_kernel', FIELD_FLOAT, 'Gen. kernel',
204
- required=False, add_to_layout=q_pyramid_tiles.layout(),
205
- default=constants.DEFAULT_PY_GEN_KERNEL,
206
- min_val=0.0, max_val=2.0)
207
- self.builder.add_field('tiles_pyramid_float_type', FIELD_COMBO, 'Precision',
208
- required=False, add_to_layout=q_pyramid_tiles.layout(),
209
- options=self.FLOAT_OPTIONS, values=constants.VALID_FLOATS,
210
- default=dict(zip(constants.VALID_FLOATS,
211
- self.FLOAT_OPTIONS))[constants.DEFAULT_PY_FLOAT])
212
- self.builder.add_field('tiles_pyramid_tile_size', FIELD_INT, 'Tile size (px)',
213
- required=False, add_to_layout=q_pyramid_tiles.layout(),
214
- default=constants.DEFAULT_PY_TILE_SIZE,
215
- min_val=128, max_val=2048)
216
- self.builder.add_field('depthmap_energy', FIELD_COMBO, 'Energy', required=False,
217
- add_to_layout=q_depthmap.layout(),
218
- options=self.ENERGY_OPTIONS, values=constants.VALID_DM_ENERGY,
219
- default=dict(zip(constants.VALID_DM_ENERGY,
220
- self.ENERGY_OPTIONS))[constants.DEFAULT_DM_ENERGY])
221
- self.builder.add_field('map_type', FIELD_COMBO, 'Map type', required=False,
222
- add_to_layout=q_depthmap.layout(),
223
- options=self.MAP_TYPE_OPTIONS, values=constants.VALID_DM_MAP,
224
- default=dict(zip(constants.VALID_DM_MAP,
225
- self.MAP_TYPE_OPTIONS))[constants.DEFAULT_DM_MAP])
252
+ self.add_field(
253
+ 'pyramid_min_size', FIELD_INT, 'Minimum size (px)',
254
+ required=False, add_to_layout=q_pyramid.layout(),
255
+ default=constants.DEFAULT_PY_MIN_SIZE, min_val=2, max_val=256)
256
+ self.add_field(
257
+ 'pyramid_kernel_size', FIELD_INT, 'Kernel size (px)',
258
+ required=False, add_to_layout=q_pyramid.layout(),
259
+ default=constants.DEFAULT_PY_KERNEL_SIZE, min_val=3, max_val=21)
260
+ self.add_field(
261
+ 'pyramid_gen_kernel', FIELD_FLOAT, 'Gen. kernel',
262
+ required=False, add_to_layout=q_pyramid.layout(),
263
+ default=constants.DEFAULT_PY_GEN_KERNEL,
264
+ min_val=0.0, max_val=2.0)
265
+ self.add_field(
266
+ 'pyramid_float_type', FIELD_COMBO, 'Precision', required=False,
267
+ add_to_layout=q_pyramid.layout(),
268
+ options=self.FLOAT_OPTIONS, values=constants.VALID_FLOATS,
269
+ default=dict(zip(constants.VALID_FLOATS,
270
+ self.FLOAT_OPTIONS))[constants.DEFAULT_PY_FLOAT])
271
+ mode = self.add_field(
272
+ 'pyramid_mode', FIELD_COMBO, 'Mode',
273
+ required=False, add_to_layout=q_pyramid.layout(),
274
+ options=self.MODE_OPTIONS, values=constants.PY_VALID_MODES,
275
+ default=dict(zip(constants.PY_VALID_MODES,
276
+ self.MODE_OPTIONS))[constants.DEFAULT_PY_MODE])
277
+ memory_limit = self.add_field(
278
+ 'pyramid_memory_limit', FIELD_FLOAT, 'Memory limit (approx., GBytes)',
279
+ required=False, add_to_layout=q_pyramid.layout(),
280
+ default=constants.DEFAULT_PY_MEMORY_LIMIT_GB,
281
+ min_val=1.0, max_val=64.0)
282
+ max_threads = self.add_field(
283
+ 'pyramid_max_threads', FIELD_INT, 'Max num. of cores',
284
+ required=False, add_to_layout=q_pyramid.layout(),
285
+ default=constants.DEFAULT_PY_MAX_THREADS,
286
+ min_val=1, max_val=64)
287
+ tile_size = self.add_field(
288
+ 'pyramid_tile_size', FIELD_INT, 'Tile size (px)',
289
+ required=False, add_to_layout=q_pyramid.layout(),
290
+ default=constants.DEFAULT_PY_TILE_SIZE,
291
+ min_val=128, max_val=2048)
292
+ n_tiled_layers = self.add_field(
293
+ 'pyramid_n_tiled_layers', FIELD_INT, 'Num. tiled layers',
294
+ required=False, add_to_layout=q_pyramid.layout(),
295
+ default=constants.DEFAULT_PY_N_TILED_LAYERS,
296
+ min_val=0, max_val=6)
297
+
298
+ def change_mode():
299
+ text = mode.currentText()
300
+ enabled = text == self.MODE_OPTIONS[2]
301
+ tile_size.setEnabled(enabled)
302
+ n_tiled_layers.setEnabled(enabled)
303
+ memory_limit.setEnabled(text == self.MODE_OPTIONS[0])
304
+ max_threads.setEnabled(text != self.MODE_OPTIONS[1])
305
+
306
+ mode.currentIndexChanged.connect(change_mode)
307
+ change_mode()
308
+ self.add_field(
309
+ 'depthmap_energy', FIELD_COMBO, 'Energy', required=False,
310
+ add_to_layout=q_depthmap.layout(),
311
+ options=self.ENERGY_OPTIONS, values=constants.VALID_DM_ENERGY,
312
+ default=dict(zip(constants.VALID_DM_ENERGY,
313
+ self.ENERGY_OPTIONS))[constants.DEFAULT_DM_ENERGY])
314
+ self.add_field(
315
+ 'map_type', FIELD_COMBO, 'Map type', required=False,
316
+ add_to_layout=q_depthmap.layout(),
317
+ options=self.MAP_TYPE_OPTIONS, values=constants.VALID_DM_MAP,
318
+ default=dict(zip(constants.VALID_DM_MAP,
319
+ self.MAP_TYPE_OPTIONS))[constants.DEFAULT_DM_MAP])
226
320
  if self.expert:
227
- self.builder.add_field('depthmap_kernel_size', FIELD_INT, 'Kernel size (px)',
228
- required=False, add_to_layout=q_depthmap.layout(),
229
- default=constants.DEFAULT_DM_KERNEL_SIZE, min_val=3, max_val=21)
230
- self.builder.add_field('depthmap_blur_size', FIELD_INT, 'Blurl size (px)',
231
- required=False, add_to_layout=q_depthmap.layout(),
232
- default=constants.DEFAULT_DM_BLUR_SIZE, min_val=1, max_val=21)
233
- self.builder.add_field('depthmap_smooth_size', FIELD_INT, 'Smooth size (px)',
234
- required=False, add_to_layout=q_depthmap.layout(),
235
- default=constants.DEFAULT_DM_SMOOTH_SIZE, min_val=0, max_val=256)
236
- self.builder.add_field('depthmap_temperature', FIELD_FLOAT, 'Temperature',
237
- required=False,
238
- add_to_layout=q_depthmap.layout(),
239
- default=constants.DEFAULT_DM_TEMPERATURE,
240
- min_val=0, max_val=1, step=0.05)
241
- self.builder.add_field('depthmap_levels', FIELD_INT, 'Levels', required=False,
242
- add_to_layout=q_depthmap.layout(),
243
- default=constants.DEFAULT_DM_LEVELS, min_val=2, max_val=6)
244
- self.builder.add_field('depthmap_float_type', FIELD_COMBO, 'Precision', required=False,
245
- add_to_layout=q_depthmap.layout(), options=self.FLOAT_OPTIONS,
246
- values=constants.VALID_FLOATS,
247
- default=dict(zip(constants.VALID_FLOATS,
248
- self.FLOAT_OPTIONS))[constants.DEFAULT_DM_FLOAT])
249
- self.builder.layout.addRow(stacked)
321
+ self.add_field(
322
+ 'depthmap_kernel_size', FIELD_INT, 'Kernel size (px)',
323
+ required=False, add_to_layout=q_depthmap.layout(),
324
+ default=constants.DEFAULT_DM_KERNEL_SIZE, min_val=3, max_val=21)
325
+ self.add_field(
326
+ 'depthmap_blur_size', FIELD_INT, 'Blurl size (px)',
327
+ required=False, add_to_layout=q_depthmap.layout(),
328
+ default=constants.DEFAULT_DM_BLUR_SIZE, min_val=1, max_val=21)
329
+ self.add_field(
330
+ 'depthmap_smooth_size', FIELD_INT, 'Smooth size (px)',
331
+ required=False, add_to_layout=q_depthmap.layout(),
332
+ default=constants.DEFAULT_DM_SMOOTH_SIZE, min_val=0, max_val=256)
333
+ self.add_field(
334
+ 'depthmap_temperature', FIELD_FLOAT, 'Temperature',
335
+ required=False, add_to_layout=q_depthmap.layout(),
336
+ default=constants.DEFAULT_DM_TEMPERATURE,
337
+ min_val=0, max_val=1, step=0.05)
338
+ self.add_field(
339
+ 'depthmap_levels', FIELD_INT, 'Levels', required=False,
340
+ add_to_layout=q_depthmap.layout(),
341
+ default=constants.DEFAULT_DM_LEVELS, min_val=2, max_val=6)
342
+ self.add_field(
343
+ 'depthmap_float_type', FIELD_COMBO, 'Precision', required=False,
344
+ add_to_layout=q_depthmap.layout(), options=self.FLOAT_OPTIONS,
345
+ values=constants.VALID_FLOATS,
346
+ default=dict(zip(constants.VALID_FLOATS,
347
+ self.FLOAT_OPTIONS))[constants.DEFAULT_DM_FLOAT])
348
+ self.add_row(stacked)
250
349
  combo.currentIndexChanged.connect(change)
251
350
 
252
351
 
@@ -254,25 +353,31 @@ class FocusStackConfigurator(FocusStackBaseConfigurator):
254
353
  def create_form(self, layout, action):
255
354
  super().create_form(layout, action)
256
355
  if self.expert:
257
- self.builder.add_field('exif_path', FIELD_REL_PATH, 'Exif data path', required=False,
258
- placeholder='relative to working path')
259
- self.builder.add_field('prefix', FIELD_TEXT, 'Ouptut filename prefix', required=False,
260
- default=constants.DEFAULT_STACK_PREFIX,
261
- placeholder=constants.DEFAULT_STACK_PREFIX)
262
- self.builder.add_field('plot_stack', FIELD_BOOL, 'Plot stack', required=False,
263
- default=constants.DEFAULT_PLOT_STACK)
356
+ self.add_field(
357
+ 'exif_path', FIELD_REL_PATH, 'Exif data path', required=False,
358
+ placeholder='relative to working path')
359
+ self.add_field(
360
+ 'prefix', FIELD_TEXT, 'Ouptut filename prefix', required=False,
361
+ default=constants.DEFAULT_STACK_PREFIX,
362
+ placeholder=constants.DEFAULT_STACK_PREFIX)
363
+ self.add_field(
364
+ 'plot_stack', FIELD_BOOL, 'Plot stack', required=False,
365
+ default=constants.DEFAULT_PLOT_STACK)
264
366
  super().common_fields(layout)
265
367
 
266
368
 
267
369
  class FocusStackBunchConfigurator(FocusStackBaseConfigurator):
268
370
  def create_form(self, layout, action):
269
371
  super().create_form(layout, action)
270
- self.builder.add_field('frames', FIELD_INT, 'Frames', required=False,
271
- default=constants.DEFAULT_FRAMES, min_val=1, max_val=100)
272
- self.builder.add_field('overlap', FIELD_INT, 'Overlapping frames', required=False,
273
- default=constants.DEFAULT_OVERLAP, min_val=0, max_val=100)
274
- self.builder.add_field('plot_stack', FIELD_BOOL, 'Plot stack', required=False,
275
- default=constants.DEFAULT_PLOT_STACK_BUNCH)
372
+ self.add_field(
373
+ 'frames', FIELD_INT, 'Frames', required=False,
374
+ default=constants.DEFAULT_FRAMES, min_val=1, max_val=100)
375
+ self.add_field(
376
+ 'overlap', FIELD_INT, 'Overlapping frames', required=False,
377
+ default=constants.DEFAULT_OVERLAP, min_val=0, max_val=100)
378
+ self.add_field(
379
+ 'plot_stack', FIELD_BOOL, 'Plot stack', required=False,
380
+ default=constants.DEFAULT_PLOT_STACK_BUNCH)
276
381
  super().common_fields(layout)
277
382
 
278
383
 
@@ -280,83 +385,101 @@ class MultiLayerConfigurator(DefaultActionConfigurator):
280
385
  def create_form(self, layout, action):
281
386
  super().create_form(layout, action)
282
387
  if self.expert:
283
- self.builder.add_field('working_path', FIELD_ABS_PATH, 'Working path', required=False)
284
- self.builder.add_field('input_path', FIELD_REL_PATH,
285
- f'Input path (separate by {constants.PATH_SEPARATOR})',
286
- required=False, multiple_entries=True,
287
- placeholder='relative to working path')
388
+ self.add_field(
389
+ 'working_path', FIELD_ABS_PATH, 'Working path', required=False)
390
+ self.add_field(
391
+ 'input_path', FIELD_REL_PATH,
392
+ f'Input path (separate by {constants.PATH_SEPARATOR})',
393
+ required=False, multiple_entries=True,
394
+ placeholder='relative to working path')
288
395
  if self.expert:
289
- self.builder.add_field('output_path', FIELD_REL_PATH, 'Output path', required=False,
290
- placeholder='relative to working path')
291
- self.builder.add_field('exif_path', FIELD_REL_PATH, 'Exif data path', required=False,
292
- placeholder='relative to working path')
293
- self.builder.add_field('scratch_output_dir', FIELD_BOOL, 'Scratch output dir.',
294
- required=False, default=True)
295
- self.builder.add_field('reverse_order', FIELD_BOOL, 'Reverse file order',
296
- required=False,
297
- default=constants.DEFAULT_MULTILAYER_FILE_REVERSE_ORDER)
396
+ self.add_field(
397
+ 'output_path', FIELD_REL_PATH, 'Output path', required=False,
398
+ placeholder='relative to working path')
399
+ self.add_field(
400
+ 'exif_path', FIELD_REL_PATH, 'Exif data path', required=False,
401
+ placeholder='relative to working path')
402
+ self.add_field(
403
+ 'scratch_output_dir', FIELD_BOOL, 'Scratch output dir.',
404
+ required=False, default=True)
405
+ self.add_field(
406
+ 'reverse_order', FIELD_BOOL, 'Reverse file order', required=False,
407
+ default=constants.DEFAULT_MULTILAYER_FILE_REVERSE_ORDER)
298
408
 
299
409
 
300
410
  class CombinedActionsConfigurator(DefaultActionConfigurator):
301
411
  def create_form(self, layout, action):
302
412
  super().create_form(layout, action)
303
413
  if self.expert:
304
- self.builder.add_field('working_path', FIELD_ABS_PATH, 'Working path', required=False)
305
- self.builder.add_field('input_path', FIELD_REL_PATH, 'Input path', required=False,
306
- must_exist=True, placeholder='relative to working path')
307
- self.builder.add_field('output_path', FIELD_REL_PATH, 'Output path', required=False,
308
- placeholder='relative to working path')
309
- self.builder.add_field('scratch_output_dir', FIELD_BOOL, 'Scratch output dir.',
310
- required=False, default=True)
414
+ self.add_field(
415
+ 'working_path', FIELD_ABS_PATH, 'Working path', required=False)
416
+ self.add_field(
417
+ 'input_path', FIELD_REL_PATH, 'Input path', required=False,
418
+ must_exist=True, placeholder='relative to working path')
419
+ self.add_field(
420
+ 'output_path', FIELD_REL_PATH, 'Output path', required=False,
421
+ placeholder='relative to working path')
422
+ self.add_field(
423
+ 'scratch_output_dir', FIELD_BOOL, 'Scratch output dir.',
424
+ required=False, default=True)
311
425
  if self.expert:
312
- self.builder.add_field('plot_path', FIELD_REL_PATH, 'Plots path', required=False,
313
- default="plots", placeholder='relative to working path')
314
- self.builder.add_field('resample', FIELD_INT, 'Resample frame stack', required=False,
315
- default=1, min_val=1, max_val=100)
316
- self.builder.add_field('ref_idx', FIELD_INT, 'Reference frame index', required=False,
317
- default=-1, min_val=-1, max_val=1000)
318
- self.builder.add_field('step_process', FIELD_BOOL, 'Step process', required=False,
319
- default=True)
426
+ self.add_field(
427
+ 'plot_path', FIELD_REL_PATH, 'Plots path', required=False,
428
+ default="plots", placeholder='relative to working path')
429
+ self.add_field(
430
+ 'resample', FIELD_INT, 'Resample frame stack', required=False,
431
+ default=1, min_val=1, max_val=100)
432
+ self.add_field(
433
+ 'reference_index', FIELD_REF_IDX, 'Reference frame', required=False,
434
+ default=0)
435
+ self.add_field(
436
+ 'step_process', FIELD_BOOL, 'Step process', required=False,
437
+ default=True)
320
438
 
321
439
 
322
440
  class MaskNoiseConfigurator(DefaultActionConfigurator):
323
441
  def create_form(self, layout, action):
324
442
  super().create_form(layout, action)
325
- self.builder.add_field('noise_mask', FIELD_REL_PATH, 'Noise mask file', required=False,
326
- path_type='file', must_exist=True,
327
- default=constants.DEFAULT_NOISE_MAP_FILENAME,
328
- placeholder=constants.DEFAULT_NOISE_MAP_FILENAME)
443
+ self.add_field(
444
+ 'noise_mask', FIELD_REL_PATH, 'Noise mask file', required=False,
445
+ path_type='file', must_exist=True,
446
+ default=constants.DEFAULT_NOISE_MAP_FILENAME,
447
+ placeholder=constants.DEFAULT_NOISE_MAP_FILENAME)
329
448
  if self.expert:
330
- self.builder.add_field('kernel_size', FIELD_INT, 'Kernel size', required=False,
331
- default=constants.DEFAULT_MN_KERNEL_SIZE, min_va=1, max_val=10)
332
- self.builder.add_field('method', FIELD_COMBO, 'Interpolation method', required=False,
333
- options=['Mean', 'Median'], default='Mean')
449
+ self.add_field(
450
+ 'kernel_size', FIELD_INT, 'Kernel size', required=False,
451
+ default=constants.DEFAULT_MN_KERNEL_SIZE, min_val=1, max_val=10)
452
+ self.add_field(
453
+ 'method', FIELD_COMBO, 'Interpolation method', required=False,
454
+ options=['Mean', 'Median'], default='Mean')
334
455
 
335
456
 
336
- class VignettingConfigurator(DefaultActionConfigurator):
337
- def create_form(self, layout, action):
338
- super().create_form(layout, action)
339
- if self.expert:
340
- self.builder.add_field('r_steps', FIELD_INT, 'Radial steps', required=False,
341
- default=constants.DEFAULT_R_STEPS, min_val=1, max_val=1000)
342
- self.builder.add_field('black_threshold', FIELD_INT, 'Black intensity threshold',
343
- required=False, default=constants.DEFAULT_BLACK_THRESHOLD,
344
- min_val=0, max_val=1000)
345
- self.builder.add_field('subsample', FIELD_INT, 'Subsample factor', required=False,
346
- default=constants.DEFAULT_VIGN_SUBSAMPLE, min_val=1, max_val=256)
347
- self.builder.add_field('fast_subsampling', FIELD_BOOL, 'Fast subsampling',
348
- required=False, default=constants.DEFAULT_VIGN_FAST_SUBSAMPLING)
349
- self.builder.add_field('max_correction', FIELD_FLOAT, 'Max. correction', required=False,
350
- default=constants.DEFAULT_MAX_CORRECTION,
351
- min_val=0, max_val=1, step=0.05)
352
- self.add_bold_label("Miscellanea:")
353
- self.builder.add_field('plot_correction', FIELD_BOOL, 'Plot correction', required=False,
354
- default=False)
355
- self.builder.add_field('plot_summary', FIELD_BOOL, 'Plot summary', required=False,
356
- default=False)
457
+ class SubsampleActionConfigurator(DefaultActionConfigurator):
458
+ def __init__(self, expert, current_wd):
459
+ super().__init__(expert, current_wd)
460
+ self.subsample_field = None
461
+ self.fast_subsampling_field = None
462
+
463
+ def add_subsample_fields(self):
464
+ self.subsample_field = self.add_field(
465
+ 'subsample', FIELD_COMBO, 'Subsample', required=False,
466
+ options=constants.FIELD_SUBSAMPLE_OPTIONS,
467
+ values=constants.FIELD_SUBSAMPLE_VALUES,
468
+ default=constants.FIELD_SUBSAMPLE_DEFAULT)
469
+ self.fast_subsampling_field = self.add_field(
470
+ 'fast_subsampling', FIELD_BOOL, 'Fast subsampling', required=False,
471
+ default=constants.DEFAULT_ALIGN_FAST_SUBSAMPLING)
472
+
473
+ self.subsample_field.currentTextChanged.connect(self.change_subsample)
357
474
 
475
+ self.change_subsample()
358
476
 
359
- class AlignFramesConfigurator(DefaultActionConfigurator):
477
+ def change_subsample(self):
478
+ self.fast_subsampling_field.setEnabled(
479
+ self.subsample_field.currentText() not in constants.FIELD_SUBSAMPLE_OPTIONS[:2])
480
+
481
+
482
+ class AlignFramesConfigurator(SubsampleActionConfigurator):
360
483
  BORDER_MODE_OPTIONS = ['Constant', 'Replicate', 'Replicate and blur']
361
484
  TRANSFORM_OPTIONS = ['Rigid', 'Homography']
362
485
  METHOD_OPTIONS = ['Random Sample Consensus (RANSAC)', 'Least Median (LMEDS)']
@@ -412,21 +535,18 @@ class AlignFramesConfigurator(DefaultActionConfigurator):
412
535
  self.matching_method_field = None
413
536
  if self.expert:
414
537
  self.add_bold_label("Feature identification:")
415
-
416
538
  self.info_label = QLabel()
417
539
  self.info_label.setStyleSheet("color: orange; font-style: italic;")
418
540
  self.info_label.setVisible(False)
419
541
  layout.addRow(self.info_label)
420
-
421
- self.detector_field = self.builder.add_field(
542
+ self.detector_field = self.add_field(
422
543
  'detector', FIELD_COMBO, 'Detector', required=False,
423
544
  options=constants.VALID_DETECTORS, default=constants.DEFAULT_DETECTOR)
424
- self.descriptor_field = self.builder.add_field(
545
+ self.descriptor_field = self.add_field(
425
546
  'descriptor', FIELD_COMBO, 'Descriptor', required=False,
426
547
  options=constants.VALID_DESCRIPTORS, default=constants.DEFAULT_DESCRIPTOR)
427
-
428
548
  self.add_bold_label("Feature matching:")
429
- self.matching_method_field = self.builder.add_field(
549
+ self.matching_method_field = self.add_field(
430
550
  'match_method', FIELD_COMBO, 'Match method', required=False,
431
551
  options=self.MATCHING_METHOD_OPTIONS, values=constants.VALID_MATCHING_METHODS,
432
552
  default=constants.DEFAULT_MATCHING_METHOD)
@@ -434,46 +554,45 @@ class AlignFramesConfigurator(DefaultActionConfigurator):
434
554
  "SIFT: Requires SIFT descriptor and K-NN matching\n"
435
555
  "ORB/AKAZE: Work best with Hamming distance"
436
556
  )
437
-
438
557
  self.descriptor_field.setToolTip(
439
558
  "SIFT: Requires K-NN matching\n"
440
559
  "ORB/AKAZE: Require Hamming distance with ORB/AKAZE detectors"
441
560
  )
442
-
443
561
  self.matching_method_field.setToolTip(
444
562
  "Automatically selected based on detector/descriptor combination"
445
563
  )
446
-
447
564
  self.detector_field.currentIndexChanged.connect(self.change_match_config)
448
565
  self.descriptor_field.currentIndexChanged.connect(self.change_match_config)
449
566
  self.matching_method_field.currentIndexChanged.connect(self.change_match_config)
450
- self.builder.add_field('flann_idx_kdtree', FIELD_INT, 'Flann idx kdtree',
451
- required=False,
452
- default=constants.DEFAULT_FLANN_IDX_KDTREE,
453
- min_val=0, max_val=10)
454
- self.builder.add_field('flann_trees', FIELD_INT, 'Flann trees', required=False,
455
- default=constants.DEFAULT_FLANN_TREES,
456
- min_val=0, max_val=10)
457
- self.builder.add_field('flann_checks', FIELD_INT, 'Flann checks', required=False,
458
- default=constants.DEFAULT_FLANN_CHECKS,
459
- min_val=0, max_val=1000)
460
- self.builder.add_field('threshold', FIELD_FLOAT, 'Threshold', required=False,
461
- default=constants.DEFAULT_ALIGN_THRESHOLD,
462
- min_val=0, max_val=1, step=0.05)
463
-
567
+ self.add_field(
568
+ 'flann_idx_kdtree', FIELD_INT, 'Flann idx kdtree', required=False,
569
+ default=constants.DEFAULT_FLANN_IDX_KDTREE,
570
+ min_val=0, max_val=10)
571
+ self.add_field(
572
+ 'flann_trees', FIELD_INT, 'Flann trees', required=False,
573
+ default=constants.DEFAULT_FLANN_TREES,
574
+ min_val=0, max_val=10)
575
+ self.add_field(
576
+ 'flann_checks', FIELD_INT, 'Flann checks', required=False,
577
+ default=constants.DEFAULT_FLANN_CHECKS,
578
+ min_val=0, max_val=1000)
579
+ self.add_field(
580
+ 'threshold', FIELD_FLOAT, 'Threshold', required=False,
581
+ default=constants.DEFAULT_ALIGN_THRESHOLD,
582
+ min_val=0, max_val=1, step=0.05)
464
583
  self.add_bold_label("Transform:")
465
- transform = self.builder.add_field(
584
+ transform = self.add_field(
466
585
  'transform', FIELD_COMBO, 'Transform', required=False,
467
586
  options=self.TRANSFORM_OPTIONS, values=constants.VALID_TRANSFORMS,
468
587
  default=constants.DEFAULT_TRANSFORM)
469
- method = self.builder.add_field(
588
+ method = self.add_field(
470
589
  'align_method', FIELD_COMBO, 'Align method', required=False,
471
590
  options=self.METHOD_OPTIONS, values=constants.VALID_ALIGN_METHODS,
472
591
  default=constants.DEFAULT_ALIGN_METHOD)
473
- rans_threshold = self.builder.add_field(
592
+ rans_threshold = self.add_field(
474
593
  'rans_threshold', FIELD_FLOAT, 'RANSAC threshold (px)', required=False,
475
594
  default=constants.DEFAULT_RANS_THRESHOLD, min_val=0, max_val=20, step=0.1)
476
- self.builder.add_field(
595
+ self.add_field(
477
596
  'min_good_matches', FIELD_INT, "Min. good matches", required=False,
478
597
  default=constants.DEFAULT_ALIGN_MIN_GOOD_MATCHES, min_val=0, max_val=500)
479
598
 
@@ -483,17 +602,19 @@ class AlignFramesConfigurator(DefaultActionConfigurator):
483
602
  rans_threshold.setEnabled(True)
484
603
  elif text == self.METHOD_OPTIONS[1]:
485
604
  rans_threshold.setEnabled(False)
605
+
486
606
  method.currentIndexChanged.connect(change_method)
487
607
  change_method()
488
- self.builder.add_field('align_confidence', FIELD_FLOAT, 'Confidence (%)',
489
- required=False, decimals=1,
490
- default=constants.DEFAULT_ALIGN_CONFIDENCE,
491
- min_val=70.0, max_val=100.0, step=0.1)
608
+ self.add_field(
609
+ 'align_confidence', FIELD_FLOAT, 'Confidence (%)',
610
+ required=False, decimals=1,
611
+ default=constants.DEFAULT_ALIGN_CONFIDENCE,
612
+ min_val=70.0, max_val=100.0, step=0.1)
492
613
 
493
- refine_iters = self.builder.add_field(
614
+ refine_iters = self.add_field(
494
615
  'refine_iters', FIELD_INT, 'Refinement iterations (Rigid)', required=False,
495
616
  default=constants.DEFAULT_REFINE_ITERS, min_val=0, max_val=1000)
496
- max_iters = self.builder.add_field(
617
+ max_iters = self.add_field(
497
618
  'max_iters', FIELD_INT, 'Max. iterations (Homography)', required=False,
498
619
  default=constants.DEFAULT_ALIGN_MAX_ITERS, min_val=0, max_val=5000)
499
620
 
@@ -505,37 +626,36 @@ class AlignFramesConfigurator(DefaultActionConfigurator):
505
626
  elif text == self.TRANSFORM_OPTIONS[1]:
506
627
  refine_iters.setEnabled(False)
507
628
  max_iters.setEnabled(True)
629
+
508
630
  transform.currentIndexChanged.connect(change_transform)
509
631
  change_transform()
510
- subsample = self.builder.add_field(
511
- 'subsample', FIELD_INT, 'Subsample factor', required=False,
512
- default=constants.DEFAULT_ALIGN_SUBSAMPLE, min_val=1, max_val=256)
513
- fast_subsampling = self.builder.add_field(
514
- 'fast_subsampling', FIELD_BOOL, 'Fast subsampling', required=False,
515
- default=constants.DEFAULT_ALIGN_FAST_SUBSAMPLING)
516
-
517
- def change_subsample():
518
- fast_subsampling.setEnabled(subsample.value() > 1)
519
- subsample.valueChanged.connect(change_subsample)
520
- change_subsample()
632
+ self.add_field(
633
+ 'abort_abnormal', FIELD_BOOL, 'Abort on abnormal transf.',
634
+ required=False, default=constants.DEFAULT_ALIGN_ABORT_ABNORMAL)
635
+ self.add_subsample_fields()
521
636
  self.add_bold_label("Border:")
522
- self.builder.add_field('border_mode', FIELD_COMBO, 'Border mode', required=False,
523
- options=self.BORDER_MODE_OPTIONS,
524
- values=constants.VALID_BORDER_MODES,
525
- default=constants.DEFAULT_BORDER_MODE)
526
- self.builder.add_field('border_value', FIELD_INT_TUPLE,
527
- 'Border value (if constant)', required=False, size=4,
528
- default=constants.DEFAULT_BORDER_VALUE,
529
- labels=constants.RGBA_LABELS,
530
- min_val=constants.DEFAULT_BORDER_VALUE, max_val=[255] * 4)
531
- self.builder.add_field('border_blur', FIELD_FLOAT, 'Border blur', required=False,
532
- default=constants.DEFAULT_BORDER_BLUR,
533
- min_val=0, max_val=1000, step=1)
637
+ self.add_field(
638
+ 'border_mode', FIELD_COMBO, 'Border mode', required=False,
639
+ options=self.BORDER_MODE_OPTIONS,
640
+ values=constants.VALID_BORDER_MODES,
641
+ default=constants.DEFAULT_BORDER_MODE)
642
+ self.add_field(
643
+ 'border_value', FIELD_INT_TUPLE,
644
+ 'Border value (if constant)', required=False, size=4,
645
+ default=constants.DEFAULT_BORDER_VALUE,
646
+ labels=constants.RGBA_LABELS,
647
+ min_val=constants.DEFAULT_BORDER_VALUE, max_val=[255] * 4)
648
+ self.add_field(
649
+ 'border_blur', FIELD_FLOAT, 'Border blur', required=False,
650
+ default=constants.DEFAULT_BORDER_BLUR,
651
+ min_val=0, max_val=1000, step=1)
534
652
  self.add_bold_label("Miscellanea:")
535
- self.builder.add_field('plot_summary', FIELD_BOOL, 'Plot summary',
536
- required=False, default=False)
537
- self.builder.add_field('plot_matches', FIELD_BOOL, 'Plot matches',
538
- required=False, default=False)
653
+ self.add_field(
654
+ 'plot_summary', FIELD_BOOL, 'Plot summary',
655
+ required=False, default=False)
656
+ self.add_field(
657
+ 'plot_matches', FIELD_BOOL, 'Plot matches',
658
+ required=False, default=False)
539
659
 
540
660
  def update_params(self, params: Dict[str, Any]) -> bool:
541
661
  if self.detector_field and self.descriptor_field and self.matching_method_field:
@@ -555,35 +675,59 @@ class AlignFramesConfigurator(DefaultActionConfigurator):
555
675
  return super().update_params(params)
556
676
 
557
677
 
558
- class BalanceFramesConfigurator(DefaultActionConfigurator):
678
+ class BalanceFramesConfigurator(SubsampleActionConfigurator):
559
679
  CORRECTION_MAP_OPTIONS = ['Linear', 'Gamma', 'Match histograms']
560
- CHANNEL_OPTIONS = ['Luminosity', 'RGB', 'HSV', 'HLS']
680
+ CHANNEL_OPTIONS = ['Luminosity', 'RGB', 'HSV', 'HLS', 'LAB']
681
+
682
+ def create_form(self, layout, action):
683
+ super().create_form(layout, action)
684
+ if self.expert:
685
+ self.add_field(
686
+ 'mask_size', FIELD_FLOAT, 'Mask size', required=False,
687
+ default=0, min_val=0, max_val=5, step=0.1)
688
+ self.add_field(
689
+ 'intensity_interval', FIELD_INT_TUPLE, 'Intensity range',
690
+ required=False, size=2,
691
+ default=[v for k, v in constants.DEFAULT_INTENSITY_INTERVAL.items()],
692
+ labels=['min', 'max'], min_val=[-1] * 2, max_val=[65536] * 2)
693
+ self.add_subsample_fields()
694
+ self.add_field(
695
+ 'corr_map', FIELD_COMBO, 'Correction map', required=False,
696
+ options=self.CORRECTION_MAP_OPTIONS, values=constants.VALID_BALANCE,
697
+ default='Linear')
698
+ self.add_field(
699
+ 'channel', FIELD_COMBO, 'Channel', required=False,
700
+ options=self.CHANNEL_OPTIONS, values=constants.VALID_BALANCE_CHANNELS,
701
+ default='Luminosity')
702
+ self.add_bold_label("Miscellanea:")
703
+ self.add_field(
704
+ 'plot_summary', FIELD_BOOL, 'Plot summary',
705
+ required=False, default=False)
706
+ self.add_field(
707
+ 'plot_histograms', FIELD_BOOL, 'Plot histograms',
708
+ required=False, default=False)
709
+
561
710
 
711
+ class VignettingConfigurator(SubsampleActionConfigurator):
562
712
  def create_form(self, layout, action):
563
713
  super().create_form(layout, action)
564
714
  if self.expert:
565
- self.builder.add_field('mask_size', FIELD_FLOAT, 'Mask size', required=False,
566
- default=0, min_val=0, max_val=5, step=0.1)
567
- self.builder.add_field('intensity_interval', FIELD_INT_TUPLE, 'Intensity range',
568
- required=False, size=2,
569
- default=[v for k, v in
570
- constants.DEFAULT_INTENSITY_INTERVAL.items()],
571
- labels=['min', 'max'], min_val=[-1] * 2, max_val=[65536] * 2)
572
- self.builder.add_field('subsample', FIELD_INT, 'Subsample factor', required=False,
573
- default=constants.DEFAULT_BALANCE_SUBSAMPLE,
574
- min_val=1, max_val=256)
575
- self.builder.add_field('fast_subsampling', FIELD_BOOL, 'Fast subsampling',
576
- required=False,
577
- default=constants.DEFAULT_BALANCE_FAST_SUBSAMPLING)
578
- self.builder.add_field('corr_map', FIELD_COMBO, 'Correction map', required=False,
579
- options=self.CORRECTION_MAP_OPTIONS, values=constants.VALID_BALANCE,
580
- default='Linear')
581
- self.builder.add_field('channel', FIELD_COMBO, 'Channel', required=False,
582
- options=self.CHANNEL_OPTIONS,
583
- values=constants.VALID_BALANCE_CHANNELS,
584
- default='Luminosity')
715
+ self.add_field(
716
+ 'r_steps', FIELD_INT, 'Radial steps', required=False,
717
+ default=constants.DEFAULT_R_STEPS, min_val=1, max_val=1000)
718
+ self.add_field(
719
+ 'black_threshold', FIELD_INT, 'Black intensity threshold',
720
+ required=False, default=constants.DEFAULT_BLACK_THRESHOLD,
721
+ min_val=0, max_val=1000)
722
+ self.add_subsample_fields()
723
+ self.add_field(
724
+ 'max_correction', FIELD_FLOAT, 'Max. correction', required=False,
725
+ default=constants.DEFAULT_MAX_CORRECTION,
726
+ min_val=0, max_val=1, step=0.05)
585
727
  self.add_bold_label("Miscellanea:")
586
- self.builder.add_field('plot_summary', FIELD_BOOL, 'Plot summary',
587
- required=False, default=False)
588
- self.builder.add_field('plot_histograms', FIELD_BOOL, 'Plot histograms',
589
- required=False, default=False)
728
+ self.add_field(
729
+ 'plot_correction', FIELD_BOOL, 'Plot correction', required=False,
730
+ default=False)
731
+ self.add_field(
732
+ 'plot_summary', FIELD_BOOL, 'Plot summary', required=False,
733
+ default=False)