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.

Files changed (45) hide show
  1. shinestacker/_version.py +1 -1
  2. shinestacker/algorithms/align.py +184 -80
  3. shinestacker/algorithms/align_auto.py +13 -11
  4. shinestacker/algorithms/align_parallel.py +41 -16
  5. shinestacker/algorithms/base_stack_algo.py +1 -1
  6. shinestacker/algorithms/noise_detection.py +10 -8
  7. shinestacker/algorithms/pyramid_tiles.py +1 -1
  8. shinestacker/algorithms/stack.py +9 -0
  9. shinestacker/algorithms/stack_framework.py +49 -25
  10. shinestacker/algorithms/utils.py +5 -1
  11. shinestacker/algorithms/vignetting.py +16 -3
  12. shinestacker/app/settings_dialog.py +303 -136
  13. shinestacker/config/constants.py +10 -5
  14. shinestacker/config/settings.py +29 -8
  15. shinestacker/core/core_utils.py +1 -0
  16. shinestacker/core/exceptions.py +1 -1
  17. shinestacker/core/framework.py +9 -4
  18. shinestacker/gui/action_config.py +23 -20
  19. shinestacker/gui/action_config_dialog.py +107 -64
  20. shinestacker/gui/gui_images.py +27 -3
  21. shinestacker/gui/gui_run.py +1 -2
  22. shinestacker/gui/img/dark/close-round-line-icon.png +0 -0
  23. shinestacker/gui/img/dark/forward-button-icon.png +0 -0
  24. shinestacker/gui/img/dark/play-button-round-icon.png +0 -0
  25. shinestacker/gui/img/dark/plus-round-line-icon.png +0 -0
  26. shinestacker/gui/img/dark/shinestacker_bkg.png +0 -0
  27. shinestacker/gui/main_window.py +20 -7
  28. shinestacker/gui/menu_manager.py +18 -7
  29. shinestacker/gui/new_project.py +18 -9
  30. shinestacker/gui/project_controller.py +13 -6
  31. shinestacker/gui/project_editor.py +12 -2
  32. shinestacker/gui/project_model.py +4 -4
  33. shinestacker/gui/tab_widget.py +16 -6
  34. shinestacker/retouch/adjustments.py +5 -0
  35. {shinestacker-1.7.0.dist-info → shinestacker-1.8.1.dist-info}/METADATA +35 -39
  36. {shinestacker-1.7.0.dist-info → shinestacker-1.8.1.dist-info}/RECORD +45 -40
  37. /shinestacker/gui/img/{close-round-line-icon.png → light/close-round-line-icon.png} +0 -0
  38. /shinestacker/gui/img/{forward-button-icon.png → light/forward-button-icon.png} +0 -0
  39. /shinestacker/gui/img/{play-button-round-icon.png → light/play-button-round-icon.png} +0 -0
  40. /shinestacker/gui/img/{plus-round-line-icon.png → light/plus-round-line-icon.png} +0 -0
  41. /shinestacker/gui/{ico → img/light}/shinestacker_bkg.png +0 -0
  42. {shinestacker-1.7.0.dist-info → shinestacker-1.8.1.dist-info}/WHEEL +0 -0
  43. {shinestacker-1.7.0.dist-info → shinestacker-1.8.1.dist-info}/entry_points.txt +0 -0
  44. {shinestacker-1.7.0.dist-info → shinestacker-1.8.1.dist-info}/licenses/LICENSE +0 -0
  45. {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, resample=1,
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
- list_dir = os.listdir(output_dir)
134
- if len(list_dir) > 0:
153
+ if len(os.listdir(output_dir)):
135
154
  if self.scratch_output_dir:
136
- if self.enabled:
137
- for filename in list_dir:
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, step_process=False, **kwargs):
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
- ReferenceFrameTask.__init__(self, name, enabled, **kwargs)
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 image '
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.sub_message(
305
- color_str(": null input received, action skipped",
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 image '
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 file resulted from processing input file: {os.path.basename(input_path)}",
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():
@@ -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
- fig.savefig(filename, dpi=150)
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, fsolve
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
- self.corrections[i][idx] = fsolve(lambda x: sigmoid_model(x, *params) /
186
- self.v0 - p, r0_fit)[0]
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(