shinestacker 1.0.2__py3-none-any.whl → 1.0.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/_version.py CHANGED
@@ -1 +1 @@
1
- __version__ = '1.0.2'
1
+ __version__ = '1.0.4'
@@ -10,7 +10,7 @@ from PIL.TiffImagePlugin import IFDRational
10
10
  from PIL.ExifTags import TAGS
11
11
  import tifffile
12
12
  from .. config.constants import constants
13
- from .utils import write_img
13
+ from .utils import write_img, extension_jpg, extension_tif, extension_png
14
14
 
15
15
  IMAGEWIDTH = 256
16
16
  IMAGELENGTH = 257
@@ -48,11 +48,10 @@ def extract_enclosed_data_for_jpg(data, head, foot):
48
48
  def get_exif(exif_filename):
49
49
  if not os.path.isfile(exif_filename):
50
50
  raise RuntimeError(f"File does not exist: {exif_filename}")
51
- ext = exif_filename.split(".")[-1]
52
51
  image = Image.open(exif_filename)
53
- if ext in ('tif', 'tiff'):
52
+ if extension_tif(exif_filename):
54
53
  return image.tag_v2 if hasattr(image, 'tag_v2') else image.getexif()
55
- if ext in ('jpeg', 'jpg'):
54
+ if extension_jpg(exif_filename):
56
55
  exif_data = image.getexif()
57
56
  with open(exif_filename, 'rb') as f:
58
57
  data = extract_enclosed_data_for_jpg(f.read(), b'<?xpacket', b'<?xpacket end="w"?>')
@@ -158,42 +157,40 @@ def write_image_with_exif_data(exif, image, out_filename, verbose=False):
158
157
  if exif is None:
159
158
  write_img(out_filename, image)
160
159
  return None
161
- ext = out_filename.split(".")[-1]
162
160
  if verbose:
163
161
  print_exif(exif)
164
- if ext in ('jpeg', 'jpg'):
162
+ if extension_jpg(out_filename):
165
163
  cv2.imwrite(out_filename, image, [int(cv2.IMWRITE_JPEG_QUALITY), 100])
166
164
  add_exif_data_to_jpg_file(exif, out_filename, out_filename, verbose)
167
- elif ext in ('tiff', 'tif'):
165
+ elif extension_tif(out_filename):
168
166
  metadata = {"description": f"image generated with {constants.APP_STRING} package"}
169
167
  extra_tags, exif_tags = exif_extra_tags_for_tif(exif)
170
168
  tifffile.imwrite(out_filename, image, metadata=metadata, compression='adobe_deflate',
171
169
  extratags=extra_tags, **exif_tags)
172
- elif ext == 'png':
170
+ elif extension_png(out_filename):
173
171
  image.save(out_filename, 'PNG', exif=exif, quality=100)
174
172
  return exif
175
173
 
176
174
 
177
175
  def save_exif_data(exif, in_filename, out_filename=None, verbose=False):
178
- ext = in_filename.split(".")[-1]
179
176
  if out_filename is None:
180
177
  out_filename = in_filename
181
178
  if exif is None:
182
179
  raise RuntimeError('No exif data provided.')
183
180
  if verbose:
184
181
  print_exif(exif)
185
- if ext in ('tiff', 'tif'):
182
+ if extension_tif(in_filename):
186
183
  image_new = tifffile.imread(in_filename)
187
184
  else:
188
185
  image_new = Image.open(in_filename)
189
- if ext in ('jpeg', 'jpg'):
186
+ if extension_jpg(in_filename):
190
187
  add_exif_data_to_jpg_file(exif, in_filename, out_filename, verbose)
191
- elif ext in ('tiff', 'tif'):
188
+ elif extension_tif(in_filename):
192
189
  metadata = {"description": f"image generated with {constants.APP_STRING} package"}
193
190
  extra_tags, exif_tags = exif_extra_tags_for_tif(exif)
194
191
  tifffile.imwrite(out_filename, image_new, metadata=metadata, compression='adobe_deflate',
195
192
  extratags=extra_tags, **exif_tags)
196
- elif ext == 'png':
193
+ elif extension_png(in_filename):
197
194
  image_new.save(out_filename, 'PNG', exif=exif, quality=100)
198
195
  return exif
199
196
 
@@ -13,6 +13,7 @@ from .. config.constants import constants
13
13
  from .. config.config import config
14
14
  from .. core.colors import color_str
15
15
  from .. core.framework import JobBase
16
+ from .utils import EXTENSIONS_TIF, EXTENSIONS_JPG, EXTENSIONS_PNG
16
17
  from .stack_framework import FrameMultiDirectory
17
18
  from .exif import exif_extra_tags_for_tif, get_exif
18
19
 
