shinestacker 0.3.2__py3-none-any.whl → 0.3.4__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/__init__.py +2 -1
- shinestacker/_version.py +1 -1
- shinestacker/algorithms/__init__.py +3 -2
- shinestacker/algorithms/align.py +102 -64
- shinestacker/algorithms/balance.py +89 -42
- shinestacker/algorithms/base_stack_algo.py +42 -0
- shinestacker/algorithms/core_utils.py +6 -6
- shinestacker/algorithms/denoise.py +4 -1
- shinestacker/algorithms/depth_map.py +28 -39
- shinestacker/algorithms/exif.py +43 -38
- shinestacker/algorithms/multilayer.py +48 -28
- shinestacker/algorithms/noise_detection.py +34 -23
- shinestacker/algorithms/pyramid.py +42 -42
- shinestacker/algorithms/sharpen.py +1 -0
- shinestacker/algorithms/stack.py +42 -41
- shinestacker/algorithms/stack_framework.py +111 -65
- shinestacker/algorithms/utils.py +12 -11
- shinestacker/algorithms/vignetting.py +48 -22
- shinestacker/algorithms/white_balance.py +1 -0
- shinestacker/app/about_dialog.py +6 -2
- shinestacker/app/app_config.py +1 -0
- shinestacker/app/gui_utils.py +20 -0
- shinestacker/app/help_menu.py +1 -0
- shinestacker/app/main.py +9 -18
- shinestacker/app/open_frames.py +5 -4
- shinestacker/app/project.py +5 -16
- shinestacker/app/retouch.py +5 -17
- shinestacker/core/colors.py +4 -4
- shinestacker/core/core_utils.py +1 -1
- shinestacker/core/exceptions.py +2 -1
- shinestacker/core/framework.py +46 -33
- shinestacker/core/logging.py +9 -10
- shinestacker/gui/action_config.py +253 -197
- shinestacker/gui/actions_window.py +32 -28
- shinestacker/gui/colors.py +1 -0
- shinestacker/gui/gui_images.py +7 -3
- shinestacker/gui/gui_logging.py +3 -2
- shinestacker/gui/gui_run.py +53 -38
- shinestacker/gui/main_window.py +69 -25
- shinestacker/gui/new_project.py +35 -2
- shinestacker/gui/project_converter.py +21 -20
- shinestacker/gui/project_editor.py +45 -52
- shinestacker/gui/project_model.py +15 -23
- shinestacker/retouch/{filter_base.py → base_filter.py} +7 -4
- shinestacker/retouch/brush.py +1 -0
- shinestacker/retouch/brush_gradient.py +17 -3
- shinestacker/retouch/brush_preview.py +14 -10
- shinestacker/retouch/brush_tool.py +28 -19
- shinestacker/retouch/denoise_filter.py +3 -2
- shinestacker/retouch/display_manager.py +11 -5
- shinestacker/retouch/exif_data.py +1 -0
- shinestacker/retouch/file_loader.py +13 -9
- shinestacker/retouch/filter_manager.py +1 -0
- shinestacker/retouch/image_editor.py +14 -48
- shinestacker/retouch/image_editor_ui.py +10 -5
- shinestacker/retouch/image_filters.py +4 -2
- shinestacker/retouch/image_viewer.py +33 -31
- shinestacker/retouch/io_gui_handler.py +25 -13
- shinestacker/retouch/io_manager.py +3 -2
- shinestacker/retouch/layer_collection.py +79 -23
- shinestacker/retouch/shortcuts_help.py +1 -0
- shinestacker/retouch/undo_manager.py +7 -0
- shinestacker/retouch/unsharp_mask_filter.py +3 -2
- shinestacker/retouch/white_balance_filter.py +11 -6
- {shinestacker-0.3.2.dist-info → shinestacker-0.3.4.dist-info}/METADATA +10 -4
- shinestacker-0.3.4.dist-info/RECORD +86 -0
- shinestacker-0.3.2.dist-info/RECORD +0 -85
- {shinestacker-0.3.2.dist-info → shinestacker-0.3.4.dist-info}/WHEEL +0 -0
- {shinestacker-0.3.2.dist-info → shinestacker-0.3.4.dist-info}/entry_points.txt +0 -0
- {shinestacker-0.3.2.dist-info → shinestacker-0.3.4.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-0.3.2.dist-info → shinestacker-0.3.4.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, R0914, R0912, R0915, W0718
|
|
1
2
|
import os.path
|
|
2
3
|
import os
|
|
4
|
+
import traceback
|
|
3
5
|
import json
|
|
4
6
|
import jsonpickle
|
|
5
|
-
import traceback
|
|
6
7
|
from PySide6.QtWidgets import QMessageBox, QFileDialog, QDialog
|
|
7
8
|
from .. core.core_utils import get_app_base_path
|
|
8
9
|
from .. config.constants import constants
|
|
@@ -31,7 +32,7 @@ class ActionsWindow(ProjectEditor):
|
|
|
31
32
|
|
|
32
33
|
def mark_as_modified(self):
|
|
33
34
|
self._modified_project = True
|
|
34
|
-
self.
|
|
35
|
+
self.project_buffer.append(self.project.clone())
|
|
35
36
|
self.update_title()
|
|
36
37
|
|
|
37
38
|
def close_project(self):
|
|
@@ -123,19 +124,21 @@ class ActionsWindow(ProjectEditor):
|
|
|
123
124
|
if not self._check_unsaved_changes():
|
|
124
125
|
return
|
|
125
126
|
if file_path is False:
|
|
126
|
-
file_path, _ = QFileDialog.getOpenFileName(
|
|
127
|
+
file_path, _ = QFileDialog.getOpenFileName(
|
|
128
|
+
self, "Open Project", "", "Project Files (*.fsp);;All Files (*)")
|
|
127
129
|
if file_path:
|
|
128
130
|
try:
|
|
129
131
|
self._current_file = file_path
|
|
130
|
-
self._current_file_wd = '' if os.path.isabs(file_path)
|
|
132
|
+
self._current_file_wd = '' if os.path.isabs(file_path) \
|
|
133
|
+
else os.path.dirname(file_path)
|
|
131
134
|
if not os.path.isabs(self._current_file_wd):
|
|
132
135
|
self._current_file_wd = os.path.abspath(self._current_file_wd)
|
|
133
136
|
self._current_file = os.path.basename(self._current_file)
|
|
134
|
-
|
|
137
|
+
with open(file_path, 'r', encoding="utf-8") as file:
|
|
138
|
+
json_obj = json.load(file)
|
|
135
139
|
pp = file_path.split('/')
|
|
136
140
|
if len(pp) > 1:
|
|
137
141
|
os.chdir('/'.join(pp[:-1]))
|
|
138
|
-
json_obj = json.load(file)
|
|
139
142
|
project = Project.from_dict(json_obj['project'])
|
|
140
143
|
if project is None:
|
|
141
144
|
raise RuntimeError(f"Project from file {file_path} produced a null project.")
|
|
@@ -155,21 +158,23 @@ class ActionsWindow(ProjectEditor):
|
|
|
155
158
|
if 'working_path' in job.params.keys():
|
|
156
159
|
working_path = job.params['working_path']
|
|
157
160
|
if not os.path.isdir(working_path):
|
|
158
|
-
QMessageBox.warning(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
161
|
+
QMessageBox.warning(
|
|
162
|
+
self, "Working path not found",
|
|
163
|
+
f'''The working path specified in the project file for the job:
|
|
164
|
+
"{job.params['name']}"
|
|
165
|
+
was not found.\n
|
|
166
|
+
Please, select a valid working path.''')
|
|
163
167
|
self.edit_action(job)
|
|
164
168
|
for action in job.sub_actions:
|
|
165
169
|
if 'working_path' in job.params.keys():
|
|
166
170
|
working_path = job.params['working_path']
|
|
167
171
|
if working_path != '' and not os.path.isdir(working_path):
|
|
168
|
-
QMessageBox.warning(
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
172
|
+
QMessageBox.warning(
|
|
173
|
+
self, "Working path not found",
|
|
174
|
+
f'''The working path specified in the project file for the job:
|
|
175
|
+
"{job.params['name']}"
|
|
176
|
+
was not found.\n
|
|
177
|
+
Please, select a valid working path.''')
|
|
173
178
|
self.edit_action(action)
|
|
174
179
|
|
|
175
180
|
def current_file_name(self):
|
|
@@ -182,7 +187,8 @@ class ActionsWindow(ProjectEditor):
|
|
|
182
187
|
self.save_project_as()
|
|
183
188
|
|
|
184
189
|
def save_project_as(self):
|
|
185
|
-
file_path, _ = QFileDialog.getSaveFileName(
|
|
190
|
+
file_path, _ = QFileDialog.getSaveFileName(
|
|
191
|
+
self, "Save Project As", "", "Project Files (*.fsp);;All Files (*)")
|
|
186
192
|
if file_path:
|
|
187
193
|
if not file_path.endswith('.fsp'):
|
|
188
194
|
file_path += '.fsp'
|
|
@@ -198,9 +204,10 @@ class ActionsWindow(ProjectEditor):
|
|
|
198
204
|
'project': self.project.to_dict(),
|
|
199
205
|
'version': 1
|
|
200
206
|
})
|
|
201
|
-
path = f"{self._current_file_wd}/{file_path}"
|
|
202
|
-
|
|
203
|
-
|
|
207
|
+
path = f"{self._current_file_wd}/{file_path}" \
|
|
208
|
+
if self._current_file_wd != '' else file_path
|
|
209
|
+
with open(path, 'w', encoding="utf-8") as f:
|
|
210
|
+
f.write(json_obj)
|
|
204
211
|
self._modified_project = False
|
|
205
212
|
except Exception as e:
|
|
206
213
|
QMessageBox.critical(self, "Error", f"Cannot save file:\n{str(e)}")
|
|
@@ -215,12 +222,8 @@ class ActionsWindow(ProjectEditor):
|
|
|
215
222
|
if reply == QMessageBox.Save:
|
|
216
223
|
self.save_project()
|
|
217
224
|
return True
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
else:
|
|
221
|
-
return False
|
|
222
|
-
else:
|
|
223
|
-
return True
|
|
225
|
+
return reply == QMessageBox.Discard
|
|
226
|
+
return True
|
|
224
227
|
|
|
225
228
|
def on_job_edit(self, item):
|
|
226
229
|
index = self.job_list.row(item)
|
|
@@ -257,7 +260,8 @@ class ActionsWindow(ProjectEditor):
|
|
|
257
260
|
break
|
|
258
261
|
if current_action:
|
|
259
262
|
if not is_sub_action:
|
|
260
|
-
self.set_enabled_sub_actions_gui(
|
|
263
|
+
self.set_enabled_sub_actions_gui(
|
|
264
|
+
current_action.type_name == constants.ACTION_COMBO)
|
|
261
265
|
dialog = ActionConfigDialog(current_action, self._current_file_wd, self)
|
|
262
266
|
if dialog.exec() == QDialog.Accepted:
|
|
263
267
|
self.on_job_selected(job_index)
|
|
@@ -273,7 +277,7 @@ class ActionsWindow(ProjectEditor):
|
|
|
273
277
|
if self.job_list.hasFocus():
|
|
274
278
|
current_action = job
|
|
275
279
|
elif self.action_list.hasFocus():
|
|
276
|
-
job_row,
|
|
280
|
+
job_row, _action_row, pos = self.get_current_action()
|
|
277
281
|
if pos.actions is not None:
|
|
278
282
|
current_action = pos.action if not pos.is_sub_action else pos.sub_action
|
|
279
283
|
if current_action is not None:
|
shinestacker/gui/colors.py
CHANGED
shinestacker/gui/gui_images.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, W0718, E1101, C0103
|
|
1
2
|
import webbrowser
|
|
2
3
|
import subprocess
|
|
3
4
|
import os
|
|
@@ -75,7 +76,8 @@ class GuiImageView(QWidget):
|
|
|
75
76
|
self.setLayout(self.layout)
|
|
76
77
|
pixmap = QPixmap(file_path)
|
|
77
78
|
if pixmap:
|
|
78
|
-
scaled_pixmap = pixmap.scaledToWidth(
|
|
79
|
+
scaled_pixmap = pixmap.scaledToWidth(
|
|
80
|
+
gui_constants.GUI_IMG_WIDTH, Qt.SmoothTransformation)
|
|
79
81
|
self.image_label.setPixmap(scaled_pixmap)
|
|
80
82
|
else:
|
|
81
83
|
raise RuntimeError(f"Can't load file: {file_path}.")
|
|
@@ -112,7 +114,8 @@ class GuiOpenApp(QWidget):
|
|
|
112
114
|
self.setLayout(self.layout)
|
|
113
115
|
pixmap = QPixmap(file_path)
|
|
114
116
|
if pixmap:
|
|
115
|
-
scaled_pixmap = pixmap.scaledToWidth(
|
|
117
|
+
scaled_pixmap = pixmap.scaledToWidth(
|
|
118
|
+
gui_constants.GUI_IMG_WIDTH, Qt.SmoothTransformation)
|
|
116
119
|
self.image_label.setPixmap(scaled_pixmap)
|
|
117
120
|
else:
|
|
118
121
|
raise RuntimeError(f"Can't load file: {file_path}.")
|
|
@@ -134,7 +137,8 @@ class GuiOpenApp(QWidget):
|
|
|
134
137
|
try:
|
|
135
138
|
os.system(f"{self.app} -f {self.file_path} &")
|
|
136
139
|
except Exception as e:
|
|
137
|
-
raise RuntimeError(
|
|
140
|
+
raise RuntimeError(
|
|
141
|
+
f"Can't open file {self.file_path} with app: {self.app}.\n{str(e)}") from e
|
|
138
142
|
else:
|
|
139
143
|
app = None
|
|
140
144
|
stacked_widget = self.parent().window().findChild(QStackedWidget)
|
shinestacker/gui/gui_logging.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, W0212, R0903
|
|
1
2
|
import logging
|
|
2
3
|
from PySide6.QtWidgets import QWidget, QTextEdit, QMessageBox, QStatusBar
|
|
3
4
|
from PySide6.QtGui import QTextCursor, QTextOption, QFont
|
|
@@ -87,8 +88,8 @@ class SimpleHtmlHandler(QObject, logging.Handler):
|
|
|
87
88
|
try:
|
|
88
89
|
msg = self.format(record)
|
|
89
90
|
self.html_signal.emit(msg)
|
|
90
|
-
except
|
|
91
|
-
logging.error(f"Logging error: {e}")
|
|
91
|
+
except RuntimeError as e:
|
|
92
|
+
logging.error(msg=f"Logging error: {e}")
|
|
92
93
|
|
|
93
94
|
|
|
94
95
|
class GuiLogger(QWidget):
|
shinestacker/gui/gui_run.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, R0903, R0915, R0914, R0917, R0913, R0902
|
|
1
2
|
import time
|
|
2
3
|
from PySide6.QtWidgets import (QWidget, QPushButton, QVBoxLayout, QHBoxLayout, QProgressBar,
|
|
3
4
|
QMessageBox, QScrollArea, QSizePolicy, QFrame, QLabel, QComboBox)
|
|
@@ -50,6 +51,7 @@ class TimerProgressBar(QProgressBar):
|
|
|
50
51
|
super().setValue(0)
|
|
51
52
|
self.set_running_style()
|
|
52
53
|
self._start_time = -1
|
|
54
|
+
self._current_time = -1
|
|
53
55
|
|
|
54
56
|
def set_style(self, bar_color=None):
|
|
55
57
|
if bar_color is None:
|
|
@@ -77,11 +79,11 @@ class TimerProgressBar(QProgressBar):
|
|
|
77
79
|
m = (ss % 3600) // 60
|
|
78
80
|
s = (ss % 3600) % 60
|
|
79
81
|
x = secs - ss
|
|
80
|
-
t_str = "{:02d}"
|
|
82
|
+
t_str = f"{s:02d}" + f"{x:.1f}s".lstrip('0')
|
|
81
83
|
if m > 0:
|
|
82
|
-
t_str = "{:02d}:{}"
|
|
84
|
+
t_str = f"{m:02d}:{t_str}"
|
|
83
85
|
if h > 0:
|
|
84
|
-
t_str = "{:02d}:{}"
|
|
86
|
+
t_str = f"{h:02d}:{t_str}"
|
|
85
87
|
if m > 0 or h > 0:
|
|
86
88
|
t_str = t_str.lstrip('0')
|
|
87
89
|
elif 0 < s < 10:
|
|
@@ -114,9 +116,11 @@ class TimerProgressBar(QProgressBar):
|
|
|
114
116
|
self.check_time(self.maximum())
|
|
115
117
|
self.setValue(self.maximum())
|
|
116
118
|
|
|
119
|
+
# pylint: disable=C0103
|
|
117
120
|
def setValue(self, val):
|
|
118
121
|
self.check_time(val)
|
|
119
122
|
super().setValue(val)
|
|
123
|
+
# pylint: enable=C0103
|
|
120
124
|
|
|
121
125
|
def set_running_style(self):
|
|
122
126
|
self.set_style(action_running_color)
|
|
@@ -192,7 +196,9 @@ class RunWindow(QTextEditLogger):
|
|
|
192
196
|
self.retouch_widget.setStyleSheet(BLUE_COMBO_STYLE)
|
|
193
197
|
self.retouch_widget.addItems(options)
|
|
194
198
|
self.retouch_widget.setEnabled(False)
|
|
195
|
-
self.retouch_widget.currentIndexChanged.connect(
|
|
199
|
+
self.retouch_widget.currentIndexChanged.connect(
|
|
200
|
+
lambda: self.retouch(
|
|
201
|
+
self.retouch_paths[self.retouch_widget.currentIndex() - 1]))
|
|
196
202
|
self.status_bar.addPermanentWidget(self.retouch_widget)
|
|
197
203
|
|
|
198
204
|
self.stop_button = QPushButton("Stop")
|
|
@@ -238,40 +244,40 @@ class RunWindow(QTextEditLogger):
|
|
|
238
244
|
self.close_window_callback(self.id_str())
|
|
239
245
|
|
|
240
246
|
@Slot(int, str)
|
|
241
|
-
def handle_before_action(self,
|
|
242
|
-
if 0 <=
|
|
243
|
-
self.color_widgets[self.row_widget_id][
|
|
247
|
+
def handle_before_action(self, run_id, _name):
|
|
248
|
+
if 0 <= run_id < len(self.color_widgets[self.row_widget_id]):
|
|
249
|
+
self.color_widgets[self.row_widget_id][run_id].set_color(*action_running_color.tuple())
|
|
244
250
|
self.progress_bar.start(1)
|
|
245
|
-
if
|
|
251
|
+
if run_id == -1:
|
|
246
252
|
self.progress_bar.set_running_style()
|
|
247
253
|
|
|
248
254
|
@Slot(int, str)
|
|
249
|
-
def handle_after_action(self,
|
|
250
|
-
if 0 <=
|
|
251
|
-
self.color_widgets[self.row_widget_id][
|
|
255
|
+
def handle_after_action(self, run_id, _name):
|
|
256
|
+
if 0 <= run_id < len(self.color_widgets[self.row_widget_id]):
|
|
257
|
+
self.color_widgets[self.row_widget_id][run_id].set_color(*action_done_color.tuple())
|
|
252
258
|
self.progress_bar.stop()
|
|
253
|
-
if
|
|
259
|
+
if run_id == -1:
|
|
254
260
|
self.row_widget_id += 1
|
|
255
261
|
self.progress_bar.set_done_style()
|
|
256
262
|
|
|
257
263
|
@Slot(int, str, str)
|
|
258
|
-
def handle_step_counts(self,
|
|
264
|
+
def handle_step_counts(self, _run_id, _name, steps):
|
|
259
265
|
self.progress_bar.start(steps)
|
|
260
266
|
|
|
261
267
|
@Slot(int, str)
|
|
262
|
-
def handle_begin_steps(self,
|
|
268
|
+
def handle_begin_steps(self, _run_id, _name):
|
|
263
269
|
self.progress_bar.start(1)
|
|
264
270
|
|
|
265
271
|
@Slot(int, str)
|
|
266
|
-
def handle_end_steps(self,
|
|
272
|
+
def handle_end_steps(self, _run_id, _name):
|
|
267
273
|
self.progress_bar.stop()
|
|
268
274
|
|
|
269
275
|
@Slot(int, str, str)
|
|
270
|
-
def handle_after_step(self,
|
|
276
|
+
def handle_after_step(self, _run_id, _name, step):
|
|
271
277
|
self.progress_bar.setValue(step)
|
|
272
278
|
|
|
273
279
|
@Slot(int, str, str)
|
|
274
|
-
def handle_save_plot(self,
|
|
280
|
+
def handle_save_plot(self, _run_id, name, path):
|
|
275
281
|
label = QLabel(name, self)
|
|
276
282
|
label.setStyleSheet("QLabel {margin-top: 5px; font-weight: bold;}")
|
|
277
283
|
self.image_layout.addWidget(label)
|
|
@@ -290,10 +296,12 @@ class RunWindow(QTextEditLogger):
|
|
|
290
296
|
self.image_area_widget.setFixedWidth(needed_width)
|
|
291
297
|
self.right_area.updateGeometry()
|
|
292
298
|
self.image_area_widget.updateGeometry()
|
|
293
|
-
QTimer.singleShot(
|
|
299
|
+
QTimer.singleShot(
|
|
300
|
+
0, lambda: self.right_area.verticalScrollBar().setValue(
|
|
301
|
+
self.right_area.verticalScrollBar().maximum()))
|
|
294
302
|
|
|
295
303
|
@Slot(int, str, str, str)
|
|
296
|
-
def handle_open_app(self,
|
|
304
|
+
def handle_open_app(self, _run_id, name, app, path):
|
|
297
305
|
label = QLabel(name, self)
|
|
298
306
|
label.setStyleSheet("QLabel {margin-top: 5px; font-weight: bold;}")
|
|
299
307
|
self.image_layout.addWidget(label)
|
|
@@ -306,7 +314,9 @@ class RunWindow(QTextEditLogger):
|
|
|
306
314
|
self.image_area_widget.setFixedWidth(needed_width)
|
|
307
315
|
self.right_area.updateGeometry()
|
|
308
316
|
self.image_area_widget.updateGeometry()
|
|
309
|
-
QTimer.singleShot(
|
|
317
|
+
QTimer.singleShot(
|
|
318
|
+
0, lambda: self.right_area.verticalScrollBar().setValue(
|
|
319
|
+
self.right_area.verticalScrollBar().maximum()))
|
|
310
320
|
|
|
311
321
|
|
|
312
322
|
class RunWorker(LogWorker):
|
|
@@ -336,40 +346,41 @@ class RunWorker(LogWorker):
|
|
|
336
346
|
}
|
|
337
347
|
self.tag = ""
|
|
338
348
|
|
|
339
|
-
def before_action(self,
|
|
340
|
-
self.before_action_signal.emit(
|
|
349
|
+
def before_action(self, run_id, name):
|
|
350
|
+
self.before_action_signal.emit(run_id, name)
|
|
341
351
|
|
|
342
|
-
def after_action(self,
|
|
343
|
-
self.after_action_signal.emit(
|
|
352
|
+
def after_action(self, run_id, name):
|
|
353
|
+
self.after_action_signal.emit(run_id, name)
|
|
344
354
|
|
|
345
|
-
def step_counts(self,
|
|
346
|
-
self.step_counts_signal.emit(
|
|
355
|
+
def step_counts(self, run_id, name, steps):
|
|
356
|
+
self.step_counts_signal.emit(run_id, name, steps)
|
|
347
357
|
|
|
348
|
-
def begin_steps(self,
|
|
349
|
-
self.begin_steps_signal.emit(
|
|
358
|
+
def begin_steps(self, run_id, name):
|
|
359
|
+
self.begin_steps_signal.emit(run_id, name)
|
|
350
360
|
|
|
351
|
-
def end_steps(self,
|
|
352
|
-
self.end_steps_signal.emit(
|
|
361
|
+
def end_steps(self, run_id, name):
|
|
362
|
+
self.end_steps_signal.emit(run_id, name)
|
|
353
363
|
|
|
354
|
-
def after_step(self,
|
|
355
|
-
self.after_step_signal.emit(
|
|
364
|
+
def after_step(self, run_id, name, step):
|
|
365
|
+
self.after_step_signal.emit(run_id, name, step)
|
|
356
366
|
|
|
357
|
-
def save_plot(self,
|
|
358
|
-
self.save_plot_signal.emit(
|
|
367
|
+
def save_plot(self, run_id, name, path):
|
|
368
|
+
self.save_plot_signal.emit(run_id, name, path)
|
|
359
369
|
|
|
360
|
-
def open_app(self,
|
|
361
|
-
self.open_app_signal.emit(
|
|
370
|
+
def open_app(self, run_id, name, app, path):
|
|
371
|
+
self.open_app_signal.emit(run_id, name, app, path)
|
|
362
372
|
|
|
363
|
-
def check_running(self,
|
|
373
|
+
def check_running(self, _run_id, _name):
|
|
364
374
|
return self.status == constants.STATUS_RUNNING
|
|
365
375
|
|
|
366
376
|
def run(self):
|
|
377
|
+
# pylint: disable=line-too-long
|
|
367
378
|
self.status_signal.emit(f"{self.tag} running...", constants.RUN_ONGOING, "", 0)
|
|
368
379
|
self.html_signal.emit(f'''
|
|
369
380
|
<div style="margin: 2px 0; font-family: {constants.LOG_FONTS_STR};">
|
|
370
381
|
<span style="color: #{ColorPalette.DARK_BLUE.hex()}; font-style: italic; font-weigt: bold;">{self.tag} begins</span>
|
|
371
382
|
</div>
|
|
372
|
-
''')
|
|
383
|
+
''') # noqa
|
|
373
384
|
status, error_message = self.do_run()
|
|
374
385
|
if status == constants.RUN_FAILED:
|
|
375
386
|
message = f"{self.tag} failed"
|
|
@@ -380,11 +391,15 @@ class RunWorker(LogWorker):
|
|
|
380
391
|
elif status == constants.RUN_STOPPED:
|
|
381
392
|
message = f"{self.tag} stopped"
|
|
382
393
|
color = "#" + ColorPalette.DARK_RED.hex()
|
|
394
|
+
else:
|
|
395
|
+
message = ''
|
|
396
|
+
color = "#000000"
|
|
383
397
|
self.html_signal.emit(f'''
|
|
384
398
|
<div style="margin: 2px 0; font-family: {constants.LOG_FONTS_STR};">
|
|
385
399
|
<span style="color: {color}; font-style: italic; font-weight: bold;">{message}</span>
|
|
386
400
|
</div>
|
|
387
401
|
''')
|
|
402
|
+
# pylint: enable=line-too-long
|
|
388
403
|
self.end_signal.emit(status, self.id_str, message)
|
|
389
404
|
self.status_signal.emit(message, status, error_message, 0)
|
|
390
405
|
|
shinestacker/gui/main_window.py
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611, R0902, R0915, R0904, R0914, R0912, E1101, W0201
|
|
1
2
|
import os
|
|
2
3
|
import subprocess
|
|
3
4
|
from PySide6.QtWidgets import (QWidget, QVBoxLayout, QHBoxLayout, QTabWidget, QLabel, QMessageBox,
|
|
@@ -90,6 +91,7 @@ class TabWidgetWithPlaceholder(QWidget):
|
|
|
90
91
|
else:
|
|
91
92
|
self.stacked_widget.setCurrentIndex(0)
|
|
92
93
|
|
|
94
|
+
# pylint: disable=C0103
|
|
93
95
|
def addTab(self, widget, label):
|
|
94
96
|
result = self.tab_widget.addTab(widget, label)
|
|
95
97
|
self.update_placeholder_visibility()
|
|
@@ -117,6 +119,7 @@ class TabWidgetWithPlaceholder(QWidget):
|
|
|
117
119
|
|
|
118
120
|
def indexOf(self, widget):
|
|
119
121
|
return self.tab_widget.indexOf(widget)
|
|
122
|
+
# pylint: enable=C0103
|
|
120
123
|
|
|
121
124
|
|
|
122
125
|
class MainWindow(ActionsWindow, LogManager):
|
|
@@ -289,17 +292,44 @@ class MainWindow(ActionsWindow, LogManager):
|
|
|
289
292
|
self.run_all_jobs_action.triggered.connect(self.run_all_jobs)
|
|
290
293
|
menu.addAction(self.run_all_jobs_action)
|
|
291
294
|
|
|
295
|
+
def add_action_combined_actions(self):
|
|
296
|
+
self.add_action(constants.ACTION_COMBO)
|
|
297
|
+
|
|
298
|
+
def add_action_noise_detection(self):
|
|
299
|
+
self.add_action(constants.ACTION_NOISEDETECTION)
|
|
300
|
+
|
|
301
|
+
def add_action_focus_stack(self):
|
|
302
|
+
self.add_action(constants.ACTION_FOCUSSTACK)
|
|
303
|
+
|
|
304
|
+
def add_action_focus_stack_bunch(self):
|
|
305
|
+
self.add_action(constants.ACTION_FOCUSSTACKBUNCH)
|
|
306
|
+
|
|
307
|
+
def add_action_multilayer(self):
|
|
308
|
+
self.add_action(constants.ACTION_MULTILAYER)
|
|
309
|
+
|
|
310
|
+
def add_sub_action_make_noise(self):
|
|
311
|
+
self.add_sub_action(constants.ACTION_MASKNOISE)
|
|
312
|
+
|
|
313
|
+
def add_sub_action_vignetting(self):
|
|
314
|
+
self.add_sub_action(constants.ACTION_VIGNETTING)
|
|
315
|
+
|
|
316
|
+
def add_sub_action_align_frames(self):
|
|
317
|
+
self.add_sub_action(constants.ACTION_ALIGNFRAMES)
|
|
318
|
+
|
|
319
|
+
def add_sub_action_balance_frames(self):
|
|
320
|
+
self.add_sub_action(constants.ACTION_BALANCEFRAMES)
|
|
321
|
+
|
|
292
322
|
def add_actions_menu(self, menubar):
|
|
293
323
|
menu = menubar.addMenu("&Actions")
|
|
294
324
|
add_action_menu = QMenu("Add Action", self)
|
|
295
325
|
for action in constants.ACTION_TYPES:
|
|
296
326
|
entry_action = QAction(action, self)
|
|
297
327
|
entry_action.triggered.connect({
|
|
298
|
-
constants.ACTION_COMBO: self.
|
|
299
|
-
constants.ACTION_NOISEDETECTION: self.
|
|
300
|
-
constants.ACTION_FOCUSSTACK: self.
|
|
301
|
-
constants.ACTION_FOCUSSTACKBUNCH: self.
|
|
302
|
-
constants.ACTION_MULTILAYER: self.
|
|
328
|
+
constants.ACTION_COMBO: self.add_action_combined_actions,
|
|
329
|
+
constants.ACTION_NOISEDETECTION: self.add_action_noise_detection,
|
|
330
|
+
constants.ACTION_FOCUSSTACK: self.add_action_focus_stack,
|
|
331
|
+
constants.ACTION_FOCUSSTACKBUNCH: self.add_action_focus_stack_bunch,
|
|
332
|
+
constants.ACTION_MULTILAYER: self.add_action_multilayer
|
|
303
333
|
}[action])
|
|
304
334
|
add_action_menu.addAction(entry_action)
|
|
305
335
|
menu.addMenu(add_action_menu)
|
|
@@ -308,10 +338,10 @@ class MainWindow(ActionsWindow, LogManager):
|
|
|
308
338
|
for action in constants.SUB_ACTION_TYPES:
|
|
309
339
|
entry_action = QAction(action, self)
|
|
310
340
|
entry_action.triggered.connect({
|
|
311
|
-
constants.ACTION_MASKNOISE: self.
|
|
312
|
-
constants.ACTION_VIGNETTING: self.
|
|
313
|
-
constants.ACTION_ALIGNFRAMES: self.
|
|
314
|
-
constants.ACTION_BALANCEFRAMES: self.
|
|
341
|
+
constants.ACTION_MASKNOISE: self.add_sub_action_make_noise,
|
|
342
|
+
constants.ACTION_VIGNETTING: self.add_sub_action_vignetting,
|
|
343
|
+
constants.ACTION_ALIGNFRAMES: self.add_sub_action_align_frames,
|
|
344
|
+
constants.ACTION_BALANCEFRAMES: self.add_sub_action_balance_frames
|
|
315
345
|
}[action])
|
|
316
346
|
entry_action.setEnabled(False)
|
|
317
347
|
self.sub_action_menu_entries.append(entry_action)
|
|
@@ -330,7 +360,8 @@ class MainWindow(ActionsWindow, LogManager):
|
|
|
330
360
|
self.action_selector.setEnabled(False)
|
|
331
361
|
toolbar.addWidget(self.action_selector)
|
|
332
362
|
self.add_action_entry_action = QAction("Add Action", self)
|
|
333
|
-
self.add_action_entry_action.setIcon(
|
|
363
|
+
self.add_action_entry_action.setIcon(
|
|
364
|
+
QIcon(os.path.join(self.script_dir, "img/plus-round-line-icon.png")))
|
|
334
365
|
self.add_action_entry_action.setToolTip("Add action")
|
|
335
366
|
self.add_action_entry_action.triggered.connect(self.add_action)
|
|
336
367
|
self.add_action_entry_action.setEnabled(False)
|
|
@@ -340,7 +371,8 @@ class MainWindow(ActionsWindow, LogManager):
|
|
|
340
371
|
self.sub_action_selector.setEnabled(False)
|
|
341
372
|
toolbar.addWidget(self.sub_action_selector)
|
|
342
373
|
self.add_sub_action_entry_action = QAction("Add Sub Action", self)
|
|
343
|
-
self.add_sub_action_entry_action.setIcon(
|
|
374
|
+
self.add_sub_action_entry_action.setIcon(
|
|
375
|
+
QIcon(os.path.join(self.script_dir, "img/plus-round-line-icon.png")))
|
|
344
376
|
self.add_sub_action_entry_action.setToolTip("Add sub action")
|
|
345
377
|
self.add_sub_action_entry_action.triggered.connect(self.add_sub_action)
|
|
346
378
|
self.add_sub_action_entry_action.setEnabled(False)
|
|
@@ -351,6 +383,7 @@ class MainWindow(ActionsWindow, LogManager):
|
|
|
351
383
|
toolbar.addAction(self.run_job_action)
|
|
352
384
|
toolbar.addAction(self.run_all_jobs_action)
|
|
353
385
|
|
|
386
|
+
# pylint: disable=C0103
|
|
354
387
|
def contextMenuEvent(self, event):
|
|
355
388
|
item = self.job_list.itemAt(self.job_list.viewport().mapFrom(self, event.pos()))
|
|
356
389
|
current_action = None
|
|
@@ -362,7 +395,7 @@ class MainWindow(ActionsWindow, LogManager):
|
|
|
362
395
|
if item:
|
|
363
396
|
index = self.action_list.row(item)
|
|
364
397
|
self.action_list.setCurrentRow(index)
|
|
365
|
-
|
|
398
|
+
_job_row, _action_row, pos = self.get_action_at(index)
|
|
366
399
|
current_action = pos.action if not pos.is_sub_action else pos.sub_action
|
|
367
400
|
if current_action:
|
|
368
401
|
menu = QMenu(self)
|
|
@@ -375,7 +408,8 @@ class MainWindow(ActionsWindow, LogManager):
|
|
|
375
408
|
menu.addAction(edit_config_action)
|
|
376
409
|
menu.addSeparator()
|
|
377
410
|
self.current_action_working_path, name = get_action_working_path(current_action)
|
|
378
|
-
if self.current_action_working_path != '' and
|
|
411
|
+
if self.current_action_working_path != '' and \
|
|
412
|
+
os.path.exists(self.current_action_working_path):
|
|
379
413
|
action_name = "Browse Working Path" + (f" > {name}" if name != '' else '')
|
|
380
414
|
self.browse_working_path_action = QAction(action_name)
|
|
381
415
|
self.browse_working_path_action.triggered.connect(self.browse_working_path_path)
|
|
@@ -383,7 +417,8 @@ class MainWindow(ActionsWindow, LogManager):
|
|
|
383
417
|
ip, name = get_action_input_path(current_action)
|
|
384
418
|
if ip != '':
|
|
385
419
|
ips = ip.split(constants.PATH_SEPARATOR)
|
|
386
|
-
self.current_action_input_path = constants.PATH_SEPARATOR.join(
|
|
420
|
+
self.current_action_input_path = constants.PATH_SEPARATOR.join(
|
|
421
|
+
[f"{self.current_action_working_path}/{ip}" for ip in ips])
|
|
387
422
|
p_exists = False
|
|
388
423
|
for p in self.current_action_input_path.split(constants.PATH_SEPARATOR):
|
|
389
424
|
if os.path.exists(p):
|
|
@@ -410,20 +445,25 @@ class MainWindow(ActionsWindow, LogManager):
|
|
|
410
445
|
if len(retouch_path) > 0:
|
|
411
446
|
menu.addSeparator()
|
|
412
447
|
self.job_retouch_path_action = QAction("Retouch path")
|
|
413
|
-
self.job_retouch_path_action.triggered.connect(
|
|
448
|
+
self.job_retouch_path_action.triggered.connect(
|
|
449
|
+
lambda job: self.run_retouch_path(current_action, retouch_path))
|
|
414
450
|
menu.addAction(self.job_retouch_path_action)
|
|
415
451
|
menu.exec(event.globalPos())
|
|
452
|
+
# pylint: enable=C0103
|
|
416
453
|
|
|
417
454
|
def get_icon(self, icon):
|
|
418
455
|
return QIcon(os.path.join(self.script_dir, f"img/{icon}.png"))
|
|
419
456
|
|
|
420
457
|
def get_retouch_path(self, job):
|
|
421
458
|
frames_path = [get_action_output_path(action)[0]
|
|
422
|
-
for action in job.sub_actions
|
|
459
|
+
for action in job.sub_actions
|
|
460
|
+
if action.type_name == constants.ACTION_COMBO]
|
|
423
461
|
bunches_path = [get_action_output_path(action)[0]
|
|
424
|
-
for action in job.sub_actions
|
|
462
|
+
for action in job.sub_actions
|
|
463
|
+
if action.type_name == constants.ACTION_FOCUSSTACKBUNCH]
|
|
425
464
|
stack_path = [get_action_output_path(action)[0]
|
|
426
|
-
for action in job.sub_actions
|
|
465
|
+
for action in job.sub_actions
|
|
466
|
+
if action.type_name == constants.ACTION_FOCUSSTACK]
|
|
427
467
|
if len(bunches_path) > 0:
|
|
428
468
|
stack_path += [bunches_path[0]]
|
|
429
469
|
elif len(frames_path) > 0:
|
|
@@ -434,7 +474,7 @@ class MainWindow(ActionsWindow, LogManager):
|
|
|
434
474
|
stack_path = [f"{wp}/{s}" for s in stack_path]
|
|
435
475
|
return stack_path
|
|
436
476
|
|
|
437
|
-
def run_retouch_path(self,
|
|
477
|
+
def run_retouch_path(self, _job, retouch_path):
|
|
438
478
|
self.retouch_callback(retouch_path)
|
|
439
479
|
|
|
440
480
|
def browse_path(self, path):
|
|
@@ -444,9 +484,9 @@ class MainWindow(ActionsWindow, LogManager):
|
|
|
444
484
|
if running_under_windows():
|
|
445
485
|
os.startfile(os.path.normpath(p))
|
|
446
486
|
elif running_under_macos():
|
|
447
|
-
subprocess.run(['open', p])
|
|
487
|
+
subprocess.run(['open', p], check=True)
|
|
448
488
|
else:
|
|
449
|
-
subprocess.run(['xdg-open', p])
|
|
489
|
+
subprocess.run(['xdg-open', p], check=True)
|
|
450
490
|
|
|
451
491
|
def browse_working_path_path(self):
|
|
452
492
|
self.browse_path(self.current_action_working_path)
|
|
@@ -501,13 +541,14 @@ class MainWindow(ActionsWindow, LogManager):
|
|
|
501
541
|
w = self.tab_widget.widget(i)
|
|
502
542
|
if w.id_str() == id_str:
|
|
503
543
|
return i, w
|
|
544
|
+
return None, None
|
|
504
545
|
|
|
505
546
|
def get_tab_at_position(self, id_str):
|
|
506
|
-
|
|
547
|
+
_i, w = self.get_tab_and_position(id_str)
|
|
507
548
|
return w
|
|
508
549
|
|
|
509
550
|
def get_tab_position(self, id_str):
|
|
510
|
-
i,
|
|
551
|
+
i, _w = self.get_tab_and_position(id_str)
|
|
511
552
|
return i
|
|
512
553
|
|
|
513
554
|
def do_handle_end_message(self, status, id_str, message):
|
|
@@ -581,11 +622,14 @@ class MainWindow(ActionsWindow, LogManager):
|
|
|
581
622
|
self.start_thread(worker)
|
|
582
623
|
self._workers.append(worker)
|
|
583
624
|
else:
|
|
584
|
-
QMessageBox.warning(
|
|
625
|
+
QMessageBox.warning(
|
|
626
|
+
self, "Can't run Job",
|
|
627
|
+
"Job " + job.params["name"] + " is disabled.")
|
|
585
628
|
return
|
|
586
629
|
|
|
587
630
|
def run_all_jobs(self):
|
|
588
|
-
labels = [[(self.action_text(a), a.enabled() and
|
|
631
|
+
labels = [[(self.action_text(a), a.enabled() and
|
|
632
|
+
job.enabled()) for a in job.sub_actions] for job in self.project.jobs]
|
|
589
633
|
project_name = ".".join(self.current_file_name().split(".")[:-1])
|
|
590
634
|
if project_name == '':
|
|
591
635
|
project_name = '[new]'
|