shinestacker 1.0.4.post2__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 (37) hide show
  1. shinestacker/_version.py +1 -1
  2. shinestacker/algorithms/__init__.py +4 -1
  3. shinestacker/algorithms/align.py +128 -14
  4. shinestacker/algorithms/balance.py +362 -163
  5. shinestacker/algorithms/base_stack_algo.py +33 -4
  6. shinestacker/algorithms/depth_map.py +9 -12
  7. shinestacker/algorithms/multilayer.py +12 -2
  8. shinestacker/algorithms/noise_detection.py +8 -3
  9. shinestacker/algorithms/pyramid.py +57 -42
  10. shinestacker/algorithms/pyramid_auto.py +141 -0
  11. shinestacker/algorithms/pyramid_tiles.py +264 -0
  12. shinestacker/algorithms/stack.py +14 -11
  13. shinestacker/algorithms/stack_framework.py +17 -11
  14. shinestacker/algorithms/utils.py +180 -1
  15. shinestacker/algorithms/vignetting.py +23 -5
  16. shinestacker/config/constants.py +31 -5
  17. shinestacker/gui/action_config.py +6 -7
  18. shinestacker/gui/action_config_dialog.py +425 -258
  19. shinestacker/gui/base_form_dialog.py +11 -6
  20. shinestacker/gui/flow_layout.py +105 -0
  21. shinestacker/gui/gui_run.py +24 -19
  22. shinestacker/gui/main_window.py +4 -3
  23. shinestacker/gui/menu_manager.py +12 -2
  24. shinestacker/gui/new_project.py +28 -22
  25. shinestacker/gui/project_controller.py +40 -23
  26. shinestacker/gui/project_converter.py +6 -6
  27. shinestacker/gui/project_editor.py +21 -7
  28. shinestacker/gui/time_progress_bar.py +2 -2
  29. shinestacker/retouch/exif_data.py +5 -5
  30. shinestacker/retouch/shortcuts_help.py +4 -4
  31. shinestacker/retouch/vignetting_filter.py +12 -8
  32. {shinestacker-1.0.4.post2.dist-info → shinestacker-1.2.0.dist-info}/METADATA +20 -1
  33. {shinestacker-1.0.4.post2.dist-info → shinestacker-1.2.0.dist-info}/RECORD +37 -34
  34. {shinestacker-1.0.4.post2.dist-info → shinestacker-1.2.0.dist-info}/WHEEL +0 -0
  35. {shinestacker-1.0.4.post2.dist-info → shinestacker-1.2.0.dist-info}/entry_points.txt +0 -0
  36. {shinestacker-1.0.4.post2.dist-info → shinestacker-1.2.0.dist-info}/licenses/LICENSE +0 -0
  37. {shinestacker-1.0.4.post2.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,79 +131,105 @@ 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)
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)
159
233
  q_pyramid, q_depthmap = QWidget(), QWidget()
160
234
  for q in [q_pyramid, q_depthmap]:
161
235
  layout = QFormLayout()
@@ -170,61 +244,109 @@ class FocusStackBaseConfigurator(DefaultActionConfigurator):
170
244
 
171
245
  def change():
172
246
  text = combo.currentText()
173
- if text == 'Pyramid':
247
+ if text == constants.STACK_ALGO_PYRAMID:
174
248
  stacked.setCurrentWidget(q_pyramid)
175
- elif text == 'Depth map':
249
+ elif text == constants.STACK_ALGO_DEPTH_MAP:
176
250
  stacked.setCurrentWidget(q_depthmap)
177
251
  change()
178
252
  if self.expert:
