shinestacker 1.0.3__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.3'
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:
@@ -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
@@ -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")
@@ -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 = []
@@ -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,
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()
@@ -151,11 +149,9 @@ class IOGuiHandler(QObject, LayerCollectionHandler):
151
149
  for img, label in zip(stack, labels):
152
150
  self.add_layer_label(label)
153
151
  self.add_layer(img)
154
- self.finish_loading_setup(
155
- stack, labels, master,
156
- "Selected frames imported")
152
+ self.finish_loading_setup("Selected frames imported")
157
153
 
158
- def finish_loading_setup(self, stack, labels, master, message):
154
+ def finish_loading_setup(self, message):
159
155
  self.display_manager.update_thumbnails()
160
156
  self.mark_as_modified_requested.emit(True)
161
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.3
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
@@ -1,26 +1,26 @@
1
1
  shinestacker/__init__.py,sha256=uq2fjAw2z_6TpH3mOcWFZ98GoEPRsNhTAK8N0MMm_e8,448
2
- shinestacker/_version.py,sha256=MpVHFFoITiYyPltTb_qFrdeX2entdTm4x0PczXi3txY,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=MJqU_9LpcCi-0quJNszaFHOqna68ZqmImZQ50njOBio,9247
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
15
  shinestacker/algorithms/stack_framework.py,sha256=peAlUUl7y8OcquhjQoXpiwsEhZw6zgZnzwt1IDpf4aU,12466
16
- shinestacker/algorithms/utils.py,sha256=2XFa16Q8JVq4C2iXZFOvv98GVKqxceFG73yFZNHJSqI,2475
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,10 +41,10 @@ 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
44
+ shinestacker/gui/gui_run.py,sha256=Lf_hXWPk1bgAYumxqjPHSK8UfiAIUR7A047ECGKD_uU,15183
45
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
47
+ shinestacker/gui/new_project.py,sha256=DbndYiaxwjW0OJGJ3N9JwpwLrXVsdtoSSlFRf6T8yEA,10781
48
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
@@ -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
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=52Wk19DjXtgCBcBWz9t0wUEpt8RS67FohdG44OOnu8c,11541
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.3.dist-info/licenses/LICENSE,sha256=pWgb-bBdsU2Gd2kwAXxketnm5W_2u8_fIeWEgojfrxs,7651
87
- shinestacker-1.0.3.dist-info/METADATA,sha256=3iFJO-177VFRPs8m5UnV2LhmiwO9wI5Bf3_x_DzkVbk,5903
88
- shinestacker-1.0.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
89
- shinestacker-1.0.3.dist-info/entry_points.txt,sha256=SY6g1LqtMmp23q1DGwLUDT_dhLX9iss8DvWkiWLyo_4,166
90
- shinestacker-1.0.3.dist-info/top_level.txt,sha256=MhijwnBVX5psfsyX8JZjqp3SYiWPsKe69f3Gnyze4Fw,13
91
- shinestacker-1.0.3.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,,