shinestacker 1.7.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.py +9 -0
- shinestacker/algorithms/stack_framework.py +49 -25
- shinestacker/algorithms/utils.py +5 -1
- shinestacker/algorithms/vignetting.py +16 -3
- shinestacker/app/settings_dialog.py +303 -136
- shinestacker/config/constants.py +10 -5
- shinestacker/config/settings.py +29 -8
- shinestacker/core/core_utils.py +1 -0
- shinestacker/core/exceptions.py +1 -1
- shinestacker/core/framework.py +9 -4
- shinestacker/gui/action_config.py +23 -20
- shinestacker/gui/action_config_dialog.py +107 -64
- shinestacker/gui/gui_images.py +27 -3
- shinestacker/gui/gui_run.py +1 -2
- shinestacker/gui/img/dark/close-round-line-icon.png +0 -0
- shinestacker/gui/img/dark/forward-button-icon.png +0 -0
- shinestacker/gui/img/dark/play-button-round-icon.png +0 -0
- shinestacker/gui/img/dark/plus-round-line-icon.png +0 -0
- shinestacker/gui/img/dark/shinestacker_bkg.png +0 -0
- shinestacker/gui/main_window.py +20 -7
- shinestacker/gui/menu_manager.py +18 -7
- shinestacker/gui/new_project.py +18 -9
- shinestacker/gui/project_controller.py +13 -6
- shinestacker/gui/project_editor.py +12 -2
- shinestacker/gui/project_model.py +4 -4
- shinestacker/gui/tab_widget.py +16 -6
- shinestacker/retouch/adjustments.py +5 -0
- {shinestacker-1.7.0.dist-info → shinestacker-1.8.1.dist-info}/METADATA +35 -39
- {shinestacker-1.7.0.dist-info → shinestacker-1.8.1.dist-info}/RECORD +45 -40
- /shinestacker/gui/img/{close-round-line-icon.png → light/close-round-line-icon.png} +0 -0
- /shinestacker/gui/img/{forward-button-icon.png → light/forward-button-icon.png} +0 -0
- /shinestacker/gui/img/{play-button-round-icon.png → light/play-button-round-icon.png} +0 -0
- /shinestacker/gui/img/{plus-round-line-icon.png → light/plus-round-line-icon.png} +0 -0
- /shinestacker/gui/{ico → img/light}/shinestacker_bkg.png +0 -0
- {shinestacker-1.7.0.dist-info → shinestacker-1.8.1.dist-info}/WHEEL +0 -0
- {shinestacker-1.7.0.dist-info → shinestacker-1.8.1.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.7.0.dist-info → shinestacker-1.8.1.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.7.0.dist-info → shinestacker-1.8.1.dist-info}/top_level.txt +0 -0
|
@@ -1,141 +1,321 @@
|
|
|
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 .. gui.config_dialog import ConfigDialog
|
|
4
|
+
from PySide6.QtWidgets import QLabel, QCheckBox, QComboBox, QDoubleSpinBox, QSpinBox
|
|
5
5
|
from .. config.settings import Settings
|
|
6
6
|
from .. config.constants import constants
|
|
7
7
|
from .. config.gui_constants import gui_constants
|
|
8
|
+
from .. gui.config_dialog import ConfigDialog
|
|
9
|
+
from .. gui.action_config import add_tab, create_tab_widget
|
|
10
|
+
from .. gui.action_config_dialog import AlignFramesConfigBase
|
|
11
|
+
|
|
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
|
|
8
121
|
|
|
122
|
+
def get_value(self):
|
|
123
|
+
return self.widget.itemData(self.widget.currentIndex())
|
|
9
124
|
|
|
10
|
-
|
|
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
|
+
|
|
172
|
+
class SettingsDialog(ConfigDialog, AlignFramesConfigBase):
|
|
11
173
|
update_project_config_requested = Signal()
|
|
12
174
|
update_retouch_config_requested = Signal()
|
|
13
175
|
|
|
14
176
|
def __init__(self, parent=None, project_settings=True, retouch_settings=True):
|
|
177
|
+
AlignFramesConfigBase.__init__(self)
|
|
15
178
|
self.project_settings = project_settings
|
|
16
179
|
self.retouch_settings = retouch_settings
|
|
17
180
|
self.settings = Settings.instance()
|
|
18
|
-
self.
|
|
19
|
-
self.
|
|
20
|
-
self.
|
|
21
|
-
self.focus_stack_max_threads = None
|
|
22
|
-
self.view_strategy = None
|
|
23
|
-
self.min_mouse_step_brush_fraction = None
|
|
24
|
-
self.paint_refresh_time = None
|
|
25
|
-
self.display_refresh_time = None
|
|
26
|
-
self.cursor_update_time = None
|
|
181
|
+
self.project_parameters = []
|
|
182
|
+
self.retouch_parameters = []
|
|
183
|
+
self._init_parameters()
|
|
27
184
|
super().__init__("Settings", parent)
|
|
28
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
|
+
|
|
29
252
|
def create_form_content(self):
|
|
253
|
+
self.tab_widget = create_tab_widget(self.container_layout)
|
|
30
254
|
if self.project_settings:
|
|
31
|
-
self.
|
|
32
|
-
|
|
33
|
-
separator.setFrameShape(QFrame.HLine)
|
|
34
|
-
separator.setFrameShadow(QFrame.Sunken)
|
|
35
|
-
separator.setLineWidth(1)
|
|
36
|
-
self.container_layout.addRow(separator)
|
|
255
|
+
project_tab_layout = add_tab(self.tab_widget, "Project Settings")
|
|
256
|
+
self.create_project_settings(project_tab_layout)
|
|
37
257
|
if self.retouch_settings:
|
|
38
|
-
self.
|
|
258
|
+
retouch_tab_layout = add_tab(self.tab_widget, "Retouch Settings")
|
|
259
|
+
self.create_retouch_settings(retouch_tab_layout)
|
|
39
260
|
|
|
40
|
-
def create_project_settings(self):
|
|
41
|
-
|
|
261
|
+
def create_project_settings(self, layout=None):
|
|
262
|
+
if layout is None:
|
|
263
|
+
layout = self.container_layout
|
|
264
|
+
label = QLabel("Project settings:")
|
|
42
265
|
label.setStyleSheet("font-weight: bold")
|
|
43
|
-
|
|
44
|
-
self.
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
self.
|
|
49
|
-
self.
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
self.align_frames_max_threads.setValue(
|
|
57
|
-
self.settings.get('align_frames_params')['max_threads'])
|
|
58
|
-
self.container_layout.addRow("Max num. of cores, align frames:",
|
|
59
|
-
self.align_frames_max_threads)
|
|
60
|
-
|
|
61
|
-
self.focus_stack_max_threads = QSpinBox()
|
|
62
|
-
self.focus_stack_max_threads.setRange(0, 64)
|
|
63
|
-
self.focus_stack_max_threads.setValue(
|
|
64
|
-
self.settings.get('align_frames_params')['max_threads'])
|
|
65
|
-
self.container_layout.addRow("Max num. of cores, focus stacking:",
|
|
66
|
-
self.focus_stack_max_threads)
|
|
67
|
-
|
|
68
|
-
def create_retouch_settings(self):
|
|
69
|
-
label = QLabel("Retouch settings")
|
|
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)
|
|
271
|
+
self.info_label = QLabel()
|
|
272
|
+
self.info_label.setStyleSheet("color: orange; font-style: italic;")
|
|
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:")
|
|
70
279
|
label.setStyleSheet("font-weight: bold")
|
|
71
|
-
|
|
72
|
-
self.
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
self.
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
self.
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
self.settings.get('cursor_update_time'))
|
|
105
|
-
self.container_layout.addRow("Cursor refresh time:",
|
|
106
|
-
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)
|
|
107
313
|
|
|
108
314
|
def accept(self):
|
|
109
|
-
|
|
110
|
-
self.
|
|
111
|
-
|
|
112
|
-
self.
|
|
113
|
-
'combined_actions_params', {
|
|
114
|
-
'max_threads': self.combined_actions_max_threads.value()
|
|
115
|
-
})
|
|
116
|
-
self.settings.set(
|
|
117
|
-
'align_frames_params', {
|
|
118
|
-
'max_threads': self.align_frames_max_threads.value()
|
|
119
|
-
})
|
|
120
|
-
self.settings.set(
|
|
121
|
-
'focus_stack_params', {
|
|
122
|
-
'max_threads': self.focus_stack_max_threads.value()
|
|
123
|
-
})
|
|
124
|
-
self.settings.set(
|
|
125
|
-
'focus_stack_bunch:params', {
|
|
126
|
-
'max_threads': self.focus_stack_max_threads.value()
|
|
127
|
-
})
|
|
128
|
-
if self.retouch_settings:
|
|
129
|
-
self.settings.set(
|
|
130
|
-
'view_strategy', self.view_strategy.itemData(self.view_strategy.currentIndex()))
|
|
131
|
-
self.settings.set(
|
|
132
|
-
'min_mouse_step_brush_fraction', self.min_mouse_step_brush_fraction.value())
|
|
133
|
-
self.settings.set(
|
|
134
|
-
'paint_refresh_time', self.paint_refresh_time.value())
|
|
135
|
-
self.settings.set(
|
|
136
|
-
'display_refresh_time', self.display_refresh_time.value())
|
|
137
|
-
self.settings.set(
|
|
138
|
-
'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())
|
|
139
319
|
self.settings.update()
|
|
140
320
|
if self.project_settings:
|
|
141
321
|
self.update_project_config_requested.emit()
|
|
@@ -144,27 +324,14 @@ class SettingsDialog(ConfigDialog):
|
|
|
144
324
|
super().accept()
|
|
145
325
|
|
|
146
326
|
def reset_to_defaults(self):
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
self.view_strategy.setCurrentIndex(idx)
|
|
156
|
-
self.min_mouse_step_brush_fraction.setValue(
|
|
157
|
-
gui_constants.DEFAULT_MIN_MOUSE_STEP_BRUSH_FRACTION)
|
|
158
|
-
self.paint_refresh_time.setValue(
|
|
159
|
-
gui_constants.DEFAULT_PAINT_REFRESH_TIME)
|
|
160
|
-
self.display_refresh_time.setValue(
|
|
161
|
-
gui_constants.DEFAULT_DISPLAY_REFRESH_TIME)
|
|
162
|
-
self.cursor_update_time.setValue(
|
|
163
|
-
gui_constants.DEFAULT_CURSOR_UPDATE_TIME)
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
def show_settings_dialog(parent, project_settings, retouch_settings,
|
|
167
|
-
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):
|
|
168
335
|
dialog = SettingsDialog(parent, project_settings, retouch_settings)
|
|
169
336
|
dialog.update_project_config_requested.connect(handle_project_config)
|
|
170
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"
|
shinestacker/config/settings.py
CHANGED
|
@@ -39,20 +39,26 @@ DEFAULT_SETTINGS = {
|
|
|
39
39
|
'cursor_update_time': gui_constants.DEFAULT_CURSOR_UPDATE_TIME,
|
|
40
40
|
'min_mouse_step_brush_fraction': gui_constants.DEFAULT_MIN_MOUSE_STEP_BRUSH_FRACTION,
|
|
41
41
|
'combined_actions_params': {
|
|
42
|
-
'max_threads': constants.
|
|
42
|
+
'max_threads': constants.DEFAULT_FWK_MAX_THREADS
|
|
43
43
|
},
|
|
44
44
|
'align_frames_params': {
|
|
45
|
-
'
|
|
45
|
+
'memory_limit': constants.DEFAULT_ALIGN_MEMORY_LIMIT_GB,
|
|
46
|
+
'max_threads': constants.DEFAULT_ALIGN_MAX_THREADS,
|
|
47
|
+
'detector': constants.DEFAULT_DETECTOR,
|
|
48
|
+
'descriptor': constants.DEFAULT_DESCRIPTOR,
|
|
49
|
+
'match_method': constants.DEFAULT_MATCHING_METHOD
|
|
46
50
|
},
|
|
47
51
|
'focus_stack_params': {
|
|
52
|
+
'memory_limit': constants.DEFAULT_PY_MEMORY_LIMIT_GB,
|
|
48
53
|
'max_threads': constants.DEFAULT_PY_MAX_THREADS
|
|
49
54
|
},
|
|
50
55
|
'focus_stack_bunch_params': {
|
|
56
|
+
'memory_limit': constants.DEFAULT_ALIGN_MEMORY_LIMIT_GB,
|
|
51
57
|
'max_threads': constants.DEFAULT_PY_MAX_THREADS
|
|
52
58
|
}
|
|
53
59
|
}
|
|
54
60
|
|
|
55
|
-
|
|
61
|
+
CURRENT_SETTINGS_FILE_VERSION = 1
|
|
56
62
|
|
|
57
63
|
|
|
58
64
|
class Settings(StdPathFile):
|
|
@@ -63,18 +69,30 @@ class Settings(StdPathFile):
|
|
|
63
69
|
if Settings._instance is not None:
|
|
64
70
|
raise RuntimeError("Settings is a singleton.")
|
|
65
71
|
super().__init__(filename)
|
|
66
|
-
self.settings =
|
|
72
|
+
self.settings = self._deep_copy_defaults()
|
|
67
73
|
file_path = self.get_file_path()
|
|
68
74
|
if os.path.isfile(file_path):
|
|
69
75
|
try:
|
|
70
76
|
with open(file_path, 'r', encoding="utf-8") as file:
|
|
71
77
|
json_data = json.load(file)
|
|
72
|
-
|
|
78
|
+
file_settings = json_data['settings']
|
|
79
|
+
self._deep_merge_settings(file_settings)
|
|
73
80
|
except Exception as e:
|
|
74
81
|
traceback.print_tb(e.__traceback__)
|
|
75
82
|
print(f"Can't read file from path {file_path}. Default settings ignored.")
|
|
76
|
-
|
|
77
|
-
|
|
83
|
+
|
|
84
|
+
def _deep_copy_defaults(self):
|
|
85
|
+
return json.loads(json.dumps(DEFAULT_SETTINGS))
|
|
86
|
+
|
|
87
|
+
def _deep_merge_settings(self, file_settings):
|
|
88
|
+
for key, value in file_settings.items():
|
|
89
|
+
if key in self.settings:
|
|
90
|
+
if isinstance(value, dict) and isinstance(self.settings[key], dict):
|
|
91
|
+
for sub_key, sub_value in value.items():
|
|
92
|
+
if sub_key in self.settings[key]:
|
|
93
|
+
self.settings[key][sub_key] = sub_value
|
|
94
|
+
else:
|
|
95
|
+
self.settings[key] = value
|
|
78
96
|
|
|
79
97
|
@classmethod
|
|
80
98
|
def instance(cls, filename="shinestacker-settings.txt"):
|
|
@@ -96,7 +114,10 @@ class Settings(StdPathFile):
|
|
|
96
114
|
try:
|
|
97
115
|
config_dir = self.get_config_dir()
|
|
98
116
|
os.makedirs(config_dir, exist_ok=True)
|
|
99
|
-
json_data = {
|
|
117
|
+
json_data = {
|
|
118
|
+
'version': CURRENT_SETTINGS_FILE_VERSION,
|
|
119
|
+
'settings': self.settings
|
|
120
|
+
}
|
|
100
121
|
json_obj = jsonpickle.encode(json_data)
|
|
101
122
|
with open(self.get_file_path(), 'w', encoding="utf-8") as f:
|
|
102
123
|
f.write(json_obj)
|
shinestacker/core/core_utils.py
CHANGED
shinestacker/core/exceptions.py
CHANGED
|
@@ -30,7 +30,7 @@ class AlignmentError(FocusStackError):
|
|
|
30
30
|
def __init__(self, index, details):
|
|
31
31
|
self.index = index
|
|
32
32
|
self.details = details
|
|
33
|
-
super().__init__(f"Alignment failed for
|
|
33
|
+
super().__init__(f"Alignment failed for frame {index}: {details}")
|
|
34
34
|
|
|
35
35
|
|
|
36
36
|
class BitDepthError(FocusStackError):
|
shinestacker/core/framework.py
CHANGED
|
@@ -146,6 +146,9 @@ class TaskBase:
|
|
|
146
146
|
def sub_message_r(self, msg='', level=logging.INFO):
|
|
147
147
|
self.sub_message(msg, level, self.end_r, self.begin_r, False)
|
|
148
148
|
|
|
149
|
+
def end_job(self):
|
|
150
|
+
pass
|
|
151
|
+
|
|
149
152
|
|
|
150
153
|
class Job(TaskBase):
|
|
151
154
|
def __init__(self, name, logger_name=None, log_file='', callbacks=None, **kwargs):
|
|
@@ -188,12 +191,14 @@ class Job(TaskBase):
|
|
|
188
191
|
self.id, self.name) is False:
|
|
189
192
|
raise RunStopException(self.name)
|
|
190
193
|
a.run()
|
|
194
|
+
for a in self.__actions:
|
|
195
|
+
a.end_job()
|
|
191
196
|
|
|
192
197
|
|
|
193
198
|
class SequentialTask(TaskBase):
|
|
194
199
|
def __init__(self, name, enabled=True, **kwargs):
|
|
195
|
-
self.max_threads = kwargs.pop('max_threads', constants.
|
|
196
|
-
self.chunk_submit = kwargs.pop('chunk_submit', constants.
|
|
200
|
+
self.max_threads = kwargs.pop('max_threads', constants.DEFAULT_FWK_MAX_THREADS)
|
|
201
|
+
self.chunk_submit = kwargs.pop('chunk_submit', constants.DEFAULT_FWK_CHUNK_SUBMIT)
|
|
197
202
|
TaskBase.__init__(self, name, enabled, **kwargs)
|
|
198
203
|
self.total_action_counts = None
|
|
199
204
|
self.current_action_count = None
|
|
@@ -261,11 +266,11 @@ class SequentialTask(TaskBase):
|
|
|
261
266
|
try:
|
|
262
267
|
result = future.result()
|
|
263
268
|
if result:
|
|
264
|
-
self.
|
|
269
|
+
self.print_message_r(color_str(
|
|
265
270
|
f"completed processing step: {self.idx_tot_str(idx)}",
|
|
266
271
|
constants.LOG_COLOR_LEVEL_1))
|
|
267
272
|
else:
|
|
268
|
-
self.
|
|
273
|
+
self.print_message_r(color_str(
|
|
269
274
|
f"failed processing step: {self.idx_tot_str(idx)}",
|
|
270
275
|
constants.LOG_COLOR_WARNING))
|
|
271
276
|
self.current_action_count += 1
|