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

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

Potentially problematic release.


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

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