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.
- shinestacker/_version.py +1 -1
- shinestacker/algorithms/__init__.py +4 -1
- shinestacker/algorithms/align.py +117 -3
- shinestacker/algorithms/balance.py +362 -163
- shinestacker/algorithms/base_stack_algo.py +6 -0
- shinestacker/algorithms/depth_map.py +1 -1
- shinestacker/algorithms/multilayer.py +12 -2
- shinestacker/algorithms/noise_detection.py +1 -1
- shinestacker/algorithms/pyramid.py +3 -2
- shinestacker/algorithms/pyramid_auto.py +141 -0
- shinestacker/algorithms/pyramid_tiles.py +199 -44
- shinestacker/algorithms/stack.py +3 -3
- shinestacker/algorithms/stack_framework.py +13 -4
- shinestacker/algorithms/utils.py +175 -1
- shinestacker/algorithms/vignetting.py +23 -5
- shinestacker/config/constants.py +29 -6
- shinestacker/gui/action_config.py +6 -7
- shinestacker/gui/action_config_dialog.py +425 -280
- shinestacker/gui/base_form_dialog.py +11 -6
- shinestacker/gui/main_window.py +3 -2
- shinestacker/gui/menu_manager.py +12 -2
- shinestacker/gui/new_project.py +27 -22
- shinestacker/gui/project_controller.py +39 -23
- shinestacker/gui/project_converter.py +2 -8
- shinestacker/gui/project_editor.py +21 -7
- shinestacker/retouch/exif_data.py +5 -5
- shinestacker/retouch/shortcuts_help.py +4 -4
- shinestacker/retouch/vignetting_filter.py +12 -8
- {shinestacker-1.1.0.dist-info → shinestacker-1.2.0.dist-info}/METADATA +1 -1
- {shinestacker-1.1.0.dist-info → shinestacker-1.2.0.dist-info}/RECORD +34 -33
- {shinestacker-1.1.0.dist-info → shinestacker-1.2.0.dist-info}/WHEEL +0 -0
- {shinestacker-1.1.0.dist-info → shinestacker-1.2.0.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.1.0.dist-info → shinestacker-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {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
|
|
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(
|
|
19
|
+
class ActionConfigDialog(QDialog):
|
|
20
20
|
def __init__(self, action: ActionConfig, current_wd, parent=None):
|
|
21
|
-
super().__init__(
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
99
|
-
|
|
100
|
-
|
|
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.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
self.
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
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.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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.
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
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.
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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.
|
|
154
|
-
|
|
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.
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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.
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
self.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
self.
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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.
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
self.
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
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.
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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.
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
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.
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
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.
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
self.
|
|
296
|
-
|
|
297
|
-
|
|
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.
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
self.
|
|
319
|
-
|
|
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.
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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.
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
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
|
|
337
|
-
def
|
|
338
|
-
super().
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
self.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
self.
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
511
|
-
'
|
|
512
|
-
default=constants.
|
|
513
|
-
|
|
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.
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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.
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
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(
|
|
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.
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
self.
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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.
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
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)
|