shinestacker 1.0.3__tar.gz → 1.0.4__tar.gz

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 (136) hide show
  1. {shinestacker-1.0.3 → shinestacker-1.0.4}/CHANGELOG.md +9 -0
  2. {shinestacker-1.0.3/src/shinestacker.egg-info → shinestacker-1.0.4}/PKG-INFO +1 -1
  3. shinestacker-1.0.4/src/shinestacker/_version.py +1 -0
  4. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/algorithms/exif.py +10 -13
  5. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/algorithms/multilayer.py +5 -4
  6. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/algorithms/utils.py +47 -7
  7. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/app/main.py +99 -2
  8. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/gui_run.py +5 -4
  9. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/new_project.py +6 -11
  10. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/file_loader.py +3 -4
  11. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/io_gui_handler.py +3 -7
  12. {shinestacker-1.0.3 → shinestacker-1.0.4/src/shinestacker.egg-info}/PKG-INFO +1 -1
  13. shinestacker-1.0.3/src/shinestacker/_version.py +0 -1
  14. {shinestacker-1.0.3 → shinestacker-1.0.4}/.coveragerc +0 -0
  15. {shinestacker-1.0.3 → shinestacker-1.0.4}/.flake8 +0 -0
  16. {shinestacker-1.0.3 → shinestacker-1.0.4}/.github/workflows/ci-multiplatform.yml +0 -0
  17. {shinestacker-1.0.3 → shinestacker-1.0.4}/.github/workflows/pylint.yml +0 -0
  18. {shinestacker-1.0.3 → shinestacker-1.0.4}/.github/workflows/pypi-publish.yml +0 -0
  19. {shinestacker-1.0.3 → shinestacker-1.0.4}/.github/workflows/release.yml +0 -0
  20. {shinestacker-1.0.3 → shinestacker-1.0.4}/.gitignore +0 -0
  21. {shinestacker-1.0.3 → shinestacker-1.0.4}/.pylintrc +0 -0
  22. {shinestacker-1.0.3 → shinestacker-1.0.4}/.readthedocs.yaml +0 -0
  23. {shinestacker-1.0.3 → shinestacker-1.0.4}/LICENSE +0 -0
  24. {shinestacker-1.0.3 → shinestacker-1.0.4}/MANIFEST.in +0 -0
  25. {shinestacker-1.0.3 → shinestacker-1.0.4}/README.md +0 -0
  26. {shinestacker-1.0.3 → shinestacker-1.0.4}/THIRD_PARTY_LICENSES.txt +0 -0
  27. {shinestacker-1.0.3 → shinestacker-1.0.4}/docs/alignment.md +0 -0
  28. {shinestacker-1.0.3 → shinestacker-1.0.4}/docs/api.md +0 -0
  29. {shinestacker-1.0.3 → shinestacker-1.0.4}/docs/balancing.md +0 -0
  30. {shinestacker-1.0.3 → shinestacker-1.0.4}/docs/conf.py +0 -0
  31. {shinestacker-1.0.3 → shinestacker-1.0.4}/docs/focus_stacking.md +0 -0
  32. {shinestacker-1.0.3 → shinestacker-1.0.4}/docs/gui.md +0 -0
  33. {shinestacker-1.0.3 → shinestacker-1.0.4}/docs/index.md +0 -0
  34. {shinestacker-1.0.3 → shinestacker-1.0.4}/docs/job.md +0 -0
  35. {shinestacker-1.0.3 → shinestacker-1.0.4}/docs/main.md +0 -0
  36. {shinestacker-1.0.3 → shinestacker-1.0.4}/docs/multilayer.md +0 -0
  37. {shinestacker-1.0.3 → shinestacker-1.0.4}/docs/noise.md +0 -0
  38. {shinestacker-1.0.3 → shinestacker-1.0.4}/docs/requirements.txt +0 -0
  39. {shinestacker-1.0.3 → shinestacker-1.0.4}/docs/vignetting.md +0 -0
  40. {shinestacker-1.0.3 → shinestacker-1.0.4}/img/coffee.gif +0 -0
  41. {shinestacker-1.0.3 → shinestacker-1.0.4}/img/coffee_stack.jpg +0 -0
  42. {shinestacker-1.0.3 → shinestacker-1.0.4}/img/extreme-vignetting.jpg +0 -0
  43. {shinestacker-1.0.3 → shinestacker-1.0.4}/img/flies.gif +0 -0
  44. {shinestacker-1.0.3 → shinestacker-1.0.4}/img/flies_stack.jpg +0 -0
  45. {shinestacker-1.0.3 → shinestacker-1.0.4}/img/flow-diagram.png +0 -0
  46. {shinestacker-1.0.3 → shinestacker-1.0.4}/img/gui-finder.png +0 -0
  47. {shinestacker-1.0.3 → shinestacker-1.0.4}/img/gui-project-new.png +0 -0
  48. {shinestacker-1.0.3 → shinestacker-1.0.4}/img/gui-project-run.png +0 -0
  49. {shinestacker-1.0.3 → shinestacker-1.0.4}/img/gui-retouch.png +0 -0
  50. {shinestacker-1.0.3 → shinestacker-1.0.4}/pyproject.toml +0 -0
  51. {shinestacker-1.0.3 → shinestacker-1.0.4}/requirements.txt +0 -0
  52. {shinestacker-1.0.3 → shinestacker-1.0.4}/scripts/build_release.py +0 -0
  53. {shinestacker-1.0.3 → shinestacker-1.0.4}/scripts/git-rev-list.sh +0 -0
  54. {shinestacker-1.0.3 → shinestacker-1.0.4}/scripts/validate-tomli.py +0 -0
  55. {shinestacker-1.0.3 → shinestacker-1.0.4}/setup.cfg +0 -0
  56. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/__init__.py +0 -0
  57. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/algorithms/__init__.py +0 -0
  58. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/algorithms/align.py +0 -0
  59. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/algorithms/balance.py +0 -0
  60. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/algorithms/base_stack_algo.py +0 -0
  61. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/algorithms/denoise.py +0 -0
  62. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/algorithms/depth_map.py +0 -0
  63. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/algorithms/noise_detection.py +0 -0
  64. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/algorithms/pyramid.py +0 -0
  65. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/algorithms/sharpen.py +0 -0
  66. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/algorithms/stack.py +0 -0
  67. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/algorithms/stack_framework.py +0 -0
  68. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/algorithms/vignetting.py +0 -0
  69. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/algorithms/white_balance.py +0 -0
  70. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/app/__init__.py +0 -0
  71. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/app/about_dialog.py +0 -0
  72. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/app/gui_utils.py +0 -0
  73. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/app/help_menu.py +0 -0
  74. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/app/open_frames.py +0 -0
  75. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/app/project.py +0 -0
  76. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/app/retouch.py +0 -0
  77. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/config/__init__.py +0 -0
  78. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/config/config.py +0 -0
  79. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/config/constants.py +0 -0
  80. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/config/gui_constants.py +0 -0
  81. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/core/__init__.py +0 -0
  82. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/core/colors.py +0 -0
  83. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/core/core_utils.py +0 -0
  84. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/core/exceptions.py +0 -0
  85. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/core/framework.py +0 -0
  86. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/core/logging.py +0 -0
  87. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/__init__.py +0 -0
  88. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/action_config.py +0 -0
  89. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/action_config_dialog.py +0 -0
  90. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/base_form_dialog.py +0 -0
  91. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/colors.py +0 -0
  92. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/gui_images.py +0 -0
  93. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/gui_logging.py +0 -0
  94. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/ico/focus_stack_bkg.png +0 -0
  95. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/ico/shinestacker.icns +0 -0
  96. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/ico/shinestacker.ico +0 -0
  97. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/ico/shinestacker.png +0 -0
  98. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/ico/shinestacker.svg +0 -0
  99. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/img/close-round-line-icon.png +0 -0
  100. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/img/forward-button-icon.png +0 -0
  101. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/img/play-button-round-icon.png +0 -0
  102. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/img/plus-round-line-icon.png +0 -0
  103. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/main_window.py +0 -0
  104. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/menu_manager.py +0 -0
  105. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/project_controller.py +0 -0
  106. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/project_converter.py +0 -0
  107. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/project_editor.py +0 -0
  108. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/project_model.py +0 -0
  109. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/select_path_widget.py +0 -0
  110. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/tab_widget.py +0 -0
  111. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/gui/time_progress_bar.py +0 -0
  112. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/__init__.py +0 -0
  113. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/base_filter.py +0 -0
  114. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/brush.py +0 -0
  115. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/brush_gradient.py +0 -0
  116. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/brush_preview.py +0 -0
  117. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/brush_tool.py +0 -0
  118. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/denoise_filter.py +0 -0
  119. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/display_manager.py +0 -0
  120. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/exif_data.py +0 -0
  121. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/filter_manager.py +0 -0
  122. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/icon_container.py +0 -0
  123. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/image_editor_ui.py +0 -0
  124. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/image_viewer.py +0 -0
  125. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/io_manager.py +0 -0
  126. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/layer_collection.py +0 -0
  127. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/shortcuts_help.py +0 -0
  128. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/undo_manager.py +0 -0
  129. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/unsharp_mask_filter.py +0 -0
  130. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/vignetting_filter.py +0 -0
  131. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker/retouch/white_balance_filter.py +0 -0
  132. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker.egg-info/SOURCES.txt +0 -0
  133. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker.egg-info/dependency_links.txt +0 -0
  134. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker.egg-info/entry_points.txt +0 -0
  135. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker.egg-info/requires.txt +0 -0
  136. {shinestacker-1.0.3 → shinestacker-1.0.4}/src/shinestacker.egg-info/top_level.txt +0 -0
@@ -2,6 +2,15 @@
2
2
 
3
3
  This page reports the main releases only and the main changes therein.
4
4
 
5
+ ## [v1.0.4] - 2025-08-26
6
+ **Bug fixes**
7
+
8
+ ### Changes
9
+
10
+ * extensions are treated in lower case (e.g.: both jpg and JPG)
11
+ * added retouch menu action: import frames from current project
12
+
13
+ ---
5
14
  ## [v1.0.3] - 2025-08-26
6
15
  **Bug fixes**
7
16
 
@@ -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
@@ -0,0 +1 @@
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
 
@@ -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 +0,0 @@
1
- __version__ = '1.0.3'
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes