shinestacker 1.7.0__py3-none-any.whl → 1.8.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 +184 -80
- shinestacker/algorithms/align_auto.py +13 -11
- shinestacker/algorithms/align_parallel.py +41 -16
- shinestacker/algorithms/base_stack_algo.py +1 -1
- shinestacker/algorithms/noise_detection.py +10 -8
- shinestacker/algorithms/pyramid_tiles.py +1 -1
- shinestacker/algorithms/stack.py +9 -0
- shinestacker/algorithms/stack_framework.py +49 -25
- shinestacker/algorithms/utils.py +5 -1
- shinestacker/algorithms/vignetting.py +16 -3
- shinestacker/app/settings_dialog.py +303 -136
- shinestacker/config/constants.py +10 -5
- shinestacker/config/settings.py +29 -8
- shinestacker/core/core_utils.py +1 -0
- shinestacker/core/exceptions.py +1 -1
- shinestacker/core/framework.py +9 -4
- shinestacker/gui/action_config.py +23 -20
- shinestacker/gui/action_config_dialog.py +107 -64
- shinestacker/gui/gui_images.py +27 -3
- shinestacker/gui/gui_run.py +1 -2
- shinestacker/gui/img/dark/close-round-line-icon.png +0 -0
- shinestacker/gui/img/dark/forward-button-icon.png +0 -0
- shinestacker/gui/img/dark/play-button-round-icon.png +0 -0
- shinestacker/gui/img/dark/plus-round-line-icon.png +0 -0
- shinestacker/gui/img/dark/shinestacker_bkg.png +0 -0
- shinestacker/gui/main_window.py +20 -7
- shinestacker/gui/menu_manager.py +18 -7
- shinestacker/gui/new_project.py +18 -9
- shinestacker/gui/project_controller.py +13 -6
- shinestacker/gui/project_editor.py +12 -2
- shinestacker/gui/project_model.py +4 -4
- shinestacker/gui/tab_widget.py +16 -6
- shinestacker/retouch/adjustments.py +5 -0
- {shinestacker-1.7.0.dist-info → shinestacker-1.8.1.dist-info}/METADATA +35 -39
- {shinestacker-1.7.0.dist-info → shinestacker-1.8.1.dist-info}/RECORD +45 -40
- /shinestacker/gui/img/{close-round-line-icon.png → light/close-round-line-icon.png} +0 -0
- /shinestacker/gui/img/{forward-button-icon.png → light/forward-button-icon.png} +0 -0
- /shinestacker/gui/img/{play-button-round-icon.png → light/play-button-round-icon.png} +0 -0
- /shinestacker/gui/img/{plus-round-line-icon.png → light/plus-round-line-icon.png} +0 -0
- /shinestacker/gui/{ico → img/light}/shinestacker_bkg.png +0 -0
- {shinestacker-1.7.0.dist-info → shinestacker-1.8.1.dist-info}/WHEEL +0 -0
- {shinestacker-1.7.0.dist-info → shinestacker-1.8.1.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.7.0.dist-info → shinestacker-1.8.1.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.7.0.dist-info → shinestacker-1.8.1.dist-info}/top_level.txt +0 -0
|
@@ -46,7 +46,8 @@ class StackJob(Job):
|
|
|
46
46
|
class ImageSequenceManager:
|
|
47
47
|
def __init__(self, name, input_path='', output_path='', working_path='',
|
|
48
48
|
plot_path=constants.DEFAULT_PLOTS_PATH,
|
|
49
|
-
scratch_output_dir=True,
|
|
49
|
+
scratch_output_dir=True, delete_output_at_end=False,
|
|
50
|
+
resample=1,
|
|
50
51
|
reverse_order=constants.DEFAULT_FILE_REVERSE_ORDER, **_kwargs):
|
|
51
52
|
self.name = name
|
|
52
53
|
self.working_path = working_path
|
|
@@ -56,6 +57,7 @@ class ImageSequenceManager:
|
|
|
56
57
|
self._resample = resample
|
|
57
58
|
self.reverse_order = reverse_order
|
|
58
59
|
self.scratch_output_dir = scratch_output_dir
|
|
60
|
+
self.delete_output_at_end = delete_output_at_end
|
|
59
61
|
self.enabled = None
|
|
60
62
|
self.base_message = ''
|
|
61
63
|
self._input_full_path = None
|
|
@@ -122,6 +124,24 @@ class ImageSequenceManager:
|
|
|
122
124
|
constants.LOG_COLOR_LEVEL_2))
|
|
123
125
|
self.base_message = color_str(self.name, constants.LOG_COLOR_LEVEL_1, "bold")
|
|
124
126
|
|
|
127
|
+
def scratch_outout_folder(self):
|
|
128
|
+
if self.enabled:
|
|
129
|
+
output_dir = self.output_full_path()
|
|
130
|
+
list_dir = os.listdir(output_dir)
|
|
131
|
+
n_files = len(list_dir)
|
|
132
|
+
if n_files > 0:
|
|
133
|
+
for filename in list_dir:
|
|
134
|
+
file_path = os.path.join(output_dir, filename)
|
|
135
|
+
if os.path.isfile(file_path):
|
|
136
|
+
os.remove(file_path)
|
|
137
|
+
self.print_message(
|
|
138
|
+
color_str(f"output directory {self.output_path} content erased",
|
|
139
|
+
'yellow'))
|
|
140
|
+
else:
|
|
141
|
+
self.print_message(
|
|
142
|
+
color_str(f"module disabled, output directory {self.output_path}"
|
|
143
|
+
" not scratched", 'yellow'))
|
|
144
|
+
|
|
125
145
|
def init(self, job):
|
|
126
146
|
if self.working_path == '':
|
|
127
147
|
self.working_path = job.working_path
|
|
@@ -130,22 +150,10 @@ class ImageSequenceManager:
|
|
|
130
150
|
if not os.path.exists(output_dir):
|
|
131
151
|
os.makedirs(output_dir)
|
|
132
152
|
else:
|
|
133
|
-
|
|
134
|
-
if len(list_dir) > 0:
|
|
153
|
+
if len(os.listdir(output_dir)):
|
|
135
154
|
if self.scratch_output_dir:
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
file_path = os.path.join(output_dir, filename)
|
|
139
|
-
if os.path.isfile(file_path):
|
|
140
|
-
os.remove(file_path)
|
|
141
|
-
self.print_message(
|
|
142
|
-
color_str(f": output directory {self.output_path} content erased",
|
|
143
|
-
'yellow'))
|
|
144
|
-
else:
|
|
145
|
-
self.print_message(
|
|
146
|
-
color_str(f": module disabled, output directory {self.output_path}"
|
|
147
|
-
" not scratched", 'yellow'))
|
|
148
|
-
else:
|
|
155
|
+
self.scratch_outout_folder()
|
|
156
|
+
elif self.enabled:
|
|
149
157
|
self.print_message(
|
|
150
158
|
color_str(
|
|
151
159
|
f": output directory {self.output_path} not empty, "
|
|
@@ -168,6 +176,11 @@ class ImageSequenceManager:
|
|
|
168
176
|
self._input_filepaths.append(filepath)
|
|
169
177
|
job.add_action_path(self.output_path)
|
|
170
178
|
|
|
179
|
+
def end_job(self):
|
|
180
|
+
if self.delete_output_at_end:
|
|
181
|
+
self.scratch_outout_folder()
|
|
182
|
+
os.rmdir(self.output_full_path())
|
|
183
|
+
|
|
171
184
|
def folder_list_str(self):
|
|
172
185
|
if isinstance(self.input_full_path(), list):
|
|
173
186
|
file_list = ", ".join(
|
|
@@ -178,7 +191,8 @@ class ImageSequenceManager:
|
|
|
178
191
|
|
|
179
192
|
|
|
180
193
|
class ReferenceFrameTask(SequentialTask, ImageSequenceManager):
|
|
181
|
-
def __init__(self, name, enabled=True, reference_index=0,
|
|
194
|
+
def __init__(self, name, enabled=True, reference_index=0,
|
|
195
|
+
step_process=constants.DEFAULT_COMBINED_ACTIONS_STEP_PROCESS, **kwargs):
|
|
182
196
|
ImageSequenceManager.__init__(self, name, **kwargs)
|
|
183
197
|
SequentialTask.__init__(self, name, enabled)
|
|
184
198
|
self.ref_idx = reference_index
|
|
@@ -206,6 +220,9 @@ class ReferenceFrameTask(SequentialTask, ImageSequenceManager):
|
|
|
206
220
|
def end(self):
|
|
207
221
|
SequentialTask.end(self)
|
|
208
222
|
|
|
223
|
+
def end_job(self):
|
|
224
|
+
ImageSequenceManager.end_job(self)
|
|
225
|
+
|
|
209
226
|
def run_frame(self, _idx, _ref_idx):
|
|
210
227
|
return None
|
|
211
228
|
|
|
@@ -260,7 +277,8 @@ class SubAction:
|
|
|
260
277
|
|
|
261
278
|
class CombinedActions(ReferenceFrameTask):
|
|
262
279
|
def __init__(self, name, actions=[], enabled=True, **kwargs):
|
|
263
|
-
|
|
280
|
+
step_process = kwargs.pop('step_process', constants.DEFAULT_COMBINED_ACTIONS_STEP_PROCESS)
|
|
281
|
+
ReferenceFrameTask.__init__(self, name, enabled, step_process=step_process, **kwargs)
|
|
264
282
|
self._actions = actions
|
|
265
283
|
self._metadata = (None, None)
|
|
266
284
|
|
|
@@ -278,11 +296,15 @@ class CombinedActions(ReferenceFrameTask):
|
|
|
278
296
|
self._metadata = get_img_metadata(img)
|
|
279
297
|
return img
|
|
280
298
|
|
|
299
|
+
def frame_str(self, idx=-1):
|
|
300
|
+
if self.run_sequential():
|
|
301
|
+
idx = self.current_action_count
|
|
302
|
+
return f"frame {idx + 1}/{self.total_action_counts}"
|
|
303
|
+
|
|
281
304
|
def run_frame(self, idx, ref_idx):
|
|
282
305
|
input_path = self.input_filepath(idx)
|
|
283
306
|
self.print_message(
|
|
284
|
-
color_str(f'read input
|
|
285
|
-
f'{idx + 1}/{self.total_action_counts}, '
|
|
307
|
+
color_str(f'read input {self.frame_str(idx)}, '
|
|
286
308
|
f'{os.path.basename(input_path)}', constants.LOG_COLOR_LEVEL_3))
|
|
287
309
|
img = read_img(input_path)
|
|
288
310
|
validate_image(img, *(self._metadata))
|
|
@@ -301,20 +323,19 @@ class CombinedActions(ReferenceFrameTask):
|
|
|
301
323
|
if img is not None:
|
|
302
324
|
img = a.run_frame(idx, ref_idx, img)
|
|
303
325
|
else:
|
|
304
|
-
self.
|
|
305
|
-
color_str("
|
|
326
|
+
self.print_message(
|
|
327
|
+
color_str("null input received, action skipped",
|
|
306
328
|
constants.LOG_COLOR_ALERT),
|
|
307
329
|
level=logging.WARNING)
|
|
308
330
|
if img is not None:
|
|
309
331
|
output_path = os.path.join(self.output_full_path(), os.path.basename(input_path))
|
|
310
332
|
self.print_message(
|
|
311
|
-
color_str(f'write output
|
|
312
|
-
f'{idx + 1}/{self.total_action_counts}, '
|
|
333
|
+
color_str(f'write output {self.frame_str(idx)}, '
|
|
313
334
|
f'{os.path.basename(output_path)}', constants.LOG_COLOR_LEVEL_3))
|
|
314
335
|
write_img(output_path, img)
|
|
315
336
|
return img
|
|
316
337
|
self.print_message(color_str(
|
|
317
|
-
f"no output
|
|
338
|
+
f"no output resulted from processing input file: {os.path.basename(input_path)}",
|
|
318
339
|
constants.LOG_COLOR_ALERT), level=logging.WARNING)
|
|
319
340
|
return None
|
|
320
341
|
|
|
@@ -323,6 +344,9 @@ class CombinedActions(ReferenceFrameTask):
|
|
|
323
344
|
if a.enabled:
|
|
324
345
|
a.end()
|
|
325
346
|
|
|
347
|
+
def end_job(self):
|
|
348
|
+
ReferenceFrameTask.end_job(self)
|
|
349
|
+
|
|
326
350
|
def sequential_processing(self):
|
|
327
351
|
for a in self._actions:
|
|
328
352
|
if a.sequential_processing():
|
shinestacker/algorithms/utils.py
CHANGED
|
@@ -140,8 +140,12 @@ def save_plot(filename, fig=None):
|
|
|
140
140
|
if not os.path.isdir(dir_path):
|
|
141
141
|
os.makedirs(dir_path)
|
|
142
142
|
if fig is None:
|
|
143
|
+
logging_level = logging.getLogger().level
|
|
144
|
+
logger = logging.getLogger()
|
|
145
|
+
logger.setLevel(logging.WARNING)
|
|
143
146
|
fig = plt.gcf()
|
|
144
|
-
|
|
147
|
+
fig.savefig(filename, dpi=150)
|
|
148
|
+
logger.setLevel(logging_level)
|
|
145
149
|
if config.JUPYTER_NOTEBOOK:
|
|
146
150
|
plt.show()
|
|
147
151
|
plt.close(fig)
|
|
@@ -4,7 +4,7 @@ import traceback
|
|
|
4
4
|
import logging
|
|
5
5
|
import numpy as np
|
|
6
6
|
import matplotlib.pyplot as plt
|
|
7
|
-
from scipy.optimize import curve_fit,
|
|
7
|
+
from scipy.optimize import curve_fit, bisect
|
|
8
8
|
import cv2
|
|
9
9
|
from .. core.colors import color_str
|
|
10
10
|
from .. core.core_utils import setup_matplotlib_mode
|
|
@@ -181,9 +181,22 @@ class Vignetting(SubAction):
|
|
|
181
181
|
self.process.callback(
|
|
182
182
|
constants.CALLBACK_SAVE_PLOT, self.process.id,
|
|
183
183
|
f"{self.process.name}: intensity\nframe {idx_str}", plot_path)
|
|
184
|
+
|
|
184
185
|
for i, p in enumerate(self.percentiles):
|
|
185
|
-
|
|
186
|
-
|
|
186
|
+
s1 = sigmoid_model(0, *params) / self.v0
|
|
187
|
+
s2 = sigmoid_model(self.r_max, *params) / self.v0
|
|
188
|
+
if s1 > p and s2 < p:
|
|
189
|
+
try:
|
|
190
|
+
c = bisect(lambda x: sigmoid_model(x, *params) / self.v0 - p, 0, self.r_max)
|
|
191
|
+
except Exception as e:
|
|
192
|
+
traceback.print_tb(e.__traceback__)
|
|
193
|
+
self.process.sub_message(color_str(f": {str(e).lower()}", "yellow"),
|
|
194
|
+
level=logging.WARNING)
|
|
195
|
+
elif s1 <= p:
|
|
196
|
+
c = 0
|
|
197
|
+
else:
|
|
198
|
+
c = self.r_max
|
|
199
|
+
self.corrections[i][idx] = c
|
|
187
200
|
self.process.print_message(
|
|
188
201
|
color_str(f"{self.process.idx_tot_str(idx)}: correct vignetting", "cyan"))
|
|
189
202
|
return correct_vignetting(
|