179
- self.builder.add_field('pyramid_min_size', FIELD_INT, 'Minimum size (px)',
180
- required=False, add_to_layout=q_pyramid.layout(),
181
- default=constants.DEFAULT_PY_MIN_SIZE, min_val=2, max_val=256)
182
- self.builder.add_field('pyramid_kernel_size', FIELD_INT, 'Kernel size (px)',
183
- required=False, add_to_layout=q_pyramid.layout(),
184
- default=constants.DEFAULT_PY_KERNEL_SIZE, min_val=3, max_val=21)
185
- self.builder.add_field('pyramid_gen_kernel', FIELD_FLOAT, 'Gen. kernel',
186
- required=False, add_to_layout=q_pyramid.layout(),
187
- default=constants.DEFAULT_PY_GEN_KERNEL,
188
- min_val=0.0, max_val=2.0)
189
- self.builder.add_field('pyramid_float_type', FIELD_COMBO, 'Precision', required=False,
190
- add_to_layout=q_pyramid.layout(),
191
- options=self.FLOAT_OPTIONS, values=constants.VALID_FLOATS,
192
- default=dict(zip(constants.VALID_FLOATS,
193
- self.FLOAT_OPTIONS))[constants.DEFAULT_PY_FLOAT])
194
- self.builder.add_field('depthmap_energy', FIELD_COMBO, 'Energy', required=False,
195
- add_to_layout=q_depthmap.layout(),
196
- options=self.ENERGY_OPTIONS, values=constants.VALID_DM_ENERGY,
197
- default=dict(zip(constants.VALID_DM_ENERGY,
198
- self.ENERGY_OPTIONS))[constants.DEFAULT_DM_ENERGY])
199
- self.builder.add_field('map_type', FIELD_COMBO, 'Map type', required=False,
200
- add_to_layout=q_depthmap.layout(),
201
- options=self.MAP_TYPE_OPTIONS, values=constants.VALID_DM_MAP,
202
- default=dict(zip(constants.VALID_DM_MAP,
203
- 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])
204
321
  if self.expert:
205
- self.builder.add_field('depthmap_kernel_size', FIELD_INT, 'Kernel size (px)',
206
- required=False, add_to_layout=q_depthmap.layout(),
207
- default=constants.DEFAULT_DM_KERNEL_SIZE, min_val=3, max_val=21)
208
- self.builder.add_field('depthmap_blur_size', FIELD_INT, 'Blurl size (px)',
209
- required=False, add_to_layout=q_depthmap.layout(),
210
- default=constants.DEFAULT_DM_BLUR_SIZE, min_val=1, max_val=21)
211
- self.builder.add_field('depthmap_smooth_size', FIELD_INT, 'Smooth size (px)',
212
- required=False, add_to_layout=q_depthmap.layout(),
213
- default=constants.DEFAULT_DM_SMOOTH_SIZE, min_val=0, max_val=256)
214
- self.builder.add_field('depthmap_temperature', FIELD_FLOAT, 'Temperature',
215
- required=False,
216
- add_to_layout=q_depthmap.layout(),
217
- default=constants.DEFAULT_DM_TEMPERATURE,
218
- min_val=0, max_val=1, step=0.05)
219
- self.builder.add_field('depthmap_levels', FIELD_INT, 'Levels', required=False,
220
- add_to_layout=q_depthmap.layout(),
221
- default=constants.DEFAULT_DM_LEVELS, min_val=2, max_val=6)
222
- self.builder.add_field('depthmap_float_type', FIELD_COMBO, 'Precision', required=False,
223
- add_to_layout=q_depthmap.layout(), options=self.FLOAT_OPTIONS,
224
- values=constants.VALID_FLOATS,
225
- default=dict(zip(constants.VALID_FLOATS,
226
- self.FLOAT_OPTIONS))[constants.DEFAULT_DM_FLOAT])
227
- 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)
228
350
  combo.currentIndexChanged.connect(change)
229
351
 
230
352
 
@@ -232,25 +354,31 @@ class FocusStackConfigurator(FocusStackBaseConfigurator):
232
354
  def create_form(self, layout, action):
233
355
  super().create_form(layout, action)
234
356
  if self.expert:
235
- self.builder.add_field('exif_path', FIELD_REL_PATH, 'Exif data path', required=False,
236
- placeholder='relative to working path')
237
- self.builder.add_field('prefix', FIELD_TEXT, 'Ouptut filename prefix', required=False,
238
- default=constants.DEFAULT_STACK_PREFIX,
239
- placeholder=constants.DEFAULT_STACK_PREFIX)
240
- self.builder.add_field('plot_stack', FIELD_BOOL, 'Plot stack', required=False,
241
- 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)
242
367
  super().common_fields(layout)
