shinestacker 1.3.0__py3-none-any.whl → 1.3.1__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 +1 -1
- shinestacker/algorithms/align.py +35 -27
- shinestacker/algorithms/align_auto.py +15 -3
- shinestacker/algorithms/align_parallel.py +65 -25
- shinestacker/algorithms/base_stack_algo.py +14 -20
- shinestacker/algorithms/depth_map.py +9 -14
- shinestacker/algorithms/pyramid.py +8 -22
- shinestacker/algorithms/pyramid_auto.py +5 -14
- shinestacker/algorithms/pyramid_tiles.py +18 -20
- shinestacker/algorithms/stack_framework.py +1 -1
- shinestacker/algorithms/utils.py +16 -0
- shinestacker/app/gui_utils.py +10 -0
- shinestacker/app/main.py +3 -1
- shinestacker/app/project.py +3 -1
- shinestacker/app/retouch.py +3 -1
- shinestacker/gui/action_config_dialog.py +302 -272
- shinestacker/gui/colors.py +1 -0
- shinestacker/gui/folder_file_selection.py +5 -0
- shinestacker/gui/main_window.py +4 -4
- shinestacker/gui/new_project.py +5 -5
- shinestacker/gui/project_editor.py +6 -4
- shinestacker/gui/sys_mon.py +24 -23
- {shinestacker-1.3.0.dist-info → shinestacker-1.3.1.dist-info}/METADATA +1 -1
- {shinestacker-1.3.0.dist-info → shinestacker-1.3.1.dist-info}/RECORD +28 -28
- {shinestacker-1.3.0.dist-info → shinestacker-1.3.1.dist-info}/WHEEL +0 -0
- {shinestacker-1.3.0.dist-info → shinestacker-1.3.1.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.3.0.dist-info → shinestacker-1.3.1.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.3.0.dist-info → shinestacker-1.3.1.dist-info}/top_level.txt +0 -0
|
@@ -10,7 +10,7 @@ from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
|
10
10
|
import numpy as np
|
|
11
11
|
from .. config.constants import constants
|
|
12
12
|
from .. core.exceptions import RunStopException
|
|
13
|
-
from .utils import read_img
|
|
13
|
+
from .utils import read_img, read_and_validate_img
|
|
14
14
|
from .pyramid import PyramidBase
|
|
15
15
|
|
|
16
16
|
|
|
@@ -47,11 +47,11 @@ class PyramidTilesStack(PyramidBase):
|
|
|
47
47
|
return n_steps + self.n_tiles
|
|
48
48
|
|
|
49
49
|
def _process_single_image_wrapper(self, args):
|
|
50
|
-
img_path,
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
level_count = self.process_single_image(img, self.n_levels,
|
|
54
|
-
return
|
|
50
|
+
img_path, idx, _n = args
|
|
51
|
+
img = read_and_validate_img(img_path, self.shape, self.dtype)
|
|
52
|
+
self.check_running(self.cleanup_temp_files)
|
|
53
|
+
level_count = self.process_single_image(img, self.n_levels, idx)
|
|
54
|
+
return idx, level_count
|
|
55
55
|
|
|
56
56
|
def process_single_image(self, img, levels, img_index):
|
|
57
57
|
laplacian = self.single_image_laplacian(img, levels)
|
|
@@ -160,10 +160,11 @@ class PyramidTilesStack(PyramidBase):
|
|
|
160
160
|
gc.collect()
|
|
161
161
|
return np.zeros((y_end - y, x_end - x, 3), dtype=self.float_type)
|
|
162
162
|
|
|
163
|
-
def fuse_pyramids(self, all_level_counts
|
|
163
|
+
def fuse_pyramids(self, all_level_counts):
|
|
164
|
+
num_images = self.num_images()
|
|
164
165
|
max_levels = max(all_level_counts)
|
|
165
166
|
fused = []
|
|
166
|
-
count =
|
|
167
|
+
count = super().total_steps(num_images)
|
|
167
168
|
for level in range(max_levels - 1, -1, -1):
|
|
168
169
|
self.print_message(f': fusing pyramids, layer: {level + 1}')
|
|
169
170
|
if level < self.n_tiled_layers:
|
|
@@ -201,12 +202,11 @@ class PyramidTilesStack(PyramidBase):
|
|
|
201
202
|
return fused[::-1]
|
|
202
203
|
|
|
203
204
|
def focus_stack(self):
|
|
204
|
-
|
|
205
|
-
self.focus_stack_validate(self.cleanup_temp_files)
|
|
206
|
-
all_level_counts = [0] * n
|
|
205
|
+
all_level_counts = [0] * self.num_images()
|
|
207
206
|
if self.num_threads > 1:
|
|
208
207
|
self.print_message(f': starting parallel processing on {self.num_threads} cores')
|
|
209
|
-
args_list = [(file_path, i,
|
|
208
|
+
args_list = [(file_path, i, self.num_images())
|
|
209
|
+
for i, file_path in enumerate(self.filenames)]
|
|
210
210
|
executor = None
|
|
211
211
|
try:
|
|
212
212
|
executor = ThreadPoolExecutor(max_workers=self.num_threads)
|
|
@@ -222,12 +222,11 @@ class PyramidTilesStack(PyramidBase):
|
|
|
222
222
|
all_level_counts[img_index] = level_count
|
|
223
223
|
completed_count += 1
|
|
224
224
|
self.print_message(
|
|
225
|
-
": processing completed,
|
|
226
|
-
f"{self.idx_tot_str(completed_count - 1)}")
|
|
225
|
+
f": processing completed, {self.image_str(completed_count - 1)}")
|
|
227
226
|
except Exception as e:
|
|
228
227
|
self.print_message(
|
|
229
|
-
f"Error processing
|
|
230
|
-
self.after_step(completed_count
|
|
228
|
+
f"Error processing {self.image_str(i)}: {str(e)}")
|
|
229
|
+
self.after_step(completed_count)
|
|
231
230
|
self.check_running(lambda: None)
|
|
232
231
|
except RunStopException:
|
|
233
232
|
self.print_message(": stopping image processing...")
|
|
@@ -242,16 +241,15 @@ class PyramidTilesStack(PyramidBase):
|
|
|
242
241
|
else:
|
|
243
242
|
for i, file_path in enumerate(self.filenames):
|
|
244
243
|
self.print_message(
|
|
245
|
-
f": processing
|
|
246
|
-
f"{self.idx_tot_str(i)}")
|
|
244
|
+
f": processing {self.image_str(i)}")
|
|
247
245
|
img = read_img(file_path)
|
|
248
246
|
level_count = self.process_single_image(img, self.n_levels, i)
|
|
249
247
|
all_level_counts[i] = level_count
|
|
250
|
-
self.after_step(i +
|
|
248
|
+
self.after_step(i + 1)
|
|
251
249
|
self.check_running(lambda: None)
|
|
252
250
|
try:
|
|
253
251
|
self.check_running(lambda: None)
|
|
254
|
-
fused_pyramid = self.fuse_pyramids(all_level_counts
|
|
252
|
+
fused_pyramid = self.fuse_pyramids(all_level_counts)
|
|
255
253
|
stacked_image = self.collapse(fused_pyramid)
|
|
256
254
|
return stacked_image.astype(self.dtype)
|
|
257
255
|
except RunStopException:
|
|
@@ -117,7 +117,7 @@ class ImageSequenceManager:
|
|
|
117
117
|
assert False, "this method should be overwritten"
|
|
118
118
|
|
|
119
119
|
def set_filelist(self):
|
|
120
|
-
file_folder = self.input_full_path()
|
|
120
|
+
file_folder = os.path.relpath(self.input_full_path(), self.working_path)
|
|
121
121
|
self.print_message(color_str(f"{self.num_input_filepaths()} files in folder: {file_folder}",
|
|
122
122
|
constants.LOG_COLOR_LEVEL_2))
|
|
123
123
|
self.base_message = color_str(self.name, constants.LOG_COLOR_LEVEL_1, "bold")
|
shinestacker/algorithms/utils.py
CHANGED
|
@@ -87,6 +87,17 @@ def img_bw(img):
|
|
|
87
87
|
return cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
|
|
88
88
|
|
|
89
89
|
|
|
90
|
+
def get_first_image_file(filenames):
|
|
91
|
+
first_img_file = None
|
|
92
|
+
for filename in filenames:
|
|
93
|
+
if os.path.isfile(filename) and extension_tif_jpg(filename):
|
|
94
|
+
first_img_file = filename
|
|
95
|
+
break
|
|
96
|
+
if first_img_file is None:
|
|
97
|
+
raise ValueError("No valid image files found")
|
|
98
|
+
return first_img_file
|
|
99
|
+
|
|
100
|
+
|
|
90
101
|
def get_img_file_shape(file_path):
|
|
91
102
|
img = read_img(file_path)
|
|
92
103
|
return img.shape[:2]
|
|
@@ -106,6 +117,11 @@ def validate_image(img, expected_shape=None, expected_dtype=None):
|
|
|
106
117
|
raise ShapeError(expected_shape, shape)
|
|
107
118
|
if expected_dtype and dtype != expected_dtype:
|
|
108
119
|
raise BitDepthError(expected_dtype, dtype)
|
|
120
|
+
return img
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def read_and_validate_img(filename, expected_shape=None, expected_dtype=None):
|
|
124
|
+
return validate_image(read_img(filename), expected_shape, expected_dtype)
|
|
109
125
|
|
|
110
126
|
|
|
111
127
|
def save_plot(filename):
|
shinestacker/app/gui_utils.py
CHANGED
|
@@ -53,3 +53,13 @@ def fill_app_menu(app, app_menu):
|
|
|
53
53
|
exit_action.setShortcut(quit_short)
|
|
54
54
|
exit_action.triggered.connect(app.quit)
|
|
55
55
|
app_menu.addAction(exit_action)
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def set_css_style(app):
|
|
59
|
+
css_style = """
|
|
60
|
+
QToolTip {
|
|
61
|
+
color: black;
|
|
62
|
+
border: 1px solid black;
|
|
63
|
+
}
|
|
64
|
+
"""
|
|
65
|
+
app.setStyleSheet(css_style)
|
shinestacker/app/main.py
CHANGED
|
@@ -16,7 +16,8 @@ from shinestacker.config.constants import constants
|
|
|
16
16
|
from shinestacker.core.logging import setup_logging
|
|
17
17
|
from shinestacker.gui.main_window import MainWindow
|
|
18
18
|
from shinestacker.retouch.image_editor_ui import ImageEditorUI
|
|
19
|
-
from shinestacker.app.gui_utils import
|
|
19
|
+
from shinestacker.app.gui_utils import (
|
|
20
|
+
disable_macos_special_menu_items, fill_app_menu, set_css_style)
|
|
20
21
|
from shinestacker.app.help_menu import add_help_action
|
|
21
22
|
from shinestacker.app.open_frames import open_frames
|
|
22
23
|
|
|
@@ -233,6 +234,7 @@ expert options are visible by default.
|
|
|
233
234
|
app.setWindowIcon(QIcon(icon_path))
|
|
234
235
|
main_app = MainApp()
|
|
235
236
|
app.main_app = main_app
|
|
237
|
+
set_css_style(app)
|
|
236
238
|
main_app.show()
|
|
237
239
|
main_app.activateWindow()
|
|
238
240
|
if args['expert']:
|
shinestacker/app/project.py
CHANGED
|
@@ -14,7 +14,8 @@ config.init(DISABLE_TQDM=True, DONT_USE_NATIVE_MENU=True)
|
|
|
14
14
|
from shinestacker.config.constants import constants
|
|
15
15
|
from shinestacker.core.logging import setup_logging
|
|
16
16
|
from shinestacker.gui.main_window import MainWindow
|
|
17
|
-
from shinestacker.app.gui_utils import
|
|
17
|
+
from shinestacker.app.gui_utils import (
|
|
18
|
+
disable_macos_special_menu_items, fill_app_menu, set_css_style)
|
|
18
19
|
from shinestacker.app.help_menu import add_help_action
|
|
19
20
|
|
|
20
21
|
|
|
@@ -63,6 +64,7 @@ expert options are visible by default.
|
|
|
63
64
|
disable_macos_special_menu_items()
|
|
64
65
|
icon_path = f"{os.path.dirname(__file__)}/../gui/ico/shinestacker.png"
|
|
65
66
|
app.setWindowIcon(QIcon(icon_path))
|
|
67
|
+
set_css_style(app)
|
|
66
68
|
window = ProjectApp()
|
|
67
69
|
if args['expert']:
|
|
68
70
|
window.set_expert_options()
|
shinestacker/app/retouch.py
CHANGED
|
@@ -9,7 +9,8 @@ from shinestacker.config.config import config
|
|
|
9
9
|
config.init(DISABLE_TQDM=True, DONT_USE_NATIVE_MENU=True)
|
|
10
10
|
from shinestacker.config.constants import constants
|
|
11
11
|
from shinestacker.retouch.image_editor_ui import ImageEditorUI
|
|
12
|
-
from shinestacker.app.gui_utils import
|
|
12
|
+
from shinestacker.app.gui_utils import (
|
|
13
|
+
disable_macos_special_menu_items, fill_app_menu, set_css_style)
|
|
13
14
|
from shinestacker.app.help_menu import add_help_action
|
|
14
15
|
from shinestacker.app.open_frames import open_frames
|
|
15
16
|
|
|
@@ -60,6 +61,7 @@ Multiple directories can be specified separated by ';'.
|
|
|
60
61
|
disable_macos_special_menu_items()
|
|
61
62
|
icon_path = f"{os.path.dirname(__file__)}/../gui/ico/shinestacker.png"
|
|
62
63
|
app.setWindowIcon(QIcon(icon_path))
|
|
64
|
+
set_css_style(app)
|
|
63
65
|
editor = RetouchApp()
|
|
64
66
|
app.editor = editor
|
|
65
67
|
editor.show()
|