shinestacker 0.3.5__py3-none-any.whl → 0.3.6__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 CHANGED
@@ -1 +1 @@
1
- __version__ = '0.3.5'
1
+ __version__ = '0.3.6'
@@ -12,7 +12,7 @@ class PyramidBase(BaseStackAlgo):
12
12
  kernel_size=constants.DEFAULT_PY_KERNEL_SIZE,
13
13
  gen_kernel=constants.DEFAULT_PY_GEN_KERNEL,
14
14
  float_type=constants.DEFAULT_PY_FLOAT):
15
- super().__init__("pyramid", 1, float_type)
15
+ super().__init__("pyramid", 2, float_type)
16
16
  self.min_size = min_size
17
17
  self.kernel_size = kernel_size
18
18
  self.pad_amount = (kernel_size - 1) // 2
@@ -151,11 +151,11 @@ class PyramidStack(PyramidBase):
151
151
  metadata = None
152
152
  all_laplacians = []
153
153
  levels = None
154
+ n = len(filenames)
154
155
  for i, img_path in enumerate(filenames):
155
156
  self.print_message(f": validating file {img_path.split('/')[-1]}")
156
157
 
157
158
  img, metadata, updated = self.read_image_and_update_metadata(img_path, metadata)
158
-
159
159
  if updated:
160
160
  self.dtype = metadata[1]
161
161
  self.num_pixel_values = constants.NUM_UINT8 \
@@ -163,14 +163,17 @@ class PyramidStack(PyramidBase):
163
163
  self.max_pixel_value = constants.MAX_UINT8 \
164
164
  if self.dtype == np.uint8 else constants.MAX_UINT16
165
165
  levels = int(np.log2(min(img.shape[:2]) / self.min_size))
166
-
167
166
  if self.do_step_callback:
168
167
  self.process.callback('after_step', self.process.id, self.process.name, i)
169
168
  if self.process.callback('check_running', self.process.id, self.process.name) is False:
170
169
  raise RunStopException(self.name)
171
- for img_path in filenames:
170
+ for i, img_path in enumerate(filenames):
172
171
  self.print_message(f": processing file {img_path.split('/')[-1]}")
173
172
  img = read_img(img_path)
174
173
  all_laplacians.append(self.process_single_image(img, levels))
174
+ if self.do_step_callback:
175
+ self.process.callback('after_step', self.process.id, self.process.name, i + n)
176
+ if self.process.callback('check_running', self.process.id, self.process.name) is False:
177
+ raise RunStopException(self.name)
175
178
  stacked_image = self.collapse(self.fuse_pyramids(all_laplacians))
176
179
  return stacked_image.astype(self.dtype)
@@ -1,28 +1,15 @@
1
1
  # pylint: disable=C0114, C0115, C0116, C0103, W0201
2
- class _AppConfig:
3
- _initialized = False
4
- _instance = None
2
+ from .. config.config import _ConfigBase
5
3
 
4
+
5
+ class _AppConfig(_ConfigBase):
6
6
  def __new__(cls):
7
- if cls._instance is None:
8
- cls._instance = super().__new__(cls)
9
- cls._instance._init_defaults()
10
- return cls._instance
7
+ return _ConfigBase.__new__(cls)
11
8
 
12
9
  def _init_defaults(self):
13
10
  self._DONT_USE_NATIVE_MENU = True
14
11
  self._COMBINED_APP = False
15
12
 
16
- def init(self, **kwargs):
17
- if self._initialized:
18
- raise RuntimeError("Config already initialized")
19
- for k, v in kwargs.items():
20
- if hasattr(self, f"_{k}"):
21
- setattr(self, f"_{k}", v)
22
- else:
23
- raise AttributeError(f"Invalid config key: {k}")
24
- self._initialized = True
25
-
26
13
  @property
27
14
  def DONT_USE_NATIVE_MENU(self):
28
15
  return self._DONT_USE_NATIVE_MENU
@@ -31,10 +18,5 @@ class _AppConfig:
31
18
  def COMBINED_APP(self):
32
19
  return self._COMBINED_APP
33
20
 
34
- def __setattr__(self, name, value):
35
- if self._initialized and name.startswith('_'):
36
- raise AttributeError("Can't change config after initialization")
37
- super().__setattr__(name, value)
38
-
39
21
 
40
22
  app_config = _AppConfig()
@@ -1,5 +1,5 @@
1
1
  # pylint: disable=C0114, C0115, C0116, C0103, R0903, W0718, W0104, W0201, E0602
2
- class _Config:
2
+ class _ConfigBase:
3
3
  _initialized = False
4
4
  _instance = None
5
5
 
@@ -9,16 +9,6 @@ class _Config:
9
9
  cls._instance._init_defaults()
10
10
  return cls._instance
11
11
 
12
- def _init_defaults(self):
13
- self._DISABLE_TQDM = False
14
- self._COMBINED_APP = False
15
- self._DONT_USE_NATIVE_MENU = True
16
- try:
17
- __IPYTHON__ # noqa
18
- self._JUPYTER_NOTEBOOK = True
19
- except Exception:
20
- self._JUPYTER_NOTEBOOK = False
21
-
22
12
  def init(self, **kwargs):
23
13
  if self._initialized:
24
14
  raise RuntimeError("Config already initialized")
@@ -29,6 +19,26 @@ class _Config:
29
19
  raise AttributeError(f"Invalid config key: {k}")
30
20
  self._initialized = True
31
21
 
22
+ def __setattr__(self, name, value):
23
+ if self._initialized and name.startswith('_'):
24
+ raise AttributeError("Can't change config after initialization")
25
+ super().__setattr__(name, value)
26
+
27
+ class _Config(_ConfigBase):
28
+
29
+ def __new__(cls):
30
+ return _ConfigBase.__new__(cls)
31
+
32
+ def _init_defaults(self):
33
+ self._DISABLE_TQDM = False
34
+ self._COMBINED_APP = False
35
+ self._DONT_USE_NATIVE_MENU = True
36
+ try:
37
+ __IPYTHON__ # noqa
38
+ self._JUPYTER_NOTEBOOK = True
39
+ except Exception:
40
+ self._JUPYTER_NOTEBOOK = False
41
+
32
42
  @property
33
43
  def DISABLE_TQDM(self):
34
44
  return self._DISABLE_TQDM
@@ -45,10 +55,5 @@ class _Config:
45
55
  def COMBINED_APP(self):
46
56
  return self._COMBINED_APP
47
57
 
48
- def __setattr__(self, name, value):
49
- if self._initialized and name.startswith('_'):
50
- raise AttributeError("Can't change config after initialization")
51
- super().__setattr__(name, value)
52
-
53
58
 
54
59
  config = _Config()
@@ -10,8 +10,10 @@ from PySide6.QtWidgets import (QWidget, QPushButton, QHBoxLayout, QFileDialog, Q
10
10
  QAbstractItemView, QListView)
11
11
  from PySide6.QtCore import Qt, QTimer
