shinestacker 1.0.4.post2__py3-none-any.whl → 1.2.0__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/__init__.py +4 -1
- shinestacker/algorithms/align.py +128 -14
- shinestacker/algorithms/balance.py +362 -163
- shinestacker/algorithms/base_stack_algo.py +33 -4
- shinestacker/algorithms/depth_map.py +9 -12
- shinestacker/algorithms/multilayer.py +12 -2
- shinestacker/algorithms/noise_detection.py +8 -3
- shinestacker/algorithms/pyramid.py +57 -42
- shinestacker/algorithms/pyramid_auto.py +141 -0
- shinestacker/algorithms/pyramid_tiles.py +264 -0
- shinestacker/algorithms/stack.py +14 -11
- shinestacker/algorithms/stack_framework.py +17 -11
- shinestacker/algorithms/utils.py +180 -1
- shinestacker/algorithms/vignetting.py +23 -5
- shinestacker/config/constants.py +31 -5
- shinestacker/gui/action_config.py +6 -7
- shinestacker/gui/action_config_dialog.py +425 -258
- shinestacker/gui/base_form_dialog.py +11 -6
- shinestacker/gui/flow_layout.py +105 -0
- shinestacker/gui/gui_run.py +24 -19
- shinestacker/gui/main_window.py +4 -3
- shinestacker/gui/menu_manager.py +12 -2
- shinestacker/gui/new_project.py +28 -22
- shinestacker/gui/project_controller.py +40 -23
- shinestacker/gui/project_converter.py +6 -6
- shinestacker/gui/project_editor.py +21 -7
- shinestacker/gui/time_progress_bar.py +2 -2
- shinestacker/retouch/exif_data.py +5 -5
- shinestacker/retouch/shortcuts_help.py +4 -4
- shinestacker/retouch/vignetting_filter.py +12 -8
- {shinestacker-1.0.4.post2.dist-info → shinestacker-1.2.0.dist-info}/METADATA +20 -1
- {shinestacker-1.0.4.post2.dist-info → shinestacker-1.2.0.dist-info}/RECORD +37 -34
- {shinestacker-1.0.4.post2.dist-info → shinestacker-1.2.0.dist-info}/WHEEL +0 -0
- {shinestacker-1.0.4.post2.dist-info → shinestacker-1.2.0.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.0.4.post2.dist-info → shinestacker-1.2.0.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.0.4.post2.dist-info → shinestacker-1.2.0.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, R0902, E1101, W0718, W0640, R0913, R0917, R0914
|
|
2
|
+
import math
|
|
2
3
|
import traceback
|
|
3
4
|
import logging
|
|
4
5
|
import numpy as np
|
|
@@ -49,16 +50,30 @@ def fit_sigmoid(radii, intensities):
|
|
|
49
50
|
return res
|
|
50
51
|
|
|
51
52
|
|
|
53
|
+
def subsample_factor(subsample, image):
|
|
54
|
+
if subsample == 0:
|
|
55
|
+
h, w = image.shape[:2]
|
|
56
|
+
img_res = (float(h) / 1000) * (float(w) / 1000)
|
|
57
|
+
target_res = constants.DEFAULT_BALANCE_RES_TARGET_MPX
|
|
58
|
+
subsample = int(1 + math.floor(img_res / target_res))
|
|
59
|
+
return subsample
|
|
60
|
+
|
|
61
|
+
|
|
52
62
|
def img_subsampled(image, subsample=constants.DEFAULT_VIGN_SUBSAMPLE,
|
|
53
63
|
fast_subsampling=constants.DEFAULT_VIGN_FAST_SUBSAMPLING):
|
|
54
64
|
image_bw = cv2.cvtColor(img_8bit(image), cv2.COLOR_BGR2GRAY)
|
|
55
|
-
|
|
65
|
+
if subsample == 0:
|
|
66
|
+
subsample = subsample_factor(subsample, image)
|
|
67
|
+
img_sub = image_bw if subsample == 1 else img_subsample(image_bw, subsample, fast_subsampling)
|
|
68
|
+
return img_sub
|
|
56
69
|
|
|
57
70
|
|
|
58
71
|
def compute_fit_parameters(
|
|
59
72
|
image, r_steps, radii=None, intensities=None,
|
|
60
73
|
subsample=constants.DEFAULT_VIGN_SUBSAMPLE,
|
|
61
74
|
fast_subsampling=constants.DEFAULT_VIGN_FAST_SUBSAMPLING):
|
|
75
|
+
if subsample == 0:
|
|
76
|
+
subsample = subsample_factor(subsample, image)
|
|
62
77
|
image_sub = img_subsampled(image, subsample, fast_subsampling)
|
|
63
78
|
if radii is None and intensities is None:
|
|
64
79
|
radii, intensities = radial_mean_intensity(image_sub, r_steps)
|
|
@@ -77,6 +92,8 @@ def correct_vignetting(
|
|
|
77
92
|
if params is None:
|
|
78
93
|
if r_steps is None:
|
|
79
94
|
raise RuntimeError("Either r_steps or pars must not be None")
|
|
95
|
+
if subsample == 0:
|
|
96
|
+
subsample = subsample_factor(subsample, image)
|
|
80
97
|
params = compute_fit_parameters(
|
|
81
98
|
image, r_steps, subsample=subsample, fast_subsampling=fast_subsampling)
|
|
82
99
|
if v0 is None:
|
|
@@ -121,11 +138,12 @@ class Vignetting(SubAction):
|
|
|
121
138
|
h, w = img_0.shape[:2]
|
|
122
139
|
self.w_2, self.h_2 = w / 2, h / 2
|
|
123
140
|
self.r_max = np.sqrt((w / 2)**2 + (h / 2)**2)
|
|
124
|
-
|
|
141
|
+
subsample = subsample_factor(self.subsample, img_0)
|
|
142
|
+
image_sub = img_subsampled(img_0, subsample, self.fast_subsampling)
|
|
125
143
|
radii, intensities = radial_mean_intensity(image_sub, self.r_steps)
|
|
126
144
|
try:
|
|
127
145
|
params = compute_fit_parameters(
|
|
128
|
-
img_0, self.r_steps, radii, intensities,
|
|
146
|
+
img_0, self.r_steps, radii, intensities, subsample, self.fast_subsampling)
|
|
129
147
|
except Exception as e:
|
|
130
148
|
traceback.print_tb(e.__traceback__)
|
|
131
149
|
self.process.sub_message(
|
|
@@ -144,7 +162,7 @@ class Vignetting(SubAction):
|
|
|
144
162
|
if self.plot_correction:
|
|
145
163
|
plt.figure(figsize=(10, 5))
|
|
146
164
|
plt.plot(radii, intensities, label="image mean intensity")
|
|
147
|
-
plt.plot(radii, sigmoid_model(radii *
|
|
165
|
+
plt.plot(radii, sigmoid_model(radii * subsample, *params), label="sigmoid fit")
|
|
148
166
|
plt.xlabel('radius (pixels)')
|
|
149
167
|
plt.ylabel('mean intensity')
|
|
150
168
|
plt.legend()
|
|
@@ -165,7 +183,7 @@ class Vignetting(SubAction):
|
|
|
165
183
|
self.process.sub_message_r(color_str(": correct vignetting", "cyan"))
|
|
166
184
|
return correct_vignetting(
|
|
167
185
|
img_0, self.max_correction, self.black_threshold, None, params, self.v0,
|
|
168
|
-
|
|
186
|
+
subsample, self.fast_subsampling)
|
|
169
187
|
|
|
170
188
|
def begin(self, process):
|
|
171
189
|
self.process = process
|
shinestacker/config/constants.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, C0103, R0903
|
|
2
2
|
import sys
|
|
3
3
|
import re
|
|
4
|
+
import os
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class _Constants:
|
|
@@ -12,6 +13,9 @@ class _Constants:
|
|
|
12
13
|
NUM_UINT16 = 65536
|
|
13
14
|
MAX_UINT8 = 255
|
|
14
15
|
MAX_UINT16 = 65535
|
|
16
|
+
ONE_KILO = 1024
|
|
17
|
+
ONE_MEGA = ONE_KILO**2
|
|
18
|
+
ONE_GIGA = ONE_KILO**3
|
|
15
19
|
|
|
16
20
|
LOG_FONTS = ['Monaco', 'Menlo', ' Lucida Console', 'Courier New', 'Courier', 'monospace']
|
|
17
21
|
LOG_FONTS_STR = ", ".join(LOG_FONTS)
|
|
@@ -39,9 +43,16 @@ class _Constants:
|
|
|
39
43
|
STACK_ALGO_DEFAULT = STACK_ALGO_PYRAMID
|
|
40
44
|
DEFAULT_PLOTS_PATH = 'plots'
|
|
41
45
|
|
|
46
|
+
FIELD_SUBSAMPLE_VALUES_1 = [2, 3, 4, 6, 8, 12, 16, 24, 32]
|
|
47
|
+
FIELD_SUBSAMPLE_OPTIONS_1 = [f"1/{n} × 1/{n}" for n in FIELD_SUBSAMPLE_VALUES_1]
|
|
48
|
+
FIELD_SUBSAMPLE_VALUES = [0, 1] + FIELD_SUBSAMPLE_VALUES_1
|
|
49
|
+
FIELD_SUBSAMPLE_OPTIONS = ['Auto', 'Full resolution'] + FIELD_SUBSAMPLE_OPTIONS_1
|
|
50
|
+
FIELD_SUBSAMPLE_DEFAULT = FIELD_SUBSAMPLE_VALUES[0]
|
|
51
|
+
|
|
42
52
|
PATH_SEPARATOR = ';'
|
|
43
53
|
|
|
44
54
|
LOG_COLOR_ALERT = 'red'
|
|
55
|
+
LOG_COLOR_WARNING = 'yellow'
|
|
45
56
|
LOG_COLOR_LEVEL_JOB = 'green'
|
|
46
57
|
LOG_COLOR_LEVEL_1 = 'blue'
|
|
47
58
|
LOG_COLOR_LEVEL_2 = 'magenta'
|
|
@@ -49,8 +60,10 @@ class _Constants:
|
|
|
49
60
|
|
|
50
61
|
DEFAULT_FILE_REVERSE_ORDER = False
|
|
51
62
|
DEFAULT_MULTILAYER_FILE_REVERSE_ORDER = True
|
|
63
|
+
MULTILAYER_WARNING_MEM_GB = 1
|
|
52
64
|
|
|
53
65
|
DEFAULT_NOISE_MAP_FILENAME = "noise-map/hot_pixels.png"
|
|
66
|
+
DEFAULT_NOISE_MAX_FRAMES = 10
|
|
54
67
|
DEFAULT_MN_KERNEL_SIZE = 3
|
|
55
68
|
INTERPOLATE_MEAN = 'MEAN'
|
|
56
69
|
INTERPOLATE_MEDIAN = 'MEDIAN'
|
|
@@ -103,11 +116,13 @@ class _Constants:
|
|
|
103
116
|
DEFAULT_REFINE_ITERS = 100
|
|
104
117
|
DEFAULT_ALIGN_CONFIDENCE = 99.9
|
|
105
118
|
DEFAULT_ALIGN_MAX_ITERS = 2000
|
|
119
|
+
DEFAULT_ALIGN_ABORT_ABNORMAL = True
|
|
106
120
|
DEFAULT_BORDER_VALUE = [0] * 4
|
|
107
121
|
DEFAULT_BORDER_BLUR = 50
|
|
108
|
-
DEFAULT_ALIGN_SUBSAMPLE =
|
|
122
|
+
DEFAULT_ALIGN_SUBSAMPLE = 0
|
|
123
|
+
DEFAULT_ALIGN_RES_TARGET_MPX = 2
|
|
109
124
|
DEFAULT_ALIGN_FAST_SUBSAMPLING = False
|
|
110
|
-
DEFAULT_ALIGN_MIN_GOOD_MATCHES =
|
|
125
|
+
DEFAULT_ALIGN_MIN_GOOD_MATCHES = 50
|
|
111
126
|
|
|
112
127
|
BALANCE_LINEAR = "LINEAR"
|
|
113
128
|
BALANCE_GAMMA = "GAMMA"
|
|
@@ -118,9 +133,12 @@ class _Constants:
|
|
|
118
133
|
BALANCE_RGB = "RGB"
|
|
119
134
|
BALANCE_HSV = "HSV"
|
|
120
135
|
BALANCE_HLS = "HLS"
|
|
121
|
-
|
|
136
|
+
BALANCE_LAB = "LAB"
|
|
137
|
+
VALID_BALANCE_CHANNELS = [BALANCE_LUMI, BALANCE_RGB, BALANCE_HSV, BALANCE_HLS,
|
|
138
|
+
BALANCE_LAB]
|
|
122
139
|
|
|
123
|
-
DEFAULT_BALANCE_SUBSAMPLE =
|
|
140
|
+
DEFAULT_BALANCE_SUBSAMPLE = 0
|
|
141
|
+
DEFAULT_BALANCE_RES_TARGET_MPX = 2
|
|
124
142
|
DEFAULT_BALANCE_FAST_SUBSAMPLING = False
|
|
125
143
|
DEFAULT_CORR_MAP = BALANCE_LINEAR
|
|
126
144
|
DEFAULT_CHANNEL = BALANCE_LUMI
|
|
@@ -129,7 +147,8 @@ class _Constants:
|
|
|
129
147
|
DEFAULT_R_STEPS = 100
|
|
130
148
|
DEFAULT_BLACK_THRESHOLD = 1.0
|
|
131
149
|
DEFAULT_MAX_CORRECTION = 1
|
|
132
|
-
DEFAULT_VIGN_SUBSAMPLE =
|
|
150
|
+
DEFAULT_VIGN_SUBSAMPLE = 0
|
|
151
|
+
DEFAULT_VIGN_RES_TARGET_MPX = 2
|
|
133
152
|
DEFAULT_VIGN_FAST_SUBSAMPLING = False
|
|
134
153
|
|
|
135
154
|
FLOAT_32 = 'float-32'
|
|
@@ -160,6 +179,13 @@ class _Constants:
|
|
|
160
179
|
DEFAULT_PY_MIN_SIZE = 32
|
|
161
180
|
DEFAULT_PY_KERNEL_SIZE = 5
|
|
162
181
|
DEFAULT_PY_GEN_KERNEL = 0.4
|
|
182
|
+
DEFAULT_PY_TILE_SIZE = 512
|
|
183
|
+
DEFAULT_PY_N_TILED_LAYERS = 2
|
|
184
|
+
DEFAULT_PY_MEMORY_LIMIT_GB = 8
|
|
185
|
+
DEFAULT_PY_MAX_THREADS = min(os.cpu_count() or 4, 8)
|
|
186
|
+
DEFAULT_PY_MODE = 'auto'
|
|
187
|
+
PY_VALID_MODES = ['auto', 'memory', 'tiled']
|
|
188
|
+
MIN_PY_TILE_SIZE = 256
|
|
163
189
|
|
|
164
190
|
DEFAULT_PLOT_STACK_BUNCH = False
|
|
165
191
|
DEFAULT_PLOT_STACK = True
|
|
@@ -10,7 +10,6 @@ from PySide6.QtWidgets import (QPushButton, QHBoxLayout, QFileDialog, QLabel, QC
|
|
|
10
10
|
from .. config.constants import constants
|
|
11
11
|
from .select_path_widget import (create_select_file_paths_widget, create_layout_widget_no_margins,
|
|
12
12
|
create_layout_widget_and_connect)
|
|
13
|
-
from .project_model import ActionConfig
|
|
14
13
|
|
|
15
14
|
FIELD_TEXT = 'text'
|
|
16
15
|
FIELD_ABS_PATH = 'abs_path'
|
|
@@ -30,23 +29,23 @@ class ActionConfigurator(ABC):
|
|
|
30
29
|
self.current_wd = current_wd
|
|
31
30
|
|
|
32
31
|
@abstractmethod
|
|
33
|
-
def create_form(self, layout, action
|
|
32
|
+
def create_form(self, layout, action, tag="Action"):
|
|
34
33
|
pass
|
|
35
34
|
|
|
36
35
|
@abstractmethod
|
|
37
|
-
def update_params(self, params
|
|
36
|
+
def update_params(self, params):
|
|
38
37
|
pass
|
|
39
38
|
|
|
40
39
|
|
|
41
40
|
class FieldBuilder:
|
|
42
41
|
def __init__(self, layout, action, current_wd):
|
|
43
|
-
self.
|
|
42
|
+
self.main_layout = layout
|
|
44
43
|
self.action = action
|
|
45
44
|
self.current_wd = current_wd
|
|
46
45
|
self.fields = {}
|
|
47
46
|
|
|
48
|
-
def add_field(self, tag
|
|
49
|
-
required
|
|
47
|
+
def add_field(self, tag, field_type, label,
|
|
48
|
+
required=False, add_to_layout=None, **kwargs):
|
|
50
49
|
if field_type == FIELD_TEXT:
|
|
51
50
|
widget = self.create_text_field(tag, **kwargs)
|
|
52
51
|
elif field_type == FIELD_ABS_PATH:
|
|
@@ -93,7 +92,7 @@ class FieldBuilder:
|
|
|
93
92
|
**kwargs
|
|
94
93
|
}
|
|
95
94
|
if add_to_layout is None:
|
|
96
|
-
add_to_layout = self.
|
|
95
|
+
add_to_layout = self.main_layout
|
|
97
96
|
add_to_layout.addRow(f"{label}:", widget)
|
|
98
97
|
return widget
|
|
99
98
|
|