shinestacker 1.2.1__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 +126 -94
- shinestacker/algorithms/align_auto.py +64 -0
- shinestacker/algorithms/align_parallel.py +296 -0
- shinestacker/algorithms/balance.py +3 -1
- shinestacker/algorithms/base_stack_algo.py +11 -2
- shinestacker/algorithms/multilayer.py +8 -8
- shinestacker/algorithms/noise_detection.py +10 -10
- shinestacker/algorithms/pyramid.py +4 -4
- shinestacker/algorithms/pyramid_auto.py +16 -10
- shinestacker/algorithms/pyramid_tiles.py +19 -11
- shinestacker/algorithms/stack.py +21 -17
- shinestacker/algorithms/stack_framework.py +97 -46
- shinestacker/algorithms/vignetting.py +13 -10
- shinestacker/app/main.py +7 -3
- shinestacker/config/constants.py +60 -25
- shinestacker/config/gui_constants.py +1 -1
- shinestacker/core/core_utils.py +4 -0
- shinestacker/core/framework.py +104 -23
- shinestacker/gui/action_config.py +4 -5
- shinestacker/gui/action_config_dialog.py +152 -12
- shinestacker/gui/base_form_dialog.py +2 -2
- shinestacker/gui/folder_file_selection.py +101 -0
- shinestacker/gui/gui_run.py +12 -10
- shinestacker/gui/main_window.py +6 -1
- shinestacker/gui/new_project.py +171 -73
- shinestacker/gui/project_controller.py +10 -6
- shinestacker/gui/project_converter.py +4 -2
- shinestacker/gui/project_editor.py +37 -27
- shinestacker/gui/select_path_widget.py +1 -1
- shinestacker/gui/sys_mon.py +96 -0
- 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.1.dist-info → shinestacker-1.3.0.dist-info}/METADATA +6 -6
- {shinestacker-1.2.1.dist-info → shinestacker-1.3.0.dist-info}/RECORD +40 -36
- {shinestacker-1.2.1.dist-info → shinestacker-1.3.0.dist-info}/WHEEL +0 -0
- {shinestacker-1.2.1.dist-info → shinestacker-1.3.0.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.2.1.dist-info → shinestacker-1.3.0.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.2.1.dist-info → shinestacker-1.3.0.dist-info}/top_level.txt +0 -0
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, E0611, R0913, R0917, R0915, R0912
|
|
2
|
-
# pylint: disable=E0606, W0718, R1702, W0102, W0221
|
|
2
|
+
# pylint: disable=E0606, W0718, R1702, W0102, W0221, R0914
|
|
3
|
+
import os
|
|
3
4
|
import traceback
|
|
4
|
-
from
|
|
5
|
-
from PySide6.QtWidgets import (QWidget, QPushButton, QHBoxLayout, QLabel, QScrollArea,
|
|
5
|
+
from PySide6.QtWidgets import (QWidget, QPushButton, QHBoxLayout, QLabel, QScrollArea, QSizePolicy,
|
|
6
6
|
QMessageBox, QStackedWidget, QFormLayout, QDialog)
|
|
7
7
|
from PySide6.QtCore import Qt, QTimer
|
|
8
8
|
from .. config.constants import constants
|
|
@@ -14,6 +14,7 @@ from . action_config import (
|
|
|
14
14
|
FIELD_TEXT, FIELD_ABS_PATH, FIELD_REL_PATH, FIELD_FLOAT,
|
|
15
15
|
FIELD_INT, FIELD_INT_TUPLE, FIELD_BOOL, FIELD_COMBO, FIELD_REF_IDX
|
|
16
16
|
)
|
|
17
|
+
from .folder_file_selection import FolderFileSelectionWidget
|
|
17
18
|
|
|
18
19
|
|
|
19
20
|
class ActionConfigDialog(QDialog):
|
|
@@ -104,8 +105,6 @@ class ActionConfigDialog(QDialog):
|
|
|
104
105
|
if self.configurator.update_params(self.action.params):
|
|
105
106
|
self.parent().mark_as_modified(True, "Modify Configuration")
|
|
106
107
|
super().accept()
|
|
107
|
-
else:
|
|
108
|
-
self.parent().project_editor.pop_undo()
|
|
109
108
|
|
|
110
109
|
def reset_to_defaults(self):
|
|
111
110
|
builder = self.configurator.get_builder()
|
|
@@ -124,7 +123,7 @@ class NoNameActionConfigurator(ActionConfigurator):
|
|
|
124
123
|
def get_builder(self):
|
|
125
124
|
return self.builder
|
|
126
125
|
|
|
127
|
-
def update_params(self, params
|
|
126
|
+
def update_params(self, params):
|
|
128
127
|
return self.builder.update_params(params)
|
|
129
128
|
|
|
130
129
|
def add_bold_label(self, label):
|
|
@@ -139,6 +138,24 @@ class NoNameActionConfigurator(ActionConfigurator):
|
|
|
139
138
|
required=False, add_to_layout=None, **kwargs):
|
|
140
139
|
return self.builder.add_field(tag, field_type, label, required, add_to_layout, **kwargs)
|
|
141
140
|
|
|
141
|
+
def labelled_widget(self, label, widget):
|
|
142
|
+
row = QWidget()
|
|
143
|
+
layout = QHBoxLayout()
|
|
144
|
+
layout.setContentsMargins(2, 2, 2, 2)
|
|
145
|
+
layout.setSpacing(8)
|
|
146
|
+
label_widget = QLabel(label)
|
|
147
|
+
label_widget.setFixedWidth(120)
|
|
148
|
+
label_widget.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Preferred)
|
|
149
|
+
layout.addWidget(label_widget)
|
|
150
|
+
layout.addWidget(widget)
|
|
151
|
+
layout.setStretch(0, 1)
|
|
152
|
+
layout.setStretch(1, 3)
|
|
153
|
+
row.setLayout(layout)
|
|
154
|
+
return row
|
|
155
|
+
|
|
156
|
+
def add_labelled_row(self, label, widget):
|
|
157
|
+
self.add_row(self.labelled_widget(label, widget))
|
|
158
|
+
|
|
142
159
|
|
|
143
160
|
class DefaultActionConfigurator(NoNameActionConfigurator):
|
|
144
161
|
def create_form(self, layout, action, tag='Action'):
|
|
@@ -148,13 +165,98 @@ class DefaultActionConfigurator(NoNameActionConfigurator):
|
|
|
148
165
|
|
|
149
166
|
|
|
150
167
|
class JobConfigurator(DefaultActionConfigurator):
|
|
168
|
+
def __init__(self, expert, current_wd):
|
|
169
|
+
super().__init__(expert, current_wd)
|
|
170
|
+
self.working_path_label = None
|
|
171
|
+
self.input_path_label = None
|
|
172
|
+
self.frames_label = None
|
|
173
|
+
self.input_widget = None
|
|
174
|
+
|
|
151
175
|
def create_form(self, layout, action):
|
|
152
176
|
super().create_form(layout, action, "Job")
|
|
153
|
-
self.
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
177
|
+
self.input_widget = FolderFileSelectionWidget()
|
|
178
|
+
self.frames_label = QLabel("0")
|
|
179
|
+
working_path = action.params.get('working_path', '')
|
|
180
|
+
input_path = action.params.get('input_path', '')
|
|
181
|
+
input_filepaths = action.params.get('input_filepaths', [])
|
|
182
|
+
if isinstance(input_filepaths, str) and input_filepaths:
|
|
183
|
+
input_filepaths = input_filepaths.split(constants.PATH_SEPARATOR)
|
|
184
|
+
self.working_path_label = QLabel(working_path or "Not set")
|
|
185
|
+
self.input_path_label = QLabel(input_path or "Not set")
|
|
186
|
+
has_existing_data = working_path or input_path or input_filepaths
|
|
187
|
+
if input_filepaths:
|
|
188
|
+
self.input_widget.files_mode_radio.setChecked(True)
|
|
189
|
+
self.input_widget.selected_files = input_filepaths
|
|
190
|
+
if input_filepaths:
|
|
191
|
+
parent_dir = os.path.dirname(input_filepaths[0])
|
|
192
|
+
self.input_widget.path_edit.setText(parent_dir)
|
|
193
|
+
elif input_path and working_path:
|
|
194
|
+
self.input_widget.folder_mode_radio.setChecked(True)
|
|
195
|
+
input_fullpath = os.path.join(working_path, input_path)
|
|
196
|
+
self.input_widget.path_edit.setText(input_fullpath)
|
|
197
|
+
elif input_path:
|
|
198
|
+
self.input_widget.folder_mode_radio.setChecked(True)
|
|
199
|
+
self.input_widget.path_edit.setText(input_path)
|
|
200
|
+
self.input_widget.text_changed_connect(self.update_paths_and_frames)
|
|
201
|
+
self.input_widget.folder_mode_radio.toggled.connect(self.update_paths_and_frames)
|
|
202
|
+
self.input_widget.files_mode_radio.toggled.connect(self.update_paths_and_frames)
|
|
203
|
+
self.add_bold_label("Input Selection:")
|
|
204
|
+
self.add_row(self.input_widget)
|
|
205
|
+
self.add_labelled_row("Number of frames: ", self.frames_label)
|
|
206
|
+
self.add_bold_label("Derived Paths:")
|
|
207
|
+
self.add_labelled_row("Working path: ", self.working_path_label)
|
|
208
|
+
self.add_labelled_row("Input path:", self.input_path_label)
|
|
209
|
+
if not has_existing_data:
|
|
210
|
+
self.update_paths_and_frames()
|
|
211
|
+
else:
|
|
212
|
+
self.update_frames_count()
|
|
213
|
+
|
|
214
|
+
def update_frames_count(self):
|
|
215
|
+
if self.input_widget.get_selection_mode() == 'files':
|
|
216
|
+
count = len(self.input_widget.get_selected_files())
|
|
217
|
+
else:
|
|
218
|
+
count = self.count_image_files(self.input_widget.get_path())
|
|
219
|
+
self.frames_label.setText(str(count))
|
|
220
|
+
|
|
221
|
+
def update_paths_and_frames(self):
|
|
222
|
+
self.update_frames_count()
|
|
223
|
+
selection_mode = self.input_widget.get_selection_mode()
|
|
224
|
+
selected_files = self.input_widget.get_selected_files()
|
|
225
|
+
input_path = self.input_widget.get_path()
|
|
226
|
+
if selection_mode == 'files' and selected_files:
|
|
227
|
+
input_path = os.path.dirname(selected_files[0])
|
|
228
|
+
input_path_value = os.path.basename(os.path.normpath(input_path)) if input_path else ""
|
|
229
|
+
working_path_value = os.path.dirname(input_path) if input_path else ""
|
|
230
|
+
self.input_path_label.setText(input_path_value or "Not set")
|
|
231
|
+
self.working_path_label.setText(working_path_value or "Not set")
|
|
232
|
+
|
|
233
|
+
def count_image_files(self, path):
|
|
234
|
+
if not path or not os.path.isdir(path):
|
|
235
|
+
return 0
|
|
236
|
+
count = 0
|
|
237
|
+
for filename in os.listdir(path):
|
|
238
|
+
if filename.lower().endswith(('.png', '.jpg', '.jpeg', '.tif', '.tiff')):
|
|
239
|
+
count += 1
|
|
240
|
+
return count
|
|
241
|
+
|
|
242
|
+
def update_params(self, params):
|
|
243
|
+
if not super().update_params(params):
|
|
244
|
+
return False
|
|
245
|
+
selection_mode = self.input_widget.get_selection_mode()
|
|
246
|
+
selected_files = self.input_widget.get_selected_files()
|
|
247
|
+
input_path = self.input_widget.get_path()
|
|
248
|
+
if selection_mode == 'files' and selected_files:
|
|
249
|
+
params['input_filepaths'] = self.input_widget.get_selected_filenames()
|
|
250
|
+
params['input_path'] = os.path.dirname(selected_files[0])
|
|
251
|
+
else:
|
|
252
|
+
params['input_filepaths'] = []
|
|
253
|
+
params['input_path'] = input_path
|
|
254
|
+
if 'working_path' not in params or not params['working_path']:
|
|
255
|
+
if params['input_path']:
|
|
256
|
+
params['working_path'] = os.path.dirname(params['input_path'])
|
|
257
|
+
else:
|
|
258
|
+
params['working_path'] = ''
|
|
259
|
+
return True
|
|
158
260
|
|
|
159
261
|
|
|
160
262
|
class NoiseDetectionConfigurator(DefaultActionConfigurator):
|
|
@@ -435,6 +537,13 @@ class CombinedActionsConfigurator(DefaultActionConfigurator):
|
|
|
435
537
|
self.add_field(
|
|
436
538
|
'step_process', FIELD_BOOL, 'Step process', required=False,
|
|
437
539
|
default=True)
|
|
540
|
+
self.add_field(
|
|
541
|
+
'max_threads', FIELD_INT, 'Max num. of cores',
|
|
542
|
+
required=False, default=constants.DEFAULT_MAX_FWK_THREADS,
|
|
543
|
+
min_val=1, max_val=64)
|
|
544
|
+
self.add_field(
|
|
545
|
+
'chunk_submit', FIELD_BOOL, 'Submit in chunks',
|
|
546
|
+
required=False, default=constants.DEFAULT_MAX_FWK_CHUNK_SUBMIT)
|
|
438
547
|
|
|
439
548
|
|
|
440
549
|
class MaskNoiseConfigurator(DefaultActionConfigurator):
|
|
@@ -484,6 +593,7 @@ class AlignFramesConfigurator(SubsampleActionConfigurator):
|
|
|
484
593
|
TRANSFORM_OPTIONS = ['Rigid', 'Homography']
|
|
485
594
|
METHOD_OPTIONS = ['Random Sample Consensus (RANSAC)', 'Least Median (LMEDS)']
|
|
486
595
|
MATCHING_METHOD_OPTIONS = ['K-nearest neighbors', 'Hamming distance']
|
|
596
|
+
MODE_OPTIONS = ['Auto', 'Sequential', 'Parallel']
|
|
487
597
|
|
|
488
598
|
def __init__(self, expert, current_wd):
|
|
489
599
|
super().__init__(expert, current_wd)
|
|
@@ -650,6 +760,36 @@ class AlignFramesConfigurator(SubsampleActionConfigurator):
|
|
|
650
760
|
default=constants.DEFAULT_BORDER_BLUR,
|
|
651
761
|
min_val=0, max_val=1000, step=1)
|
|
652
762
|
self.add_bold_label("Miscellanea:")
|
|
763
|
+
if self.expert:
|
|
764
|
+
mode = self.add_field(
|
|
765
|
+
'mode', FIELD_COMBO, 'Mode',
|
|
766
|
+
required=False, options=self.MODE_OPTIONS, values=constants.ALIGN_VALID_MODES,
|
|
767
|
+
default=dict(zip(constants.ALIGN_VALID_MODES,
|
|
768
|
+
self.MODE_OPTIONS))[constants.DEFAULT_ALIGN_MODE])
|
|
769
|
+
memory_limit = self.add_field(
|
|
770
|
+
'memory_limit', FIELD_FLOAT, 'Memory limit (approx., GBytes)',
|
|
771
|
+
required=False, default=constants.DEFAULT_ALIGN_MEMORY_LIMIT_GB,
|
|
772
|
+
min_val=1.0, max_val=64.0)
|
|
773
|
+
max_threads = self.add_field(
|
|
774
|
+
'max_threads', FIELD_INT, 'Max num. of cores',
|
|
775
|
+
required=False, default=constants.DEFAULT_ALIGN_MAX_THREADS,
|
|
776
|
+
min_val=1, max_val=64)
|
|
777
|
+
chunk_submit = self.add_field(
|
|
778
|
+
'chunk_submit', FIELD_BOOL, 'Submit in chunks',
|
|
779
|
+
required=False, default=constants.DEFAULT_ALIGN_CHUNK_SUBMIT)
|
|
780
|
+
bw_matching = self.add_field(
|
|
781
|
+
'bw_matching', FIELD_BOOL, 'Match using black & white',
|
|
782
|
+
required=False, default=constants.DEFAULT_ALIGN_BW_MATCHING)
|
|
783
|
+
|
|
784
|
+
def change_mode():
|
|
785
|
+
text = mode.currentText()
|
|
786
|
+
enabled = text != self.MODE_OPTIONS[1]
|
|
787
|
+
memory_limit.setEnabled(enabled)
|
|
788
|
+
max_threads.setEnabled(enabled)
|
|
789
|
+
chunk_submit.setEnabled(enabled)
|
|
790
|
+
bw_matching.setEnabled(enabled)
|
|
791
|
+
|
|
792
|
+
mode.currentIndexChanged.connect(change_mode)
|
|
653
793
|
self.add_field(
|
|
654
794
|
'plot_summary', FIELD_BOOL, 'Plot summary',
|
|
655
795
|
required=False, default=False)
|
|
@@ -657,7 +797,7 @@ class AlignFramesConfigurator(SubsampleActionConfigurator):
|
|
|
657
797
|
'plot_matches', FIELD_BOOL, 'Plot matches',
|
|
658
798
|
required=False, default=False)
|
|
659
799
|
|
|
660
|
-
def update_params(self, params
|
|
800
|
+
def update_params(self, params):
|
|
661
801
|
if self.detector_field and self.descriptor_field and self.matching_method_field:
|
|
662
802
|
try:
|
|
663
803
|
detector = self.detector_field.currentText()
|
|
@@ -13,10 +13,10 @@ def create_form_layout(parent):
|
|
|
13
13
|
|
|
14
14
|
|
|
15
15
|
class BaseFormDialog(QDialog):
|
|
16
|
-
def __init__(self, title, parent=None):
|
|
16
|
+
def __init__(self, title, width=500, parent=None):
|
|
17
17
|
super().__init__(parent)
|
|
18
18
|
self.setWindowTitle(title)
|
|
19
|
-
self.resize(
|
|
19
|
+
self.resize(width, self.height())
|
|
20
20
|
self.form_layout = create_form_layout(self)
|
|
21
21
|
|
|
22
22
|
def add_row_to_layout(self, item):
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E0611
|
|
2
|
+
import os
|
|
3
|
+
from PySide6.QtWidgets import (QWidget, QRadioButton, QButtonGroup, QLineEdit,
|
|
4
|
+
QPushButton, QHBoxLayout, QVBoxLayout, QFileDialog, QMessageBox)
|
|
5
|
+
from PySide6.QtCore import Qt
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class FolderFileSelectionWidget(QWidget):
|
|
9
|
+
def __init__(self, parent=None):
|
|
10
|
+
super().__init__(parent)
|
|
11
|
+
self.selection_mode = 'folder' # 'folder' or 'files'
|
|
12
|
+
self.selected_files = []
|
|
13
|
+
self.setup_ui()
|
|
14
|
+
|
|
15
|
+
def setup_ui(self):
|
|
16
|
+
self.mode_group = QButtonGroup(self)
|
|
17
|
+
self.folder_mode_radio = QRadioButton("Select Folder")
|
|
18
|
+
self.folder_mode_radio.setMaximumWidth(100)
|
|
19
|
+
self.files_mode_radio = QRadioButton("Select Files")
|
|
20
|
+
self.files_mode_radio.setMaximumWidth(100)
|
|
21
|
+
self.folder_mode_radio.setChecked(True)
|
|
22
|
+
self.mode_group.addButton(self.folder_mode_radio)
|
|
23
|
+
self.mode_group.addButton(self.files_mode_radio)
|
|
24
|
+
self.path_edit = QLineEdit()
|
|
25
|
+
self.path_edit.setPlaceholderText("input files folder")
|
|
26
|
+
self.browse_button = QPushButton("Browse Folder...")
|
|
27
|
+
self.browse_button.setFixedWidth(120)
|
|
28
|
+
main_layout = QVBoxLayout()
|
|
29
|
+
main_layout.setSpacing(10)
|
|
30
|
+
main_layout.setAlignment(Qt.AlignLeft)
|
|
31
|
+
mode_layout = QHBoxLayout()
|
|
32
|
+
mode_layout.setContentsMargins(2, 2, 2, 2)
|
|
33
|
+
mode_layout.setSpacing(20)
|
|
34
|
+
mode_layout.addWidget(self.folder_mode_radio)
|
|
35
|
+
mode_layout.addWidget(self.files_mode_radio)
|
|
36
|
+
mode_layout.addStretch()
|
|
37
|
+
main_layout.addLayout(mode_layout)
|
|
38
|
+
input_layout = QHBoxLayout()
|
|
39
|
+
input_layout.setContentsMargins(2, 2, 2, 2)
|
|
40
|
+
input_layout.setSpacing(8)
|
|
41
|
+
input_layout.setAlignment(Qt.AlignLeft)
|
|
42
|
+
input_layout.addWidget(self.path_edit)
|
|
43
|
+
input_layout.addWidget(self.browse_button)
|
|
44
|
+
main_layout.addLayout(input_layout)
|
|
45
|
+
self.setLayout(main_layout)
|
|
46
|
+
self.folder_mode_radio.toggled.connect(self.update_selection_mode)
|
|
47
|
+
self.files_mode_radio.toggled.connect(self.update_selection_mode)
|
|
48
|
+
self.browse_button.clicked.connect(self.handle_browse)
|
|
49
|
+
|
|
50
|
+
def update_selection_mode(self):
|
|
51
|
+
if self.folder_mode_radio.isChecked():
|
|
52
|
+
self.selection_mode = 'folder'
|
|
53
|
+
self.browse_button.setText("Browse Folder...")
|
|
54
|
+
# self.path_edit.setPlaceholderText("input files folder")
|
|
55
|
+
else:
|
|
56
|
+
self.selection_mode = 'files'
|
|
57
|
+
self.browse_button.setText("Browse Files...")
|
|
58
|
+
# self.path_edit.setPlaceholderText("input files")
|
|
59
|
+
|
|
60
|
+
def handle_browse(self):
|
|
61
|
+
if self.selection_mode == 'folder':
|
|
62
|
+
self.browse_folder()
|
|
63
|
+
else:
|
|
64
|
+
self.browse_files()
|
|
65
|
+
|
|
66
|
+
def browse_folder(self):
|
|
67
|
+
path = QFileDialog.getExistingDirectory(self, "Select Input Folder")
|
|
68
|
+
if path:
|
|
69
|
+
self.selected_files = []
|
|
70
|
+
self.path_edit.setText(path)
|
|
71
|
+
|
|
72
|
+
def browse_files(self):
|
|
73
|
+
files, _ = QFileDialog.getOpenFileNames(
|
|
74
|
+
self, "Select Input Files", "",
|
|
75
|
+
"Image files (*.png *.jpg *.jpeg *.tif *.tiff)"
|
|
76
|
+
)
|
|
77
|
+
if files:
|
|
78
|
+
parent_dir = os.path.dirname(files[0])
|
|
79
|
+
if all(os.path.dirname(f) == parent_dir for f in files):
|
|
80
|
+
self.selected_files = files
|
|
81
|
+
self.path_edit.setText(parent_dir)
|
|
82
|
+
else:
|
|
83
|
+
QMessageBox.warning(
|
|
84
|
+
self, "Invalid Selection",
|
|
85
|
+
"All files must be in the same directory."
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
def get_selection_mode(self):
|
|
89
|
+
return self.selection_mode
|
|
90
|
+
|
|
91
|
+
def get_selected_files(self):
|
|
92
|
+
return self.selected_files
|
|
93
|
+
|
|
94
|
+
def get_selected_filenames(self):
|
|
95
|
+
return [os.path.basename(file_path) for file_path in self.selected_files]
|
|
96
|
+
|
|
97
|
+
def get_path(self):
|
|
98
|
+
return self.path_edit.text()
|
|
99
|
+
|
|
100
|
+
def text_changed_connect(self, callback):
|
|
101
|
+
self.path_edit.textChanged.connect(callback)
|
shinestacker/gui/gui_run.py
CHANGED
|
@@ -18,6 +18,7 @@ from .colors import (
|
|
|
18
18
|
ACTION_STOPPED_COLOR, ACTION_FAILED_COLOR)
|
|
19
19
|
from .time_progress_bar import TimerProgressBar
|
|
20
20
|
from .flow_layout import FlowLayout
|
|
21
|
+
from .sys_mon import StatusBarSystemMonitor
|
|
21
22
|
|
|
22
23
|
|
|
23
24
|
class ColorButton(QPushButton):
|
|
@@ -96,7 +97,8 @@ class RunWindow(QTextEditLogger):
|
|
|
96
97
|
self.right_area.setMaximumWidth(0)
|
|
97
98
|
self.image_area_widget.setFixedWidth(0)
|
|
98
99
|
layout.addLayout(output_layout)
|
|
99
|
-
|
|
100
|
+
self.system_monitor = StatusBarSystemMonitor(self)
|
|
101
|
+
self.status_bar.addPermanentWidget(self.system_monitor)
|
|
100
102
|
n_paths = len(self.retouch_paths) if self.retouch_paths else 0
|
|
101
103
|
if n_paths == 1:
|
|
102
104
|
self.retouch_widget = QPushButton(f"Retouch {self.retouch_paths[0][0]}")
|
|
@@ -284,15 +286,15 @@ class RunWorker(LogWorker):
|
|
|
284
286
|
self.id_str = id_str
|
|
285
287
|
self.status = constants.STATUS_RUNNING
|
|
286
288
|
self.callbacks = {
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
289
|
+
constants.CALLBACK_BEFORE_ACTION: self.before_action,
|
|
290
|
+
constants.CALLBACK_AFTER_ACTION: self.after_action,
|
|
291
|
+
constants.CALLBACK_STEP_COUNTS: self.step_counts,
|
|
292
|
+
constants.CALLBACK_BEGIN_STEPS: self.begin_steps,
|
|
293
|
+
constants.CALLBACK_END_STEPS: self.end_steps,
|
|
294
|
+
constants.CALLBACK_AFTER_STEP: self.after_step,
|
|
295
|
+
constants.CALLBACK_CHECK_RUNNING: self.check_running,
|
|
296
|
+
constants.CALLBACK_SAVE_PLOT: self.save_plot,
|
|
297
|
+
constants.CALLBACK_OPEN_APP: self.open_app
|
|
296
298
|
}
|
|
297
299
|
self.tag = ""
|
|
298
300
|
|
shinestacker/gui/main_window.py
CHANGED
|
@@ -43,7 +43,10 @@ class ProjectLogWorker(RunWorker):
|
|
|
43
43
|
|
|
44
44
|
LIST_STYLE_SHEET = f"""
|
|
45
45
|
QListWidget::item:selected {{
|
|
46
|
-
background-color: #{ColorPalette.LIGHT_BLUE.hex()}
|
|
46
|
+
background-color: #{ColorPalette.LIGHT_BLUE.hex()};
|
|
47
|
+
}}
|
|
48
|
+
QListWidget::item:hover {{
|
|
49
|
+
background-color: #F0F0F0;
|
|
47
50
|
}}
|
|
48
51
|
"""
|
|
49
52
|
|
|
@@ -421,6 +424,8 @@ class MainWindow(QMainWindow, LogManager):
|
|
|
421
424
|
for worker in self._workers:
|
|
422
425
|
worker.stop()
|
|
423
426
|
self.close()
|
|
427
|
+
return True
|
|
428
|
+
return False
|
|
424
429
|
|
|
425
430
|
def toggle_expert_options(self):
|
|
426
431
|
self.expert_options = self.menu_manager.expert_options_action.isChecked()
|