shinestacker 1.2.0__py3-none-any.whl → 1.3.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/align.py +148 -115
- shinestacker/algorithms/align_auto.py +64 -0
- shinestacker/algorithms/align_parallel.py +296 -0
- shinestacker/algorithms/balance.py +14 -13
- shinestacker/algorithms/base_stack_algo.py +11 -2
- shinestacker/algorithms/multilayer.py +14 -15
- shinestacker/algorithms/noise_detection.py +13 -14
- shinestacker/algorithms/pyramid.py +4 -4
- shinestacker/algorithms/pyramid_auto.py +16 -10
- shinestacker/algorithms/pyramid_tiles.py +19 -11
- shinestacker/algorithms/stack.py +30 -26
- shinestacker/algorithms/stack_framework.py +200 -178
- shinestacker/algorithms/vignetting.py +16 -13
- shinestacker/app/main.py +7 -3
- shinestacker/config/constants.py +63 -26
- shinestacker/config/gui_constants.py +1 -1
- shinestacker/core/core_utils.py +4 -0
- shinestacker/core/framework.py +114 -33
- shinestacker/gui/action_config.py +57 -5
- shinestacker/gui/action_config_dialog.py +156 -17
- shinestacker/gui/base_form_dialog.py +2 -2
- shinestacker/gui/folder_file_selection.py +101 -0
- shinestacker/gui/gui_images.py +10 -10
- shinestacker/gui/gui_run.py +13 -11
- shinestacker/gui/main_window.py +10 -5
- shinestacker/gui/menu_manager.py +4 -0
- shinestacker/gui/new_project.py +171 -74
- shinestacker/gui/project_controller.py +13 -9
- shinestacker/gui/project_converter.py +4 -2
- shinestacker/gui/project_editor.py +72 -53
- shinestacker/gui/select_path_widget.py +1 -1
- shinestacker/gui/sys_mon.py +96 -0
- shinestacker/gui/tab_widget.py +3 -3
- shinestacker/gui/time_progress_bar.py +4 -3
- shinestacker/retouch/exif_data.py +1 -1
- shinestacker/retouch/image_editor_ui.py +2 -0
- {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/METADATA +6 -6
- {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/RECORD +43 -39
- {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/WHEEL +0 -0
- {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/top_level.txt +0 -0
shinestacker/gui/new_project.py
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
# pylint: disable=C0114, C0115, C0116, E0611, R0915, R0902
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, R0915, R0902, R0914, R0911, R0912, R0904
|
|
2
2
|
import os
|
|
3
3
|
import numpy as np
|
|
4
4
|
from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QLabel, QCheckBox, QSpinBox,
|
|
5
|
-
QMessageBox)
|
|
5
|
+
QMessageBox, QGroupBox, QVBoxLayout, QFormLayout, QSizePolicy)
|
|
6
6
|
from PySide6.QtGui import QIcon
|
|
7
7
|
from PySide6.QtCore import Qt
|
|
8
8
|
from .. config.gui_constants import gui_constants
|
|
9
9
|
from .. config.constants import constants
|
|
10
10
|
from .. algorithms.utils import read_img, extension_tif_jpg
|
|
11
11
|
from .. algorithms.stack import get_bunches
|
|
12
|
-
from .
|
|
12
|
+
from .folder_file_selection import FolderFileSelectionWidget
|
|
13
13
|
from .base_form_dialog import BaseFormDialog
|
|
14
14
|
|
|
15
15
|
DEFAULT_NO_COUNT_LABEL = " - "
|
|
@@ -17,18 +17,18 @@ DEFAULT_NO_COUNT_LABEL = " - "
|
|
|
17
17
|
|
|
18
18
|
class NewProjectDialog(BaseFormDialog):
|
|
19
19
|
def __init__(self, parent=None):
|
|
20
|
-
super().__init__("New Project", parent)
|
|
20
|
+
super().__init__("New Project", 600, parent)
|
|
21
21
|
self.create_form()
|
|
22
22
|
button_box = QHBoxLayout()
|
|
23
|
-
ok_button = QPushButton("OK")
|
|
24
|
-
ok_button.setFocus()
|
|
23
|
+
self.ok_button = QPushButton("OK")
|
|
25
24
|
cancel_button = QPushButton("Cancel")
|
|
26
|
-
button_box.addWidget(ok_button)
|
|
25
|
+
button_box.addWidget(self.ok_button)
|
|
27
26
|
button_box.addWidget(cancel_button)
|
|
28
27
|
self.add_row_to_layout(button_box)
|
|
29
|
-
ok_button.clicked.connect(self.accept)
|
|
28
|
+
self.ok_button.clicked.connect(self.accept)
|
|
30
29
|
cancel_button.clicked.connect(self.reject)
|
|
31
30
|
self.n_image_files = 0
|
|
31
|
+
self.selected_filenames = []
|
|
32
32
|
|
|
33
33
|
def expert(self):
|
|
34
34
|
return self.parent().expert_options
|
|
@@ -43,20 +43,8 @@ class NewProjectDialog(BaseFormDialog):
|
|
|
43
43
|
self.form_layout.addRow(label)
|
|
44
44
|
|
|
45
45
|
def create_form(self):
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
icon_pixmap = app_icon.pixmap(128, 128)
|
|
49
|
-
icon_label = QLabel()
|
|
50
|
-
icon_label.setPixmap(icon_pixmap)
|
|
51
|
-
icon_label.setAlignment(Qt.AlignCenter)
|
|
52
|
-
self.form_layout.addRow(icon_label)
|
|
53
|
-
spacer = QLabel("")
|
|
54
|
-
spacer.setFixedHeight(10)
|
|
55
|
-
self.form_layout.addRow(spacer)
|
|
56
|
-
|
|
57
|
-
self.input_folder, container = create_select_file_paths_widget(
|
|
58
|
-
'', 'input files folder', 'input files folder')
|
|
59
|
-
self.input_folder.textChanged.connect(self.update_bunches_label)
|
|
46
|
+
self.input_widget = FolderFileSelectionWidget()
|
|
47
|
+
self.input_widget.text_changed_connect(self.input_submitted)
|
|
60
48
|
self.noise_detection = QCheckBox()
|
|
61
49
|
self.noise_detection.setChecked(gui_constants.NEW_PROJECT_NOISE_DETECTION)
|
|
62
50
|
self.vignetting_correction = QCheckBox()
|
|
@@ -65,7 +53,6 @@ class NewProjectDialog(BaseFormDialog):
|
|
|
65
53
|
self.align_frames.setChecked(gui_constants.NEW_PROJECT_ALIGN_FRAMES)
|
|
66
54
|
self.balance_frames = QCheckBox()
|
|
67
55
|
self.balance_frames.setChecked(gui_constants.NEW_PROJECT_BALANCE_FRAMES)
|
|
68
|
-
|
|
69
56
|
self.bunch_stack = QCheckBox()
|
|
70
57
|
self.bunch_stack.setChecked(gui_constants.NEW_PROJECT_BUNCH_STACK)
|
|
71
58
|
self.bunch_frames = QSpinBox()
|
|
@@ -78,46 +65,121 @@ class NewProjectDialog(BaseFormDialog):
|
|
|
78
65
|
self.bunch_overlap.setValue(constants.DEFAULT_OVERLAP)
|
|
79
66
|
self.bunches_label = QLabel(DEFAULT_NO_COUNT_LABEL)
|
|
80
67
|
self.frames_label = QLabel(DEFAULT_NO_COUNT_LABEL)
|
|
81
|
-
|
|
82
68
|
self.update_bunch_options(gui_constants.NEW_PROJECT_BUNCH_STACK)
|
|
83
69
|
self.bunch_stack.toggled.connect(self.update_bunch_options)
|
|
84
70
|
self.bunch_frames.valueChanged.connect(self.update_bunches_label)
|
|
85
71
|
self.bunch_overlap.valueChanged.connect(self.update_bunches_label)
|
|
86
|
-
|
|
87
72
|
self.focus_stack_pyramid = QCheckBox()
|
|
88
73
|
self.focus_stack_pyramid.setChecked(gui_constants.NEW_PROJECT_FOCUS_STACK_PYRAMID)
|
|
89
74
|
self.focus_stack_depth_map = QCheckBox()
|
|
90
75
|
self.focus_stack_depth_map.setChecked(gui_constants.NEW_PROJECT_FOCUS_STACK_DEPTH_MAP)
|
|
91
76
|
self.multi_layer = QCheckBox()
|
|
92
77
|
self.multi_layer.setChecked(gui_constants.NEW_PROJECT_MULTI_LAYER)
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
78
|
+
step1_group = QGroupBox("1️⃣ Select Input")
|
|
79
|
+
step1_layout = QVBoxLayout()
|
|
80
|
+
step1_layout.setContentsMargins(15, 0, 15, 15)
|
|
81
|
+
step1_layout.addWidget(
|
|
82
|
+
QLabel("Select a folder containing "
|
|
83
|
+
"all your images, or specific image files."))
|
|
84
|
+
input_form = QFormLayout()
|
|
85
|
+
input_form.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
|
|
86
|
+
input_form.setFormAlignment(Qt.AlignLeft)
|
|
87
|
+
input_form.setLabelAlignment(Qt.AlignLeft)
|
|
88
|
+
self.input_widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
|
89
|
+
self.frames_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
|
90
|
+
input_form.addRow("Input:", self.input_widget)
|
|
91
|
+
input_form.addRow("Number of frames: ", self.frames_label)
|
|
92
|
+
step1_layout.addLayout(input_form)
|
|
93
|
+
step1_group.setLayout(step1_layout)
|
|
94
|
+
self.form_layout.addRow(step1_group)
|
|
95
|
+
step2_group = QGroupBox("2️⃣ Basic Options")
|
|
96
|
+
step2_layout = QFormLayout()
|
|
97
|
+
step2_layout.setContentsMargins(15, 0, 15, 15)
|
|
98
|
+
step2_layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
|
|
99
|
+
step2_layout.setFormAlignment(Qt.AlignLeft)
|
|
100
|
+
step2_layout.setLabelAlignment(Qt.AlignLeft)
|
|
101
|
+
for widget in [self.noise_detection, self.vignetting_correction, self.align_frames,
|
|
102
|
+
self.balance_frames, self.bunch_stack, self.bunch_frames,
|
|
103
|
+
self.bunch_overlap, self.focus_stack_pyramid,
|
|
104
|
+
self.focus_stack_depth_map, self.multi_layer]:
|
|
105
|
+
widget.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
|
99
106
|
if self.expert():
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
107
|
+
step2_layout.addRow("Automatic noise detection:", self.noise_detection)
|
|
108
|
+
step2_layout.addRow("Vignetting correction:", self.vignetting_correction)
|
|
109
|
+
step2_layout.addRow(
|
|
110
|
+
# f" {constants.ACTION_ICONS[constants.ACTION_ALIGNFRAMES]} "
|
|
111
|
+
"Align layers:", self.align_frames)
|
|
112
|
+
step2_layout.addRow(
|
|
113
|
+
# f" {constants.ACTION_ICONS[constants.ACTION_BALANCEFRAMES]} "
|
|
114
|
+
"Balance layers:", self.balance_frames)
|
|
115
|
+
step2_layout.addRow(
|
|
116
|
+
# f" {constants.ACTION_ICONS[constants.ACTION_FOCUSSTACKBUNCH]} "
|
|
117
|
+
"Bunch stack:", self.bunch_stack)
|
|
118
|
+
step2_layout.addRow("Bunch frames:", self.bunch_frames)
|
|
119
|
+
step2_layout.addRow("Bunch overlap:", self.bunch_overlap)
|
|
120
|
+
self.bunches_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
|
121
|
+
step2_layout.addRow("Number of bunches: ", self.bunches_label)
|
|
108
122
|
if self.expert():
|
|
109
|
-
|
|
110
|
-
|
|
123
|
+
step2_layout.addRow(
|
|
124
|
+
f" {constants.ACTION_ICONS[constants.ACTION_FOCUSSTACK]} "
|
|
125
|
+
"Focus stack (pyramid):", self.focus_stack_pyramid)
|
|
126
|
+
step2_layout.addRow(
|
|
127
|
+
f" {constants.ACTION_ICONS[constants.ACTION_FOCUSSTACK]} "
|
|
128
|
+
"Focus stack (depth map):", self.focus_stack_depth_map)
|
|
111
129
|
else:
|
|
112
|
-
|
|
130
|
+
step2_layout.addRow(
|
|
131
|
+
f" {constants.ACTION_ICONS[constants.ACTION_FOCUSSTACK]} "
|
|
132
|
+
"Focus stack:", self.focus_stack_pyramid)
|
|
113
133
|
if self.expert():
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
self.
|
|
119
|
-
|
|
120
|
-
|
|
134
|
+
step2_layout.addRow(
|
|
135
|
+
f" {constants.ACTION_ICONS[constants.ACTION_MULTILAYER]} "
|
|
136
|
+
"Save multi layer TIFF:", self.multi_layer)
|
|
137
|
+
step2_group.setLayout(step2_layout)
|
|
138
|
+
self.form_layout.addRow(step2_group)
|
|
139
|
+
step3_group = QGroupBox("3️⃣ Confirmation")
|
|
140
|
+
step3_layout = QVBoxLayout()
|
|
141
|
+
step3_layout.setContentsMargins(15, 0, 15, 15)
|
|
142
|
+
step3_layout.addWidget(
|
|
143
|
+
QLabel("Click 🆗 to confirm and prepare the job."))
|
|
144
|
+
step3_layout.addWidget(
|
|
145
|
+
QLabel("Select: <b>View</b> > <b>Expert options</b> for advanced configuration."))
|
|
146
|
+
step3_group.setLayout(step3_layout)
|
|
147
|
+
self.form_layout.addRow(step3_group)
|
|
148
|
+
step4_group = QGroupBox("4️⃣ Execution")
|
|
149
|
+
step4_layout = QHBoxLayout()
|
|
150
|
+
step4_layout.setContentsMargins(15, 0, 15, 15)
|
|
151
|
+
step4_layout.addWidget(QLabel("Press ▶️ to run your job."))
|
|
152
|
+
icon_path = f"{os.path.dirname(__file__)}/ico/shinestacker.png"
|
|
153
|
+
app_icon = QIcon(icon_path)
|
|
154
|
+
icon_pixmap = app_icon.pixmap(80, 80)
|
|
155
|
+
icon_label = QLabel()
|
|
156
|
+
icon_label.setPixmap(icon_pixmap)
|
|
157
|
+
icon_label.setAlignment(Qt.AlignRight)
|
|
158
|
+
step4_layout.addWidget(icon_label)
|
|
159
|
+
step4_group.setLayout(step4_layout)
|
|
160
|
+
self.form_layout.addRow(step4_group)
|
|
161
|
+
group_style = """
|
|
162
|
+
QGroupBox {
|
|
163
|
+
font-weight: bold;
|
|
164
|
+
border: 2px solid #cccccc;
|
|
165
|
+
border-radius: 5px;
|
|
166
|
+
margin-top: 10px;
|
|
167
|
+
padding-top: 15px;
|
|
168
|
+
background-color: #f8f8f8;
|
|
169
|
+
}
|
|
170
|
+
QGroupBox::title {
|
|
171
|
+
subcontrol-origin: margin;
|
|
172
|
+
left: 10px;
|
|
173
|
+
padding: 0 5px 0 5px;
|
|
174
|
+
background-color: #f8f8f8;
|
|
175
|
+
}
|
|
176
|
+
"""
|
|
177
|
+
for group in [step1_group, step2_group, step3_group, step4_group]:
|
|
178
|
+
group.setStyleSheet(group_style)
|
|
179
|
+
group.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
|
|
180
|
+
self.form_layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
|
|
181
|
+
self.form_layout.setFormAlignment(Qt.AlignLeft)
|
|
182
|
+
self.form_layout.setLabelAlignment(Qt.AlignLeft)
|
|
121
183
|
|
|
122
184
|
def update_bunch_options(self, checked):
|
|
123
185
|
self.bunch_frames.setEnabled(checked)
|
|
@@ -125,7 +187,7 @@ class NewProjectDialog(BaseFormDialog):
|
|
|
125
187
|
self.update_bunches_label()
|
|
126
188
|
|
|
127
189
|
def update_bunches_label(self):
|
|
128
|
-
if not self.
|
|
190
|
+
if not self.input_widget.get_path():
|
|
129
191
|
return
|
|
130
192
|
|
|
131
193
|
def count_image_files(path):
|
|
@@ -136,8 +198,13 @@ class NewProjectDialog(BaseFormDialog):
|
|
|
136
198
|
if extension_tif_jpg(filename):
|
|
137
199
|
count += 1
|
|
138
200
|
return count
|
|
139
|
-
|
|
140
|
-
|
|
201
|
+
if self.input_widget.get_selection_mode() == 'files' and \
|
|
202
|
+
self.input_widget.get_selected_files():
|
|
203
|
+
self.n_image_files = len(self.input_widget.get_selected_files())
|
|
204
|
+
self.selected_filenames = self.input_widget.get_selected_filenames()
|
|
205
|
+
else:
|
|
206
|
+
self.n_image_files = count_image_files(self.input_widget.get_path())
|
|
207
|
+
self.selected_filenames = []
|
|
141
208
|
if self.n_image_files == 0:
|
|
142
209
|
self.bunches_label.setText(DEFAULT_NO_COUNT_LABEL)
|
|
143
210
|
self.frames_label.setText(DEFAULT_NO_COUNT_LABEL)
|
|
@@ -147,43 +214,64 @@ class NewProjectDialog(BaseFormDialog):
|
|
|
147
214
|
bunches = get_bunches(list(range(self.n_image_files)),
|
|
148
215
|
self.bunch_frames.value(),
|
|
149
216
|
self.bunch_overlap.value())
|
|
150
|
-
self.bunches_label.setText(f"{len(bunches)}")
|
|
217
|
+
self.bunches_label.setText(f"{max(1, len(bunches))}")
|
|
151
218
|
else:
|
|
152
219
|
self.bunches_label.setText(DEFAULT_NO_COUNT_LABEL)
|
|
153
220
|
|
|
221
|
+
def input_submitted(self):
|
|
222
|
+
self.update_bunches_label()
|
|
223
|
+
self.ok_button.setFocus()
|
|
224
|
+
|
|
154
225
|
def accept(self):
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
QMessageBox.warning(self, "Invalid Path", "The specified folder does not exist")
|
|
226
|
+
input_path = self.input_widget.get_path()
|
|
227
|
+
selection_mode = self.input_widget.get_selection_mode()
|
|
228
|
+
selected_files = self.input_widget.get_selected_files()
|
|
229
|
+
if not input_path:
|
|
230
|
+
QMessageBox.warning(self, "Input Required", "Please select an input folder or files")
|
|
161
231
|
return
|
|
162
|
-
if
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
232
|
+
if selection_mode == 'files':
|
|
233
|
+
if not selected_files:
|
|
234
|
+
QMessageBox.warning(self, "Invalid Selection", "No files selected")
|
|
235
|
+
return
|
|
236
|
+
for file_path in selected_files:
|
|
237
|
+
if not os.path.exists(file_path):
|
|
238
|
+
QMessageBox.warning(self, "Invalid Path",
|
|
239
|
+
f"The file {file_path} does not exist")
|
|
240
|
+
return
|
|
241
|
+
else:
|
|
242
|
+
if not os.path.exists(input_path):
|
|
243
|
+
QMessageBox.warning(self, "Invalid Path", "The specified folder does not exist")
|
|
244
|
+
return
|
|
245
|
+
if not os.path.isdir(input_path):
|
|
246
|
+
QMessageBox.warning(self, "Invalid Path", "The specified path is not a folder")
|
|
247
|
+
return
|
|
248
|
+
parent_dir = os.path.dirname(input_path)
|
|
249
|
+
if not parent_dir:
|
|
250
|
+
parent_dir = input_path
|
|
251
|
+
if len(parent_dir.split('/')) < 2:
|
|
166
252
|
QMessageBox.warning(self, "Invalid Path", "The path must have a parent folder")
|
|
167
253
|
return
|
|
168
254
|
if self.n_image_files > 0 and not self.bunch_stack.isChecked():
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
255
|
+
if selection_mode == 'files' and selected_files:
|
|
256
|
+
file_path = selected_files[0]
|
|
257
|
+
else:
|
|
258
|
+
path = self.input_widget.get_path()
|
|
259
|
+
files = os.listdir(path)
|
|
260
|
+
file_path = None
|
|
261
|
+
for filename in files:
|
|
262
|
+
full_path = os.path.join(path, filename)
|
|
263
|
+
if extension_tif_jpg(full_path):
|
|
264
|
+
file_path = full_path
|
|
265
|
+
break
|
|
177
266
|
if file_path is None:
|
|
178
267
|
QMessageBox.warning(
|
|
179
|
-
self, "Invalid input", "Could not find images
|
|
268
|
+
self, "Invalid input", "Could not find images in the selected path")
|
|
180
269
|
return
|
|
181
270
|
img = read_img(file_path)
|
|
182
271
|
height, width = img.shape[:2]
|
|
183
272
|
n_bytes = 1 if img.dtype == np.uint8 else 2
|
|
184
273
|
n_bits = 8 if img.dtype == np.uint8 else 16
|
|
185
274
|
n_gbytes = float(n_bytes * height * width * self.n_image_files) / constants.ONE_GIGA
|
|
186
|
-
print("GBytes: ", n_gbytes)
|
|
187
275
|
if n_gbytes > 1 and not self.bunch_stack.isChecked():
|
|
188
276
|
msg = QMessageBox()
|
|
189
277
|
msg.setStyleSheet("""
|
|
@@ -218,7 +306,16 @@ class NewProjectDialog(BaseFormDialog):
|
|
|
218
306
|
super().accept()
|
|
219
307
|
|
|
220
308
|
def get_input_folder(self):
|
|
221
|
-
return self.
|
|
309
|
+
return self.input_widget.get_path()
|
|
310
|
+
|
|
311
|
+
def get_selected_files(self):
|
|
312
|
+
return self.input_widget.get_selected_files()
|
|
313
|
+
|
|
314
|
+
def get_selected_filenames(self):
|
|
315
|
+
return self.input_widget.get_selected_filenames()
|
|
316
|
+
|
|
317
|
+
def get_selection_mode(self):
|
|
318
|
+
return self.input_widget.get_selection_mode()
|
|
222
319
|
|
|
223
320
|
def get_noise_detection(self):
|
|
224
321
|
return self.noise_detection.isChecked()
|
|
@@ -29,8 +29,8 @@ class ProjectController(QObject):
|
|
|
29
29
|
def refresh_ui(self, job_row=-1, action_row=-1):
|
|
30
30
|
self.refresh_ui_requested.emit(job_row, action_row)
|
|
31
31
|
|
|
32
|
-
def mark_as_modified(self, modified=True):
|
|
33
|
-
self.project_editor.mark_as_modified(modified)
|
|
32
|
+
def mark_as_modified(self, modified=True, description=''):
|
|
33
|
+
self.project_editor.mark_as_modified(modified, description)
|
|
34
34
|
|
|
35
35
|
def modified(self):
|
|
36
36
|
return self.project_editor.modified()
|
|
@@ -143,7 +143,6 @@ class ProjectController(QObject):
|
|
|
143
143
|
return
|
|
144
144
|
os.chdir(get_app_base_path())
|
|
145
145
|
self.set_current_file_path('')
|
|
146
|
-
self.mark_as_modified(False)
|
|
147
146
|
self.update_title()
|
|
148
147
|
self.clear_job_list()
|
|
149
148
|
self.clear_action_list()
|
|
@@ -156,6 +155,7 @@ class ProjectController(QObject):
|
|
|
156
155
|
input_folder = dialog.get_input_folder().split('/')
|
|
157
156
|
working_path = '/'.join(input_folder[:-1])
|
|
158
157
|
input_path = input_folder[-1]
|
|
158
|
+
selected_filenames = dialog.get_selected_filenames()
|
|
159
159
|
if dialog.get_noise_detection():
|
|
160
160
|
job_noise = ActionConfig(
|
|
161
161
|
constants.ACTION_JOB,
|
|
@@ -165,10 +165,14 @@ class ProjectController(QObject):
|
|
|
165
165
|
{'name': f'{input_path}-detect-noise'})
|
|
166
166
|
job_noise.add_sub_action(noise_detection)
|
|
167
167
|
self.add_job_to_project(job_noise)
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
168
|
+
job_params = {
|
|
169
|
+
'name': f'{input_path}-focus-stack',
|
|
170
|
+
'working_path': working_path,
|
|
171
|
+
'input_path': input_path
|
|
172
|
+
}
|
|
173
|
+
if len(selected_filenames) > 0:
|
|
174
|
+
job_params['input_filepaths'] = selected_filenames
|
|
175
|
+
job = ActionConfig(constants.ACTION_JOB, job_params)
|
|
172
176
|
preprocess_name = ''
|
|
173
177
|
if dialog.get_noise_detection() or dialog.get_vignetting_correction() or \
|
|
174
178
|
dialog.get_align_frames() or dialog.get_balance_frames():
|
|
@@ -227,7 +231,7 @@ class ProjectController(QObject):
|
|
|
227
231
|
'input_path': constants.PATH_SEPARATOR.join(multi_input_path)})
|
|
228
232
|
job.add_sub_action(multi_layer)
|
|
229
233
|
self.add_job_to_project(job)
|
|
230
|
-
self.
|
|
234
|
+
self.project_editor.set_modified(True)
|
|
231
235
|
self.refresh_ui(0, -1)
|
|
232
236
|
|
|
233
237
|
def open_project(self, file_path=False):
|
|
@@ -369,4 +373,4 @@ class ProjectController(QObject):
|
|
|
369
373
|
dialog = self.action_config_dialog(action)
|
|
370
374
|
if dialog.exec() == QDialog.Accepted:
|
|
371
375
|
self.on_job_selected(self.current_job_index())
|
|
372
|
-
self.mark_as_modified()
|
|
376
|
+
# self.mark_as_modified(True. "Edit Action") <-- done by dialog
|
|
@@ -6,7 +6,7 @@ from .. core.exceptions import InvalidOptionError, RunStopException
|
|
|
6
6
|
from .. algorithms.stack_framework import StackJob, CombinedActions
|
|
7
7
|
from .. algorithms.noise_detection import NoiseDetection, MaskNoise
|
|
8
8
|
from .. algorithms.vignetting import Vignetting
|
|
9
|
-
from .. algorithms.
|
|
9
|
+
from .. algorithms.align_auto import AlignFramesAuto
|
|
10
10
|
from .. algorithms.balance import BalanceFrames
|
|
11
11
|
from .. algorithms.stack import FocusStack, FocusStackBunch
|
|
12
12
|
from .. algorithms.pyramid_auto import PyramidAutoStack
|
|
@@ -93,7 +93,7 @@ class ProjectConverter:
|
|
|
93
93
|
return Vignetting(**params)
|
|
94
94
|
if action_config.type_name == constants.ACTION_ALIGNFRAMES:
|
|
95
95
|
params = {k: v for k, v in action_config.params.items() if k != 'name'}
|
|
96
|
-
return
|
|
96
|
+
return AlignFramesAuto(**params)
|
|
97
97
|
if action_config.type_name == constants.ACTION_BALANCEFRAMES:
|
|
98
98
|
params = {k: v for k, v in action_config.params.items() if k != 'name'}
|
|
99
99
|
if 'intensity_interval' in params.keys():
|
|
@@ -133,7 +133,9 @@ class ProjectConverter:
|
|
|
133
133
|
enabled = action_config.params.get('enabled', True)
|
|
134
134
|
working_path = action_config.params.get('working_path', '')
|
|
135
135
|
input_path = action_config.params.get('input_path', '')
|
|
136
|
+
input_filepaths = action_config.params.get('input_filepaths', [])
|
|
136
137
|
stack_job = StackJob(name, working_path, enabled=enabled, input_path=input_path,
|
|
138
|
+
input_filepaths=input_filepaths,
|
|
137
139
|
logger_name=logger_name, callbacks=callbacks)
|
|
138
140
|
for sub in action_config.sub_actions:
|
|
139
141
|
action = self.action(sub)
|