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.

Files changed (43) hide show
  1. shinestacker/_version.py +1 -1
  2. shinestacker/algorithms/align.py +148 -115
  3. shinestacker/algorithms/align_auto.py +64 -0
  4. shinestacker/algorithms/align_parallel.py +296 -0
  5. shinestacker/algorithms/balance.py +14 -13
  6. shinestacker/algorithms/base_stack_algo.py +11 -2
  7. shinestacker/algorithms/multilayer.py +14 -15
  8. shinestacker/algorithms/noise_detection.py +13 -14
  9. shinestacker/algorithms/pyramid.py +4 -4
  10. shinestacker/algorithms/pyramid_auto.py +16 -10
  11. shinestacker/algorithms/pyramid_tiles.py +19 -11
  12. shinestacker/algorithms/stack.py +30 -26
  13. shinestacker/algorithms/stack_framework.py +200 -178
  14. shinestacker/algorithms/vignetting.py +16 -13
  15. shinestacker/app/main.py +7 -3
  16. shinestacker/config/constants.py +63 -26
  17. shinestacker/config/gui_constants.py +1 -1
  18. shinestacker/core/core_utils.py +4 -0
  19. shinestacker/core/framework.py +114 -33
  20. shinestacker/gui/action_config.py +57 -5
  21. shinestacker/gui/action_config_dialog.py +156 -17
  22. shinestacker/gui/base_form_dialog.py +2 -2
  23. shinestacker/gui/folder_file_selection.py +101 -0
  24. shinestacker/gui/gui_images.py +10 -10
  25. shinestacker/gui/gui_run.py +13 -11
  26. shinestacker/gui/main_window.py +10 -5
  27. shinestacker/gui/menu_manager.py +4 -0
  28. shinestacker/gui/new_project.py +171 -74
  29. shinestacker/gui/project_controller.py +13 -9
  30. shinestacker/gui/project_converter.py +4 -2
  31. shinestacker/gui/project_editor.py +72 -53
  32. shinestacker/gui/select_path_widget.py +1 -1
  33. shinestacker/gui/sys_mon.py +96 -0
  34. shinestacker/gui/tab_widget.py +3 -3
  35. shinestacker/gui/time_progress_bar.py +4 -3
  36. shinestacker/retouch/exif_data.py +1 -1
  37. shinestacker/retouch/image_editor_ui.py +2 -0
  38. {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/METADATA +6 -6
  39. {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/RECORD +43 -39
  40. {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/WHEEL +0 -0
  41. {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/entry_points.txt +0 -0
  42. {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/licenses/LICENSE +0 -0
  43. {shinestacker-1.2.0.dist-info → shinestacker-1.3.0.dist-info}/top_level.txt +0 -0
@@ -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 .select_path_widget import create_select_file_paths_widget
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
- icon_path = f"{os.path.dirname(__file__)}/ico/shinestacker.png"
47
- app_icon = QIcon(icon_path)
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
- self.add_bold_label("1️⃣ Select input folder, all images therein will be merged. ")
95
- self.form_layout.addRow("Input folder:", container)
96
- self.form_layout.addRow("Number of frames: ", self.frames_label)
97
- self.add_label("")
98
- self.add_bold_label("2️⃣ Select basic options.")
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
- self.form_layout.addRow("Automatic noise detection:", self.noise_detection)
101
- self.form_layout.addRow("Vignetting correction:", self.vignetting_correction)
102
- self.form_layout.addRow("Align layers:", self.align_frames)
103
- self.form_layout.addRow("Balance layers:", self.balance_frames)
104
- self.form_layout.addRow("Bunch stack:", self.bunch_stack)
105
- self.form_layout.addRow("Bunch frames:", self.bunch_frames)
106
- self.form_layout.addRow("Bunch overlap:", self.bunch_overlap)
107
- self.form_layout.addRow("Number of bunches: ", self.bunches_label)
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
- self.form_layout.addRow("Focus stack (pyramid):", self.focus_stack_pyramid)
110
- self.form_layout.addRow("Focus stack (depth map):", self.focus_stack_depth_map)
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
- self.form_layout.addRow("Focus stack:", self.focus_stack_pyramid)
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
- self.form_layout.addRow("Save multi layer TIFF:", self.multi_layer)
115
- self.add_label("")
116
- self.add_bold_label("3️⃣ Push 🆗 for further options, then press ▶️ to run.")
117
- self.add_label("")
118
- self.add_label("4️⃣ "
119
- "Select: <b>View</b> > <b>Expert options</b> "
120
- "to unlock advanced configuration.")
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.input_folder.text():
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
- self.n_image_files = count_image_files(self.input_folder.text())
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
- input_folder = self.input_folder.text()
156
- if not input_folder:
157
- QMessageBox.warning(self, "Input Required", "Please select an input folder")
158
- return
159
- if not os.path.exists(input_folder):
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 not os.path.isdir(input_folder):
163
- QMessageBox.warning(self, "Invalid Path", "The specified path is not a folder")
164
- return
165
- if len(input_folder.split('/')) < 2:
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
- path = self.input_folder.text()
170
- files = os.listdir(path)
171
- file_path = None
172
- for filename in files:
173
- full_path = os.path.join(path, filename)
174
- if extension_tif_jpg(full_path):
175
- file_path = full_path
176
- break
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 now in the selected path")
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.input_folder.text()
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
- job = ActionConfig(constants.ACTION_JOB,
169
- {'name': f'{input_path}-focus-stack',
170
- 'working_path': working_path,
171
- 'input_path': input_path})
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.mark_as_modified(True)
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.align import AlignFrames
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 AlignFrames(**params)
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)