12
12
  from .. config.constants import constants
13
- from .project_model import ActionConfig
14
13
  from .. algorithms.align import validate_align_config
14
+ from .project_model import ActionConfig
15
+ from .select_path_widget import (create_select_file_paths_widget, create_layout_widget_no_margins,
16
+ create_layout_widget_and_connect)
15
17
 
16
18
  FIELD_TEXT = 'text'
17
19
  FIELD_ABS_PATH = 'abs_path'
@@ -200,25 +202,11 @@ class FieldBuilder:
200
202
  return edit
201
203
 
202
204
  def create_abs_path_field(self, tag, **kwargs):
203
- value = self.action.params.get(tag, '')
204
- edit = QLineEdit(value)
205
- edit.setPlaceholderText(kwargs.get('placeholder', ''))
206
- button = QPushButton("Browse...")
207
-
208
- def browse():
209
- path = QFileDialog.getExistingDirectory(None, f"Select {tag.replace('_', ' ')}")
210
- if path:
211
- edit.setText(path)
212
- button.clicked.connect(browse)
213
- button.setAutoDefault(False)
214
- layout = QHBoxLayout()
215
- layout.addWidget(edit)
216
- layout.addWidget(button)
217
- layout.setContentsMargins(0, 0, 0, 0)
218
- container = QWidget()
219
- container.setLayout(layout)
220
- container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
221
- return container
205
+ return create_select_file_paths_widget(
206
+ self.action.params.get(tag, ''),
207
+ kwargs.get('placeholder', ''),
208
+ tag.replace('_', ' ')
209
+ )
222
210
 
223
211
  def create_rel_path_field(self, tag, **kwargs):
224
212
  value = self.action.params.get(tag, kwargs.get('default', ''))
@@ -326,17 +314,8 @@ class FieldBuilder:
326
314
  except ValueError as e:
327
315
  traceback.print_tb(e.__traceback__)
328
316
  QMessageBox.warning(None, "Error", "Could not compute relative path")
317
+ return create_layout_widget_and_connect(button, edit, browse)
329
318
 
330
- button.clicked.connect(browse)
331
- button.setAutoDefault(False)
332
- layout = QHBoxLayout()
333
- layout.addWidget(edit)
334
- layout.addWidget(button)
335
- layout.setContentsMargins(0, 0, 0, 0)
336
- container = QWidget()
337
- container.setLayout(layout)
338
- container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
339
- return container
340
319
 
341
320
  def create_float_field(self, tag, default=0.0, min_val=0.0, max_val=1.0,
342
321
  step=0.1, decimals=2):
@@ -369,11 +348,7 @@ class FieldBuilder:
369
348
  layout.addWidget(label)
370
349
  layout.addWidget(spin)
371
350
  layout.setStretch(layout.count() - 1, 1)
372
- layout.setContentsMargins(0, 0, 0, 0)
373
- container = QWidget()
374
- container.setLayout(layout)
375
- container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
376
- return container
351
+ return create_layout_widget_no_margins(layout)
377
352
 
378
353
  def create_combo_field(self, tag, options=None, default=None, **kwargs):
379
354
  options = options or []
@@ -1,7 +1,7 @@
1
1
  # pylint: disable=C0114, C0115, C0116, E0611, R0914, R0912, R0915, W0718
2
2
  import os.path
3
3
  import os
4
- import traceback
4
+ # import traceback
5
5
  import json
6
6
  import jsonpickle
7
7
  from PySide6.QtWidgets import QMessageBox, QFileDialog, QDialog
@@ -20,8 +20,9 @@ class ActionsWindow(ProjectEditor):
20
20
 
21
21
  def update_title(self):
22
22
  title = constants.APP_TITLE
23
- if self._current_file:
24
- title += f" - {os.path.basename(self._current_file)}"
23
+ file_name = self.current_file_name()
24
+ if file_name:
25
+ title += f" - {file_name}"
25
26
  if self._modified_project:
26
27
  title += " *"
27
28
  self.window().setWindowTitle(title)
@@ -34,7 +35,7 @@ class ActionsWindow(ProjectEditor):
34
35
  def close_project(self):
35
36
  if self._check_unsaved_changes():
36
37
  self.set_project(Project())
37
- self._current_file = None
38
+ self.set_current_file_path('')
38
39
  self.update_title()
39
40
  self.job_list.clear()
40
41
  self.action_list.clear()
@@ -44,17 +45,17 @@ class ActionsWindow(ProjectEditor):
44
45
  if not self._check_unsaved_changes():
45
46
  return
46
47
  os.chdir(get_app_base_path())
47
- self._current_file = None
48
+ self.set_current_file_path('')
48
49
  self._modified_project = False
49
50
  self.update_title()
50
51
  self.job_list.clear()
51
52
  self.action_list.clear()
53
+ self.set_project(Project())
52
54
  dialog = NewProjectDialog(self)
53
55
  if dialog.exec() == QDialog.Accepted:
54
56
  input_folder = dialog.get_input_folder().split('/')
55
57
  working_path = '/'.join(input_folder[:-1])
56
58
  input_path = input_folder[-1]
57
- project = Project()
58
59
  if dialog.get_noise_detection():
59
60
  job_noise = ActionConfig(constants.ACTION_JOB,
60
61
  {'name': 'detect-noise', 'working_path': working_path,
@@ -62,7 +63,7 @@ class ActionsWindow(ProjectEditor):
62
63
  noise_detection = ActionConfig(constants.ACTION_NOISEDETECTION,
63
64
  {'name': 'detect-noise'})
64
65
  job_noise.add_sub_action(noise_detection)
65
- project.jobs.append(job_noise)
66
+ self.project.jobs.append(job_noise)
66
67
  job = ActionConfig(constants.ACTION_JOB,
67
68
  {'name': 'focus-stack', 'working_path': working_path,
68
69
  'input_path': input_path})
@@ -111,8 +112,7 @@ class ActionsWindow(ProjectEditor):
111
112
  {'name': 'multi-layer',
112
113
  'input_path': ','.join(input_path)})
113
114
  job.add_sub_action(multi_layer)
114
- project.jobs.append(job)
115
- self.set_project(project)
115
+ self.project.jobs.append(job)
116
116
  self._modified_project = True
117
117
  self.refresh_ui(0, -1)
118
118
 
@@ -124,17 +124,9 @@ class ActionsWindow(ProjectEditor):
124
124
  self, "Open Project", "", "Project Files (*.fsp);;All Files (*)")
125
125
  if file_path:
126
126
  try:
127
- self._current_file = file_path
128
- self._current_file_wd = '' if os.path.isabs(file_path) \
129
- else os.path.dirname(file_path)
130
- if not os.path.isabs(self._current_file_wd):
131
- self._current_file_wd = os.path.abspath(self._current_file_wd)
132
- self._current_file = os.path.basename(self._current_file)
133
- with open(file_path, 'r', encoding="utf-8") as file:
127
+ self.set_current_file_path(file_path)
128
+ with open(self.current_file_path(), 'r', encoding="utf-8") as file:
134
129
  json_obj = json.load(file)