@@ -27,13 +28,13 @@ def write_multilayer_tiff(input_files, output_file, labels=None, exif_path='', c
27
28
  msg = ", ".join(extensions)
28
29
  raise RuntimeError("All input files must have the same extension. "
29
30
  f"Input list has the following extensions: {msg}.")
30
- extension = extensions[0]
31
- if extension in ('tif', 'tiff'):
31
+ extension = extensions[0].lower()
32
+ if extension in EXTENSIONS_TIF:
32
33
  images = [tifffile.imread(p) for p in input_files]
33
- elif extension in ('jpg', 'jpeg'):
34
+ elif extension in EXTENSIONS_JPG:
34
35
  images = [cv2.imread(p) for p in input_files]
35
36
  images = [cv2.cvtColor(i, cv2.COLOR_BGR2RGB) for i in images]
36
- elif extension == 'png':
37
+ elif extension in EXTENSIONS_PNG:
37
38
  images = [cv2.imread(p, cv2.IMREAD_UNCHANGED) for p in input_files]
38
39
  images = [cv2.cvtColor(i, cv2.COLOR_BGR2RGB) for i in images]
39
40
  if labels is None:
@@ -177,7 +178,7 @@ class MultiLayer(JobBase, FrameMultiDirectory):
177
178
  raise RuntimeError("input_path option must contain a path or an array of paths")
178
179
  if len(paths) == 0:
179
180
  self.print_message(color_str("no input paths specified",
180
- constants.LOG_COLOR_LEVEL_ALERT),
181
+ constants.LOG_COLOR_ALERT),
181
182
  level=logging.WARNING)
182
183
  return
183
184
  files = self.folder_filelist()
@@ -186,7 +187,7 @@ class MultiLayer(JobBase, FrameMultiDirectory):
186
187
  color_str(f"no input in {len(paths)} specified path" +
187
188
  ('s' if len(paths) > 1 else '') + ": "
188
189
  ", ".join([f"'{p}'" for p in paths]),
189
- constants.LOG_COLOR_LEVEL_ALERT),
190
+ constants.LOG_COLOR_ALERT),
190
191
  level=logging.WARNING)
191
192
  return
