shinestacker 0.2.0.post1.dev1__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/__init__.py +3 -0
- shinestacker/_version.py +1 -0
- shinestacker/algorithms/__init__.py +14 -0
- shinestacker/algorithms/align.py +307 -0
- shinestacker/algorithms/balance.py +367 -0
- shinestacker/algorithms/core_utils.py +22 -0
- shinestacker/algorithms/depth_map.py +164 -0
- shinestacker/algorithms/exif.py +238 -0
- shinestacker/algorithms/multilayer.py +187 -0
- shinestacker/algorithms/noise_detection.py +182 -0
- shinestacker/algorithms/pyramid.py +176 -0
- shinestacker/algorithms/stack.py +112 -0
- shinestacker/algorithms/stack_framework.py +248 -0
- shinestacker/algorithms/utils.py +71 -0
- shinestacker/algorithms/vignetting.py +137 -0
- shinestacker/app/__init__.py +0 -0
- shinestacker/app/about_dialog.py +24 -0
- shinestacker/app/app_config.py +39 -0
- shinestacker/app/gui_utils.py +35 -0
- shinestacker/app/help_menu.py +16 -0
- shinestacker/app/main.py +176 -0
- shinestacker/app/open_frames.py +39 -0
- shinestacker/app/project.py +91 -0
- shinestacker/app/retouch.py +82 -0
- shinestacker/config/__init__.py +4 -0
- shinestacker/config/config.py +53 -0
- shinestacker/config/constants.py +174 -0
- shinestacker/config/gui_constants.py +85 -0
- shinestacker/core/__init__.py +5 -0
- shinestacker/core/colors.py +60 -0
- shinestacker/core/core_utils.py +52 -0
- shinestacker/core/exceptions.py +50 -0
- shinestacker/core/framework.py +210 -0
- shinestacker/core/logging.py +89 -0
- shinestacker/gui/__init__.py +0 -0
- shinestacker/gui/action_config.py +879 -0
- shinestacker/gui/actions_window.py +283 -0
- shinestacker/gui/colors.py +57 -0
- shinestacker/gui/gui_images.py +152 -0
- shinestacker/gui/gui_logging.py +213 -0
- shinestacker/gui/gui_run.py +393 -0
- shinestacker/gui/img/close-round-line-icon.png +0 -0
- shinestacker/gui/img/forward-button-icon.png +0 -0
- shinestacker/gui/img/play-button-round-icon.png +0 -0
- shinestacker/gui/img/plus-round-line-icon.png +0 -0
- shinestacker/gui/main_window.py +599 -0
- shinestacker/gui/new_project.py +170 -0
- shinestacker/gui/project_converter.py +148 -0
- shinestacker/gui/project_editor.py +539 -0
- shinestacker/gui/project_model.py +138 -0
- shinestacker/retouch/__init__.py +0 -0
- shinestacker/retouch/brush.py +9 -0
- shinestacker/retouch/brush_controller.py +57 -0
- shinestacker/retouch/brush_preview.py +126 -0
- shinestacker/retouch/exif_data.py +65 -0
- shinestacker/retouch/file_loader.py +104 -0
- shinestacker/retouch/image_editor.py +651 -0
- shinestacker/retouch/image_editor_ui.py +380 -0
- shinestacker/retouch/image_viewer.py +356 -0
- shinestacker/retouch/shortcuts_help.py +98 -0
- shinestacker/retouch/undo_manager.py +38 -0
- shinestacker-0.2.0.post1.dev1.dist-info/METADATA +55 -0
- shinestacker-0.2.0.post1.dev1.dist-info/RECORD +67 -0
- shinestacker-0.2.0.post1.dev1.dist-info/WHEEL +5 -0
- shinestacker-0.2.0.post1.dev1.dist-info/entry_points.txt +4 -0
- shinestacker-0.2.0.post1.dev1.dist-info/licenses/LICENSE +1 -0
- shinestacker-0.2.0.post1.dev1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from .. config.constants import constants
|
|
3
|
+
from .brush_preview import create_brush_mask
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BrushController:
|
|
7
|
+
def __init__(self, brush):
|
|
8
|
+
self._brush_mask_cache = {}
|
|
9
|
+
self.brush = brush
|
|
10
|
+
|
|
11
|
+
def apply_brush_operation(self, master_layer, source_layer, dest_layer, mask_layer, view_pos, image_viewer):
|
|
12
|
+
if master_layer is None or source_layer is None:
|
|
13
|
+
return False
|
|
14
|
+
if dest_layer is None:
|
|
15
|
+
dest_layer = master_layer
|
|
16
|
+
scene_pos = image_viewer.mapToScene(view_pos)
|
|
17
|
+
x_center = int(round(scene_pos.x()))
|
|
18
|
+
y_center = int(round(scene_pos.y()))
|
|
19
|
+
radius = int(round(self.brush.size // 2))
|
|
20
|
+
h, w = master_layer.shape[:2]
|
|
21
|
+
x_start, x_end = max(0, x_center - radius), min(w, x_center + radius + 1)
|
|
22
|
+
y_start, y_end = max(0, y_center - radius), min(h, y_center + radius + 1)
|
|
23
|
+
if x_start >= x_end or y_start >= y_end:
|
|
24
|
+
return 0, 0, 0, 0
|
|
25
|
+
mask = self._get_brush_mask(radius)
|
|
26
|
+
if mask is None:
|
|
27
|
+
return 0, 0, 0, 0
|
|
28
|
+
master_area = master_layer[y_start:y_end, x_start:x_end]
|
|
29
|
+
source_area = source_layer[y_start:y_end, x_start:x_end]
|
|
30
|
+
dest_area = dest_layer[y_start:y_end, x_start:x_end]
|
|
31
|
+
mask_layer_area = mask_layer[y_start:y_end, x_start:x_end]
|
|
32
|
+
mask_area = mask[y_start - (y_center - radius):y_end - (y_center - radius), x_start - (x_center - radius):x_end - (x_center - radius)]
|
|
33
|
+
mask_layer_area[:] = np.clip(mask_layer_area + mask_area * self.brush.flow / 100.0, 0.0, 1.0) # np.maximum(mask_layer_area, mask_area)
|
|
34
|
+
self._apply_mask(master_area, source_area, mask_layer_area, dest_area)
|
|
35
|
+
return x_start, y_start, x_end, y_end
|
|
36
|
+
|
|
37
|
+
def _get_brush_mask(self, radius):
|
|
38
|
+
mask_key = (radius, self.brush.hardness)
|
|
39
|
+
if mask_key not in self._brush_mask_cache.keys():
|
|
40
|
+
full_mask = create_brush_mask(size=radius * 2 + 1, hardness_percent=self.brush.hardness,
|
|
41
|
+
opacity_percent=self.brush.opacity)
|
|
42
|
+
self._brush_mask_cache[mask_key] = full_mask
|
|
43
|
+
return self._brush_mask_cache[mask_key]
|
|
44
|
+
|
|
45
|
+
def _apply_mask(self, master_area, source_area, mask_area, dest_area):
|
|
46
|
+
opacity_factor = float(self.brush.opacity) / 100.0
|
|
47
|
+
effective_mask = np.clip(mask_area * opacity_factor, 0, 1)
|
|
48
|
+
dtype = master_area.dtype
|
|
49
|
+
max_px_value = constants.MAX_UINT16 if dtype == np.uint16 else constants.MAX_UINT8
|
|
50
|
+
if master_area.ndim == 3:
|
|
51
|
+
dest_area[:] = np.clip(master_area * (1 - effective_mask[..., np.newaxis]) + source_area * # noqa
|
|
52
|
+
effective_mask[..., np.newaxis], 0, max_px_value).astype(dtype)
|
|
53
|
+
else:
|
|
54
|
+
dest_area[:] = np.clip(master_area * (1 - effective_mask) + source_area * effective_mask, 0, max_px_value).astype(dtype)
|
|
55
|
+
|
|
56
|
+
def clear_cache(self):
|
|
57
|
+
self._brush_mask_cache.clear()
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
from PySide6.QtWidgets import QGraphicsPixmapItem
|
|
3
|
+
from PySide6.QtCore import Qt, QPointF
|
|
4
|
+
from PySide6.QtGui import QPixmap, QPainter, QImage
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def brush_profile_lower_limited(r, hardness):
|
|
8
|
+
if hardness >= 1.0:
|
|
9
|
+
result = np.where(r < 1.0, 1.0, 0.0)
|
|
10
|
+
else:
|
|
11
|
+
r_lim = np.where(r < 1.0, r, 1.0)
|
|
12
|
+
k = 1.0 / (1.0 - hardness)
|
|
13
|
+
result = 0.5 * (np.cos(np.pi * np.power(r_lim, k)) + 1.0)
|
|
14
|
+
return result
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def brush_profile(r, hardness):
|
|
18
|
+
h = 2.0 * hardness - 1.0
|
|
19
|
+
if h >= 1.0:
|
|
20
|
+
result = np.where(r < 1.0, 1.0, 0.0)
|
|
21
|
+
elif h >= 0:
|
|
22
|
+
k = 1.0 / (1.0 - hardness)
|
|
23
|
+
result = 0.5 * (np.cos(np.pi * np.power(np.where(r < 1.0, r, 1.0), k)) + 1.0)
|
|
24
|
+
elif h < 0:
|
|
25
|
+
k = 1.0 / (1.0 + hardness)
|
|
26
|
+
result = np.where(r < 1.0, 0.5 * (1.0 - np.cos(np.pi * np.power(1.0 - np.where(r < 1.0, r, 1.0), k))), 0.0)
|
|
27
|
+
else:
|
|
28
|
+
result = np.zeros_like(r)
|
|
29
|
+
return result
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def create_brush_mask(size, hardness_percent, opacity_percent):
|
|
33
|
+
radius = size / 2.0
|
|
34
|
+
center = (size - 1) / 2.0
|
|
35
|
+
h, o = hardness_percent / 100.0, opacity_percent / 100.0
|
|
36
|
+
y, x = np.ogrid[:size, :size]
|
|
37
|
+
r = np.sqrt((x - center)**2 + (y - center)**2) / radius
|
|
38
|
+
mask = np.clip(brush_profile(r, h), 0.0, 1.0) * o
|
|
39
|
+
return mask
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class BrushPreviewItem(QGraphicsPixmapItem):
|
|
43
|
+
def __init__(self):
|
|
44
|
+
super().__init__()
|
|
45
|
+
self.setVisible(False)
|
|
46
|
+
self.setZValue(500)
|
|
47
|
+
self.setTransformationMode(Qt.SmoothTransformation)
|
|
48
|
+
|
|
49
|
+
def get_layer_area(self, layer, x, y, w, h):
|
|
50
|
+
if not isinstance(layer, np.ndarray):
|
|
51
|
+
self.hide()
|
|
52
|
+
return None
|
|
53
|
+
height, width = layer.shape[:2]
|
|
54
|
+
x_start, y_start = max(0, x), max(0, y)
|
|
55
|
+
x_end, y_end = min(width, x + w), min(height, y + h)
|
|
56
|
+
if x_end <= x_start or y_end <= y_start:
|
|
57
|
+
self.hide()
|
|
58
|
+
return None
|
|
59
|
+
area = np.ascontiguousarray(layer[y_start:y_end, x_start:x_end])
|
|
60
|
+
if area.ndim == 2: # grayscale
|
|
61
|
+
area = np.ascontiguousarray(np.stack([area] * 3, axis=-1))
|
|
62
|
+
elif area.shape[2] == 4: # RGBA
|
|
63
|
+
area = np.ascontiguousarray(area[..., :3]) # RGB
|
|
64
|
+
if area.dtype == np.uint8:
|
|
65
|
+
return area.astype(np.float32) / 256.0
|
|
66
|
+
elif area.dtype == np.uint16:
|
|
67
|
+
return area.astype(np.float32) / 65536.0
|
|
68
|
+
else:
|
|
69
|
+
raise Exception("Bitmas is neither 8 bit nor 16, but of type " + area.dtype)
|
|
70
|
+
|
|
71
|
+
def update(self, editor, pos, size):
|
|
72
|
+
try:
|
|
73
|
+
if editor.current_stack is None or not hasattr(editor, 'image_viewer') or size <= 0:
|
|
74
|
+
self.hide()
|
|
75
|
+
return
|
|
76
|
+
radius = size // 2
|
|
77
|
+
if isinstance(pos, QPointF):
|
|
78
|
+
scene_pos = pos
|
|
79
|
+
else:
|
|
80
|
+
cursor_pos = editor.image_viewer.mapFromGlobal(pos)
|
|
81
|
+
scene_pos = editor.image_viewer.mapToScene(cursor_pos)
|
|
82
|
+
x = int(scene_pos.x() - radius + 0.5)
|
|
83
|
+
y = int(scene_pos.y() - radius)
|
|
84
|
+
w = h = size
|
|
85
|
+
if editor.current_layer < 0 or editor.current_layer >= len(editor.current_stack):
|
|
86
|
+
self.hide()
|
|
87
|
+
return
|
|
88
|
+
layer_area = self.get_layer_area(editor.current_stack[editor.current_layer], x, y, w, h)
|
|
89
|
+
master_area = self.get_layer_area(editor.master_layer, x, y, w, h)
|
|
90
|
+
if layer_area is None or master_area is None:
|
|
91
|
+
self.hide()
|
|
92
|
+
return
|
|
93
|
+
height, width = editor.current_stack[editor.current_layer].shape[:2]
|
|
94
|
+
full_mask = create_brush_mask(size=size, hardness_percent=editor.brush.hardness,
|
|
95
|
+
opacity_percent=editor.brush.opacity)[:, :, np.newaxis]
|
|
96
|
+
mask_x_start = max(0, -x) if x < 0 else 0
|
|
97
|
+
mask_y_start = max(0, -y) if y < 0 else 0
|
|
98
|
+
mask_x_end = size - (max(0, (x + w) - width)) if (x + w) > width else size
|
|
99
|
+
mask_y_end = size - (max(0, (y + h) - height)) if (y + h) > height else size
|
|
100
|
+
mask_area = full_mask[mask_y_start:mask_y_end, mask_x_start:mask_x_end]
|
|
101
|
+
area = (layer_area * mask_area + master_area * (1 - mask_area)) * 255.0
|
|
102
|
+
area = area.astype(np.uint8)
|
|
103
|
+
qimage = QImage(area.data, area.shape[1], area.shape[0], area.strides[0], QImage.Format_RGB888)
|
|
104
|
+
mask = QPixmap(w, h)
|
|
105
|
+
mask.fill(Qt.transparent)
|
|
106
|
+
painter = QPainter(mask)
|
|
107
|
+
painter.setPen(Qt.NoPen)
|
|
108
|
+
painter.setBrush(Qt.black)
|
|
109
|
+
painter.drawEllipse(0, 0, w, h)
|
|
110
|
+
painter.end()
|
|
111
|
+
pixmap = QPixmap.fromImage(qimage)
|
|
112
|
+
final_pixmap = QPixmap(w, h)
|
|
113
|
+
final_pixmap.fill(Qt.transparent)
|
|
114
|
+
painter = QPainter(final_pixmap)
|
|
115
|
+
painter.drawPixmap(0, 0, pixmap)
|
|
116
|
+
painter.setCompositionMode(QPainter.CompositionMode_DestinationIn)
|
|
117
|
+
painter.drawPixmap(0, 0, mask)
|
|
118
|
+
painter.end()
|
|
119
|
+
self.setPixmap(final_pixmap)
|
|
120
|
+
x_start, y_start = max(0, x), max(0, y)
|
|
121
|
+
self.setPos(x_start, y_start)
|
|
122
|
+
self.show()
|
|
123
|
+
except Exception:
|
|
124
|
+
import traceback
|
|
125
|
+
traceback.print_exc()
|
|
126
|
+
self.hide()
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from PIL.TiffImagePlugin import IFDRational
|
|
3
|
+
from PySide6.QtWidgets import QFormLayout, QHBoxLayout, QPushButton, QDialog, QLabel
|
|
4
|
+
from PySide6.QtGui import QIcon
|
|
5
|
+
from PySide6.QtCore import Qt
|
|
6
|
+
from .. core.core_utils import get_app_base_path
|
|
7
|
+
from .. algorithms.exif import exif_dict
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ExifData(QDialog):
|
|
11
|
+
def __init__(self, exif, parent=None):
|
|
12
|
+
super().__init__(parent)
|
|
13
|
+
self.exif = exif
|
|
14
|
+
self.setWindowTitle("EXIF data")
|
|
15
|
+
self.resize(500, self.height())
|
|
16
|
+
self.layout = QFormLayout(self)
|
|
17
|
+
self.layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
|
|
18
|
+
self.layout.setRowWrapPolicy(QFormLayout.DontWrapRows)
|
|
19
|
+
self.layout.setFormAlignment(Qt.AlignLeft | Qt.AlignTop)
|
|
20
|
+
self.layout.setLabelAlignment(Qt.AlignLeft)
|
|
21
|
+
self.create_form()
|
|
22
|
+
button_box = QHBoxLayout()
|
|
23
|
+
ok_button = QPushButton("OK")
|
|
24
|
+
ok_button.setFocus()
|
|
25
|
+
button_box.addWidget(ok_button)
|
|
26
|
+
self.layout.addRow(button_box)
|
|
27
|
+
ok_button.clicked.connect(self.accept)
|
|
28
|
+
|
|
29
|
+
def add_bold_label(self, label):
|
|
30
|
+
label = QLabel(label)
|
|
31
|
+
label.setStyleSheet("font-weight: bold")
|
|
32
|
+
self.layout.addRow(label)
|
|
33
|
+
|
|
34
|
+
def create_form(self):
|
|
35
|
+
icon_path = f'{get_app_base_path()}'
|
|
36
|
+
if os.path.exists(f'{icon_path}/ico'):
|
|
37
|
+
icon_path = f'{icon_path}/ico'
|
|
38
|
+
else:
|
|
39
|
+
icon_path = f'{icon_path}/../ico'
|
|
40
|
+
icon_path = f'{icon_path}/shinestacker.png'
|
|
41
|
+
app_icon = QIcon(icon_path)
|
|
42
|
+
icon_pixmap = app_icon.pixmap(128, 128)
|
|
43
|
+
icon_label = QLabel()
|
|
44
|
+
icon_label.setPixmap(icon_pixmap)
|
|
45
|
+
icon_label.setAlignment(Qt.AlignCenter)
|
|
46
|
+
self.layout.addRow(icon_label)
|
|
47
|
+
spacer = QLabel("")
|
|
48
|
+
spacer.setFixedHeight(10)
|
|
49
|
+
self.layout.addRow(spacer)
|
|
50
|
+
self.add_bold_label("EXIF data")
|
|
51
|
+
shortcuts = {}
|
|
52
|
+
if self.exif is None:
|
|
53
|
+
shortcuts['Warning:'] = 'no EXIF data found'
|
|
54
|
+
else:
|
|
55
|
+
data = exif_dict(self.exif)
|
|
56
|
+
if len(data) > 0:
|
|
57
|
+
for k, (t, d) in data.items():
|
|
58
|
+
if isinstance(d, IFDRational):
|
|
59
|
+
d = f"{d.numerator}/{d.denominator}"
|
|
60
|
+
else:
|
|
61
|
+
d = f"{d}"
|
|
62
|
+
if "<<<" not in d and k != 'IPTCNAA':
|
|
63
|
+
self.layout.addRow(f"<b>{k}:</b>", QLabel(d))
|
|
64
|
+
else:
|
|
65
|
+
self.layout.addRow("-", QLabel("Empty EXIF dictionary"))
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import traceback
|
|
3
|
+
import cv2
|
|
4
|
+
import os
|
|
5
|
+
from psdtags import PsdChannelId
|
|
6
|
+
from PySide6.QtCore import QThread, Signal
|
|
7
|
+
from .. algorithms.utils import read_img
|
|
8
|
+
from .. algorithms.multilayer import read_multilayer_tiff
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class FileLoader(QThread):
|
|
12
|
+
finished = Signal(object, object, object)
|
|
13
|
+
error = Signal(str)
|
|
14
|
+
|
|
15
|
+
def __init__(self, path):
|
|
16
|
+
super().__init__()
|
|
17
|
+
self.path = path
|
|
18
|
+
|
|
19
|
+
def run(self):
|
|
20
|
+
try:
|
|
21
|
+
current_stack, current_labels = self.load_stack(self.path)
|
|
22
|
+
if current_stack is None or len(current_stack) == 0:
|
|
23
|
+
self.error.emit("Empty or invalid stack")
|
|
24
|
+
return
|
|
25
|
+
if current_labels:
|
|
26
|
+
master_indices = [i for i, label in enumerate(current_labels) if label.lower() == "master"]
|
|
27
|
+
else:
|
|
28
|
+
master_indices = []
|
|
29
|
+
master_index = -1 if len(master_indices) == 0 else master_indices[0]
|
|
30
|
+
if master_index == -1:
|
|
31
|
+
master_layer = current_stack[0].copy()
|
|
32
|
+
else:
|
|
33
|
+
current_labels.pop(master_index)
|
|
34
|
+
master_layer = current_stack[master_index].copy()
|
|
35
|
+
indices = list(range(len(current_stack)))
|
|
36
|
+
indices.remove(master_index)
|
|
37
|
+
current_stack = current_stack[indices]
|
|
38
|
+
master_layer.setflags(write=True)
|
|
39
|
+
if current_labels is None:
|
|
40
|
+
current_labels = [f"Layer {i + 1}" for i in range(len(current_stack))]
|
|
41
|
+
self.finished.emit(current_stack, current_labels, master_layer)
|
|
42
|
+
except Exception as e:
|
|
43
|
+
traceback.print_tb(e.__traceback__)
|
|
44
|
+
self.error.emit(f"Error loading file:\n{str(e)}")
|
|
45
|
+
|
|
46
|
+
def load_stack(self, path):
|
|
47
|
+
if not os.path.exists(path):
|
|
48
|
+
raise RuntimeError(f"Path {path} does not exist.")
|
|
49
|
+
if not os.path.isfile(path):
|
|
50
|
+
raise RuntimeError(f"Path {path} is not a file.")
|
|
51
|
+
extension = path.split('.')[-1]
|
|
52
|
+
if extension in ['jpg', 'jpeg']:
|
|
53
|
+
try:
|
|
54
|
+
stack = np.array([cv2.cvtColor(read_img(path), cv2.COLOR_BGR2RGB)])
|
|
55
|
+
return stack, [path.split('/')[-1].split('.')[0]]
|
|
56
|
+
except Exception as e:
|
|
57
|
+
traceback.print_tb(e.__traceback__)
|
|
58
|
+
return None, None
|
|
59
|
+
elif extension in ['tif', 'tiff']:
|
|
60
|
+
try:
|
|
61
|
+
psd_data = read_multilayer_tiff(path)
|
|
62
|
+
layers = []
|
|
63
|
+
labels = []
|
|
64
|
+
for layer in reversed(psd_data.layers.layers):
|
|
65
|
+
channels = {}
|
|
66
|
+
for channel in layer.channels:
|
|
67
|
+
channels[channel.channelid] = channel.data
|
|
68
|
+
if PsdChannelId.CHANNEL0 in channels:
|
|
69
|
+
img = np.stack([
|
|
70
|
+
channels[PsdChannelId.CHANNEL0],
|
|
71
|
+
channels[PsdChannelId.CHANNEL1],
|
|
72
|
+
channels[PsdChannelId.CHANNEL2]
|
|
73
|
+
], axis=-1)
|
|
74
|
+
layers.append(img)
|
|
75
|
+
labels.append(layer.name)
|
|
76
|
+
if layers:
|
|
77
|
+
stack = np.array(layers)
|
|
78
|
+
if labels:
|
|
79
|
+
master_indices = [i for i, label in enumerate(labels) if label.lower() == "master"]
|
|
80
|
+
if master_indices:
|
|
81
|
+
master_index = master_indices[0]
|
|
82
|
+
master_label = labels.pop(master_index)
|
|
83
|
+
master_layer = stack[master_index]
|
|
84
|
+
stack = np.delete(stack, master_index, axis=0)
|
|
85
|
+
labels.insert(0, master_label)
|
|
86
|
+
stack = np.insert(stack, 0, master_layer, axis=0)
|
|
87
|
+
return stack, labels
|
|
88
|
+
return stack, labels
|
|
89
|
+
except ValueError as e:
|
|
90
|
+
if str(e) == "TIFF file contains no ImageSourceData tag":
|
|
91
|
+
try:
|
|
92
|
+
stack = np.array([cv2.cvtColor(read_img(path), cv2.COLOR_BGR2RGB)])
|
|
93
|
+
return stack, [path.split('/')[-1].split('.')[0]]
|
|
94
|
+
except Exception as e:
|
|
95
|
+
traceback.print_tb(e.__traceback__)
|
|
96
|
+
return None, None
|
|
97
|
+
else:
|
|
98
|
+
traceback.print_tb(e.__traceback__)
|
|
99
|
+
raise e
|
|
100
|
+
except Exception as e:
|
|
101
|
+
traceback.print_tb(e.__traceback__)
|
|
102
|
+
return None, None
|
|
103
|
+
else:
|
|
104
|
+
return None, None
|