243
368
 
244
369
 
245
370
  class FocusStackBunchConfigurator(FocusStackBaseConfigurator):
246
371
  def create_form(self, layout, action):
247
372
  super().create_form(layout, action)
248
- self.builder.add_field('frames', FIELD_INT, 'Frames', required=False,
249
- default=constants.DEFAULT_FRAMES, min_val=1, max_val=100)
250
- self.builder.add_field('overlap', FIELD_INT, 'Overlapping frames', required=False,
251
- default=constants.DEFAULT_OVERLAP, min_val=0, max_val=100)
252
- self.builder.add_field('plot_stack', FIELD_BOOL, 'Plot stack', required=False,
253
- 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)
254
382
  super().common_fields(layout)
255
383
 
256
384
 
@@ -258,83 +386,101 @@ class MultiLayerConfigurator(DefaultActionConfigurator):
258
386
  def create_form(self, layout, action):
259
387
  super().create_form(layout, action)
260
388
  if self.expert:
261
- self.builder.add_field('working_path', FIELD_ABS_PATH, 'Working path', required=False)
262
- self.builder.add_field('input_path', FIELD_REL_PATH,
263
- f'Input path (separate by {constants.PATH_SEPARATOR})',
264
- required=False, multiple_entries=True,
265
- 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')
266
396
  if self.expert:
267
- self.builder.add_field('output_path', FIELD_REL_PATH, 'Output path', required=False,
268
- placeholder='relative to working path')
269
- self.builder.add_field('exif_path', FIELD_REL_PATH, 'Exif data path', required=False,
270
- placeholder='relative to working path')
271
- self.builder.add_field('scratch_output_dir', FIELD_BOOL, 'Scratch output dir.',
272
- required=False, default=True)
273
- self.builder.add_field('reverse_order', FIELD_BOOL, 'Reverse file order',
274
- required=False,
275
- 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)
276
409
 
277
410
 
278
411
  class CombinedActionsConfigurator(DefaultActionConfigurator):
279
412
  def create_form(self, layout, action):
280
413
  super().create_form(layout, action)
281
414
  if self.expert:
282
- self.builder.add_field('working_path', FIELD_ABS_PATH, 'Working path', required=False)
283
- self.builder.add_field('input_path', FIELD_REL_PATH, 'Input path', required=False,
284
- must_exist=True, placeholder='relative to working path')
285
- self.builder.add_field('output_path', FIELD_REL_PATH, 'Output path', required=False,
286
- placeholder='relative to working path')
287
- self.builder.add_field('scratch_output_dir', FIELD_BOOL, 'Scratch output dir.',
288
- 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)
289
426
  if self.expert:
290
- self.builder.add_field('plot_path', FIELD_REL_PATH, 'Plots path', required=False,
291
- default="plots", placeholder='relative to working path')
292
- self.builder.add_field('resample', FIELD_INT, 'Resample frame stack', required=False,
293
- default=1, min_val=1, max_val=100)
294
- self.builder.add_field('ref_idx', FIELD_INT, 'Reference frame index', required=False,
295
- default=-1, min_val=-1, max_val=1000)
296
- self.builder.add_field('step_process', FIELD_BOOL, 'Step process', required=False,
297
- 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)
298
439
 
299
440
 
300
441
  class MaskNoiseConfigurator(DefaultActionConfigurator):
301
442
  def create_form(self, layout, action):
302
443
  super().create_form(layout, action)
303
- self.builder.add_field('noise_mask', FIELD_REL_PATH, 'Noise mask file', required=False,
304
- path_type='file', must_exist=True,
305
- default=constants.DEFAULT_NOISE_MAP_FILENAME,
306
- 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)
307
449
  if self.expert:
308
- self.builder.add_field('kernel_size', FIELD_INT, 'Kernel size', required=False,
309
- default=constants.DEFAULT_MN_KERNEL_SIZE, min_va=1, max_val=10)
310
- self.builder.add_field('method', FIELD_COMBO, 'Interpolation method', required=False,
311
- 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')
312
456
 
313
457
 
314
- class VignettingConfigurator(DefaultActionConfigurator):
315
- def create_form(self, layout, action):
316
- super().create_form(layout, action)
317
- if self.expert:
318
- self.builder.add_field('r_steps', FIELD_INT, 'Radial steps', required=False,
319
- default=constants.DEFAULT_R_STEPS, min_val=1, max_val=1000)
320
- self.builder.add_field('black_threshold', FIELD_INT, 'Black intensity threshold',
321
- required=False, default=constants.DEFAULT_BLACK_THRESHOLD,
322
- min_val=0, max_val=1000)
323
- self.builder.add_field('subsample', FIELD_INT, 'Subsample factor', required=False,
324
- default=constants.DEFAULT_VIGN_SUBSAMPLE, min_val=1, max_val=256)
325
- self.builder.add_field('fast_subsampling', FIELD_BOOL, 'Fast subsampling',
326
- required=False, default=constants.DEFAULT_VIGN_FAST_SUBSAMPLING)
327
- self.builder.add_field('max_correction', FIELD_FLOAT, 'Max. correction', required=False,
328
- default=constants.DEFAULT_MAX_CORRECTION,
329
- min_val=0, max_val=1, step=0.05)
330
- self.add_bold_label("Miscellanea:")
331
- self.builder.add_field('plot_correction', FIELD_BOOL, 'Plot correction', required=False,
332
- default=False)
333
- self.builder.add_field('plot_summary', FIELD_BOOL, 'Plot summary', required=False,
334
- 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)
335
475
 
476
+ self.change_subsample()
336
477
 
337
- 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):
338
484
  BORDER_MODE_OPTIONS = ['Constant', 'Replicate', 'Replicate and blur']
339
485
  TRANSFORM_OPTIONS = ['Rigid', 'Homography']
340
486
  METHOD_OPTIONS = ['Random Sample Consensus (RANSAC)', 'Least Median (LMEDS)']
@@ -390,21 +536,18 @@ class AlignFramesConfigurator(DefaultActionConfigurator):
390
536
  self.matching_method_field = None
391
537
  if self.expert:
392
538
  self.add_bold_label("Feature identification:")
393
-
394
539
  self.info_label = QLabel()
395
540
  self.info_label.setStyleSheet("color: orange; font-style: italic;")
396
541
  self.info_label.setVisible(False)
397
542
  layout.addRow(self.info_label)
398
-
399
- self.detector_field = self.builder.add_field(
543
+ self.detector_field = self.add_field(
400
544
  'detector', FIELD_COMBO, 'Detector', required=False,
401
545
  options=constants.VALID_DETECTORS, default=constants.DEFAULT_DETECTOR)
402
- self.descriptor_field = self.builder.add_field(
546
+ self.descriptor_field = self.add_field(
403
547
  'descriptor', FIELD_COMBO, 'Descriptor', required=False,
404
548
  options=constants.VALID_DESCRIPTORS, default=constants.DEFAULT_DESCRIPTOR)
405
-
406
549
  self.add_bold_label("Feature matching:")
407
- self.matching_method_field = self.builder.add_field(
550
+ self.matching_method_field = self.add_field(
408
551
  'match_method', FIELD_COMBO, 'Match method', required=False,
409
552
  options=self.MATCHING_METHOD_OPTIONS, values=constants.VALID_MATCHING_METHODS,
410
553
  default=constants.DEFAULT_MATCHING_METHOD)
@@ -412,46 +555,45 @@ class AlignFramesConfigurator(DefaultActionConfigurator):
412
555
  "SIFT: Requires SIFT descriptor and K-NN matching\n"
413
556
  "ORB/AKAZE: Work best with Hamming distance"
414
557
  )
415
-
416
558
  self.descriptor_field.setToolTip(
417
559
  "SIFT: Requires K-NN matching\n"
418
560
  "ORB/AKAZE: Require Hamming distance with ORB/AKAZE detectors"
419
561
  )
420
-
421
562
  self.matching_method_field.setToolTip(
422
563
  "Automatically selected based on detector/descriptor combination"
423
564
  )
424
-
425
565
  self.detector_field.currentIndexChanged.connect(self.change_match_config)
426
566
  self.descriptor_field.currentIndexChanged.connect(self.change_match_config)
427
567
  self.matching_method_field.currentIndexChanged.connect(self.change_match_config)
428
- self.builder.add_field('flann_idx_kdtree', FIELD_INT, 'Flann idx kdtree',
429
- required=False,
430
- default=constants.DEFAULT_FLANN_IDX_KDTREE,
431
- min_val=0, max_val=10)
432
- self.builder.add_field('flann_trees', FIELD_INT, 'Flann trees', required=False,
433
- default=constants.DEFAULT_FLANN_TREES,
434
- min_val=0, max_val=10)
435
- self.builder.add_field('flann_checks', FIELD_INT, 'Flann checks', required=False,
436
- default=constants.DEFAULT_FLANN_CHECKS,
437
- min_val=0, max_val=1000)
438
- self.builder.add_field('threshold', FIELD_FLOAT, 'Threshold', required=False,
439
- default=constants.DEFAULT_ALIGN_THRESHOLD,
440
- min_val=0, max_val=1, step=0.05)
441
-
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)
442
584
  self.add_bold_label("Transform:")
443
- transform = self.builder.add_field(
585
+ transform = self.add_field(
444
586
  'transform', FIELD_COMBO, 'Transform', required=False,
445
587
  options=self.TRANSFORM_OPTIONS, values=constants.VALID_TRANSFORMS,
446
588
  default=constants.DEFAULT_TRANSFORM)
447
- method = self.builder.add_field(
589
+ method = self.add_field(
448
590
  'align_method', FIELD_COMBO, 'Align method', required=False,
449
591
  options=self.METHOD_OPTIONS, values=constants.VALID_ALIGN_METHODS,
450
592
  default=constants.DEFAULT_ALIGN_METHOD)
451
- rans_threshold = self.builder.add_field(
593
+ rans_threshold = self.add_field(
452
594
  'rans_threshold', FIELD_FLOAT, 'RANSAC threshold (px)', required=False,
453
595
  default=constants.DEFAULT_RANS_THRESHOLD, min_val=0, max_val=20, step=0.1)
454
- self.builder.add_field(
596
+ self.add_field(
455
597
  'min_good_matches', FIELD_INT, "Min. good matches", required=False,
456
598
  default=constants.DEFAULT_ALIGN_MIN_GOOD_MATCHES, min_val=0, max_val=500)
457
599
 
@@ -461,17 +603,19 @@ class AlignFramesConfigurator(DefaultActionConfigurator):
461
603
  rans_threshold.setEnabled(True)
462
604
  elif text == self.METHOD_OPTIONS[1]:
463
605
  rans_threshold.setEnabled(False)
606
+
464
607
  method.currentIndexChanged.connect(change_method)
465
608
  change_method()
466
- self.builder.add_field('align_confidence', FIELD_FLOAT, 'Confidence (%)',
467
- required=False, decimals=1,
468
- default=constants.DEFAULT_ALIGN_CONFIDENCE,
469
- 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)
470
614
 
471
- refine_iters = self.builder.add_field(
615
+ refine_iters = self.add_field(
472
616
  'refine_iters', FIELD_INT, 'Refinement iterations (Rigid)', required=False,
473
617
  default=constants.DEFAULT_REFINE_ITERS, min_val=0, max_val=1000)
474
- max_iters = self.builder.add_field(
618
+ max_iters = self.add_field(
475
619
  'max_iters', FIELD_INT, 'Max. iterations (Homography)', required=False,
476
620
  default=constants.DEFAULT_ALIGN_MAX_ITERS, min_val=0, max_val=5000)
477
621
 
@@ -483,37 +627,36 @@ class AlignFramesConfigurator(DefaultActionConfigurator):
483
627
  elif text == self.TRANSFORM_OPTIONS[1]:
484
628
  refine_iters.setEnabled(False)
485
629
  max_iters.setEnabled(True)
630
+
486
631
  transform.currentIndexChanged.connect(change_transform)
487
632
  change_transform()
488
- subsample = self.builder.add_field(
489
- 'subsample', FIELD_INT, 'Subsample factor', required=False,
490
- default=constants.DEFAULT_ALIGN_SUBSAMPLE, min_val=1, max_val=256)
491
- fast_subsampling = self.builder.add_field(
492
- 'fast_subsampling', FIELD_BOOL, 'Fast subsampling', required=False,
493
- default=constants.DEFAULT_ALIGN_FAST_SUBSAMPLING)
494
-
495
- def change_subsample():
496
- fast_subsampling.setEnabled(subsample.value() > 1)
497
- subsample.valueChanged.connect(change_subsample)
498
- 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()
499
637
  self.add_bold_label("Border:")
500
- self.builder.add_field('border_mode', FIELD_COMBO, 'Border mode', required=False,
501
- options=self.BORDER_MODE_OPTIONS,
502
- values=constants.VALID_BORDER_MODES,
503
- default=constants.DEFAULT_BORDER_MODE)
504
- self.builder.add_field('border_value', FIELD_INT_TUPLE,
505
- 'Border value (if constant)', required=False, size=4,
506
- default=constants.DEFAULT_BORDER_VALUE,
507
- labels=constants.RGBA_LABELS,
508
- min_val=constants.DEFAULT_BORDER_VALUE, max_val=[255] * 4)
509
- self.builder.add_field('border_blur', FIELD_FLOAT, 'Border blur', required=False,
510
- default=constants.DEFAULT_BORDER_BLUR,
511
- 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)
512
653
  self.add_bold_label("Miscellanea:")
513
- self.builder.add_field('plot_summary', FIELD_BOOL, 'Plot summary',
514
- required=False, default=False)
515
- self.builder.add_field('plot_matches', FIELD_BOOL, 'Plot matches',
516
- 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)
517
660
 
518
661
  def update_params(self, params: Dict[str, Any]) -> bool:
519
662
  if self.detector_field and self.descriptor_field and self.matching_method_field:
@@ -533,35 +676,59 @@ class AlignFramesConfigurator(DefaultActionConfigurator):
533
676
  return super().update_params(params)
534
677
 
535
678
 
536
- class BalanceFramesConfigurator(DefaultActionConfigurator):
679
+ class BalanceFramesConfigurator(SubsampleActionConfigurator):
537
680
  CORRECTION_MAP_OPTIONS = ['Linear', 'Gamma', 'Match histograms']
538
- 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
+
539
711
 
712
+ class VignettingConfigurator(SubsampleActionConfigurator):
540
713
  def create_form(self, layout, action):
541
714
  super().create_form(layout, action)
542
715
  if self.expert:
543
- self.builder.add_field('mask_size', FIELD_FLOAT, 'Mask size', required=False,
544
- default=0, min_val=0, max_val=5, step=0.1)
545
- self.builder.add_field('intensity_interval', FIELD_INT_TUPLE, 'Intensity range',
546
- required=False, size=2,
547
- default=[v for k, v in
548
- constants.DEFAULT_INTENSITY_INTERVAL.items()],
549
- labels=['min', 'max'], min_val=[-1] * 2, max_val=[65536] * 2)
550
- self.builder.add_field('subsample', FIELD_INT, 'Subsample factor', required=False,
551
- default=constants.DEFAULT_BALANCE_SUBSAMPLE,
552
- min_val=1, max_val=256)
553
- self.builder.add_field('fast_subsampling', FIELD_BOOL, 'Fast subsampling',
554
- required=False,
555
- default=constants.DEFAULT_BALANCE_FAST_SUBSAMPLING)
556
- self.builder.add_field('corr_map', FIELD_COMBO, 'Correction map', required=False,
557
- options=self.CORRECTION_MAP_OPTIONS, values=constants.VALID_BALANCE,
558
- default='Linear')
559
- self.builder.add_field('channel', FIELD_COMBO, 'Channel', required=False,
560
- options=self.CHANNEL_OPTIONS,
561
- values=constants.VALID_BALANCE_CHANNELS,
562
- 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)
563
728
  self.add_bold_label("Miscellanea:")
564
- self.builder.add_field('plot_summary', FIELD_BOOL, 'Plot summary',
565
- required=False, default=False)
566
- self.builder.add_field('plot_histograms', FIELD_BOOL, 'Plot histograms',
567
- 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)