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,9 +1,8 @@
1
- # pylint: disable=C0114, C0115, C0116, R0904, R1702, R0917, R0913, R0902, E0611, E1131, E1121
1
+ # pylint: disable=C0114, C0115, C0116, R0903, R0904, R1702, R0917, R0913, R0902, E0611, E1131, E1121
2
2
  import os
3
3
  from dataclasses import dataclass
4
- from PySide6.QtWidgets import (QListWidget, QMessageBox,
5
- QDialog, QListWidgetItem, QLabel)
6
- from PySide6.QtCore import Qt, QObject, Signal
4
+ from PySide6.QtWidgets import QListWidget, QMessageBox, QDialog, QListWidgetItem, QLabel
5
+ from PySide6.QtCore import Qt, QObject, Signal, QEvent
7
6
  from .. config.constants import constants
8
7
  from .colors import ColorPalette
9
8
  from .action_config_dialog import ActionConfigDialog
@@ -75,28 +74,49 @@ def new_row_after_clone(job, action_row, is_sub_action, cloned):
75
74
 
76
75
 
77
76
  class ProjectUndoManager(QObject):
78
- set_enabled_undo_action_requested = Signal(bool)
77
+ set_enabled_undo_action_requested = Signal(bool, str)
79
78
 
80
79
  def __init__(self, parent=None):
81
80
  super().__init__(parent)
82
81
  self._undo_buffer = []
83
82
 
84
- def add(self, item):
85
- self._undo_buffer.append(item)
86
- self.set_enabled_undo_action_requested.emit(True)
83
+ def add(self, item, description):
84
+ self._undo_buffer.append((item, description))
85
+ self.set_enabled_undo_action_requested.emit(True, description)
87
86
 
88
87
  def pop(self):
89
88
  last = self._undo_buffer.pop()
90
89
  if len(self._undo_buffer) == 0:
91
- self.set_enabled_undo_action_requested.emit(False)
92
- return last
90
+ self.set_enabled_undo_action_requested.emit(False, '')
91
+ else:
92
+ self.set_enabled_undo_action_requested.emit(True, self._undo_buffer[-1][1])
93
+ return last[0]
93
94
 
94
95
  def filled(self):
95
96
  return len(self._undo_buffer) != 0
96
97
 
97
98
  def reset(self):
98
99
  self._undo_buffer = []
99
- self.set_enabled_undo_action_requested.emit(False)
100
+ self.set_enabled_undo_action_requested.emit(False, '')
101
+
102
+
103
+ class HandCursorListWidget(QListWidget):
104
+ def __init__(self, parent=None):
105
+ super().__init__(parent)
106
+ self.setMouseTracking(True)
107
+ self.viewport().setMouseTracking(True)
108
+
109
+ def event(self, event):
110
+ if event.type() == QEvent.HoverMove:
111
+ pos = event.position().toPoint()
112
+ item = self.itemAt(pos)
113
+ if item:
114
+ self.viewport().setCursor(Qt.PointingHandCursor)
115
+ else:
116
+ self.viewport().setCursor(Qt.ArrowCursor)
117
+ elif event.type() == QEvent.Leave:
118
+ self.viewport().setCursor(Qt.ArrowCursor)
119
+ return super().event(event)
100
120
 
101
121
 
102
122
  class ProjectEditor(QObject):
@@ -115,15 +135,15 @@ class ProjectEditor(QObject):
115
135
  self._project = None
116
136
  self._copy_buffer = None
117
137
  self._current_file_path = ''
118
- self._job_list = QListWidget()
119
- self._action_list = QListWidget()
138
+ self._job_list = HandCursorListWidget()
139
+ self._action_list = HandCursorListWidget()
120
140
  self.dialog = None
121
141
 
122
142
  def reset_undo(self):
123
143
  self.undo_manager.reset()
124
144
 
125
- def add_undo(self, item):
126
- self.undo_manager.add(item)
145
+ def add_undo(self, item, description=''):
146
+ self.undo_manager.add(item, description)
127
147
 
128
148
  def pop_undo(self):
129
149
  return self.undo_manager.pop()
@@ -131,10 +151,13 @@ class ProjectEditor(QObject):
131
151
  def filled_undo(self):
132
152
  return self.undo_manager.filled()
133
153
 
134
- def mark_as_modified(self, modified=True):
154
+ def set_modified(self, modified):
155
+ self._modified = modified
156
+
157
+ def mark_as_modified(self, modified=True, description=''):
135
158
  self._modified = modified
136
159
  if modified:
137
- self.add_undo(self._project.clone())
160
+ self.add_undo(self._project.clone(), description)
138
161
  self.modified_signal.emit(modified)
139
162
 
140
163
  def modified(self):
@@ -241,37 +264,29 @@ class ProjectEditor(QObject):
241
264
  txt = f"{job.params.get('name', '(job)')}"
242
265
  if html:
243
266
  txt = f"<b>{txt}</b>"
244
- in_path = get_action_input_path(job)
245
- return txt + (f" [⚙️ Job: 📁 {in_path[0]} → 📂 ...]" if long_name else "")
267
+ in_path = get_action_input_path(job)[0]
268
+ if os.path.isabs(in_path):
269
+ in_path = ".../" + os.path.basename(in_path)
270
+ ico = constants.ACTION_ICONS[constants.ACTION_JOB]
271
+ return txt + (f" [{ico}Job: 📁 {in_path} → 📂 ...]" if long_name else "")
246
272
 
247
273
  def action_text(self, action, is_sub_action=False, indent=True, long_name=False, html=False):
