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 +1 -1
- shinestacker/algorithms/pyramid.py +7 -4
- shinestacker/app/app_config.py +4 -22
- shinestacker/config/config.py +21 -16
- shinestacker/gui/action_config.py +10 -35
- shinestacker/gui/actions_window.py +18 -47
- shinestacker/gui/new_project.py +5 -22
- shinestacker/gui/project_editor.py +43 -20
- shinestacker/gui/select_path_widget.py +30 -0
- shinestacker/retouch/base_filter.py +12 -1
- shinestacker/retouch/denoise_filter.py +4 -10
- shinestacker/retouch/exif_data.py +3 -9
- shinestacker/retouch/icon_container.py +19 -0
- shinestacker/retouch/shortcuts_help.py +2 -13
- shinestacker/retouch/unsharp_mask_filter.py +3 -10
- shinestacker/retouch/white_balance_filter.py +5 -13
- {shinestacker-0.3.5.dist-info → shinestacker-0.3.6.dist-info}/METADATA +4 -8
- {shinestacker-0.3.5.dist-info → shinestacker-0.3.6.dist-info}/RECORD +22 -21
- shinestacker/algorithms/core_utils.py +0 -22
- {shinestacker-0.3.5.dist-info → shinestacker-0.3.6.dist-info}/WHEEL +0 -0
- {shinestacker-0.3.5.dist-info → shinestacker-0.3.6.dist-info}/entry_points.txt +0 -0
- {shinestacker-0.3.5.dist-info → shinestacker-0.3.6.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-0.3.5.dist-info → shinestacker-0.3.6.dist-info}/top_level.txt +0 -0
shinestacker/_version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = '0.3.
|
|
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",
|
|
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)
|
shinestacker/app/app_config.py
CHANGED
|
@@ -1,28 +1,15 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, C0103, W0201
|
|
2
|
-
|
|
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
|
-
|
|
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()
|
shinestacker/config/config.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, C0103, R0903, W0718, W0104, W0201, E0602
|
|
2
|
-
class
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
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
|
|
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
|
-
|
|
24
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
128
|
-
self.
|
|
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
|
-
|
|
181
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
shinestacker/gui/new_project.py
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, E0611, R0915, R0902
|
|
2
2
|
import os
|
|
3
|
-
from PySide6.QtWidgets import (
|
|
4
|
-
QDialog,
|
|
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
|
-
|
|
54
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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,
|
|
3
|
-
from PySide6.QtCore import Qt
|
|
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 =
|
|
28
|
-
|
|
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
|
-
|
|
36
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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.
|
|
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
|
[](https://pypi.org/project/shinestacker/)
|
|
39
39
|
[](https://www.qt.io/qt-for-python)
|
|
40
40
|
[](https://github.com/lucalista/shinestacker/blob/main/.github/workflows/pylint.yml)
|
|
41
|
-
[](https://shinestacker.readthedocs.io/en/latest/?badge=latest)
|
|
42
|
-
<!--
|
|
43
41
|
[](https://codecov.io/github/lucalista/shinestacker)
|
|
44
|
-
|
|
42
|
+
[](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**:
|
|
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
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
41
|
-
shinestacker/gui/actions_window.py,sha256
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
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=
|
|
80
|
-
shinestacker/retouch/white_balance_filter.py,sha256=
|
|
81
|
-
shinestacker-0.3.
|
|
82
|
-
shinestacker-0.3.
|
|
83
|
-
shinestacker-0.3.
|
|
84
|
-
shinestacker-0.3.
|
|
85
|
-
shinestacker-0.3.
|
|
86
|
-
shinestacker-0.3.
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|