135
- pp = file_path.split('/')
136
- if len(pp) > 1:
137
- os.chdir('/'.join(pp[:-1]))
138
130
  project = Project.from_dict(json_obj['project'])
139
131
  if project is None:
140
132
  raise RuntimeError(f"Project from file {file_path} produced a null project.")
@@ -145,7 +137,7 @@ class ActionsWindow(ProjectEditor):
145
137
  if self.job_list.count() > 0:
146
138
  self.job_list.setCurrentRow(0)
147
139
  except Exception as e:
148
- traceback.print_tb(e.__traceback__)
140
+ # traceback.print_tb(e.__traceback__)
149
141
  QMessageBox.critical(self, "Error", f"Cannot open file {file_path}:\n{str(e)}")
150
142
  if len(self.project.jobs) > 0:
151
143
  self.job_list.setCurrentRow(0)
@@ -173,12 +165,10 @@ class ActionsWindow(ProjectEditor):
173
165
  Please, select a valid working path.''')
174
166
  self.edit_action(action)
175
167
 
176
- def current_file_name(self):
177
- return os.path.basename(self._current_file) if self._current_file else ''
178
-
179
168
  def save_project(self):
180
- if self._current_file:
181
- self.do_save(self._current_file)
169
+ path = self.current_file_path()
170
+ if path:
171
+ self.do_save(path)
182
172
  else:
183
173
  self.save_project_as()
184
174
 
@@ -188,9 +178,8 @@ class ActionsWindow(ProjectEditor):
188
178
  if file_path:
189
179
  if not file_path.endswith('.fsp'):
190
180
  file_path += '.fsp'
191
- self._current_file_wd = ''
192
181
  self.do_save(file_path)
193
- self._current_file = file_path
182
+ self.set_current_file_path(file_path)
194
183
  self._modified_project = False
195
184
  self.update_title()
196
185
  os.chdir(os.path.dirname(file_path))
@@ -201,9 +190,7 @@ class ActionsWindow(ProjectEditor):
201
190
  'project': self.project.to_dict(),
202
191
  'version': 1
203
192
  })
204
- path = f"{self._current_file_wd}/{file_path}" \
205
- if self._current_file_wd != '' else file_path
206
- with open(path, 'w', encoding="utf-8") as f:
193
+ with open(file_path, 'w', encoding="utf-8") as f:
207
194
  f.write(json_obj)
208
195
  self._modified_project = False
209
196
  except Exception as e:
@@ -238,23 +225,7 @@ class ActionsWindow(ProjectEditor):
238
225
  if 0 <= job_index < len(self.project.jobs):
239
226
  job = self.project.jobs[job_index]
240
227
  action_index = self.action_list.row(item)
241
- action_counter = -1
242
- current_action = None
243
- is_sub_action = False
244
- for action in job.sub_actions:
245
- action_counter += 1
246
- if action_counter == action_index:
247
- current_action = action
248
- break
249
- if len(action.type_name) > 0:
250
- for sub_action in action.sub_actions:
251
- action_counter += 1
252
- if action_counter == action_index:
253
- current_action = sub_action
254
- is_sub_action = True
255
- break
256
- if current_action:
257
- break
228
+ current_action, is_sub_action = self.get_current_action_at(job, action_index)
258
229
  if current_action:
259
230
  if not is_sub_action:
260
231
  self.set_enabled_sub_actions_gui(
@@ -1,13 +1,13 @@
1
1
  # pylint: disable=C0114, C0115, C0116, E0611, R0915, R0902
2
2
  import os
3
- from PySide6.QtWidgets import (QWidget, QLineEdit, QFormLayout, QHBoxLayout, QPushButton,
4
- QDialog, QSizePolicy, QFileDialog, QLabel, QCheckBox,
5
- QSpinBox, QMessageBox)
3
+ from PySide6.QtWidgets import (QFormLayout, QHBoxLayout, QPushButton,
4
+ QDialog, QLabel, QCheckBox, QSpinBox, QMessageBox)
6
5
  from PySide6.QtGui import QIcon
7
6
  from PySide6.QtCore import Qt
8
7
  from .. config.gui_constants import gui_constants
9
8
  from .. config.constants import constants
10
9
  from .. algorithms.stack import get_bunches
10
+ from .select_path_widget import create_select_file_paths_widget
11
11
 
12
12
 
13
13
  class NewProjectDialog(QDialog):
@@ -50,25 +50,8 @@ class NewProjectDialog(QDialog):
50
50
  spacer = QLabel("")
51
51
  spacer.setFixedHeight(10)
52
52
  self.layout.addRow(spacer)
53
- self.input_folder = QLineEdit()
54
- self.input_folder .setPlaceholderText('input files folder')
55
- self.input_folder.textChanged.connect(self.update_bunches_label)
56
- button = QPushButton("Browse...")
57
-
58
- def browse():
59
- path = QFileDialog.getExistingDirectory(None, "Select input files folder")
60
- if path:
61
- self.input_folder.setText(path)
62
-
63
- button.clicked.connect(browse)
64
- button.setAutoDefault(False)
65
- layout = QHBoxLayout()
66
- layout.addWidget(self.input_folder)
67
- layout.addWidget(button)
68
- layout.setContentsMargins(0, 0, 0, 0)
69
- container = QWidget()
70
- container.setLayout(layout)
71
- container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
53
+
54
+ container = create_select_file_paths_widget('', 'input files folder', 'input files folder')
72
55
 
73
56
  self.noise_detection = QCheckBox()
74
57
  self.noise_detection.setChecked(gui_constants.NEW_PROJECT_NOISE_DETECTION)
@@ -89,10 +89,28 @@ class ProjectEditor(QMainWindow):
89
89
  self.expert_options = False
90
90
  self.script_dir = os.path.dirname(__file__)
91
91
  self.dialog = None
92
- self._current_file = None
93
- self._current_file_wd = ''
92
+ self._current_file_path = ''
94
93
  self._modified_project = False
95
94
 
95
+ def current_file_path(self):
96
+ return self._current_file_path
97
+
98
+ def current_file_directory(self):
99
+ if os.path.isdir(self._current_file_path):
100
+ return self._current_file_path
101
+ return os.path.dirname(self._current_file_path)
102
+
103
+ def current_file_name(self):
104
+ if os.path.isfile(self._current_file_path):
105
+ return os.path.basename(self._current_file_path)
106
+ return ''
107
+
108
+ def set_current_file_path(self, path):
109
+ if path and not os.path.exists(path):
110
+ raise RuntimeError(f"Path: {path} does not exist.")
111
+ self._current_file_path = os.path.abspath(path)
112
+ os.chdir(self.current_file_directory())
113
+
96
114
  def set_project(self, project):
97
115
  self.project = project
98
116
 
@@ -304,7 +322,7 @@ class ProjectEditor(QMainWindow):
304
322
  return element
305
323
 
306
324
  def action_config_dialog(self, action):
307
- return ActionConfigDialog(action, self._current_file_wd, self)
325
+ return ActionConfigDialog(action, self.current_file_directory(), self)
308
326
 
309
327
  def add_job(self):
310
328
  job_action = ActionConfig("Job")
@@ -499,6 +517,27 @@ class ProjectEditor(QMainWindow):
499
517
  self.add_list_item(self.action_list, sub_action, True)
500
518
  self.update_delete_action_state()
501
519
 
520
+ def get_current_action_at(self, job, action_index):
521
+ action_counter = -1
522
+ current_action = None
523
+ is_sub_action = False
524
+ for action in job.sub_actions:
525
+ action_counter += 1
526
+ if action_counter == action_index:
527
+ current_action = action
528
+ break
529
+ if len(action.sub_actions) > 0:
530
+ for sub_action in action.sub_actions:
531
+ action_counter += 1
532
+ if action_counter == action_index:
533
+ current_action = sub_action
534
+ is_sub_action = True
535
+ break
536
+ if current_action:
537
+ break
538
+
539
+ return current_action, is_sub_action
540
+
502
541
  def update_delete_action_state(self):
503
542
  has_job_selected = len(self.job_list.selectedItems()) > 0
504
543
  has_action_selected = len(self.action_list.selectedItems()) > 0
@@ -510,23 +549,7 @@ class ProjectEditor(QMainWindow):
510
549
  action_index = self.action_list.currentRow()
511
550
  if job_index >= 0:
512
551
  job = self.project.jobs[job_index]
513
- action_counter = -1
514
- current_action = None
515
- is_sub_action = False
516
- for action in job.sub_actions:
517
- action_counter += 1
518
- if action_counter == action_index:
519
- current_action = action
520
- break
521
- if len(action.sub_actions) > 0:
522
- for sub_action in action.sub_actions:
523
- action_counter += 1
524
- if action_counter == action_index:
525
- current_action = sub_action
526
- is_sub_action = True
527
- break
528
- if current_action:
529
- break
552
+ current_action, is_sub_action = self.get_current_action_at(job, action_index)
530
553
  enable_sub_actions = current_action is not None and \
531
554
  not is_sub_action and current_action.type_name == constants.ACTION_COMBO
532
555
  self.set_enabled_sub_actions_gui(enable_sub_actions)
@@ -0,0 +1,30 @@
1
+ # pylint: disable=C0114, C0116, E0611
2
+ from PySide6.QtWidgets import QWidget, QPushButton, QHBoxLayout, QFileDialog, QSizePolicy, QLineEdit
3
+
4
+
5
+ def create_layout_widget_no_margins(layout):
6
+ layout.setContentsMargins(0, 0, 0, 0)
7
+ container = QWidget()
8
+ container.setLayout(layout)
9
+ container.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Fixed)
10
+ return container
11
+
12
+ def create_layout_widget_and_connect(button, edit, browse):
13
+ button.clicked.connect(browse)
14
+ button.setAutoDefault(False)
15
+ layout = QHBoxLayout()
16
+ layout.addWidget(edit)
17
+ layout.addWidget(button)
18
+ return create_layout_widget_no_margins(layout)
19
+
20
+ def create_select_file_paths_widget(value, placeholder, tag):
21
+ edit = QLineEdit(value)
22
+ edit.setPlaceholderText(placeholder)
23
+ button = QPushButton("Browse...")
24
+
25
+ def browse():
26
+ path = QFileDialog.getExistingDirectory(None, f"Select {tag}")
27
+ if path:
28
+ edit.setText(path)
29
+
30
+ return create_layout_widget_and_connect(button, edit, browse)
@@ -2,7 +2,7 @@
2
2
  import traceback
3
3
  from abc import ABC, abstractmethod
4
4
  import numpy as np
5
- from PySide6.QtWidgets import QDialog, QVBoxLayout
5
+ from PySide6.QtWidgets import QDialog, QVBoxLayout, QCheckBox, QDialogButtonBox
6
6
  from PySide6.QtCore import Signal, QThread, QTimer
7
7
 
8
8
 
@@ -98,6 +98,17 @@ class BaseFilter(ABC):
98
98
  else:
99
99
  restore_original()
100
100
 
101
+ def create_base_widgets(self, layout, buttons, preview_latency):
102
+ preview_check = QCheckBox("Preview")
103
+ preview_check.setChecked(True)
104
+ layout.addWidget(preview_check)
105
+ button_box = QDialogButtonBox(buttons)
106
+ layout.addWidget(button_box)
107
+ preview_timer = QTimer()
108
+ preview_timer.setSingleShot(True)
109
+ preview_timer.setInterval(preview_latency)
110
+ return preview_check, preview_timer, button_box
111
+
101
112
  class PreviewWorker(QThread):
102
113
  finished = Signal(np.ndarray, int)
103
114
 
@@ -1,6 +1,6 @@
1
1
  # pylint: disable=C0114, C0115, C0116, E0611, W0221
2
- from PySide6.QtWidgets import QHBoxLayout, QLabel, QSlider, QCheckBox, QDialogButtonBox
3
- from PySide6.QtCore import Qt, QTimer
2
+ from PySide6.QtWidgets import QHBoxLayout, QLabel, QSlider, QDialogButtonBox
3
+ from PySide6.QtCore import Qt
4
4
  from .base_filter import BaseFilter
5
5
  from .. algorithms.denoise import denoise
6
6
 
@@ -24,14 +24,8 @@ class DenoiseFilter(BaseFilter):
24
24
  value_label = QLabel(f"{self.max_value:.2f}")
25
25
  slider_layout.addWidget(value_label)
26
26
  layout.addLayout(slider_layout)
27
- preview_check = QCheckBox("Preview")
28
- preview_check.setChecked(True)
29
- layout.addWidget(preview_check)
30
- button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
31
- layout.addWidget(button_box)
32
- preview_timer = QTimer()
33
- preview_timer.setSingleShot(True)
34
- preview_timer.setInterval(200)
27
+ preview_check, preview_timer, button_box = self.create_base_widgets(
28
+ layout, QDialogButtonBox.Ok | QDialogButtonBox.Cancel, 200)
35
29
 
36
30
  def do_preview_delayed():
37
31
  preview_timer.start()
@@ -1,10 +1,9 @@
1
1
  # pylint: disable=C0114, C0115, C0116, E0611
2
- import os
3
2
  from PIL.TiffImagePlugin import IFDRational
4
3
  from PySide6.QtWidgets import QFormLayout, QHBoxLayout, QPushButton, QDialog, QLabel
5
- from PySide6.QtGui import QIcon
6
4
  from PySide6.QtCore import Qt
7
5
  from .. algorithms.exif import exif_dict
6
+ from .icon_container import icon_container
8
7
 
9
8
 
10
9
  class ExifData(QDialog):
@@ -32,13 +31,8 @@ class ExifData(QDialog):
32
31
  self.layout.addRow(label)
33
32
 
34
33
  def create_form(self):
35
- icon_path = f"{os.path.dirname(__file__)}/../gui/ico/shinestacker.png"
36
- app_icon = QIcon(icon_path)
37
- icon_pixmap = app_icon.pixmap(128, 128)
38
- icon_label = QLabel()
39
- icon_label.setPixmap(icon_pixmap)
40
- icon_label.setAlignment(Qt.AlignCenter)
41
- self.layout.addRow(icon_label)
34
+ self.layout.addRow(icon_container())
35
+
42
36
  spacer = QLabel("")
43
37
  spacer.setFixedHeight(10)
44
38
  self.layout.addRow(spacer)
@@ -0,0 +1,19 @@
1
+ # pylint: disable=C0114, C0115, C0116, E0611
2
+ import os
3
+ from PySide6.QtWidgets import QHBoxLayout, QLabel, QWidget
4
+ from PySide6.QtGui import QIcon
5
+ from PySide6.QtCore import Qt
6
+
7
+
8
+ def icon_container():
9
+ icon_path = f"{os.path.dirname(__file__)}/../gui/ico/shinestacker.png"
10
+ app_icon = QIcon(icon_path)
11
+ pixmap = app_icon.pixmap(128, 128)
12
+ label = QLabel()
13
+ label.setPixmap(pixmap)
14
+ label.setAlignment(Qt.AlignCenter)
15
+ container = QWidget()
16
+ layout = QHBoxLayout(container)
17
+ layout.addWidget(label)
18
+ layout.setAlignment(Qt.AlignCenter)
19
+ return container
@@ -1,9 +1,8 @@
1
1
  # pylint: disable=C0114, C0115, C0116, E0611
2
- import os
3
2
  from PySide6.QtWidgets import (QFormLayout, QHBoxLayout, QPushButton, QDialog,
4
3
  QLabel, QVBoxLayout, QWidget)
5
- from PySide6.QtGui import QIcon
6
4
  from PySide6.QtCore import Qt
5
+ from .icon_container import icon_container
7
6
 
8
7
 
9
8
  class ShortcutsHelp(QDialog):
@@ -44,17 +43,7 @@ class ShortcutsHelp(QDialog):
44
43
  layout.addRow(label)
45
44
 
46
45
  def create_form(self, left_layout, right_layout):
47
- icon_path = f"{os.path.dirname(__file__)}/../gui/ico/shinestacker.png"
48
- app_icon = QIcon(icon_path)
49
- icon_pixmap = app_icon.pixmap(128, 128)
50
- icon_label = QLabel()
51
- icon_label.setPixmap(icon_pixmap)
52
- icon_label.setAlignment(Qt.AlignCenter)
53
- icon_container = QWidget()
54
- icon_container_layout = QHBoxLayout(icon_container)
55
- icon_container_layout.addWidget(icon_label)
56
- icon_container_layout.setAlignment(Qt.AlignCenter)
57
- self.layout.insertWidget(0, icon_container)
46
+ self.layout.insertWidget(0, icon_container())
58
47
 
59
48
  shortcuts = {
60
49
  "M": "show master layer",
@@ -1,5 +1,5 @@
1
1
  # pylint: disable=C0114, C0115, C0116, E0611, W0221, R0902, R0914
2
- from PySide6.QtWidgets import QHBoxLayout, QLabel, QSlider, QCheckBox, QDialogButtonBox
2
+ from PySide6.QtWidgets import QHBoxLayout, QLabel, QSlider, QDialogButtonBox
3
3
  from PySide6.QtCore import Qt, QTimer
4
4
  from .. algorithms.sharpen import unsharp_mask
5
5
  from .base_filter import BaseFilter
@@ -46,15 +46,8 @@ class UnsharpMaskFilter(BaseFilter):
46
46
  elif name == "Threshold":
47
47
  self.threshold_slider = slider
48
48
  value_labels[name] = value_label
49
-
50
- preview_check = QCheckBox("Preview")
51
- preview_check.setChecked(True)
52
- layout.addWidget(preview_check)
53
- button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
54
- layout.addWidget(button_box)
55
- preview_timer = QTimer()
56
- preview_timer.setSingleShot(True)
57
- preview_timer.setInterval(200)
49
+ preview_check, preview_timer, button_box = self.create_base_widgets(
50
+ layout, QDialogButtonBox.Ok | QDialogButtonBox.Cancel, 200)
58
51
 
59
52
  def update_value(name, value, max_val, fmt):
60
53
  float_value = max_val * value / self.max_range
@@ -1,6 +1,6 @@
1
1
  # pylint: disable=C0114, C0115, C0116, E0611, W0221, R0913, R0914, R0917
2
2
  from PySide6.QtWidgets import (QHBoxLayout, QPushButton, QFrame, QVBoxLayout, QLabel, QDialog,
3
- QApplication, QSlider, QCheckBox, QDialogButtonBox)
3
+ QApplication, QSlider, QDialogButtonBox)
4
4
  from PySide6.QtCore import Qt, QTimer
5
5
  from PySide6.QtGui import QCursor
6
6
  from .. algorithms.white_balance import white_balance_from_rgb
@@ -47,18 +47,10 @@ class WhiteBalanceFilter(BaseFilter):
47
47
  layout.addLayout(row_layout)
48
48
  pick_button = QPushButton("Pick Color")
49
49
  layout.addWidget(pick_button)
50
- preview_check = QCheckBox("Preview")
51
- preview_check.setChecked(True)
52
- layout.addWidget(preview_check)
53
- button_box = QDialogButtonBox(
54
- QDialogButtonBox.Ok |
55
- QDialogButtonBox.Reset |
56
- QDialogButtonBox.Cancel
57
- )
58
- layout.addWidget(button_box)
59
- self.preview_timer = QTimer()
60
- self.preview_timer.setSingleShot(True)
61
- self.preview_timer.setInterval(200)
50
+ preview_check, self.preview_timer, button_box = self.create_base_widgets(
51
+ layout,
52
+ QDialogButtonBox.Ok | QDialogButtonBox.Reset | QDialogButtonBox.Cancel,
53
+ 200)
62
54
  for slider in self.sliders.values():
63
55
  slider.valueChanged.connect(self.on_slider_change)
64
56
  self.preview_timer.timeout.connect(do_preview)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: shinestacker
3
- Version: 0.3.5
3
+ Version: 0.3.6
4
4
  Summary: ShineStacker
5
5
  Author-email: Luca Lista <luka.lista@gmail.com>
6
6
  License-Expression: LGPL-3.0
@@ -38,25 +38,21 @@ Dynamic: license-file
38
38
  [![Python Versions](https://img.shields.io/pypi/pyversions/shinestacker)](https://pypi.org/project/shinestacker/)
39
39
  [![Qt Versions](https://img.shields.io/badge/Qt-6-blue.svg?&logo=Qt&logoWidth=18&logoColor=white)](https://www.qt.io/qt-for-python)
40
40
  [![pylint](https://img.shields.io/badge/PyLint-9.98-yellow?logo=python&logoColor=white)](https://github.com/lucalista/shinestacker/blob/main/.github/workflows/pylint.yml)
41
- [![Documentation Status](https://readthedocs.org/projects/shinestacker/badge/?version=latest)](https://shinestacker.readthedocs.io/en/latest/?badge=latest)
42
- <!--
43
41
  [![codecov](https://codecov.io/github/lucalista/shinestacker/graph/badge.svg?token=Y5NKW6VH5G)](https://codecov.io/github/lucalista/shinestacker)
44
- -->
42
+ [![Documentation Status](https://readthedocs.org/projects/shinestacker/badge/?version=latest)](https://shinestacker.readthedocs.io/en/latest/?badge=latest)
45
43
 
46
44
 
47
45
  <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/flies.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/flies_stack.jpg' width="400" referrerpolicy="no-referrer">
48
46
 
49
47
  <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coffee.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coffee_stack.jpg' width="400" referrerpolicy="no-referrer">
50
48
 
51
- <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coins.gif' width="400" referrerpolicy="no-referrer"> <img src='https://raw.githubusercontent.com/lucalista/shinestacker/main/img/coins_stack.jpg' width="400" referrerpolicy="no-referrer">
52
49
  > **Focus stacking** for microscopy, macro photography, and computational imaging
53
50
 
54
51
  ## Key Features
55
52
  - 🚀 **Batch Processing**: Align, balance, and stack hundreds of images
56
- - 🎨 **Hybrid Workflows**: Combine Python scripting with GUI refinement
57
53
  - 🧩 **Modular Architecture**: Mix-and-match processing modules
58
54
  - 🖌️ **Retouch Editing**: Final interactive retouch of stacked image from individual frames
59
- - 📊 **Jupyter Integration**: Reproducible research notebooks
55
+ - 📊 **Jupyter Integration**: Image processing python notebooks
60
56
 
61
57
  ## Interactive GUI
62
58
 
@@ -77,7 +73,7 @@ The GUI has two main working areas:
77
73
 
78
74
  # Credits
79
75
 
80
- The main pyramid stack algorithm was initially inspired by the [Laplacian pyramids method](https://github.com/sjawhar/focus-stacking) implementation by Sami Jawhar that was used under permission of the author for initial versions of this package. The implementation in the latest releases was rewritten from the original code.
76
+ The first version of the core focus stack algorithm was initially inspired by the [Laplacian pyramids method](https://github.com/sjawhar/focus-stacking) implementation by Sami Jawhar that was used under permission of the author. The implementation in the latest releases was rewritten from the original code.
81
77
 
82
78
  # Resources
83
79
 
@@ -1,16 +1,15 @@
1
1
  shinestacker/__init__.py,sha256=uq2fjAw2z_6TpH3mOcWFZ98GoEPRsNhTAK8N0MMm_e8,448
2
- shinestacker/_version.py,sha256=e9arv8KorBrIZFQXAlN4DOQTh91btae1iR36M_3Wafk,21
2
+ shinestacker/_version.py,sha256=IbpUPwvtjLOqowcOFsWQ6LKq-FH6cI19IpvfQlxufq0,21
3
3
  shinestacker/algorithms/__init__.py,sha256=c4kRrdTLlVI70Q16XkI1RSmz5MD7npDqIpO_02jTG6g,747
4
4
  shinestacker/algorithms/align.py,sha256=1CAnVhxaYO-SUd86Mmj7lTmaqlrmUWlF-HEM5341gcs,17166
5
5
  shinestacker/algorithms/balance.py,sha256=ZBcw2Ck-CfuobIG1FxFsGVjnLvD1rtVNrTO-GrFbi3Q,16441
6
6
  shinestacker/algorithms/base_stack_algo.py,sha256=EAdVcO2UDq6UwqWZwGZFF7XXJvysyxqqVoswz4PLCdo,1353
7
- shinestacker/algorithms/core_utils.py,sha256=XJyHDUFXmN4JhbOjJqhP4dJW75B69oZaaWQrSXHUk5o,575
8
7
  shinestacker/algorithms/denoise.py,sha256=GL3Z4_6MHxSa7Wo4ZzQECZS87tHBFqO0sIVF_jPuYQU,426
9
8
  shinestacker/algorithms/depth_map.py,sha256=b88GqbRXEU3wCXBxMcStlgZ4sFJicoiZfJMD30Z4b98,7364
10
9
  shinestacker/algorithms/exif.py,sha256=gY9s6Cd4g4swo5qEjSbzuVIvl1GImCYu6ytOO9WrV0I,9435
11
10
  shinestacker/algorithms/multilayer.py,sha256=4Y6XlNJHFW74iNDFIeq_zdVtwLBnrieeMd708zJX-lo,8994
12
11
  shinestacker/algorithms/noise_detection.py,sha256=PmscQWi2v3ERTSf8SejkkSZXmTixKvh4NV9CtfuoUfM,8564
13
- shinestacker/algorithms/pyramid.py,sha256=zQdN2cr26M2iIywjr_xtknY9XvA2cc6wjMmiNOJf3Wc,8288
12
+ shinestacker/algorithms/pyramid.py,sha256=_Pk19lRQ21b3W3aHQ6DgAe9VVOfbsi2a9jrynF0qFVw,8610
14
13
  shinestacker/algorithms/sharpen.py,sha256=h7PMJBYxucg194Usp_6pvItPUMFYbT-ebAc_-7XBFUw,949
15
14
  shinestacker/algorithms/stack.py,sha256=IAa24rPMXl7F5yfcy0nw-fjsgGPpUkxqeKMqLHqvee8,4796
16
15
  shinestacker/algorithms/stack_framework.py,sha256=WZLudhjk6piQz2JULShxcoC3-3mSD-Bgh4_VT7JeG7c,12293
@@ -19,7 +18,7 @@ shinestacker/algorithms/vignetting.py,sha256=wFwi20ob1O3Memav1XQrtrOHgOtKRiK1RV4
19
18
  shinestacker/algorithms/white_balance.py,sha256=PMKsBtxOSn5aRr_Gkx1StHS4eN6kBN2EhNnhg4UG24g,501
20
19
  shinestacker/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
21
20
  shinestacker/app/about_dialog.py,sha256=QzZgTcLvkSP3_FhmPOUnwQ_YSxwJdeFrU2IAVYKDgeg,1050
22
- shinestacker/app/app_config.py,sha256=Rde_EKfM0tAs6a1qtiNkr6Bkodg5hZc6XW4hmQoQoac,1169
21
+ shinestacker/app/app_config.py,sha256=eTIRxp0t7Wic46jMTe_oY3kz7ktZbdM43C3bjshVDKg,494
23
22
  shinestacker/app/gui_utils.py,sha256=ptbUKjv5atbx5vW912_j8BVmDZpovAqZDEC48d0R2vA,2331
24
23
  shinestacker/app/help_menu.py,sha256=UOlabEY_EKV2Q1BoiU2JAM1udSSBAwXlL7d58bqxKe0,516
25
24
  shinestacker/app/main.py,sha256=RAf9WiCipYLK1rrwnXyL1sWq_28zDl9Z_eipfrdtSuY,6421
@@ -27,7 +26,7 @@ shinestacker/app/open_frames.py,sha256=bsu32iJSYJQLe_tQQbvAU5DuMDVX6MRuNdE7B5loj
27
26
  shinestacker/app/project.py,sha256=ir98-zogYmvx2QYvFbAaBUqLL03qWYkoMOIvLvmQy_w,2736
28
27
  shinestacker/app/retouch.py,sha256=ZQ-nRKnHo6xurcP34RNqaAWkmuGBjJ5jE05hTQ_ycis,2482
29
28
  shinestacker/config/__init__.py,sha256=aXxi-LmAvXd0daIFrVnTHE5OCaYeK1uf1BKMr7oaXQs,197
30
- shinestacker/config/config.py,sha256=BmcY79Ng3bN7HvYK7DzkGlrk2kTfV5R4gKTln87tfWs,1539
29
+ shinestacker/config/config.py,sha256=BshNb20Dx5HqdlpsTQbx4p-LnQ5uBP2q-h9v3pl84ss,1635
31
30
  shinestacker/config/constants.py,sha256=79bOcE44MZ0WuAVPjDwwhvNrsQTlHGyIOwmqwlLOfMU,5776
32
31
  shinestacker/config/gui_constants.py,sha256=002r96jtxV4Acel7q5NgECrcsDJzW-kOStEHqam-5Gg,2492
33
32
  shinestacker/core/__init__.py,sha256=IUEIx6SQ3DygDEHN3_E6uKpHjHtUa4a_U_1dLd_8yEU,484
@@ -37,17 +36,18 @@ shinestacker/core/exceptions.py,sha256=2-noG-ORAGdvDhL8jBQFs0xxZS4fI6UIkMqrWekgk
37
36
  shinestacker/core/framework.py,sha256=3Q3zalyZeCiUHXYbBiYadWNdtyD_3j3dcymk5_3NajM,7063
38
37
  shinestacker/core/logging.py,sha256=9SuSSy9Usbh7zqmLYMqkmy-VBkOJW000lwqAR0XQs30,3067
39
38
  shinestacker/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
40
- shinestacker/gui/action_config.py,sha256=z5rztJDmOIyXUm3CiRoXFLn1AWHqHDYkcUkLGK5mzbE,49539
41
- shinestacker/gui/actions_window.py,sha256=oKMgZwahBlxAGFjP2BXP8y7_fPmPCQS4t-d8-JDoCfs,13351
39
+ shinestacker/gui/action_config.py,sha256=RpUHjq8lmiGJQPnp55O1yd3ZPiLQG3R7jZ52m1VmiQc,48678
40
+ shinestacker/gui/actions_window.py,sha256=-ehMkGshsH22HSnn33ThAMXy7tR_cqWr14mEnXDTfXk,12025
42
41
  shinestacker/gui/colors.py,sha256=zgLRcC3fAzklx7zzyjLEsMX2i64YTxGUmQM2woYBZuw,1344
43
42
  shinestacker/gui/gui_images.py,sha256=e0KAXSPruZoRHrajfdlmOKBYoRJJQBDan1jgs7YFltY,5678
44
43
  shinestacker/gui/gui_logging.py,sha256=ESlk1EAQMdoT8pCZDFsvtU1UtF4h2GKv3wAhxJYxNis,8213
45
44
  shinestacker/gui/gui_run.py,sha256=bYXX4N__Ez7JMIJtVcTmLF2PJ3y9bCd-uvlOHsV-4gg,16230
46
45
  shinestacker/gui/main_window.py,sha256=z14PWRVDRbuySM05YCCFrn1DPU3U96xwQHy730oiLkw,28577
47
- shinestacker/gui/new_project.py,sha256=nr05vfmMH-LsKiLL_qOanSr35-6AZwv7q_CXAXXEFFc,8443
46
+ shinestacker/gui/new_project.py,sha256=cs3641RW3Uiy2VfwxeM-k254rH4BNykJyojmwxzrEi8,7758
48
47
  shinestacker/gui/project_converter.py,sha256=v-oldaw77VLsywhQcl5uhTtPD7GbGFeJo33JJRl3aG4,7453
49
- shinestacker/gui/project_editor.py,sha256=U6VIfJdtFT1Jg-J7sxgLPW5f8donVg5MBMcKFo6h65Q,21523
48
+ shinestacker/gui/project_editor.py,sha256=zwmj7PFs7X06GY4tkoDBcOL4Tl0IGo4Mf13n2qGwaJY,22245
50
49
  shinestacker/gui/project_model.py,sha256=89L0IDSAqRK2mvU1EVIrcsJas8CU-aTzUIjdL1Cv0mw,4421
50
+ shinestacker/gui/select_path_widget.py,sha256=JAxAkbQukPwBc27-EdeobxxJBG4IBfooiV-JZq3ttsY,1015
51
51
  shinestacker/gui/ico/focus_stack_bkg.png,sha256=Q86TgqvKEi_IzKI8m6aZB2a3T40UkDtexf2PdeBM9XE,163151
52
52
  shinestacker/gui/ico/shinestacker.icns,sha256=m_6WQBx8sE9jQKwIRa_B5oa7_VcNn6e2TyijeQXPjwM,337563
53
53
  shinestacker/gui/ico/shinestacker.ico,sha256=yO0NaBWA0uFov_GqHuHQbymoqLtQKt5DPWpGGmRKie0,186277
@@ -57,16 +57,17 @@ shinestacker/gui/img/forward-button-icon.png,sha256=lNw86T4TOEd_uokHYF8myGSGUXzd
57
57
  shinestacker/gui/img/play-button-round-icon.png,sha256=9j6Ks9mOGa-2cXyRFpimepAAvSaHzqJKBfxShRb4_dE,4595
58
58
  shinestacker/gui/img/plus-round-line-icon.png,sha256=LS068Hlu-CeBvJuB3dwwdJg1lZq6D5MUIv53lu1yKJA,7534
59
59
  shinestacker/retouch/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
60
- shinestacker/retouch/base_filter.py,sha256=sU2reqQpC3b-Q1iiTk2fWTtruakQvhZWC2MTRt74fFM,4228
60
+ shinestacker/retouch/base_filter.py,sha256=74GmzLjpPtn0Um0YOS8qLC1ZvhbvQX4L_AEDk-5H5nE,4717
61
61
  shinestacker/retouch/brush.py,sha256=dzD2FzSpBIPdJRmTZobcrQ1FrVd3tF__ZPnUplNE72s,357
62
62
  shinestacker/retouch/brush_gradient.py,sha256=F5SFhyzl8YTMqjJU3jK8BrIlLCYLUvITd5wz3cQE4xk,1453
63
63
  shinestacker/retouch/brush_preview.py,sha256=KlUOqA1uvLZRsz2peJ9NgsukyzsppJUw3XXr0NFCuhQ,5181
64
64
  shinestacker/retouch/brush_tool.py,sha256=kwX58_gC_Fep6zowDqOs3nG2wCIc8wrJdokDADGm6K0,8016
65
- shinestacker/retouch/denoise_filter.py,sha256=Z6oG5YVG172t1titu1KOgnKFSW8My2vzlTOSf5Ey5jY,2212
65
+ shinestacker/retouch/denoise_filter.py,sha256=eO0Cxo9xwsuiE6-JiWCFB5jf6U1kf2N3ftsDAEQ5sek,1982
66
66
  shinestacker/retouch/display_manager.py,sha256=54NRdlyVj_7GzJ7hmKf8Hnf3lJJx2jVSSpWedj-5pIc,7298
67
- shinestacker/retouch/exif_data.py,sha256=UB_CwoVN5mMLU6PfJCRaZOfarFR77a11mVaxbYZ4v-Y,2301
67
+ shinestacker/retouch/exif_data.py,sha256=uA9ck9skp8ztSUdX1SFrApgtqmxrHtfWW3vsry82H94,2026
68
68
  shinestacker/retouch/file_loader.py,sha256=723A_2w3cjn4rhvAzCq-__SWFelDRsMhkazgnb2h7Ig,4810
69
69
  shinestacker/retouch/filter_manager.py,sha256=SkioWTr6iFFpugUgZLg0a3m5b9EHdZAeyNFy39qk0z8,453
70
+ shinestacker/retouch/icon_container.py,sha256=6gw1HO1bC2FrdB4dc_iH81DQuLjzuvRGksZ2hKLT9yA,585
70
71
  shinestacker/retouch/image_editor.py,sha256=co5zUufgeb1WrD3aF1RVPh1MbcC9-92HSUa2iROnKk4,8503
71
72
  shinestacker/retouch/image_editor_ui.py,sha256=vfBALDuHtqSWIPmyfUirkygM1guwQG-gHo0AH0x8_jU,15712
72
73
  shinestacker/retouch/image_filters.py,sha256=JF2a7VATO3CGQr5_OOIPi2k7b9HvHzrhhWS73x32t-A,2883
@@ -74,13 +75,13 @@ shinestacker/retouch/image_viewer.py,sha256=oqBgaanPXWjzIaox5KSRhYOHoGvoYnWm7sqW
74
75
  shinestacker/retouch/io_gui_handler.py,sha256=2jkbPXew95rMKO2aC9hwZJGtZRg3wCCtXI0SFFiNHUI,9089
75
76
  shinestacker/retouch/io_manager.py,sha256=sNcZVEttiVdxNBVs39ZvexqOcvtjl2CvJs6BVqmGvOM,2148
76
77
  shinestacker/retouch/layer_collection.py,sha256=j1NiGGtLZ3OwrftBVNT4rb0Kq0CfWAB3t2bUrqHx1Sk,5608
77
- shinestacker/retouch/shortcuts_help.py,sha256=wjCw2TiUBaPx1OqpdZlRPsLbyLhIZkoffz9wcODtQnA,3794
78
+ shinestacker/retouch/shortcuts_help.py,sha256=dlt7OSAr9thYuoEPlirTU_YRzv5xP9vy2-9mZO7GVAA,3308
78
79
  shinestacker/retouch/undo_manager.py,sha256=_ekbcOLcPbQLY7t-o8wf-b1uA6OPY9rRyLM-KqMlQRo,3257
79
- shinestacker/retouch/unsharp_mask_filter.py,sha256=u6QDAebzAUqRvJtXyMLdXy-G0zEGb-43LzaKkK4kQ2A,3704
80
- shinestacker/retouch/white_balance_filter.py,sha256=odS_QTTLBzuN0gzhGqCC3T5mNRwE-epxPXraKhj5VQM,5132
81
- shinestacker-0.3.5.dist-info/licenses/LICENSE,sha256=cBN0P3F6BWFkfOabkhuTxwJnK1B0v50jmmzZJjGGous,80
82
- shinestacker-0.3.5.dist-info/METADATA,sha256=945j_vIxXHAitDhTRJhYYslm0iUMnjv1zcZV68DEmCQ,5279
83
- shinestacker-0.3.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
84
- shinestacker-0.3.5.dist-info/entry_points.txt,sha256=SY6g1LqtMmp23q1DGwLUDT_dhLX9iss8DvWkiWLyo_4,166
85
- shinestacker-0.3.5.dist-info/top_level.txt,sha256=MhijwnBVX5psfsyX8JZjqp3SYiWPsKe69f3Gnyze4Fw,13
86
- shinestacker-0.3.5.dist-info/RECORD,,
80
+ shinestacker/retouch/unsharp_mask_filter.py,sha256=hNJlqXYjf9Nd8KlVy09fd4TxrHa9Ofef0ZLSMHjLL6I,3481
81
+ shinestacker/retouch/white_balance_filter.py,sha256=2krwdz0X6qLWuCIEQcPtSQA_txfAsl7QUzfdsOLBrBU,4878
82
+ shinestacker-0.3.6.dist-info/licenses/LICENSE,sha256=cBN0P3F6BWFkfOabkhuTxwJnK1B0v50jmmzZJjGGous,80
83
+ shinestacker-0.3.6.dist-info/METADATA,sha256=1xiC2Bgn2sR8bqwuapC0QNodFy9fOO9p8ybGpqvHOwc,4915
84
+ shinestacker-0.3.6.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
85
+ shinestacker-0.3.6.dist-info/entry_points.txt,sha256=SY6g1LqtMmp23q1DGwLUDT_dhLX9iss8DvWkiWLyo_4,166
86
+ shinestacker-0.3.6.dist-info/top_level.txt,sha256=MhijwnBVX5psfsyX8JZjqp3SYiWPsKe69f3Gnyze4Fw,13
87
+ shinestacker-0.3.6.dist-info/RECORD,,
@@ -1,22 +0,0 @@
1
- # pylint: disable=C0114, C0116
2
- import os
3
- from ..config.config import config
4
-
5
- if not config.DISABLE_TQDM:
6
- from tqdm import tqdm
7
- from tqdm.notebook import tqdm_notebook
8
-
9
-
10
- def check_path_exists(path):
11
- if not os.path.exists(path):
12
- raise RuntimeError('Path does not exist: ' + path)
13
-
14
-
15
- def make_tqdm_bar(name, size, ncols=80):
16
- if not config.DISABLE_TQDM:
17
- if config.JUPYTER_NOTEBOOK:
18
- tbar = tqdm_notebook(desc=name, total=size)
19
- else:
20
- tbar = tqdm(desc=name, total=size, ncols=ncols)
21
- return tbar
22
- return None