248
- icon_map = {
249
- constants.ACTION_COMBO: '⚡',
250
- constants.ACTION_NOISEDETECTION: '🌫',
251
- constants.ACTION_FOCUSSTACK: '🎯',
252
- constants.ACTION_FOCUSSTACKBUNCH: '🖇',
253
- constants.ACTION_MULTILAYER: '🎞️',
254
- constants.ACTION_MASKNOISE: '🎭',
255
- constants.ACTION_VIGNETTING: '⭕️',
256
- constants.ACTION_ALIGNFRAMES: '📐',
257
- constants.ACTION_BALANCEFRAMES: '🌈'
258
- }
259
- ico = icon_map.get(action.type_name, '')
274
+ ico = constants.ACTION_ICONS.get(action.type_name, '')
260
275
  if is_sub_action and indent:
261
276
  txt = self.INDENT_SPACE
262
- if ico == '':
263
- ico = '🟣'
264
277
  else:
265
278
  txt = ''
266
- if ico == '':
267
- ico = '🔵'
268
279
  if action.params.get('name', '') != '':
269
280
  txt += f"{action.params['name']}"
270
281
  if html:
271
282
  txt = f"<b>{txt}</b>"
272
- in_path, out_path = get_action_input_path(action), get_action_output_path(action)
273
- return f"{txt} [{ico} {action.type_name}" + \
274
- (f": 📁 <i>{in_path[0]}</i> → 📂 <i>{out_path[0]}</i>]"
283
+ in_path, out_path = get_action_input_path(action)[0], get_action_output_path(action)[0]
284
+ if os.path.isabs(in_path):
285
+ in_path = ".../" + os.path.basename(in_path)
286
+ if os.path.isabs(out_path):
287
+ out_path = ".../" + os.path.basename(out_path)
288
+ return f"{txt} [{ico}{action.type_name}" + \
289
+ (f": 📁 <i>{in_path}</i> → 📂 <i>{out_path}</i>]"
275
290
  if long_name and not is_sub_action else "]")
276
291
 
277
292
  def get_job_at(self, index):
@@ -320,7 +335,7 @@ class ProjectEditor(QObject):
320
335
  new_index = job_index + delta
321
336
  if 0 <= new_index < self.num_project_jobs():
322
337
  jobs = self.project_jobs()
323
- self.mark_as_modified()
338
+ self.mark_as_modified(True, "Shift Job")
324
339
  jobs.insert(new_index, jobs.pop(job_index))
325
340
  self.refresh_ui_signal.emit(new_index, -1)
326
341
 
@@ -330,12 +345,12 @@ class ProjectEditor(QObject):
330
345
  if not pos.is_sub_action:
331
346
  new_index = pos.action_index + delta
332
347
  if 0 <= new_index < len(pos.actions):
333
- self.mark_as_modified()
348
+ self.mark_as_modified(True, "Shift Action")
334
349
  pos.actions.insert(new_index, pos.actions.pop(pos.action_index))
335
350
  else:
336
351
  new_index = pos.sub_action_index + delta
337
352
  if 0 <= new_index < len(pos.sub_actions):
338
- self.mark_as_modified()
353
+ self.mark_as_modified(True, "Shift Sub-action")
339
354
  pos.sub_actions.insert(new_index, pos.sub_actions.pop(pos.sub_action_index))
340
355
  new_row = new_row_after_insert(action_row, pos, delta)
341
356
  self.refresh_ui_signal.emit(job_row, new_row)
@@ -357,7 +372,7 @@ class ProjectEditor(QObject):
357
372
  if 0 <= job_index < self.num_project_jobs():
358
373
  job_clone = self.project_job(job_index).clone(self.CLONE_POSTFIX)
359
374
  new_job_index = job_index + 1
360
- self.mark_as_modified()
375
+ self.mark_as_modified(True, "Duplicate Job")
361
376
  self.project_jobs().insert(new_job_index, job_clone)
362
377
  self.set_current_job(new_job_index)
363
378
  self.set_current_action(new_job_index)
@@ -367,7 +382,7 @@ class ProjectEditor(QObject):
367
382
  job_row, action_row, pos = self.get_current_action()
368
383
  if not pos.actions:
369
384
  return
370
- self.mark_as_modified()
385
+ self.mark_as_modified(True, "Duplicate Action")
371
386
  job = self.project_job(job_row)
372
387
  if pos.is_sub_action:
373
388
  cloned = pos.sub_action.clone(self.CLONE_POSTFIX)
@@ -398,7 +413,7 @@ class ProjectEditor(QObject):
398
413
  reply = None
399
414
  if not confirm or reply == QMessageBox.Yes:
400
415
  self.take_job(current_index)
401
- self.mark_as_modified()
416
+ self.mark_as_modified(True, "Delete Job")
402
417
  current_job = self.project_jobs().pop(current_index)
403
418
  self.clear_action_list()
404
419
  self.refresh_ui_signal.emit(-1, -1)
@@ -420,10 +435,11 @@ class ProjectEditor(QObject):
420
435
  else:
421
436
  reply = None
422
437
  if not confirm or reply == QMessageBox.Yes:
423
- self.mark_as_modified()
424
438
  if pos.is_sub_action:
439
+ self.mark_as_modified(True, "Delete Action")
425
440
  pos.action.pop_sub_action(pos.sub_action_index)
426
441
  else:
442
+ self.mark_as_modified(True, "Delete Sub-action")
427
443
  self.project_job(job_row).pop_sub_action(pos.action_index)
428
444
  new_row = new_row_after_delete(action_row, pos)
429
445
  self.refresh_ui_signal.emit(job_row, new_row)
@@ -446,7 +462,7 @@ class ProjectEditor(QObject):
446
462
  job_action = ActionConfig("Job")
447
463
  self.dialog = self.action_config_dialog(job_action)
448
464
  if self.dialog.exec() == QDialog.Accepted:
449
- self.mark_as_modified()
465
+ self.mark_as_modified(True, "Add Job")
450
466
  self.project_jobs().append(job_action)
451
467
  self.add_list_item(self.job_list(), job_action, False)
452
468
  self.set_current_job(self.job_list_count() - 1)
@@ -467,7 +483,7 @@ class ProjectEditor(QObject):
467
483
  action.parent = self.get_current_job()
468
484
  self.dialog = self.action_config_dialog(action)
469
485
  if self.dialog.exec() == QDialog.Accepted:
470
- self.mark_as_modified()
486
+ self.mark_as_modified("Add Action")
471
487
  self.project_job(current_index).add_sub_action(action)
472
488
  self.add_list_item(self.action_list(), action, False)
473
489
  self.enable_delete_action_signal.emit(False)
@@ -507,7 +523,7 @@ class ProjectEditor(QObject):
507
523
  sub_action = ActionConfig(type_name)
508
524
  self.dialog = self.action_config_dialog(sub_action)
509
525
  if self.dialog.exec() == QDialog.Accepted:
510
- self.mark_as_modified()
526
+ self.mark_as_modified("Add Sub-action")
511
527
  action.add_sub_action(sub_action)
512
528
  self.on_job_selected(current_job_index)
513
529
  self.set_current_action(current_action_index)
@@ -535,7 +551,7 @@ class ProjectEditor(QObject):
535
551
  job_index = self.current_job_index()
536
552
  if 0 <= job_index < self.num_project_jobs():
537
553
  new_job_index = job_index
538
- self.mark_as_modified()
554
+ self.mark_as_modified(True, "Paste Job")
539
555
  self.project_jobs().insert(new_job_index, self.copy_buffer())
540
556
  self.set_current_job(new_job_index)
541
557
  self.set_current_action(new_job_index)
@@ -547,13 +563,13 @@ class ProjectEditor(QObject):
547
563
  if not pos.is_sub_action:
548
564
  if self.copy_buffer().type_name not in constants.ACTION_TYPES:
549
565
  return
550
- self.mark_as_modified()
566
+ self.mark_as_modified(True, "Paste Action")
551
567
  pos.actions.insert(pos.action_index, self.copy_buffer())
552
568
  else:
553
569
  if pos.action.type_name != constants.ACTION_COMBO or \
554
570
  self.copy_buffer().type_name not in constants.SUB_ACTION_TYPES:
555
571
  return
556
- self.mark_as_modified()
572
+ self.mark_as_modified(True, "Paste Sub-action")
557
573
  pos.sub_actions.insert(pos.sub_action_index, self.copy_buffer())
558
574
  new_row = new_row_after_paste(action_row, pos)
559
575
  self.refresh_ui_signal.emit(job_row, new_row)
@@ -597,7 +613,10 @@ class ProjectEditor(QObject):
597
613
  action_row = -1
598
614
  if current_action:
599
615
  if current_action.enabled() != enabled:
600
- self.mark_as_modified()
616
+ if enabled:
617
+ self.mark_as_modified(True, "Enable")
618
+ else:
619
+ self.mark_as_modified(True, "Disable")
601
620
  current_action.set_enabled(enabled)
602
621
  self.refresh_ui_signal.emit(job_row, action_row)
603
622
 
@@ -608,7 +627,7 @@ class ProjectEditor(QObject):
608
627
  self.set_enabled(False)
609
628
 
610
629
  def set_enabled_all(self, enable=True):
611
- self.mark_as_modified()
630
+ self.mark_as_modified(True, "Enable All")
612
631
  job_row = self.current_job_index()
613
632
  action_row = self.current_action_index()
614
633
  for j in self.project_jobs():
@@ -29,4 +29,4 @@ def create_select_file_paths_widget(value, placeholder, tag):
29
29
  if path:
30
30
  edit.setText(path)
31
31
 
32
- return edit, create_layout_widget_and_connect(button, edit, browse)
32
+ return create_layout_widget_and_connect(button, edit, browse)
@@ -0,0 +1,96 @@
1
+ # pylint: disable=C0114, C0115, C0116, E0611
2
+ import psutil
3
+ from PySide6.QtWidgets import QWidget, QHBoxLayout, QLabel, QProgressBar, QSizePolicy
4
+ from PySide6.QtCore import QTimer, Qt
5
+
6
+
7
+ class StatusBarSystemMonitor(QWidget):
8
+ def __init__(self, parent=None):
9
+ super().__init__(parent)
10
+ self.setup_ui()
11
+ self.setup_timer()
12
+ self.setFixedHeight(28)
13
+
14
+ def setup_ui(self):
15
+ bar_width = 100
16
+ bar_height = 20
17
+ layout = QHBoxLayout()
18
+ layout.setSpacing(10)
19
+ layout.setContentsMargins(0, 2, 0, 0)
20
+ layout.setAlignment(Qt.AlignLeft)
21
+ layout.setAlignment(Qt.AlignCenter)
22
+ cpu_widget = QWidget()
23
+ cpu_widget.setFixedSize(bar_width, bar_height)
24
+ self.cpu_bar = QProgressBar(cpu_widget)
25
+ self.cpu_bar.setRange(0, 100)
26
+ self.cpu_bar.setTextVisible(False)
27
+ self.cpu_bar.setGeometry(0, 0, bar_width, bar_height)
28
+ self.cpu_bar.setStyleSheet("""
29
+ QProgressBar {
30
+ border: 1px solid #cccccc;
31
+ border-radius: 5px;
32
+ background: #f0f0f0;
33
+ }
34
+ QProgressBar::chunk {
35
+ background-color: #3498db;
36
+ border-radius: 5px;
37
+ }
38
+ """)
39
+ self.cpu_label = QLabel("CPU: --%", cpu_widget)
40
+ self.cpu_label.setAlignment(Qt.AlignCenter)
41
+ self.cpu_label.setGeometry(0, 0, bar_width, bar_height)
42
+ self.cpu_label.setStyleSheet("""
43
+ QLabel {
44
+ color: #2c3e50;
45
+ font-weight: bold;
46
+ background: transparent;
47
+ font-size: 12px;
48
+ }
49
+ """)
50
+ mem_widget = QWidget()
51
+ mem_widget.setFixedSize(bar_width, bar_height)
52
+ self.mem_bar = QProgressBar(mem_widget)
53
+ self.mem_bar.setRange(0, 100)
54
+ self.mem_bar.setTextVisible(False)
55
+ self.mem_bar.setGeometry(0, 0, bar_width, bar_height)
56
+ self.mem_bar.setStyleSheet("""
57
+ QProgressBar {
58
+ border: 1px solid #cccccc;
59
+ border-radius: 5px;
60
+ background: #f0f0f0;
61
+ }
62
+ QProgressBar::chunk {
63
+ background-color: #2ecc71;
64
+ border-radius: 5px;
65
+ }
66
+ """)
67
+ self.mem_label = QLabel("MEM: --%", mem_widget)
68
+ self.mem_label.setAlignment(Qt.AlignCenter)
69
+ self.mem_label.setGeometry(0, 0, bar_width, bar_height)
70
+ self.mem_label.setStyleSheet("""
71
+ QLabel {
72
+ color: #2c3e50;
73
+ font-weight: bold;
74
+ background: transparent;
75
+ font-size: 12px;
76
+ }
77
+ """)
78
+ layout.addWidget(cpu_widget)
79
+ layout.addWidget(mem_widget)
80
+ layout.addStretch()
81
+ self.setLayout(layout)
82
+ self.setSizePolicy(QSizePolicy.Fixed, QSizePolicy.Fixed)
83
+
84
+ def setup_timer(self):
85
+ self.timer = QTimer()
86
+ self.timer.timeout.connect(self.update_stats)
87
+ self.timer.start(1000)
88
+
89
+ def update_stats(self):
90
+ cpu_percent = psutil.cpu_percent()
91
+ memory = psutil.virtual_memory()
92
+ mem_percent = memory.percent
93
+ self.cpu_bar.setValue(int(cpu_percent))
94
+ self.cpu_label.setText(f"CPU: {cpu_percent:.1f}%")
95
+ self.mem_bar.setValue(int(mem_percent))
96
+ self.mem_label.setText(f"MEM: {mem_percent:.1f}%")
@@ -12,10 +12,10 @@ class TabWidgetWithPlaceholder(QWidget):
12
12
 
13
13
  def __init__(self, parent=None):
14
14
  super().__init__(parent)
15
- self.layout = QVBoxLayout(self)
16
- self.layout.setContentsMargins(0, 0, 0, 0)
15
+ self.main_layout = QVBoxLayout(self)
16
+ self.main_layout.setContentsMargins(0, 0, 0, 0)
17
17
  self.stacked_widget = QStackedWidget()
18
- self.layout.addWidget(self.stacked_widget)
18
+ self.main_layout.addWidget(self.stacked_widget)
19
19
  self.tab_widget = QTabWidget()
20
20
  self.stacked_widget.addWidget(self.tab_widget)
21
21
  self.placeholder = QLabel()
@@ -39,13 +39,14 @@ class TimerProgressBar(QProgressBar):
39
39
  """)
40
40
 
41
41
  def time_str(self, secs):
42
- x = secs % 1
43
- ss = int(secs // 1)
42
+ xsecs = int(secs * 10)
43
+ x = xsecs % 10
44
+ ss = xsecs // 10
44
45
  s = ss % 60
45
46
  mm = ss // 60
46
47
  m = mm % 60
47
48
  h = mm // 60
48
- t_str = f"{s:02d}" + f"{x:.1f}s".lstrip('0')
49
+ t_str = f"{s:02d}.{x:1d}s"
49
50
  if m > 0:
50
51
  t_str = f"{m:02d}:{t_str}"
51
52
  if h > 0:
@@ -9,7 +9,7 @@ from .. gui.base_form_dialog import BaseFormDialog
9
9
 
10
10
  class ExifData(BaseFormDialog):
11
11
  def __init__(self, exif, parent=None):
12
- super().__init__("EXIF data", parent)
12
+ super().__init__("EXIF data", parent=parent)
13
13
  self.exif = exif
14
14
  self.create_form()
15
15
  button_container = QWidget()
@@ -658,6 +658,8 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
658
658
  def quit(self):
659
659
  if self.check_unsaved_changes():
660
660
  self.close()
661
+ return True
662
+ return False
661
663
 
662
664
  def undo(self):
663
665
  if self.undo_manager.undo(self.master_layer()):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shinestacker
3
- Version: 1.2.0
3
+ Version: 1.3.0
4
4
  Summary: ShineStacker
5
5
  Author-email: Luca Lista <luka.lista@gmail.com>
6
6
  License-Expression: LGPL-3.0
@@ -20,6 +20,7 @@ Requires-Dist: numpy
20
20
  Requires-Dist: opencv_python
21
21
  Requires-Dist: pillow
22
22
  Requires-Dist: psdtags
23
+ Requires-Dist: psutil
23
24
  Requires-Dist: PySide6
24
25
  Requires-Dist: scipy
25
26
  Requires-Dist: tifffile
@@ -69,6 +70,10 @@ The GUI has two main working areas:
69
70
 
70
71
  <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/gui-retouch.png' width="600" referrerpolicy="no-referrer">
71
72
 
73
+ # Resources
74
+
75
+ 🌍 [Website on WordPress](https://shinestacker.wordpress.com) • 📖 [Main documentation](https://shinestacker.readthedocs.io) • 📝 [Changelog](https://github.com/lucalista/shinestacker/blob/main/CHANGELOG.md)
76
+
72
77
  # Note for macOS users
73
78
 
74
79
  **The following note is only relevant if you download the application as compressed archive from the [release page](https://github.com/lucalista/shinestacker/releases).**
@@ -88,11 +93,6 @@ xattr -cr ~/Downloads/shinestacker/shinestacker.app
88
93
 
89
94
  macOS adds a quarantine flag to all files downloaded from the internet. The above command removes that flag while preserving all other application functionality.
90
95
 
91
- # Resources
92
-
93
- 🌍 [Website on WordPress](https://shinestacker.wordpress.com) • 📖 [Main documentation](https://shinestacker.readthedocs.io) • 📝 [Changelog](https://github.com/lucalista/shinestacker/blob/main/CHANGELOG.md)
94
-
95
-
96
96
  # Credits
97
97
 
98
98
  The first version of the core focus stack algorithm was initially inspired by the [Laplacian pyramids method](https://github.com/sjawhar/focus-stacking) implementation by Sami Jawhar that was used under permission of the author. The implementation in the latest releases was rewritten from the original code.
@@ -1,60 +1,64 @@
1
1
  shinestacker/__init__.py,sha256=uq2fjAw2z_6TpH3mOcWFZ98GoEPRsNhTAK8N0MMm_e8,448
2
- shinestacker/_version.py,sha256=5GblYyMbk8JySosj59Rvi2uzLqfP-DAs77ikwTafXT4,21
2
+ shinestacker/_version.py,sha256=tP8c5-8yPCRUk61qFQy1AQFkbfy99N-tga3OUiJT1MA,21
3
3
  shinestacker/algorithms/__init__.py,sha256=1FwVJ3w9GGbFFkjYJRUedTvcdE4j0ieSgaH9RC9iCY4,877
4
- shinestacker/algorithms/align.py,sha256=BsE3rp4EfJyWTTbC6U1leQQqURD724YLAmxY-1wM-ok,22558
5
- shinestacker/algorithms/balance.py,sha256=rz_lmBwnJBYLnYt6yJ30BXWmMwpmmmA3rKUznEWY0eo,23463
6
- shinestacker/algorithms/base_stack_algo.py,sha256=W-VSrCF0-lE_OOsxsnZvJ3BI0NqRKIRMciQV-ui5t_g,2515
4
+ shinestacker/algorithms/align.py,sha256=tVgLzn0vV9sIrFj4fZezBYHxhK2o_xrjCm8k55OoAYQ,23514
5
+ shinestacker/algorithms/align_auto.py,sha256=wIv9iSOhzxdQiPo0GG1Dv9WSLgFGjCJM7I2uqMl-cGc,3093
6
+ shinestacker/algorithms/align_parallel.py,sha256=3tGhrSr9IPz8fv0FUW-AAq9-e3BEJadwS9DYTZm-Sgo,14812
7
+ shinestacker/algorithms/balance.py,sha256=KJ8eXWYyqRVQa7_iZWQhZZ9BfO4wNve5nhZxunK7B5k,23583
8
+ shinestacker/algorithms/base_stack_algo.py,sha256=jiqckBGQnP536OPfTW0Kzfawcpk1L-bByhCjoyQyBGw,2841
7
9
  shinestacker/algorithms/denoise.py,sha256=GL3Z4_6MHxSa7Wo4ZzQECZS87tHBFqO0sIVF_jPuYQU,426
8
10
  shinestacker/algorithms/depth_map.py,sha256=m0_Qm8FLDeSWyQEMNx29PzXp_VFGar7gY3jWxq_10t8,5713
9
11
  shinestacker/algorithms/exif.py,sha256=SM4ZDDe8hCJ3xY6053FNndOiwzEStzdp0WrXurlcHVc,9429
10
- shinestacker/algorithms/multilayer.py,sha256=5aIaIU2GX1eoZbGL4z-Xo8XiFlCvIcZKRDYnbjjdojI,9906
11
- shinestacker/algorithms/noise_detection.py,sha256=lP5iJLFA5xWN-tpyNDH9AWuoZsL7rKhrxwDcBBc9nCk,9250
12
- shinestacker/algorithms/pyramid.py,sha256=lmd-lw4bzrpcfBaLnBXHuOJ9J7-5sWq4dC9p_EejqXA,8881
13
- shinestacker/algorithms/pyramid_auto.py,sha256=ByDH7Xblqj4YfNwsCWwN3wv2xL6gYp2lFnvpNPSEawM,6161
14
- shinestacker/algorithms/pyramid_tiles.py,sha256=SzjTSheme8MP8nQXfOu8QHbzrtpuQX2aIsBVr5aM4Mc,12165
12
+ shinestacker/algorithms/multilayer.py,sha256=WlB4L5oY9qra3w7Qahg-tqO6S_s3pMB_LmGR8PPR_7w,9904
13
+ shinestacker/algorithms/noise_detection.py,sha256=KSdMDER5GNOCTD6DIAbjJvRDFvrVorul3xr5maXtCh8,9298
14
+ shinestacker/algorithms/pyramid.py,sha256=drGLeGRQ2_QMmbpOFaFI7kSOSuvraOcsWJNTXbBUT6k,8838
15
+ shinestacker/algorithms/pyramid_auto.py,sha256=TxIkOrzS2i9Dz994L_YYeL4CNRRjTWkygGL8KDjDuWo,6602
16
+ shinestacker/algorithms/pyramid_tiles.py,sha256=mjmvJil0olQJSUWDMR5Hkuu1PbI_TomTUZi8lsC10cU,12356
15
17
  shinestacker/algorithms/sharpen.py,sha256=h7PMJBYxucg194Usp_6pvItPUMFYbT-ebAc_-7XBFUw,949
16
- shinestacker/algorithms/stack.py,sha256=fLDps52QT8bsC3Pz8niU0h7cGc70q9goKsgA8wvu_Bg,5054
17
- shinestacker/algorithms/stack_framework.py,sha256=YKk-JoLV6IVlTiCbo6e-Hg2dF3hdz4CE6WFIXhMXBy8,12818
18
+ shinestacker/algorithms/stack.py,sha256=V9YX0CbNWrgAo7_uti64rmmuwU6RsRcjDoBpsES4aSE,5137
19
+ shinestacker/algorithms/stack_framework.py,sha256=L5fXv07CuO4gHBBnoHS6U9ajWB-nDYCKBroutAQzd2U,13890
18
20
  shinestacker/algorithms/utils.py,sha256=jImR2XF73gsLRZMic0kv8cyCuO2Zq21tX4kUhaTcfzI,11301
19
- shinestacker/algorithms/vignetting.py,sha256=stzrWTxJIIByq_mOI0ofE-7b0bL5MLm9dhlj_E-Kxv0,10165
21
+ shinestacker/algorithms/vignetting.py,sha256=MwhsTqmNMc6GdQl_Bbuyo8IUQPn1OoeGcWj1L6Jjybc,10274
20
22
  shinestacker/algorithms/white_balance.py,sha256=PMKsBtxOSn5aRr_Gkx1StHS4eN6kBN2EhNnhg4UG24g,501
21
23
  shinestacker/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
22
24
  shinestacker/app/about_dialog.py,sha256=pkH7nnxUP8yc0D3vRGd1jRb5cwi1nDVbQRk_OC9yLk8,4144
23
25
  shinestacker/app/gui_utils.py,sha256=08TrCj2gFGsNsF6hG7ySO2y7wcQakM5PzERkeplqNFs,2344
24
26
  shinestacker/app/help_menu.py,sha256=g8lKG_xZmXtNQaC3SIRzyROKVWva_PLEgZsQWh6zUcQ,499
25
- shinestacker/app/main.py,sha256=rcXlzsPErIN9ItbucsB6nz103vwNvsff6wSADOwFt6I,10301
27
+ shinestacker/app/main.py,sha256=VPZx_qcmFc1YmWicFjKcfQrH1br0VR5ZGuE64nsD3n0,10414
26
28
  shinestacker/app/open_frames.py,sha256=bsu32iJSYJQLe_tQQbvAU5DuMDVX6MRuNdE7B5lojZc,1488
27
29
  shinestacker/app/project.py,sha256=W0u715LZne_PNJvg9msSy27ybIjgDXiEAQdJ7_6BjYI,2774
28
30
  shinestacker/app/retouch.py,sha256=ZQ-nRKnHo6xurcP34RNqaAWkmuGBjJ5jE05hTQ_ycis,2482
29
31
  shinestacker/config/__init__.py,sha256=aXxi-LmAvXd0daIFrVnTHE5OCaYeK1uf1BKMr7oaXQs,197
30
32
  shinestacker/config/config.py,sha256=eBko2D3ADhLTIm9X6hB_a_WsIjwgfE-qmBVkhP1XSvc,1636
31
- shinestacker/config/constants.py,sha256=ncuiYyA5NgggkGbazQbccJHGzXJnl37AOyxEB-GLoB0,7116
32
- shinestacker/config/gui_constants.py,sha256=5DR-ET1oeMMD7lIsjvAwSuln89A7I9wy9VuAeRo2G64,2575
33
+ shinestacker/config/constants.py,sha256=EEdr7pZg4JpbIjUWaP7kJQfTuBB85FN739myDNAfn8A,8301
34
+ shinestacker/config/gui_constants.py,sha256=i2dHeGRnY-Wc3dVjpIEKNNxEQYhfn18IEUcvl96r89I,2575
33
35
  shinestacker/core/__init__.py,sha256=IUEIx6SQ3DygDEHN3_E6uKpHjHtUa4a_U_1dLd_8yEU,484
34
36
  shinestacker/core/colors.py,sha256=kr_tJA1iRsdck2JaYDb2lS-codZ4Ty9gdu3kHfiWvuM,1340
35
- shinestacker/core/core_utils.py,sha256=ulJhzen5McAb5n6wWNA_KB4U_PdTEr-H2TCQkVKUaOw,1421
37
+ shinestacker/core/core_utils.py,sha256=BlHbvQmDQXSONNkDx0zq_xiDTsfrS0N7r1DBTUPm8CE,1523
36
38
  shinestacker/core/exceptions.py,sha256=2-noG-ORAGdvDhL8jBQFs0xxZS4fI6UIkMqrWekgk2c,1618
37
- shinestacker/core/framework.py,sha256=zCnJuQrHNpwEgJW23_BgS7iQrLolRWTAMB1oRp_a7Kk,7447
39
+ shinestacker/core/framework.py,sha256=QaTfnzEUHwzlbyFG7KzeyteckTSWHWEEJE4d5Tc8H18,11015
38
40
  shinestacker/core/logging.py,sha256=9SuSSy9Usbh7zqmLYMqkmy-VBkOJW000lwqAR0XQs30,3067
39
41
  shinestacker/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
- shinestacker/gui/action_config.py,sha256=-J4UCbXHHlIPxRDL0Veglt4brngJP7_zHvrWof9pzsM,16895
41
- shinestacker/gui/action_config_dialog.py,sha256=USaF2QeQRNXgzLRbkV6hvVRzOMkMVyTtsAzEAMqCr-g,35191
42
- shinestacker/gui/base_form_dialog.py,sha256=M_BvjpqUWe9YuecKg0pF3vUJ-1mqF24HjKujst2j3BA,753
42
+ shinestacker/gui/action_config.py,sha256=BhKssL0xHPdNkE5hDkBy7Uw5rZOLZ8PU8hz-Nt_CdwA,19038
43
+ shinestacker/gui/action_config_dialog.py,sha256=SbYL1fxyI-N5Pt_vgkNtJw1s2f-3gd9LlfLnxw4qtDI,41894
44
+ shinestacker/gui/base_form_dialog.py,sha256=KAUQNtmJazttmOIe4E4pFifbtvcByTAhtCmcIYeA4UE,766
43
45
  shinestacker/gui/colors.py,sha256=m0pQQ-uvtIN1xmb_-N06BvC7pZYZZnq59ZSEJwutHuk,1432
44
46
  shinestacker/gui/flow_layout.py,sha256=3yBU_z7VtvHKpx1H97CHVd81eq9pe1Dcja2EZBGGKcI,3791
45
- shinestacker/gui/gui_images.py,sha256=e0KAXSPruZoRHrajfdlmOKBYoRJJQBDan1jgs7YFltY,5678
47
+ shinestacker/gui/folder_file_selection.py,sha256=V6Pdu6EtQpBzCmh_yTibJ75by6Nf-4YHgAAWWTYl-VA,3966
48
+ shinestacker/gui/gui_images.py,sha256=k39DpdsxcmYoRdHNNZj6OpFAas0GOHS4JSG542wfheg,5728
46
49
  shinestacker/gui/gui_logging.py,sha256=kiZcrC2AFYCWgPZo0O5SKw-E5cFrezwf4anS3HjPuNw,8168
47
- shinestacker/gui/gui_run.py,sha256=ahbl6xMFR78QrcBbEDMuaQpkxw6DBFtSX8DCMIyr_7I,15439
48
- shinestacker/gui/main_window.py,sha256=kgozNZWqiPePZB5oX3Twdj_Z_f1RcdoLT2PFMzT0fRM,24361
49
- shinestacker/gui/menu_manager.py,sha256=SvYV7Dmz4cqoFQtRrO4JQ0uECZ4rL3EQdQg5jNakV6Y,10015
50
- shinestacker/gui/new_project.py,sha256=Iu8Gm3oRx6tCokv-Ywm_64wsNNxj_QhU1BsyoeLkJaQ,11211
51
- shinestacker/gui/project_controller.py,sha256=ooHunFKY2-dRBoyx4r3T8vs7UOpGDZUHHaSSR5V8J_E,15821
52
- shinestacker/gui/project_converter.py,sha256=bNyC1_D_MjcTOCPlQln6CIIlX818-sw_j2omrfQIGQs,7279
53
- shinestacker/gui/project_editor.py,sha256=UPXNwjpKJi7lgaohlbtojkTkotXeGY7l_W8pVII-UM0,24208
50
+ shinestacker/gui/gui_run.py,sha256=MQPE7muBPw3KTrGDsg-MBcC6nNFYuvvojfXfq84kR8o,15759
51
+ shinestacker/gui/main_window.py,sha256=GUrGhZerYXCMwMeB2K51oSHaJAgtMTPj6AfOgkkmwoc,24521
52
+ shinestacker/gui/menu_manager.py,sha256=ZsND0e-vM263-6unwKUtYAtLbb4YgvIQabh5lCiT2ow,10179
53
+ shinestacker/gui/new_project.py,sha256=Gwg57Ze3D5AQeTCbuI2oNLMqrDnpFRIfNOfWAbO3nUk,16145
54
+ shinestacker/gui/project_controller.py,sha256=hvSNGrQM-yDHH3e132oouxBtgRv7mUG7lrigUl21BsA,16043
55
+ shinestacker/gui/project_converter.py,sha256=Gmna0HwbvACcXiX74TaQYumif8ZV8sZ2APLTMM-L1mU,7436
56
+ shinestacker/gui/project_editor.py,sha256=j7bBH4u5g6CO8Mv541ceDrs7GKsWZ-eVKpCPTKF6zVY,25368
54
57
  shinestacker/gui/project_model.py,sha256=eRUmH3QmRzDtPtZoxgT6amKzN8_5XzwjHgEJeL-_JOE,4263
55
- shinestacker/gui/select_path_widget.py,sha256=OfQImOmkzbvl5BBshmb7ePWrSGDJQ8VvyaAOypHAGd4,1023
56
- shinestacker/gui/tab_widget.py,sha256=6iUifK-wu0EzjVFccKHirhA2fENglVi6xREKiD96aaY,2950
57
- shinestacker/gui/time_progress_bar.py,sha256=4_5DT_EzFdVJi5bgd9TEpoTJXeU3M08CF91cZLi75Wc,3016
58
+ shinestacker/gui/select_path_widget.py,sha256=HSwgSr702w5Et4c-6nkRXnIpm1KFqKJetAF5xQNa5zI,1017
59
+ shinestacker/gui/sys_mon.py,sha256=h_Mg99bceXPSX_oK8t_IuRJoSsVo3uthcTnwe8tOYhM,3378
60
+ shinestacker/gui/tab_widget.py,sha256=VgRmuktWXCgbXbV7c1Tho0--W5_EmmzXPfzRZgwhGfg,2965
61
+ shinestacker/gui/time_progress_bar.py,sha256=7_sllrQgayjRh__mwJ0-4lghXIakuRAx8wWucJ6olYs,3028
58
62
  shinestacker/gui/ico/focus_stack_bkg.png,sha256=Q86TgqvKEi_IzKI8m6aZB2a3T40UkDtexf2PdeBM9XE,163151
59
63
  shinestacker/gui/ico/shinestacker.icns,sha256=3IshIOv0uFexYsAEPkE9xiyuw8mB5X5gffekOUhFlt0,45278
60
64
  shinestacker/gui/ico/shinestacker.ico,sha256=8IMRk-toObWUz8iDXA-zHBWQ8Ps3vXN5u5ZEyw7sP3c,109613
@@ -72,11 +76,11 @@ shinestacker/retouch/brush_preview.py,sha256=QKD3pL7n7YJbIibinUFYKv7lkyq_AWLpt6o
72
76
  shinestacker/retouch/brush_tool.py,sha256=nxnEuvTioPNw1WeWsT20X1zl-LNZ8i-1ExOcihikEjk,8618
73
77
  shinestacker/retouch/denoise_filter.py,sha256=TDUHzhRKlKvCa3D5SCYCZKTpjcl81kGwmONsgSDtO1k,440
74
78
  shinestacker/retouch/display_manager.py,sha256=XPbOBmoYc_jNA791WkWkOSaFHb0ztCZechl2p2KSlwQ,9597
75
- shinestacker/retouch/exif_data.py,sha256=WF40bTh0bwIqSQLMkGMCycEED06_q35-TqrBNAyaB-k,1889
79
+ shinestacker/retouch/exif_data.py,sha256=LF-fRXW-reMq-xJ_QRE5j8DC2LVGKIlC6MR3QbC1cdg,1896
76
80
  shinestacker/retouch/file_loader.py,sha256=z02-A8_uDZxayI1NFTxT2GVUvEBWStchX9hlN1o5-0U,4784
77
81
  shinestacker/retouch/filter_manager.py,sha256=SdYIZkZBUvuB6wDG0moGWav5sfEvIcB9ioUJR5wJFts,388
78
82
  shinestacker/retouch/icon_container.py,sha256=6gw1HO1bC2FrdB4dc_iH81DQuLjzuvRGksZ2hKLT9yA,585
79
- shinestacker/retouch/image_editor_ui.py,sha256=cMGiqyPGqJmBaXMAc0WImDPf_hmxO4KiJtaaSpiW9EU,29869
83
+ shinestacker/retouch/image_editor_ui.py,sha256=eYOHR_ihekQ7bWZUk7jXqNDpi5WYOAyTgvi3_QxnmTE,29914
80
84
  shinestacker/retouch/image_viewer.py,sha256=3ebrLHTDtGd_EbIT2nNFRUjH836rblmmK7jZ62YcJ2U,19564
81
85
  shinestacker/retouch/io_gui_handler.py,sha256=pT-49uP0GROMOjZ70LoMLgXHnqSDq8ieAlAKGw0t1TM,11418
82
86
  shinestacker/retouch/io_manager.py,sha256=JUAA--AK0mVa1PTErJTnBFjaXIle5Qs7Ow0Wkd8at0o,2437
@@ -86,9 +90,9 @@ shinestacker/retouch/undo_manager.py,sha256=_ekbcOLcPbQLY7t-o8wf-b1uA6OPY9rRyLM-
86
90
  shinestacker/retouch/unsharp_mask_filter.py,sha256=uFnth8fpZFGhdIgJCnS8x5v6lBQgJ3hX0CBke9pFXeM,3510
87
91
  shinestacker/retouch/vignetting_filter.py,sha256=MA97rQkSL0D-Nh-n2L4AiPR064RoTROkvza4tw84g9U,3658
88
92
  shinestacker/retouch/white_balance_filter.py,sha256=glMBYlmrF-i_OrB3sGUpjZE6X4FQdyLC4GBy2bWtaFc,6056
89
- shinestacker-1.2.0.dist-info/licenses/LICENSE,sha256=pWgb-bBdsU2Gd2kwAXxketnm5W_2u8_fIeWEgojfrxs,7651
90
- shinestacker-1.2.0.dist-info/METADATA,sha256=01kq9TSpVY2_U67ZhjbPIORbMmWTOWpuiiOFty3IM1Y,6951
91
- shinestacker-1.2.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
92
- shinestacker-1.2.0.dist-info/entry_points.txt,sha256=SY6g1LqtMmp23q1DGwLUDT_dhLX9iss8DvWkiWLyo_4,166
93
- shinestacker-1.2.0.dist-info/top_level.txt,sha256=MhijwnBVX5psfsyX8JZjqp3SYiWPsKe69f3Gnyze4Fw,13
94
- shinestacker-1.2.0.dist-info/RECORD,,
93
+ shinestacker-1.3.0.dist-info/licenses/LICENSE,sha256=pWgb-bBdsU2Gd2kwAXxketnm5W_2u8_fIeWEgojfrxs,7651
94
+ shinestacker-1.3.0.dist-info/METADATA,sha256=lJFdzbmkUcsMTN05Lyivq-uM3-IuKUyi-PEfR8JptyA,6972
95
+ shinestacker-1.3.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
96
+ shinestacker-1.3.0.dist-info/entry_points.txt,sha256=SY6g1LqtMmp23q1DGwLUDT_dhLX9iss8DvWkiWLyo_4,166
97
+ shinestacker-1.3.0.dist-info/top_level.txt,sha256=MhijwnBVX5psfsyX8JZjqp3SYiWPsKe69f3Gnyze4Fw,13
98
+ shinestacker-1.3.0.dist-info/RECORD,,