192
193
  self.print_message(color_str("merging frames in " + self.folder_list_str(),
@@ -92,7 +92,7 @@ class FramePaths:
92
92
  ('' if self.working_path[-1] == '/' else '/') + self.plot_path
93
93
  if not os.path.exists(self.plot_path):
94
94
  os.makedirs(self.plot_path)
95
- if self.input_path == '':
95
+ if self.input_path in ['', []]:
96
96
  if len(job.paths) == 0:
97
97
  raise RuntimeError(f"Job {job.name} does not have any configured path")
98
98
  self.input_path = job.paths[-1]
@@ -8,25 +8,65 @@ from .. config.config import config
8
8
  from .. core.exceptions import ShapeError, BitDepthError
9
9
 
10
10
 
11
+ def get_path_extension(path):
12
+ return os.path.splitext(path)[1].lstrip('.')
13
+
14
+
15
+ EXTENSIONS_TIF = ['tif', 'tiff']
16
+ EXTENSIONS_JPG = ['jpg', 'jpeg']
17
+ EXTENSIONS_PNG = ['png']
18
+ EXTENSIONS_PDF = ['pdf']
19
+
20
+
21
+ def extension_in(path, exts):
22
+ return get_path_extension(path).lower() in exts
23
+
24
+
25
+ def extension_tif(path):
26
+ return extension_in(path, EXTENSIONS_TIF)
27
+
28
+
29
+ def extension_jpg(path):
30
+ return extension_in(path, EXTENSIONS_JPG)
31
+
32
+
33
+ def extension_png(path):
34
+ return extension_in(path, EXTENSIONS_PNG)
35
+
36
+
37
+ def extension_pdf(path):
38
+ return extension_in(path, EXTENSIONS_PDF)
39
+
40
+
41
+ def extension_tif_jpg(path):
42
+ return extension_in(path, EXTENSIONS_TIF + EXTENSIONS_JPG)
43
+
44
+
45
+ def extension_tif_png(path):
46
+ return extension_in(path, EXTENSIONS_TIF + EXTENSIONS_PNG)
47
+
48
+
49
+ def extension_jpg_png(path):
50
+ return extension_in(path, EXTENSIONS_JPG + EXTENSIONS_PNG)
51
+
52
+
11
53
  def read_img(file_path):
12
54
  if not os.path.isfile(file_path):
13
55
  raise RuntimeError("File does not exist: " + file_path)
14
- ext = file_path.split(".")[-1]
15
56
  img = None
16
- if ext in ['jpeg', 'jpg']:
57
+ if extension_jpg(file_path):
17
58
  img = cv2.imread(file_path)
18
- elif ext in ['tiff', 'tif', 'png']:
59
+ elif extension_tif_png(file_path):
19
60
  img = cv2.imread(file_path, cv2.IMREAD_UNCHANGED)
20
61
  return img
21
62
 
22
63
 
23
64
  def write_img(file_path, img):
24
- ext = file_path.split(".")[-1]
25
- if ext in ['jpeg', 'jpg']:
65
+ if extension_jpg(file_path):
26
66
  cv2.imwrite(file_path, img, [int(cv2.IMWRITE_JPEG_QUALITY), 100])
27
- elif ext in ['tiff', 'tif']:
67
+ elif extension_tif(file_path):
28
68
  cv2.imwrite(file_path, img, [int(cv2.IMWRITE_TIFF_COMPRESSION), 1])
29
- elif ext == 'png':
69
+ elif extension_png(file_path):
30
70
  cv2.imwrite(file_path, img)
31
71
 
32
72
 
shinestacker/app/main.py CHANGED
@@ -6,9 +6,10 @@ import argparse
6
6
  import matplotlib
7
7
  import matplotlib.backends.backend_pdf
8
8
  matplotlib.use('agg')
9
- from PySide6.QtWidgets import QApplication, QMainWindow, QStackedWidget, QMenu
9
+ from PySide6.QtWidgets import (QApplication, QMainWindow, QVBoxLayout, QHBoxLayout, QStackedWidget,
10
+ QMenu, QMessageBox, QDialog, QLabel, QListWidget, QPushButton)
10
11
  from PySide6.QtGui import QAction, QIcon, QGuiApplication
11
- from PySide6.QtCore import Qt, QEvent, QTimer
12
+ from PySide6.QtCore import Qt, QEvent, QTimer, Signal
12
13
  from shinestacker.config.config import config
13
14
  config.init(DISABLE_TQDM=True, COMBINED_APP=True, DONT_USE_NATIVE_MENU=True)
14
15
  from shinestacker.config.constants import constants
@@ -20,6 +21,59 @@ from shinestacker.app.help_menu import add_help_action
20
21
  from shinestacker.app.open_frames import open_frames
21
22
 
22
23
 
24
+ class SelectionDialog(QDialog):
25
+ selection_made = Signal(str, bool)
26
+
27
+ def __init__(self, title, message, items, parent=None):
28
+ super().__init__(parent)
29
+ self.setWindowTitle(title)
30
+ self.setModal(True)
31
+ self.selected_item = ""
32
+ self.setup_ui(message, items)
33
+ self.setMinimumSize(300, 300)
34
+
35
+ def setup_ui(self, message, items):
36
+ layout = QVBoxLayout(self)
37
+ if message:
38
+ label = QLabel(message)
39
+ layout.addWidget(label)
40
+ self.list_widget = QListWidget()
41
+ self.list_widget.addItems(items)
42
+ self.list_widget.itemSelectionChanged.connect(self.on_selection_changed)
43
+ layout.addWidget(self.list_widget)
44
+ button_layout = QHBoxLayout()
45
+ self.ok_button = QPushButton("OK")
46
+ self.ok_button.clicked.connect(self.accept)
47
+ self.ok_button.setEnabled(False)
48
+ button_layout.addWidget(self.ok_button)
49
+ cancel_button = QPushButton("Cancel")
50
+ cancel_button.clicked.connect(self.reject)
51
+ button_layout.addWidget(cancel_button)
52
+ layout.addLayout(button_layout)
53
+
54
+ def on_selection_changed(self):
55
+ selected_items = self.list_widget.selectedItems()
56
+ self.ok_button.setEnabled(len(selected_items) > 0)
57
+
58
+ def accept(self):
59
+ selected_items = self.list_widget.selectedItems()
60
+ if selected_items:
61
+ self.selected_item = selected_items[0].text()
62
+ self.selection_made.emit(self.selected_item, True)
63
+ super().accept()
64
+
65
+ def reject(self):
66
+ self.selected_item = ""
67
+ self.selection_made.emit("", False)
68
+ super().reject()
69
+
70
+ @staticmethod
71
+ def get_selection(title, message, items, parent=None):
72
+ dialog = SelectionDialog(title, message, items, parent)
73
+ result = dialog.exec()
74
+ return dialog.selected_item if result == QDialog.Accepted else ""
75
+
76
+
23
77
  class MainApp(QMainWindow):
24
78
  def __init__(self):
25
79
  super().__init__()
@@ -41,6 +95,17 @@ class MainApp(QMainWindow):
41
95
  self.retouch_window.menuBar().actions()[0], self.app_menu)
42
96
  add_help_action(self.project_window)
43
97
  add_help_action(self.retouch_window)
98
+ file_menu = None
99
+ for action in self.retouch_window.menuBar().actions():
100
+ if action.text() == "&File":
101
+ file_menu = action.menu()
102
+ break
103
+ if file_menu is not None:
104
+ import_action = QAction("Import From Current Project", self)
105
+ import_action.triggered.connect(self.import_from_project)
106
+ file_menu.addAction(import_action)
107
+ else:
108
+ raise RuntimeError("File menu not found!")
44
109
 
45
110
  def switch_to_project(self):
46
111
  self.switch_app(0)
@@ -91,6 +156,38 @@ class MainApp(QMainWindow):
91
156
  else:
92
157
  self.retouch_window.io_gui_handler.open_file(filename)
93
158
 
159
+ def import_from_project(self):
160
+ project = self.project_window.project()
161
+ if project is None:
162
+ QMessageBox.warning(self.parent(),
163
+ "No Active Project", "No project has been created or opened.")
164
+ return
165
+ if len(project.jobs) == 0:
166
+ QMessageBox.warning(self.parent(),
167
+ "No Jobs In Project", "The current project has no job. "
168
+ "Create and run a job first.")
169
+ return
170
+ if len(project.jobs) > 1:
171
+ job_names = [job.params['name'] for job in project.jobs]
172
+ job_name = SelectionDialog.get_selection(
173
+ "Job Selection",
174
+ "Please select one of the active jobs:",
175
+ job_names
176
+ )
177
+ job = None
178
+ for job in project.jobs:
179
+ if job.params['name'] == job_name:
180
+ break
181
+ if job is None:
182
+ return
183
+ else:
184
+ job = project.jobs[0]
185
+ retouch_path = self.project_window.get_retouch_path(job)
186
+ if isinstance(retouch_path, list):
187
+ open_frames(self.retouch_window, None, ";".join(retouch_path))
188
+ else:
189
+ self.retouch_window.io_gui_handler.open_file(retouch_path)
190
+
94
191
 
95
192
  class Application(QApplication):
96
193
  def event(self, event):
@@ -1,4 +1,5 @@
1
1
  # pylint: disable=C0114, C0115, C0116, E0611, R0903, R0915, R0914, R0917, R0913, R0902
2
+ import os
2
3
  from PySide6.QtWidgets import (QWidget, QPushButton, QVBoxLayout, QHBoxLayout,
3
4
  QMessageBox, QScrollArea, QSizePolicy, QFrame, QLabel, QComboBox)
4
5
  from PySide6.QtGui import QColor
@@ -7,6 +8,7 @@ from PySide6.QtCore import Signal, Slot
7
8
  from .. config.constants import constants
8
9
  from .. config.gui_constants import gui_constants
9
10
  from .colors import RED_BUTTON_STYLE, BLUE_BUTTON_STYLE, BLUE_COMBO_STYLE
11
+ from .. algorithms.utils import extension_tif_jpg, extension_pdf
10
12
  from .gui_logging import LogWorker, QTextEditLogger
11
13
  from .gui_images import GuiPdfView, GuiImageView, GuiOpenApp
12
14
  from .colors import (
@@ -200,13 +202,12 @@ class RunWindow(QTextEditLogger):
200
202
  label = QLabel(name, self)
201
203
  label.setStyleSheet("QLabel {margin-top: 5px; font-weight: bold;}")
202
204
  self.image_layout.addWidget(label)
203
- ext = path.split('.')[-1].lower()
204
- if ext == 'pdf':
205
+ if extension_pdf(path):
205
206
  image_view = GuiPdfView(path, self)
206
- elif ext in ['jpg', 'jpeg', 'tif', 'tiff', 'png']:
207
+ elif extension_tif_jpg(path):
207
208
  image_view = GuiImageView(path, self)
208
209
  else:
209
- raise RuntimeError("Can't visualize file type {ext}.")
210
+ raise RuntimeError(f"Can't visualize file type {os.path.splitext(path)[1]}.")
210
211
  self.image_views.append(image_view)
211
212
  self.image_layout.addWidget(image_view)
212
213
  max_width = max(pv.size().width() for pv in self.image_views) if self.image_views else 0
@@ -278,7 +278,7 @@ class MainWindow(QMainWindow, LogManager):
278
278
  current_action = None
279
279
  if item:
280
280
  index = self.job_list().row(item)
281
- current_action = self.get_job_at(index)
281
+ current_action = self.project_editor.get_job_at(index)
282
282
  self.set_current_job(index)
283
283
  item = self.action_list().itemAt(self.action_list().viewport().mapFrom(self, event.pos()))
284
284
  if item:
@@ -328,7 +328,7 @@ class MainWindow(QMainWindow, LogManager):
328
328
  self.current_action_output_path = f"{self.current_action_working_path}/{op}"
329
329
  if os.path.exists(self.current_action_output_path):
330
330
  action_name = "Browse Output Path" + (f" > {name}" if name != '' else '')
331
- n_files = len(next(os.walk(op))[2])
331
+ n_files = len(next(os.walk(self.current_action_output_path))[2])
332
332
  s = "" if n_files == 1 else "s"
333
333
  action_name += f" ({n_files} file{s})"
334
334
  self.browse_output_path_action = QAction(action_name)
@@ -7,13 +7,12 @@ 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
- from .. algorithms.utils import read_img
10
+ from .. algorithms.utils import read_img, extension_tif_jpg
11
11
  from .. algorithms.stack import get_bunches
12
12
  from .select_path_widget import create_select_file_paths_widget
13
13
  from .base_form_dialog import BaseFormDialog
14
14
 
15
15
  DEFAULT_NO_COUNT_LABEL = " - "
16
- EXTENSIONS = ['jpg', 'jpeg', 'tif', 'tiff']
17
16
 
18
17
 
19
18
  class NewProjectDialog(BaseFormDialog):
@@ -133,10 +132,8 @@ class NewProjectDialog(BaseFormDialog):
133
132
  return 0
134
133
  count = 0
135
134
  for filename in os.listdir(path):
136
- if '.' in filename:
137
- ext = filename.lower().split('.')[-1]
138
- if ext in EXTENSIONS:
139
- count += 1
135
+ if extension_tif_jpg(filename):
136
+ count += 1
140
137
  return count
141
138
 
142
139
  self.n_image_files = count_image_files(self.input_folder.text())
@@ -173,11 +170,9 @@ class NewProjectDialog(BaseFormDialog):
173
170
  file_path = None
174
171
  for filename in files:
175
172
  full_path = os.path.join(path, filename)
176
- if os.path.isfile(full_path):
177
- ext = full_path.split(".")[-1].lower()
178
- if ext in EXTENSIONS:
179
- file_path = full_path
180
- break
173
+ if extension_tif_jpg(full_path):
174
+ file_path = full_path
175
+ break
181
176
  if file_path is None:
182
177
  QMessageBox.warning(
183
178
  self, "Invalid input", "Could not find images now in the selected path")
@@ -204,11 +204,12 @@ class ProjectController(QObject):
204
204
  input_path.append("focus-stack-depth-map")
205
205
  if dialog.get_bunch_stack():
206
206
  input_path.append("bunches")
207
- else:
208
- input_path.append(input_path)
209
- multi_layer = ActionConfig(constants.ACTION_MULTILAYER,
210
- {'name': 'multi-layer',
211
- 'input_path': ','.join(input_path)})
207
+ multi_layer = ActionConfig(
208
+ constants.ACTION_MULTILAYER,
209
+ {
210
+ 'name': 'multi-layer',
211
+ 'input_path': constants.PATH_SEPARATOR.join(input_path)
212
+ })
212
213
  job.add_sub_action(multi_layer)
213
214
  self.add_job_to_project(job)
214
215
  self.mark_as_modified(True)
@@ -5,7 +5,7 @@ import numpy as np
5
5
  import cv2
6
6
  from psdtags import PsdChannelId
7
7
  from PySide6.QtCore import QThread, Signal
8
- from .. algorithms.utils import read_img
8
+ from .. algorithms.utils import read_img, extension_tif, extension_jpg
9
9
  from .. algorithms.multilayer import read_multilayer_tiff
10
10
 
11
11
 
@@ -50,15 +50,14 @@ class FileLoader(QThread):
50
50
  raise RuntimeError(f"Path {path} does not exist.")
51
51
  if not os.path.isfile(path):
52
52
  raise RuntimeError(f"Path {path} is not a file.")
53
- extension = path.split('.')[-1]
54
- if extension in ['jpg', 'jpeg']:
53
+ if extension_jpg(path):
55
54
  try:
56
55
  stack = np.array([cv2.cvtColor(read_img(path), cv2.COLOR_BGR2RGB)])
57
56
  return stack, [path.split('/')[-1].split('.')[0]]
58
57
  except Exception as e:
59
58
  traceback.print_tb(e.__traceback__)
60
59
  return None, None
61
- elif extension in ['tif', 'tiff']:
60
+ elif extension_tif(path):
62
61
  try:
63
62
  psd_data = read_multilayer_tiff(path)
64
63
  layers = []
@@ -330,9 +330,8 @@ class ImageEditorUI(QMainWindow, LayerCollectionHandler):
330
330
  view_individual_action.setShortcut("L")
331
331
  view_individual_action.triggered.connect(self.set_view_individual)
332
332
  view_menu.addAction(view_individual_action)
333
- view_menu.addSeparator()
334
333
 
335
- toggle_view_master_individual_action = QAction("View Individual", self)
334
+ toggle_view_master_individual_action = QAction("Toggle Master/Individual", self)
336
335
  toggle_view_master_individual_action.setShortcut("T")
337
336
  toggle_view_master_individual_action.triggered.connect(self.toggle_view_master_individual)
338
337
  view_menu.addAction(toggle_view_master_individual_action)
@@ -56,9 +56,7 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
56
56
  self.set_master_layer(master_layer)
57
57
  self.undo_manager.reset()
58
58
  self.blank_layer = np.zeros(master_layer.shape[:2])
59
- self.finish_loading_setup(
60
- stack, None, master_layer, False,
61
- f"Loaded: {self.current_file_path()}")
59
+ self.finish_loading_setup(f"Loaded: {self.current_file_path()}")
62
60
 
63
61
  def on_file_error(self, error_msg):
64
62
  QApplication.restoreOverrideCursor()
@@ -137,26 +135,23 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
137
135
  msg.setText(str(e))
138
136
  msg.exec()
139
137
  return
140
- self.finish_loading_setup(
141
- stack, labels, master, True,
142
- "Selected frames imported")
143
-
144
- def finish_loading_setup(self, stack, labels, master, add_layers, message):
145
- if add_layers:
146
- if self.layer_stack() is None and len(stack) > 0:
147
- self.set_layer_stack(np.array(stack))
148
- if labels is None:
149
- labels = self.layer_labels()
150
- else:
151
- self.set_layer_labels(labels)
152
- self.set_master_layer(master)
153
- self.blank_layer = np.zeros(master.shape[:2])
138
+ if self.layer_stack() is None and len(stack) > 0:
139
+ self.set_layer_stack(np.array(stack))
140
+ if labels is None:
141
+ labels = self.layer_labels()
154
142
  else:
155
- if labels is None:
156
- labels = self.layer_labels()
157
- for img, label in zip(stack, labels):
158
- self.add_layer_label(label)
159
- self.add_layer(img)
143
+ self.set_layer_labels(labels)
144
+ self.set_master_layer(master)
145
+ self.blank_layer = np.zeros(master.shape[:2])
146
+ else:
147
+ if labels is None:
148
+ labels = self.layer_labels()
149
+ for img, label in zip(stack, labels):
150
+ self.add_layer_label(label)
151
+ self.add_layer(img)
152
+ self.finish_loading_setup("Selected frames imported")
153
+
154
+ def finish_loading_setup(self, message):
160
155
  self.display_manager.update_thumbnails()
161
156
  self.mark_as_modified_requested.emit(True)
162
157
  self.change_layer_requested.emit(0)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shinestacker
3
- Version: 1.0.2
3
+ Version: 1.0.4
4
4
  Summary: ShineStacker
5
5
  Author-email: Luca Lista <luka.lista@gmail.com>
6
6
  License-Expression: LGPL-3.0
@@ -87,6 +87,7 @@ Pyramid methods in image processing
87
87
  # License
88
88
 
89
89
  <img src="https://www.gnu.org/graphics/lgplv3-147x51.png" alt="LGPL 3 logo">
90
+
90
91
  - **Code**: The software is provided as is under the [GNU Lesser General Public License v3.0](https://www.gnu.org/licenses/lgpl-3.0.en.html). See [LICENSE](https://github.com/lucalista/shinestacker/blob/main/LICENSE) for details.
91
92
  <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/src/shinestacker/gui/ico/shinestacker.png' width="150" referrerpolicy="no-referrer" alt="Shine Stacker Logo">
92
93
 
@@ -1,26 +1,26 @@
1
1
  shinestacker/__init__.py,sha256=uq2fjAw2z_6TpH3mOcWFZ98GoEPRsNhTAK8N0MMm_e8,448
2
- shinestacker/_version.py,sha256=C8nyPP5-54GgYCcP38Lbel_pRimOW-Ra4bw6Vzp2lmE,21
2
+ shinestacker/_version.py,sha256=Oi2b5pm3sFbESQW0xgj8kqwDPX_Hxmx4gNILYpLzYqI,21
3
3
  shinestacker/algorithms/__init__.py,sha256=c4kRrdTLlVI70Q16XkI1RSmz5MD7npDqIpO_02jTG6g,747
4
4
  shinestacker/algorithms/align.py,sha256=XT4DJoD5ZvpkC1-J3W3GWmWRsXJg3qJ-3zr9erT8oW0,17514
5
5
  shinestacker/algorithms/balance.py,sha256=iSjO-pl0vQv58iEQ077EUcDTAExMKDBdtXmJXbMhazk,16721
6
6
  shinestacker/algorithms/base_stack_algo.py,sha256=AFV2QkcFNaTcnISpsWHuAVy2De9hhaPcBNjE1O0h50I,1430
7
7
  shinestacker/algorithms/denoise.py,sha256=GL3Z4_6MHxSa7Wo4ZzQECZS87tHBFqO0sIVF_jPuYQU,426
8
8
  shinestacker/algorithms/depth_map.py,sha256=FOR5M0brO5-9NnXDY7TWpc3OtKKSuzrOSoBMe0cP6Ho,6076
9
- shinestacker/algorithms/exif.py,sha256=gY9s6Cd4g4swo5qEjSbzuVIvl1GImCYu6ytOO9WrV0I,9435
10
- shinestacker/algorithms/multilayer.py,sha256=5JA6TW8oO_R3mu6cOvPno9by4md8q5sXUb8ZfsRRpmY,9259
9
+ shinestacker/algorithms/exif.py,sha256=SM4ZDDe8hCJ3xY6053FNndOiwzEStzdp0WrXurlcHVc,9429
10
+ shinestacker/algorithms/multilayer.py,sha256=-pQXDlooSMGKPhMgF-_naXdkGdolclYvSD-RrjwLiyI,9328
11
11
  shinestacker/algorithms/noise_detection.py,sha256=CDnN8pglxufY5Y-dT3mVooD4zPySdSq9CMgtDGMXBnA,8970
12
12
  shinestacker/algorithms/pyramid.py,sha256=_Pk19lRQ21b3W3aHQ6DgAe9VVOfbsi2a9jrynF0qFVw,8610
13
13
  shinestacker/algorithms/sharpen.py,sha256=h7PMJBYxucg194Usp_6pvItPUMFYbT-ebAc_-7XBFUw,949
14
14
  shinestacker/algorithms/stack.py,sha256=FCU89Of-s6C_DuMleG06c8V6fnIm9MFInvkkKtTsGBo,4906
15
- shinestacker/algorithms/stack_framework.py,sha256=frw7sbc9qOfVBYP3ZOFZEaIn9O27Wms8j_mxSW79uI0,12460
16
- shinestacker/algorithms/utils.py,sha256=2XFa16Q8JVq4C2iXZFOvv98GVKqxceFG73yFZNHJSqI,2475
15
+ shinestacker/algorithms/stack_framework.py,sha256=peAlUUl7y8OcquhjQoXpiwsEhZw6zgZnzwt1IDpf4aU,12466
16
+ shinestacker/algorithms/utils.py,sha256=0AeMVaFuhpUiIpUUFqrqAJ_-ohGVdX7-EdMyLoVflbg,3279
17
17
  shinestacker/algorithms/vignetting.py,sha256=yW-1TF4tesLWfKQOS0XxRkOEN82U-YDmMaj09C9cH4M,9552
18
18
  shinestacker/algorithms/white_balance.py,sha256=PMKsBtxOSn5aRr_Gkx1StHS4eN6kBN2EhNnhg4UG24g,501
19
19
  shinestacker/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
20
20
  shinestacker/app/about_dialog.py,sha256=pkH7nnxUP8yc0D3vRGd1jRb5cwi1nDVbQRk_OC9yLk8,4144
21
21
  shinestacker/app/gui_utils.py,sha256=08TrCj2gFGsNsF6hG7ySO2y7wcQakM5PzERkeplqNFs,2344
22
22
  shinestacker/app/help_menu.py,sha256=g8lKG_xZmXtNQaC3SIRzyROKVWva_PLEgZsQWh6zUcQ,499
23
- shinestacker/app/main.py,sha256=geZwxShTDg8dDOnsGu9TvWMw2Wru6MaX9CvlGHMnRvQ,6474
23
+ shinestacker/app/main.py,sha256=rcXlzsPErIN9ItbucsB6nz103vwNvsff6wSADOwFt6I,10301
24
24
  shinestacker/app/open_frames.py,sha256=bsu32iJSYJQLe_tQQbvAU5DuMDVX6MRuNdE7B5lojZc,1488
25
25
  shinestacker/app/project.py,sha256=W0u715LZne_PNJvg9msSy27ybIjgDXiEAQdJ7_6BjYI,2774
26
26
  shinestacker/app/retouch.py,sha256=ZQ-nRKnHo6xurcP34RNqaAWkmuGBjJ5jE05hTQ_ycis,2482
@@ -41,11 +41,11 @@ shinestacker/gui/base_form_dialog.py,sha256=yYqMee1mzw9VBx8siBS0jDk1qqsTIKJUgdjh
41
41
  shinestacker/gui/colors.py,sha256=m0pQQ-uvtIN1xmb_-N06BvC7pZYZZnq59ZSEJwutHuk,1432
42
42
  shinestacker/gui/gui_images.py,sha256=e0KAXSPruZoRHrajfdlmOKBYoRJJQBDan1jgs7YFltY,5678
43
43
  shinestacker/gui/gui_logging.py,sha256=kiZcrC2AFYCWgPZo0O5SKw-E5cFrezwf4anS3HjPuNw,8168
44
- shinestacker/gui/gui_run.py,sha256=G9jKIMCEdrCwRvicRykY0r_x2Vya-9EMn4HpZL8ydRI,15141
45
- shinestacker/gui/main_window.py,sha256=1Im8ER-eAYwvLXFp_OyMr3EkdpJ0K1krkmKy-WF3KM8,24210
44
+ shinestacker/gui/gui_run.py,sha256=Lf_hXWPk1bgAYumxqjPHSK8UfiAIUR7A047ECGKD_uU,15183
45
+ shinestacker/gui/main_window.py,sha256=l5iMk5aIi5nXPccnibB1tswc0agatrYgXULVkXo7OV0,24254
46
46
  shinestacker/gui/menu_manager.py,sha256=_L6LOikB3impEYqilqwXc0WJuunishjz57ozZlrBn7Q,9616
47
- shinestacker/gui/new_project.py,sha256=zHmGrT27L7I6YHM1L8wjt7DzukLFPddFsbVyGVHfJoc,11004
48
- shinestacker/gui/project_controller.py,sha256=BbecAO0t15Usir3CYeIBiO9gvdGAmAPwhwLxci5IS7g,15103
47
+ shinestacker/gui/new_project.py,sha256=DbndYiaxwjW0OJGJ3N9JwpwLrXVsdtoSSlFRf6T8yEA,10781
48
+ shinestacker/gui/project_controller.py,sha256=QJSQlwEJeXJyJnkp42D9NSqWBb8q2kLf_GvLfe3pe_c,15076
49
49
  shinestacker/gui/project_converter.py,sha256=_AFfU2HYKPX78l6iX6bXJrlKpdjSl63pmKzrc6kQpn8,7348
50
50
  shinestacker/gui/project_editor.py,sha256=uouzmUkrqouQlq-dqPOgSO16r1WOnGNV2v8jTcZlRXU,23749
51
51
  shinestacker/gui/project_model.py,sha256=eRUmH3QmRzDtPtZoxgT6amKzN8_5XzwjHgEJeL-_JOE,4263
@@ -70,12 +70,12 @@ shinestacker/retouch/brush_tool.py,sha256=nxnEuvTioPNw1WeWsT20X1zl-LNZ8i-1ExOcih
70
70
  shinestacker/retouch/denoise_filter.py,sha256=TDUHzhRKlKvCa3D5SCYCZKTpjcl81kGwmONsgSDtO1k,440
71
71
  shinestacker/retouch/display_manager.py,sha256=XPbOBmoYc_jNA791WkWkOSaFHb0ztCZechl2p2KSlwQ,9597
72
72
  shinestacker/retouch/exif_data.py,sha256=giqoIaMlhN6H3x8BAd73ghVHODmWIcD_QbSoDymQycU,1864
73
- shinestacker/retouch/file_loader.py,sha256=x8lzKShlgElcJYLFiDoeszeVEToUUiUbUrozAeF5SMU,4812
73
+ shinestacker/retouch/file_loader.py,sha256=z02-A8_uDZxayI1NFTxT2GVUvEBWStchX9hlN1o5-0U,4784
74
74
  shinestacker/retouch/filter_manager.py,sha256=SdYIZkZBUvuB6wDG0moGWav5sfEvIcB9ioUJR5wJFts,388
75
75
  shinestacker/retouch/icon_container.py,sha256=6gw1HO1bC2FrdB4dc_iH81DQuLjzuvRGksZ2hKLT9yA,585
76
- shinestacker/retouch/image_editor_ui.py,sha256=GNozK_P7orgl8EioemJeyR4e_LLMbKVZi4xhYW9m38U,29893
76
+ shinestacker/retouch/image_editor_ui.py,sha256=cMGiqyPGqJmBaXMAc0WImDPf_hmxO4KiJtaaSpiW9EU,29869
77
77
  shinestacker/retouch/image_viewer.py,sha256=3ebrLHTDtGd_EbIT2nNFRUjH836rblmmK7jZ62YcJ2U,19564
78
- shinestacker/retouch/io_gui_handler.py,sha256=DPFEq-29b88xqCpIwEmuwUU5Xn_7UGlFFhCDM1bvMxI,11645
78
+ shinestacker/retouch/io_gui_handler.py,sha256=pT-49uP0GROMOjZ70LoMLgXHnqSDq8ieAlAKGw0t1TM,11418
79
79
  shinestacker/retouch/io_manager.py,sha256=JUAA--AK0mVa1PTErJTnBFjaXIle5Qs7Ow0Wkd8at0o,2437
80
80
  shinestacker/retouch/layer_collection.py,sha256=Q7zoCYRn__jYkfrEC2lY1uKHWfOUbsJ27xaYHIoKVxo,5730
81
81
  shinestacker/retouch/shortcuts_help.py,sha256=SN4vNa_6yRAFaWxt5HpWn8FHgwmHrIs_wYwjl4iyDmg,3769
@@ -83,9 +83,9 @@ shinestacker/retouch/undo_manager.py,sha256=_ekbcOLcPbQLY7t-o8wf-b1uA6OPY9rRyLM-
83
83
  shinestacker/retouch/unsharp_mask_filter.py,sha256=uFnth8fpZFGhdIgJCnS8x5v6lBQgJ3hX0CBke9pFXeM,3510
84
84
  shinestacker/retouch/vignetting_filter.py,sha256=3WuoF38lQOIaU1MWmqviItuQn8NnbMN0nwV7pM9IJqU,3453
85
85
  shinestacker/retouch/white_balance_filter.py,sha256=glMBYlmrF-i_OrB3sGUpjZE6X4FQdyLC4GBy2bWtaFc,6056
86
- shinestacker-1.0.2.dist-info/licenses/LICENSE,sha256=pWgb-bBdsU2Gd2kwAXxketnm5W_2u8_fIeWEgojfrxs,7651
87
- shinestacker-1.0.2.dist-info/METADATA,sha256=BGQfH5499HQk51ANCTjSdUxjErlNHfiz1kcCf17VKZo,5902
88
- shinestacker-1.0.2.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
89
- shinestacker-1.0.2.dist-info/entry_points.txt,sha256=SY6g1LqtMmp23q1DGwLUDT_dhLX9iss8DvWkiWLyo_4,166
90
- shinestacker-1.0.2.dist-info/top_level.txt,sha256=MhijwnBVX5psfsyX8JZjqp3SYiWPsKe69f3Gnyze4Fw,13
91
- shinestacker-1.0.2.dist-info/RECORD,,
86
+ shinestacker-1.0.4.dist-info/licenses/LICENSE,sha256=pWgb-bBdsU2Gd2kwAXxketnm5W_2u8_fIeWEgojfrxs,7651
87
+ shinestacker-1.0.4.dist-info/METADATA,sha256=tKyo0ogsBjWAHk4lAshkSqx62swN-s01hXEO2mJkHOg,5903
88
+ shinestacker-1.0.4.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
89
+ shinestacker-1.0.4.dist-info/entry_points.txt,sha256=SY6g1LqtMmp23q1DGwLUDT_dhLX9iss8DvWkiWLyo_4,166
90
+ shinestacker-1.0.4.dist-info/top_level.txt,sha256=MhijwnBVX5psfsyX8JZjqp3SYiWPsKe69f3Gnyze4Fw,13
91
+ shinestacker-1.0.4.dist-info/RECORD,,