shinestacker 1.8.0__py3-none-any.whl → 1.8.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of shinestacker might be problematic. Click here for more details.
- shinestacker/_version.py +1 -1
- shinestacker/algorithms/align.py +184 -80
- shinestacker/algorithms/align_auto.py +13 -11
- shinestacker/algorithms/align_parallel.py +41 -16
- shinestacker/algorithms/base_stack_algo.py +1 -1
- shinestacker/algorithms/noise_detection.py +10 -8
- shinestacker/algorithms/pyramid_tiles.py +1 -1
- shinestacker/algorithms/stack_framework.py +14 -9
- shinestacker/algorithms/vignetting.py +16 -3
- shinestacker/app/settings_dialog.py +297 -173
- shinestacker/config/constants.py +10 -5
- shinestacker/config/settings.py +25 -7
- shinestacker/core/exceptions.py +1 -1
- shinestacker/core/framework.py +2 -2
- shinestacker/gui/action_config.py +23 -20
- shinestacker/gui/action_config_dialog.py +36 -20
- shinestacker/gui/gui_images.py +27 -3
- shinestacker/gui/new_project.py +18 -7
- shinestacker/gui/project_controller.py +13 -6
- shinestacker/gui/project_editor.py +12 -2
- shinestacker/gui/project_model.py +4 -4
- {shinestacker-1.8.0.dist-info → shinestacker-1.8.1.dist-info}/METADATA +35 -39
- {shinestacker-1.8.0.dist-info → shinestacker-1.8.1.dist-info}/RECORD +27 -27
- {shinestacker-1.8.0.dist-info → shinestacker-1.8.1.dist-info}/WHEEL +0 -0
- {shinestacker-1.8.0.dist-info → shinestacker-1.8.1.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.8.0.dist-info → shinestacker-1.8.1.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.8.0.dist-info → shinestacker-1.8.1.dist-info}/top_level.txt +0 -0
|
@@ -191,7 +191,8 @@ class ImageSequenceManager:
|
|
|
191
191
|
|
|
192
192
|
|
|
193
193
|
class ReferenceFrameTask(SequentialTask, ImageSequenceManager):
|
|
194
|
-
def __init__(self, name, enabled=True, reference_index=0,
|
|
194
|
+
def __init__(self, name, enabled=True, reference_index=0,
|
|
195
|
+
step_process=constants.DEFAULT_COMBINED_ACTIONS_STEP_PROCESS, **kwargs):
|
|
195
196
|
ImageSequenceManager.__init__(self, name, **kwargs)
|
|
196
197
|
SequentialTask.__init__(self, name, enabled)
|
|
197
198
|
self.ref_idx = reference_index
|
|
@@ -276,7 +277,8 @@ class SubAction:
|
|
|
276
277
|
|
|
277
278
|
class CombinedActions(ReferenceFrameTask):
|
|
278
279
|
def __init__(self, name, actions=[], enabled=True, **kwargs):
|
|
279
|
-
|
|
280
|
+
step_process = kwargs.pop('step_process', constants.DEFAULT_COMBINED_ACTIONS_STEP_PROCESS)
|
|
281
|
+
ReferenceFrameTask.__init__(self, name, enabled, step_process=step_process, **kwargs)
|
|
280
282
|
self._actions = actions
|
|
281
283
|
self._metadata = (None, None)
|
|
282
284
|
|
|
@@ -294,11 +296,15 @@ class CombinedActions(ReferenceFrameTask):
|
|
|
294
296
|
self._metadata = get_img_metadata(img)
|
|
295
297
|
return img
|
|
296
298
|
|
|
299
|
+
def frame_str(self, idx=-1):
|
|
300
|
+
if self.run_sequential():
|
|
301
|
+
idx = self.current_action_count
|
|
302
|
+
return f"frame {idx + 1}/{self.total_action_counts}"
|
|
303
|
+
|
|
297
304
|
def run_frame(self, idx, ref_idx):
|
|
298
305
|
input_path = self.input_filepath(idx)
|
|
299
306
|
self.print_message(
|
|
300
|
-
color_str(f'read input
|
|
301
|
-
f'{idx + 1}/{self.total_action_counts}, '
|
|
307
|
+
color_str(f'read input {self.frame_str(idx)}, '
|
|
302
308
|
f'{os.path.basename(input_path)}', constants.LOG_COLOR_LEVEL_3))
|
|
303
309
|
img = read_img(input_path)
|
|
304
310
|
validate_image(img, *(self._metadata))
|
|
@@ -317,20 +323,19 @@ class CombinedActions(ReferenceFrameTask):
|
|
|
317
323
|
if img is not None:
|
|
318
324
|
img = a.run_frame(idx, ref_idx, img)
|
|
319
325
|
else:
|
|
320
|
-
self.
|
|
321
|
-
color_str("
|
|
326
|
+
self.print_message(
|
|
327
|
+
color_str("null input received, action skipped",
|
|
322
328
|
constants.LOG_COLOR_ALERT),
|
|
323
329
|
level=logging.WARNING)
|
|
324
330
|
if img is not None:
|
|
325
331
|
output_path = os.path.join(self.output_full_path(), os.path.basename(input_path))
|
|
326
332
|
self.print_message(
|
|
327
|
-
color_str(f'write output
|
|
328
|
-
f'{idx + 1}/{self.total_action_counts}, '
|
|
333
|
+
color_str(f'write output {self.frame_str(idx)}, '
|
|
329
334
|
f'{os.path.basename(output_path)}', constants.LOG_COLOR_LEVEL_3))
|
|
330
335
|
write_img(output_path, img)
|
|
331
336
|
return img
|
|
332
337
|
self.print_message(color_str(
|
|
333
|
-
f"no output
|
|
338
|
+
f"no output resulted from processing input file: {os.path.basename(input_path)}",
|
|
334
339
|
constants.LOG_COLOR_ALERT), level=logging.WARNING)
|
|
335
340
|
return None
|
|
336
341
|
|
|
@@ -4,7 +4,7 @@ import traceback
|
|
|
4
4
|
import logging
|
|
5
5
|
import numpy as np
|
|
6
6
|
import matplotlib.pyplot as plt
|
|
7
|
-
from scipy.optimize import curve_fit,
|
|
7
|
+
from scipy.optimize import curve_fit, bisect
|
|
8
8
|
import cv2
|
|
9
9
|
from .. core.colors import color_str
|
|
10
10
|
from .. core.core_utils import setup_matplotlib_mode
|
|
@@ -181,9 +181,22 @@ class Vignetting(SubAction):
|
|
|
181
181
|
self.process.callback(
|
|
182
182
|
constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
183
183
|
f"{self.process.name}: intensity\nframe {idx_str}", plot_path)
|
|
184
|
+
|
|
184
185
|
for i, p in enumerate(self.percentiles):
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
s1 = sigmoid_model(0, *params) / self.v0
|
|
187
|
+
s2 = sigmoid_model(self.r_max, *params) / self.v0
|
|
188
|
+
if s1 > p and s2 < p:
|
|
189
|
+
try:
|
|
190
|
+
c = bisect(lambda x: sigmoid_model(x, *params) / self.v0 - p, 0, self.r_max)
|
|
191
|
+
except Exception as e:
|
|
192
|
+
traceback.print_tb(e.__traceback__)
|
|
193
|
+
self.process.sub_message(color_str(f": {str(e).lower()}", "yellow"),
|
|
194
|
+
level=logging.WARNING)
|
|
195
|
+
elif s1 <= p:
|
|
196
|
+
c = 0
|
|
197
|
+
else:
|
|
198
|
+
c = self.r_max
|
|
199
|
+
self.corrections[i][idx] = c
|
|
187
200
|
self.process.print_message(
|
|
188
201
|
color_str(f"{self.process.idx_tot_str(idx)}: correct vignetting", "cyan"))
|
|
189
202
|
return correct_vignetting(
|
|
@@ -1,13 +1,174 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, E0611,
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, R0913, R0917, E1121
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
2
3
|
from PySide6.QtCore import Signal
|
|
3
|
-
from PySide6.QtWidgets import
|
|
4
|
+
from PySide6.QtWidgets import QLabel, QCheckBox, QComboBox, QDoubleSpinBox, QSpinBox
|
|
4
5
|
from .. config.settings import Settings
|
|
5
6
|
from .. config.constants import constants
|
|
6
7
|
from .. config.gui_constants import gui_constants
|
|
7
8
|
from .. gui.config_dialog import ConfigDialog
|
|
9
|
+
from .. gui.action_config import add_tab, create_tab_widget
|
|
8
10
|
from .. gui.action_config_dialog import AlignFramesConfigBase
|
|
9
11
|
|
|
10
12
|
|
|
13
|
+
class BaseParameter(ABC):
|
|
14
|
+
def __init__(self, key, label, tooltip=""):
|
|
15
|
+
self.key = key
|
|
16
|
+
self.label = label
|
|
17
|
+
self.tooltip = tooltip
|
|
18
|
+
self.widget = None
|
|
19
|
+
|
|
20
|
+
@abstractmethod
|
|
21
|
+
def create_widget(self, parent):
|
|
22
|
+
pass
|
|
23
|
+
|
|
24
|
+
@abstractmethod
|
|
25
|
+
def get_value(self):
|
|
26
|
+
pass
|
|
27
|
+
|
|
28
|
+
@abstractmethod
|
|
29
|
+
def set_value(self, value):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
@abstractmethod
|
|
33
|
+
def set_default(self):
|
|
34
|
+
pass
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class NestedParameter(BaseParameter):
|
|
38
|
+
def __init__(self, parent_key, key, label, tooltip=""):
|
|
39
|
+
super().__init__(key, label, tooltip)
|
|
40
|
+
self.parent_key = parent_key
|
|
41
|
+
|
|
42
|
+
def get_nested_value(self, settings):
|
|
43
|
+
return settings.get(self.parent_key).get(self.key)
|
|
44
|
+
|
|
45
|
+
def set_nested_value(self, settings, value):
|
|
46
|
+
nested_dict = settings.get(self.parent_key).copy()
|
|
47
|
+
nested_dict[self.key] = value
|
|
48
|
+
settings.set(self.parent_key, nested_dict)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class CheckBoxParameter(BaseParameter):
|
|
52
|
+
def __init__(self, key, label, default_value, tooltip=""):
|
|
53
|
+
super().__init__(key, label, tooltip)
|
|
54
|
+
self.default_value = default_value
|
|
55
|
+
|
|
56
|
+
def create_widget(self, parent):
|
|
57
|
+
self.widget = QCheckBox(parent)
|
|
58
|
+
if self.tooltip:
|
|
59
|
+
self.widget.setToolTip(self.tooltip)
|
|
60
|
+
return self.widget
|
|
61
|
+
|
|
62
|
+
def get_value(self):
|
|
63
|
+
return self.widget.isChecked()
|
|
64
|
+
|
|
65
|
+
def set_value(self, value):
|
|
66
|
+
self.widget.setChecked(value)
|
|
67
|
+
|
|
68
|
+
def set_default(self):
|
|
69
|
+
self.widget.setChecked(self.default_value)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
class SpinBoxParameter(BaseParameter):
|
|
73
|
+
def __init__(self, key, label, default_value, min_val, max_val, step=1, tooltip=""):
|
|
74
|
+
super().__init__(key, label, tooltip)
|
|
75
|
+
self.default_value = default_value
|
|
76
|
+
self.min_val = min_val
|
|
77
|
+
self.max_val = max_val
|
|
78
|
+
self.step = step
|
|
79
|
+
|
|
80
|
+
def create_widget(self, parent):
|
|
81
|
+
self.widget = QSpinBox(parent)
|
|
82
|
+
self.widget.setRange(self.min_val, self.max_val)
|
|
83
|
+
self.widget.setSingleStep(self.step)
|
|
84
|
+
if self.tooltip:
|
|
85
|
+
self.widget.setToolTip(self.tooltip)
|
|
86
|
+
return self.widget
|
|
87
|
+
|
|
88
|
+
def get_value(self):
|
|
89
|
+
return self.widget.value()
|
|
90
|
+
|
|
91
|
+
def set_value(self, value):
|
|
92
|
+
self.widget.setValue(value)
|
|
93
|
+
|
|
94
|
+
def set_default(self):
|
|
95
|
+
self.widget.setValue(self.default_value)
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
class DoubleSpinBoxParameter(SpinBoxParameter):
|
|
99
|
+
def create_widget(self, parent):
|
|
100
|
+
self.widget = QDoubleSpinBox(parent)
|
|
101
|
+
self.widget.setRange(self.min_val, self.max_val)
|
|
102
|
+
self.widget.setSingleStep(self.step)
|
|
103
|
+
if self.tooltip:
|
|
104
|
+
self.widget.setToolTip(self.tooltip)
|
|
105
|
+
return self.widget
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
class ComboBoxParameter(BaseParameter):
|
|
109
|
+
def __init__(self, key, label, default_value, options, tooltip=""):
|
|
110
|
+
super().__init__(key, label, tooltip)
|
|
111
|
+
self.default_value = default_value
|
|
112
|
+
self.options = options
|
|
113
|
+
|
|
114
|
+
def create_widget(self, parent):
|
|
115
|
+
self.widget = QComboBox(parent)
|
|
116
|
+
for display_text, data in self.options:
|
|
117
|
+
self.widget.addItem(display_text, data)
|
|
118
|
+
if self.tooltip:
|
|
119
|
+
self.widget.setToolTip(self.tooltip)
|
|
120
|
+
return self.widget
|
|
121
|
+
|
|
122
|
+
def get_value(self):
|
|
123
|
+
return self.widget.itemData(self.widget.currentIndex())
|
|
124
|
+
|
|
125
|
+
def set_value(self, value):
|
|
126
|
+
idx = self.widget.findData(value)
|
|
127
|
+
if idx >= 0:
|
|
128
|
+
self.widget.setCurrentIndex(idx)
|
|
129
|
+
|
|
130
|
+
def set_default(self):
|
|
131
|
+
idx = self.widget.findData(self.default_value)
|
|
132
|
+
if idx >= 0:
|
|
133
|
+
self.widget.setCurrentIndex(idx)
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
class CallbackComboBoxParameter(ComboBoxParameter):
|
|
137
|
+
def __init__(self, key, label, default_value, options, tooltip="", on_change=None):
|
|
138
|
+
super().__init__(key, label, default_value, options, tooltip)
|
|
139
|
+
self.on_change = on_change
|
|
140
|
+
|
|
141
|
+
def create_widget(self, parent):
|
|
142
|
+
widget = super().create_widget(parent)
|
|
143
|
+
if self.on_change:
|
|
144
|
+
widget.currentIndexChanged.connect(self.on_change)
|
|
145
|
+
return widget
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
class NestedSpinBoxParameter(SpinBoxParameter, NestedParameter):
|
|
149
|
+
def __init__(self, parent_key, key, label, default_value, min_val, max_val, step=1, tooltip=""):
|
|
150
|
+
SpinBoxParameter.__init__(
|
|
151
|
+
self, key, label, default_value, min_val, max_val, step, tooltip)
|
|
152
|
+
NestedParameter.__init__(
|
|
153
|
+
self, parent_key, key, label, tooltip)
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
class NestedDoubleSpinBoxParameter(DoubleSpinBoxParameter, NestedParameter):
|
|
157
|
+
def __init__(self, parent_key, key, label, default_value, min_val, max_val, step=1, tooltip=""):
|
|
158
|
+
DoubleSpinBoxParameter.__init__(
|
|
159
|
+
self, key, label, default_value, min_val, max_val, step, tooltip)
|
|
160
|
+
NestedParameter.__init__(
|
|
161
|
+
self, parent_key, key, label, tooltip)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class NestedCallbackComboBoxParameter(CallbackComboBoxParameter, NestedParameter):
|
|
165
|
+
def __init__(self, parent_key, key, label, default_value,
|
|
166
|
+
options, tooltip="", on_change=None):
|
|
167
|
+
CallbackComboBoxParameter.__init__(
|
|
168
|
+
self, key, label, default_value, options, tooltip, on_change)
|
|
169
|
+
NestedParameter.__init__(self, parent_key, key, label, tooltip)
|
|
170
|
+
|
|
171
|
+
|
|
11
172
|
class SettingsDialog(ConfigDialog, AlignFramesConfigBase):
|
|
12
173
|
update_project_config_requested = Signal()
|
|
13
174
|
update_retouch_config_requested = Signal()
|
|
@@ -17,163 +178,144 @@ class SettingsDialog(ConfigDialog, AlignFramesConfigBase):
|
|
|
17
178
|
self.project_settings = project_settings
|
|
18
179
|
self.retouch_settings = retouch_settings
|
|
19
180
|
self.settings = Settings.instance()
|
|
20
|
-
self.
|
|
21
|
-
self.
|
|
22
|
-
self.
|
|
23
|
-
self.detector = None
|
|
24
|
-
self.descriptor = None
|
|
25
|
-
self.matching_method = None
|
|
26
|
-
self.focus_stack_max_threads = None
|
|
27
|
-
self.view_strategy = None
|
|
28
|
-
self.min_mouse_step_brush_fraction = None
|
|
29
|
-
self.paint_refresh_time = None
|
|
30
|
-
self.display_refresh_time = None
|
|
31
|
-
self.cursor_update_time = None
|
|
181
|
+
self.project_parameters = []
|
|
182
|
+
self.retouch_parameters = []
|
|
183
|
+
self._init_parameters()
|
|
32
184
|
super().__init__("Settings", parent)
|
|
33
185
|
|
|
186
|
+
def _init_parameters(self):
|
|
187
|
+
if self.project_settings:
|
|
188
|
+
self.project_parameters = [
|
|
189
|
+
CheckBoxParameter(
|
|
190
|
+
'expert_options', 'Expert options:',
|
|
191
|
+
constants.DEFAULT_EXPERT_OPTIONS),
|
|
192
|
+
NestedSpinBoxParameter(
|
|
193
|
+
'combined_actions_params', 'max_threads',
|
|
194
|
+
'Combined actions, max num. of cores:',
|
|
195
|
+
constants.DEFAULT_FWK_MAX_THREADS, 0, 64),
|
|
196
|
+
NestedDoubleSpinBoxParameter(
|
|
197
|
+
'align_frames_params', 'memory_limit',
|
|
198
|
+
'Align frames, mem. limit (approx., GBytes):',
|
|
199
|
+
constants.DEFAULT_ALIGN_MEMORY_LIMIT_GB, 1.0, 64.0, 1.0),
|
|
200
|
+
NestedSpinBoxParameter(
|
|
201
|
+
'align_frames_params', 'max_threads',
|
|
202
|
+
'Align frames, max num. of cores:',
|
|
203
|
+
constants.DEFAULT_ALIGN_MAX_THREADS, 0, 64),
|
|
204
|
+
NestedCallbackComboBoxParameter(
|
|
205
|
+
'align_frames_params', 'detector', 'Detector:',
|
|
206
|
+
constants.DEFAULT_DETECTOR, [(d, d) for d in constants.VALID_DETECTORS],
|
|
207
|
+
tooltip=self.DETECTOR_DESCRIPTOR_TOOLTIPS['detector'],
|
|
208
|
+
on_change=self.change_match_config_settings),
|
|
209
|
+
NestedCallbackComboBoxParameter(
|
|
210
|
+
'align_frames_params', 'descriptor', 'Descriptor:',
|
|
211
|
+
constants.DEFAULT_DESCRIPTOR, [(d, d) for d in constants.VALID_DESCRIPTORS],
|
|
212
|
+
tooltip=self.DETECTOR_DESCRIPTOR_TOOLTIPS['descriptor'],
|
|
213
|
+
on_change=self.change_match_config_settings),
|
|
214
|
+
NestedCallbackComboBoxParameter(
|
|
215
|
+
'align_frames_params', 'match_method', 'Match method:',
|
|
216
|
+
constants.DEFAULT_MATCHING_METHOD,
|
|
217
|
+
list(zip(self.MATCHING_METHOD_OPTIONS, constants.VALID_MATCHING_METHODS)),
|
|
218
|
+
tooltip=self.DETECTOR_DESCRIPTOR_TOOLTIPS['match_method'],
|
|
219
|
+
on_change=self.change_match_config_settings),
|
|
220
|
+
NestedDoubleSpinBoxParameter(
|
|
221
|
+
'focus_stack_params', 'memory_limit',
|
|
222
|
+
'Focus stacking, mem. limit (approx., GBytes):',
|
|
223
|
+
constants.DEFAULT_PY_MEMORY_LIMIT_GB, 1.0, 64.0, 1.0),
|
|
224
|
+
NestedSpinBoxParameter(
|
|
225
|
+
'focus_stack_params', 'max_threads', 'Focus stacking, max. num. of cores:',
|
|
226
|
+
constants.DEFAULT_PY_MAX_THREADS, 0, 64),
|
|
227
|
+
]
|
|
228
|
+
if self.retouch_settings:
|
|
229
|
+
self.retouch_parameters = [
|
|
230
|
+
ComboBoxParameter(
|
|
231
|
+
'view_strategy', 'View strategy:',
|
|
232
|
+
constants.DEFAULT_VIEW_STRATEGY,
|
|
233
|
+
[
|
|
234
|
+
("Overlaid", "overlaid"),
|
|
235
|
+
("Side by side", "sidebyside"),
|
|
236
|
+
("Top-Bottom", "topbottom")
|
|
237
|
+
]),
|
|
238
|
+
DoubleSpinBoxParameter(
|
|
239
|
+
'min_mouse_step_brush_fraction', 'Min. mouse step in brush units:',
|
|
240
|
+
gui_constants.DEFAULT_MIN_MOUSE_STEP_BRUSH_FRACTION, 0, 1, 0.02),
|
|
241
|
+
SpinBoxParameter(
|
|
242
|
+
'paint_refresh_time', 'Paint refresh time:',
|
|
243
|
+
gui_constants.DEFAULT_PAINT_REFRESH_TIME, 0, 1000),
|
|
244
|
+
SpinBoxParameter(
|
|
245
|
+
'display_refresh_time', 'Display refresh time:',
|
|
246
|
+
gui_constants.DEFAULT_DISPLAY_REFRESH_TIME, 0, 200),
|
|
247
|
+
SpinBoxParameter(
|
|
248
|
+
'cursor_update_time', 'Cursor refresh time:',
|
|
249
|
+
gui_constants.DEFAULT_CURSOR_UPDATE_TIME, 0, 50),
|
|
250
|
+
]
|
|
251
|
+
|
|
34
252
|
def create_form_content(self):
|
|
253
|
+
self.tab_widget = create_tab_widget(self.container_layout)
|
|
35
254
|
if self.project_settings:
|
|
36
|
-
self.
|
|
37
|
-
|
|
38
|
-
separator.setFrameShape(QFrame.HLine)
|
|
39
|
-
separator.setFrameShadow(QFrame.Sunken)
|
|
40
|
-
separator.setLineWidth(1)
|
|
41
|
-
self.container_layout.addRow(separator)
|
|
255
|
+
project_tab_layout = add_tab(self.tab_widget, "Project Settings")
|
|
256
|
+
self.create_project_settings(project_tab_layout)
|
|
42
257
|
if self.retouch_settings:
|
|
43
|
-
self.
|
|
258
|
+
retouch_tab_layout = add_tab(self.tab_widget, "Retouch Settings")
|
|
259
|
+
self.create_retouch_settings(retouch_tab_layout)
|
|
44
260
|
|
|
45
|
-
def create_project_settings(self):
|
|
46
|
-
|
|
261
|
+
def create_project_settings(self, layout=None):
|
|
262
|
+
if layout is None:
|
|
263
|
+
layout = self.container_layout
|
|
264
|
+
label = QLabel("Project settings:")
|
|
47
265
|
label.setStyleSheet("font-weight: bold")
|
|
48
|
-
|
|
49
|
-
self.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
self.combined_actions_max_threads.setRange(0, 64)
|
|
54
|
-
self.combined_actions_max_threads.setValue(
|
|
55
|
-
self.settings.get('combined_actions_params')['max_threads'])
|
|
56
|
-
self.container_layout.addRow("Max num. of cores, combined actions:",
|
|
57
|
-
self.combined_actions_max_threads)
|
|
58
|
-
|
|
59
|
-
self.align_frames_max_threads = QSpinBox()
|
|
60
|
-
self.align_frames_max_threads.setRange(0, 64)
|
|
61
|
-
self.align_frames_max_threads.setValue(
|
|
62
|
-
self.settings.get('align_frames_params')['max_threads'])
|
|
63
|
-
self.container_layout.addRow("Max num. of cores, align frames:",
|
|
64
|
-
self.align_frames_max_threads)
|
|
65
|
-
|
|
66
|
-
def change_match_config():
|
|
67
|
-
self.change_match_config(
|
|
68
|
-
self.detector, self.descriptor,
|
|
69
|
-
self. matching_method, self.show_info)
|
|
70
|
-
|
|
71
|
-
self.detector = QComboBox()
|
|
72
|
-
self.detector.addItems(constants.VALID_DETECTORS)
|
|
73
|
-
self.descriptor = QComboBox()
|
|
74
|
-
self.descriptor.addItems(constants.VALID_DESCRIPTORS)
|
|
75
|
-
self.matching_method = QComboBox()
|
|
266
|
+
layout.addRow(label)
|
|
267
|
+
for param in self.project_parameters:
|
|
268
|
+
widget = param.create_widget(self)
|
|
269
|
+
param.set_value(self._get_current_value(param))
|
|
270
|
+
layout.addRow(param.label, widget)
|
|
76
271
|
self.info_label = QLabel()
|
|
77
272
|
self.info_label.setStyleSheet("color: orange; font-style: italic;")
|
|
78
|
-
self.
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
self.detector.currentIndexChanged.connect(change_match_config)
|
|
85
|
-
self.descriptor.currentIndexChanged.connect(change_match_config)
|
|
86
|
-
self.matching_method.currentIndexChanged.connect(change_match_config)
|
|
87
|
-
self.container_layout.addRow('Detector:', self.detector)
|
|
88
|
-
self.container_layout.addRow('Descriptor:', self.descriptor)
|
|
89
|
-
self.container_layout.addRow(self.info_label)
|
|
90
|
-
self.container_layout.addRow('Match method:', self.matching_method)
|
|
91
|
-
|
|
92
|
-
self.focus_stack_max_threads = QSpinBox()
|
|
93
|
-
self.focus_stack_max_threads.setRange(0, 64)
|
|
94
|
-
self.focus_stack_max_threads.setValue(
|
|
95
|
-
self.settings.get('align_frames_params')['max_threads'])
|
|
96
|
-
self.container_layout.addRow("Max num. of cores, focus stacking:",
|
|
97
|
-
self.focus_stack_max_threads)
|
|
98
|
-
|
|
99
|
-
def create_retouch_settings(self):
|
|
100
|
-
label = QLabel("Retouch settings")
|
|
273
|
+
layout.addRow(self.info_label)
|
|
274
|
+
|
|
275
|
+
def create_retouch_settings(self, layout=None):
|
|
276
|
+
if layout is None:
|
|
277
|
+
layout = self.container_layout
|
|
278
|
+
label = QLabel("Retouch settings:")
|
|
101
279
|
label.setStyleSheet("font-weight: bold")
|
|
102
|
-
|
|
103
|
-
self.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
self.
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
self.
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
self.settings.get('cursor_update_time'))
|
|
136
|
-
self.container_layout.addRow("Cursor refresh time:",
|
|
137
|
-
self.cursor_update_time)
|
|
280
|
+
layout.addRow(label)
|
|
281
|
+
for param in self.retouch_parameters:
|
|
282
|
+
widget = param.create_widget(self)
|
|
283
|
+
param.set_value(self._get_current_value(param))
|
|
284
|
+
layout.addRow(param.label, widget)
|
|
285
|
+
|
|
286
|
+
def _get_current_value(self, param):
|
|
287
|
+
if isinstance(param, NestedParameter):
|
|
288
|
+
return param.get_nested_value(self.settings)
|
|
289
|
+
return self.settings.get(param.key)
|
|
290
|
+
|
|
291
|
+
def _set_current_value(self, param, value):
|
|
292
|
+
if isinstance(param, NestedParameter):
|
|
293
|
+
param.set_nested_value(self.settings, value)
|
|
294
|
+
else:
|
|
295
|
+
self.settings.set(param.key, value)
|
|
296
|
+
|
|
297
|
+
def change_match_config_settings(self):
|
|
298
|
+
detector_widget = None
|
|
299
|
+
descriptor_widget = None
|
|
300
|
+
matching_method_widget = None
|
|
301
|
+
for param in self.project_parameters:
|
|
302
|
+
if (isinstance(param, NestedParameter) and
|
|
303
|
+
param.parent_key == 'align_frames_params'):
|
|
304
|
+
if param.key == 'detector':
|
|
305
|
+
detector_widget = param.widget
|
|
306
|
+
elif param.key == 'descriptor':
|
|
307
|
+
descriptor_widget = param.widget
|
|
308
|
+
elif param.key == 'match_method':
|
|
309
|
+
matching_method_widget = param.widget
|
|
310
|
+
if detector_widget and descriptor_widget and matching_method_widget:
|
|
311
|
+
self.change_match_config(
|
|
312
|
+
detector_widget, descriptor_widget, matching_method_widget, self.show_info)
|
|
138
313
|
|
|
139
314
|
def accept(self):
|
|
140
|
-
|
|
141
|
-
self.
|
|
142
|
-
|
|
143
|
-
self.
|
|
144
|
-
'combined_actions_params', {
|
|
145
|
-
'max_threads': self.combined_actions_max_threads.value()
|
|
146
|
-
})
|
|
147
|
-
self.settings.set(
|
|
148
|
-
'align_frames_params', {
|
|
149
|
-
'max_threads':
|
|
150
|
-
self.align_frames_max_threads.value(),
|
|
151
|
-
'detector':
|
|
152
|
-
self.descriptor.currentText(),
|
|
153
|
-
'descriptor':
|
|
154
|
-
self.descriptor.currentText(),
|
|
155
|
-
'match_method':
|
|
156
|
-
self.matching_method.itemData(self.matching_method.currentIndex())
|
|
157
|
-
})
|
|
158
|
-
self.settings.set(
|
|
159
|
-
'focus_stack_params', {
|
|
160
|
-
'max_threads': self.focus_stack_max_threads.value()
|
|
161
|
-
})
|
|
162
|
-
self.settings.set(
|
|
163
|
-
'focus_stack_bunch:params', {
|
|
164
|
-
'max_threads': self.focus_stack_max_threads.value()
|
|
165
|
-
})
|
|
166
|
-
if self.retouch_settings:
|
|
167
|
-
self.settings.set(
|
|
168
|
-
'view_strategy', self.view_strategy.itemData(self.view_strategy.currentIndex()))
|
|
169
|
-
self.settings.set(
|
|
170
|
-
'min_mouse_step_brush_fraction', self.min_mouse_step_brush_fraction.value())
|
|
171
|
-
self.settings.set(
|
|
172
|
-
'paint_refresh_time', self.paint_refresh_time.value())
|
|
173
|
-
self.settings.set(
|
|
174
|
-
'display_refresh_time', self.display_refresh_time.value())
|
|
175
|
-
self.settings.set(
|
|
176
|
-
'cursor_update_time', self.cursor_update_time.value())
|
|
315
|
+
for param in self.project_parameters:
|
|
316
|
+
self._set_current_value(param, param.get_value())
|
|
317
|
+
for param in self.retouch_parameters:
|
|
318
|
+
self._set_current_value(param, param.get_value())
|
|
177
319
|
self.settings.update()
|
|
178
320
|
if self.project_settings:
|
|
179
321
|
self.update_project_config_requested.emit()
|
|
@@ -182,32 +324,14 @@ class SettingsDialog(ConfigDialog, AlignFramesConfigBase):
|
|
|
182
324
|
super().accept()
|
|
183
325
|
|
|
184
326
|
def reset_to_defaults(self):
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
self.matching_method.setCurrentIndex(idx)
|
|
194
|
-
self.focus_stack_max_threads.setValue(constants.DEFAULT_PY_MAX_THREADS)
|
|
195
|
-
if self.retouch_settings:
|
|
196
|
-
idx = self.view_strategy.findData(constants.DEFAULT_VIEW_STRATEGY)
|
|
197
|
-
if idx >= 0:
|
|
198
|
-
self.view_strategy.setCurrentIndex(idx)
|
|
199
|
-
self.min_mouse_step_brush_fraction.setValue(
|
|
200
|
-
gui_constants.DEFAULT_MIN_MOUSE_STEP_BRUSH_FRACTION)
|
|
201
|
-
self.paint_refresh_time.setValue(
|
|
202
|
-
gui_constants.DEFAULT_PAINT_REFRESH_TIME)
|
|
203
|
-
self.display_refresh_time.setValue(
|
|
204
|
-
gui_constants.DEFAULT_DISPLAY_REFRESH_TIME)
|
|
205
|
-
self.cursor_update_time.setValue(
|
|
206
|
-
gui_constants.DEFAULT_CURSOR_UPDATE_TIME)
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
def show_settings_dialog(parent, project_settings, retouch_settings,
|
|
210
|
-
handle_project_config, handle_retouch_config):
|
|
327
|
+
for param in self.project_parameters:
|
|
328
|
+
param.set_default()
|
|
329
|
+
for param in self.retouch_parameters:
|
|
330
|
+
param.set_default()
|
|
331
|
+
|
|
332
|
+
|
|
333
|
+
def show_settings_dialog(
|
|
334
|
+
parent, project_settings, retouch_settings, handle_project_config, handle_retouch_config):
|
|
211
335
|
dialog = SettingsDialog(parent, project_settings, retouch_settings)
|
|
212
336
|
dialog.update_project_config_requested.connect(handle_project_config)
|
|
213
337
|
dialog.update_retouch_config_requested.connect(handle_retouch_config)
|
shinestacker/config/constants.py
CHANGED
|
@@ -89,9 +89,12 @@ class _Constants:
|
|
|
89
89
|
DEFAULT_MULTILAYER_FILE_REVERSE_ORDER = True
|
|
90
90
|
MULTILAYER_WARNING_MEM_GB = 1
|
|
91
91
|
|
|
92
|
+
DEFAULT_COMBINED_ACTIONS_STEP_PROCESS = True
|
|
93
|
+
|
|
92
94
|
DEFAULT_PLOTS_PATH = 'plots'
|
|
93
|
-
|
|
94
|
-
|
|
95
|
+
DEFAULT_FWK_MEMORY_LIMIT_GB = 8
|
|
96
|
+
DEFAULT_FWK_MAX_THREADS = 8
|
|
97
|
+
DEFAULT_FWK_CHUNK_SUBMIT = True
|
|
95
98
|
|
|
96
99
|
FIELD_SUBSAMPLE_VALUES_1 = [2, 3, 4, 6, 8, 12, 16, 24, 32]
|
|
97
100
|
FIELD_SUBSAMPLE_OPTIONS_1 = [f"1/{n} × 1/{n}" for n in FIELD_SUBSAMPLE_VALUES_1]
|
|
@@ -99,7 +102,7 @@ class _Constants:
|
|
|
99
102
|
FIELD_SUBSAMPLE_OPTIONS = ['Auto', 'Full resolution'] + FIELD_SUBSAMPLE_OPTIONS_1
|
|
100
103
|
FIELD_SUBSAMPLE_DEFAULT = FIELD_SUBSAMPLE_VALUES[0]
|
|
101
104
|
|
|
102
|
-
DEFAULT_NOISE_MAP_FILENAME = "
|
|
105
|
+
DEFAULT_NOISE_MAP_FILENAME = "hot_pixels.png"
|
|
103
106
|
DEFAULT_NOISE_MAX_FRAMES = 10
|
|
104
107
|
DEFAULT_MN_KERNEL_SIZE = 3
|
|
105
108
|
INTERPOLATE_MEAN = 'MEAN'
|
|
@@ -135,7 +138,7 @@ class _Constants:
|
|
|
135
138
|
VALID_MATCHING_METHODS = [MATCHING_KNN, MATCHING_NORM_HAMMING]
|
|
136
139
|
VALID_TRANSFORMS = [ALIGN_RIGID, ALIGN_HOMOGRAPHY]
|
|
137
140
|
VALID_BORDER_MODES = [BORDER_CONSTANT, BORDER_REPLICATE, BORDER_REPLICATE_BLUR]
|
|
138
|
-
|
|
141
|
+
VALID_ESTIMATION_METHODS = [ALIGN_RANSAC, ALIGN_LMEDS]
|
|
139
142
|
NOKNN_METHODS = {'detectors': [DETECTOR_ORB, DETECTOR_SURF, DETECTOR_AKAZE, DETECTOR_BRISK],
|
|
140
143
|
'descriptors': [DESCRIPTOR_ORB, DESCRIPTOR_AKAZE, DESCRIPTOR_BRISK]}
|
|
141
144
|
|
|
@@ -148,7 +151,7 @@ class _Constants:
|
|
|
148
151
|
DEFAULT_ALIGN_THRESHOLD = 0.75
|
|
149
152
|
DEFAULT_TRANSFORM = ALIGN_RIGID
|
|
150
153
|
DEFAULT_BORDER_MODE = BORDER_REPLICATE_BLUR
|
|
151
|
-
|
|
154
|
+
DEFAULT_ESTIMATION_METHOD = 'RANSAC'
|
|
152
155
|
DEFAULT_RANS_THRESHOLD = 3.0 # px
|
|
153
156
|
DEFAULT_REFINE_ITERS = 100
|
|
154
157
|
DEFAULT_ALIGN_CONFIDENCE = 99.9
|
|
@@ -160,12 +163,14 @@ class _Constants:
|
|
|
160
163
|
DEFAULT_ALIGN_RES_TARGET_MPX = 2
|
|
161
164
|
DEFAULT_ALIGN_FAST_SUBSAMPLING = False
|
|
162
165
|
DEFAULT_ALIGN_MIN_GOOD_MATCHES = 20
|
|
166
|
+
DEFAULT_PHASE_CORR_FALLBACK = False
|
|
163
167
|
ALIGN_VALID_MODES = ['auto', 'sequential', 'parallel']
|
|
164
168
|
DEFAULT_ALIGN_MODE = 'auto'
|
|
165
169
|
DEFAULT_ALIGN_MEMORY_LIMIT_GB = 8
|
|
166
170
|
DEFAULT_ALIGN_MAX_THREADS = min(os.cpu_count() or 4, 8)
|
|
167
171
|
DEFAULT_ALIGN_CHUNK_SUBMIT = True
|
|
168
172
|
DEFAULT_ALIGN_BW_MATCHING = False
|
|
173
|
+
DEFAULT_ALIGN_DELTA_MAX = 2
|
|
169
174
|
|
|
170
175
|
BALANCE_LINEAR = "LINEAR"
|
|
171
176
|
BALANCE_GAMMA = "GAMMA"
|