shinestacker 0.5.0__py3-none-any.whl → 1.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of shinestacker might be problematic. Click here for more details.
- shinestacker/_version.py +1 -1
- shinestacker/algorithms/align.py +4 -12
- shinestacker/algorithms/balance.py +11 -9
- shinestacker/algorithms/depth_map.py +0 -30
- shinestacker/algorithms/utils.py +10 -0
- shinestacker/algorithms/vignetting.py +116 -70
- shinestacker/app/about_dialog.py +37 -16
- shinestacker/app/gui_utils.py +1 -1
- shinestacker/app/help_menu.py +1 -1
- shinestacker/app/main.py +2 -2
- shinestacker/app/project.py +2 -2
- shinestacker/config/constants.py +4 -1
- shinestacker/config/gui_constants.py +3 -4
- shinestacker/gui/action_config.py +5 -561
- shinestacker/gui/action_config_dialog.py +567 -0
- shinestacker/gui/base_form_dialog.py +18 -0
- shinestacker/gui/colors.py +5 -6
- shinestacker/gui/gui_logging.py +0 -1
- shinestacker/gui/gui_run.py +54 -106
- shinestacker/gui/ico/shinestacker.icns +0 -0
- shinestacker/gui/ico/shinestacker.ico +0 -0
- shinestacker/gui/ico/shinestacker.png +0 -0
- shinestacker/gui/ico/shinestacker.svg +60 -0
- shinestacker/gui/main_window.py +275 -371
- shinestacker/gui/menu_manager.py +236 -0
- shinestacker/gui/new_project.py +75 -20
- shinestacker/gui/project_converter.py +6 -6
- shinestacker/gui/project_editor.py +248 -165
- shinestacker/gui/project_model.py +2 -7
- shinestacker/gui/tab_widget.py +81 -0
- shinestacker/gui/time_progress_bar.py +95 -0
- shinestacker/retouch/base_filter.py +173 -40
- shinestacker/retouch/brush_preview.py +0 -10
- shinestacker/retouch/brush_tool.py +2 -5
- shinestacker/retouch/denoise_filter.py +5 -44
- shinestacker/retouch/exif_data.py +10 -13
- shinestacker/retouch/file_loader.py +1 -1
- shinestacker/retouch/filter_manager.py +1 -4
- shinestacker/retouch/image_editor_ui.py +318 -40
- shinestacker/retouch/image_viewer.py +34 -11
- shinestacker/retouch/io_gui_handler.py +34 -30
- shinestacker/retouch/layer_collection.py +2 -0
- shinestacker/retouch/shortcuts_help.py +12 -0
- shinestacker/retouch/unsharp_mask_filter.py +10 -10
- shinestacker/retouch/vignetting_filter.py +69 -0
- shinestacker/retouch/white_balance_filter.py +46 -14
- {shinestacker-0.5.0.dist-info → shinestacker-1.0.0.dist-info}/METADATA +14 -2
- shinestacker-1.0.0.dist-info/RECORD +90 -0
- shinestacker/app/app_config.py +0 -22
- shinestacker/gui/actions_window.py +0 -266
- shinestacker/retouch/image_editor.py +0 -197
- shinestacker/retouch/image_filters.py +0 -69
- shinestacker-0.5.0.dist-info/RECORD +0 -87
- {shinestacker-0.5.0.dist-info → shinestacker-1.0.0.dist-info}/WHEEL +0 -0
- {shinestacker-0.5.0.dist-info → shinestacker-1.0.0.dist-info}/entry_points.txt +0 -0
- {shinestacker-0.5.0.dist-info → shinestacker-1.0.0.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-0.5.0.dist-info → shinestacker-1.0.0.dist-info}/top_level.txt +0 -0
|
@@ -4,16 +4,13 @@ import traceback
|
|
|
4
4
|
from abc import ABC, abstractmethod
|
|
5
5
|
from typing import Dict, Any
|
|
6
6
|
import os.path
|
|
7
|
-
from PySide6.QtWidgets import (
|
|
8
|
-
QMessageBox, QSizePolicy,
|
|
9
|
-
|
|
10
|
-
QAbstractItemView, QListView)
|
|
11
|
-
from PySide6.QtCore import Qt, QTimer
|
|
7
|
+
from PySide6.QtWidgets import (QPushButton, QHBoxLayout, QFileDialog, QLabel, QComboBox,
|
|
8
|
+
QMessageBox, QSizePolicy, QLineEdit, QSpinBox,
|
|
9
|
+
QDoubleSpinBox, QCheckBox, QTreeView, QAbstractItemView, QListView)
|
|
12
10
|
from .. config.constants import constants
|
|
13
|
-
from .. algorithms.align import validate_align_config
|
|
14
|
-
from .project_model import ActionConfig
|
|
15
11
|
from .select_path_widget import (create_select_file_paths_widget, create_layout_widget_no_margins,
|
|
16
12
|
create_layout_widget_and_connect)
|
|
13
|
+
from .project_model import ActionConfig
|
|
17
14
|
|
|
18
15
|
FIELD_TEXT = 'text'
|
|
19
16
|
FIELD_ABS_PATH = 'abs_path'
|
|
@@ -33,7 +30,7 @@ class ActionConfigurator(ABC):
|
|
|
33
30
|
self.current_wd = current_wd
|
|
34
31
|
|
|
35
32
|
@abstractmethod
|
|
36
|
-
def create_form(self, layout
|
|
33
|
+
def create_form(self, layout, action: ActionConfig, tag="Action"):
|
|
37
34
|
pass
|
|
38
35
|
|
|
39
36
|
@abstractmethod
|
|
@@ -364,556 +361,3 @@ class FieldBuilder:
|
|
|
364
361
|
checkbox = QCheckBox()
|
|
365
362
|
checkbox.setChecked(self.action.params.get(tag, default))
|
|
366
363
|
return checkbox
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
class ActionConfigDialog(QDialog):
|
|
370
|
-
def __init__(self, action: ActionConfig, current_wd, parent=None):
|
|
371
|
-
super().__init__(parent)
|
|
372
|
-
self.current_wd = current_wd
|
|
373
|
-
self.action = action
|
|
374
|
-
self.setWindowTitle(f"Configure {action.type_name}")
|
|
375
|
-
self.resize(500, self.height())
|
|
376
|
-
self.configurator = self.get_configurator(action.type_name)
|
|
377
|
-
self.layout = QFormLayout(self)
|
|
378
|
-
self.layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
|
|
379
|
-
self.layout.setRowWrapPolicy(QFormLayout.DontWrapRows)
|
|
380
|
-
self.layout.setFormAlignment(Qt.AlignLeft | Qt.AlignTop)
|
|
381
|
-
self.layout.setLabelAlignment(Qt.AlignLeft)
|
|
382
|
-
self.configurator.create_form(self.layout, action)
|
|
383
|
-
button_box = QHBoxLayout()
|
|
384
|
-
ok_button = QPushButton("OK")
|
|
385
|
-
ok_button.setFocus()
|
|
386
|
-
cancel_button = QPushButton("Cancel")
|
|
387
|
-
reset_button = QPushButton("Reset")
|
|
388
|
-
button_box.addWidget(ok_button)
|
|
389
|
-
button_box.addWidget(cancel_button)
|
|
390
|
-
button_box.addWidget(reset_button)
|
|
391
|
-
reset_button.clicked.connect(self.reset_to_defaults)
|
|
392
|
-
self.layout.addRow(button_box)
|
|
393
|
-
ok_button.clicked.connect(self.accept)
|
|
394
|
-
cancel_button.clicked.connect(self.reject)
|
|
395
|
-
|
|
396
|
-
def get_configurator(self, action_type: str) -> ActionConfigurator:
|
|
397
|
-
configurators = {
|
|
398
|
-
constants.ACTION_JOB: JobConfigurator,
|
|
399
|
-
constants.ACTION_COMBO: CombinedActionsConfigurator,
|
|
400
|
-
constants.ACTION_NOISEDETECTION: NoiseDetectionConfigurator,
|
|
401
|
-
constants.ACTION_FOCUSSTACK: FocusStackConfigurator,
|
|
402
|
-
constants.ACTION_FOCUSSTACKBUNCH: FocusStackBunchConfigurator,
|
|
403
|
-
constants.ACTION_MULTILAYER: MultiLayerConfigurator,
|
|
404
|
-
constants.ACTION_MASKNOISE: MaskNoiseConfigurator,
|
|
405
|
-
constants.ACTION_VIGNETTING: VignettingConfigurator,
|
|
406
|
-
constants.ACTION_ALIGNFRAMES: AlignFramesConfigurator,
|
|
407
|
-
constants.ACTION_BALANCEFRAMES: BalanceFramesConfigurator,
|
|
408
|
-
}
|
|
409
|
-
return configurators.get(
|
|
410
|
-
action_type, DefaultActionConfigurator)(self.expert(), self.current_wd)
|
|
411
|
-
|
|
412
|
-
def accept(self):
|
|
413
|
-
self.parent().project_buffer.append(self.parent().project.clone())
|
|
414
|
-
if self.configurator.update_params(self.action.params):
|
|
415
|
-
self.parent().mark_as_modified()
|
|
416
|
-
super().accept()
|
|
417
|
-
else:
|
|
418
|
-
self.parent().project_buffer.pop()
|
|
419
|
-
|
|
420
|
-
def reset_to_defaults(self):
|
|
421
|
-
builder = self.configurator.get_builder()
|
|
422
|
-
if builder:
|
|
423
|
-
builder.reset_to_defaults()
|
|
424
|
-
|
|
425
|
-
def expert(self):
|
|
426
|
-
return self.parent().expert_options
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
class NoNameActionConfigurator(ActionConfigurator):
|
|
430
|
-
def __init__(self, expert, current_wd):
|
|
431
|
-
super().__init__(expert, current_wd)
|
|
432
|
-
self.builder = None
|
|
433
|
-
|
|
434
|
-
def get_builder(self):
|
|
435
|
-
return self.builder
|
|
436
|
-
|
|
437
|
-
def update_params(self, params: Dict[str, Any]) -> bool:
|
|
438
|
-
return self.builder.update_params(params)
|
|
439
|
-
|
|
440
|
-
def add_bold_label(self, label):
|
|
441
|
-
label = QLabel(label)
|
|
442
|
-
label.setStyleSheet("font-weight: bold")
|
|
443
|
-
self.builder.layout.addRow(label)
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
class DefaultActionConfigurator(NoNameActionConfigurator):
|
|
447
|
-
def create_form(self, layout, action, tag='Action'):
|
|
448
|
-
self.builder = FieldBuilder(layout, action, self.current_wd)
|
|
449
|
-
self.builder.add_field('name', FIELD_TEXT, f'{tag} name', required=True)
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
class JobConfigurator(DefaultActionConfigurator):
|
|
453
|
-
def create_form(self, layout, action):
|
|
454
|
-
super().create_form(layout, action, "Job")
|
|
455
|
-
self.builder.add_field('working_path', FIELD_ABS_PATH, 'Working path', required=True)
|
|
456
|
-
self.builder.add_field('input_path', FIELD_REL_PATH, 'Input path', required=False,
|
|
457
|
-
must_exist=True, placeholder='relative to working path')
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
class NoiseDetectionConfigurator(DefaultActionConfigurator):
|
|
461
|
-
def create_form(self, layout, action):
|
|
462
|
-
super().create_form(layout, action)
|
|
463
|
-
self.builder.add_field('working_path', FIELD_ABS_PATH, 'Working path', required=True,
|
|
464
|
-
placeholder='inherit from job')
|
|
465
|
-
self.builder.add_field('input_path', FIELD_REL_PATH,
|
|
466
|
-
f'Input path (separate by {constants.PATH_SEPARATOR})',
|
|
467
|
-
required=False, multiple_entries=True,
|
|
468
|
-
placeholder='relative to working path')
|
|
469
|
-
self.builder.add_field('max_frames', FIELD_INT, 'Max. num. of frames', required=False,
|
|
470
|
-
default=-1, min_val=-1, max_val=1000)
|
|
471
|
-
self.builder.add_field('channel_thresholds', FIELD_INT_TUPLE, 'Noise threshold',
|
|
472
|
-
required=False, size=3,
|
|
473
|
-
default=constants.DEFAULT_CHANNEL_THRESHOLDS,
|
|
474
|
-
labels=constants.RGB_LABELS, min_val=[1] * 3,
|
|
475
|
-
max_val=[1000] * 3)
|
|
476
|
-
if self.expert:
|
|
477
|
-
self.builder.add_field('blur_size', FIELD_INT, 'Blur size (px)', required=False,
|
|
478
|
-
default=constants.DEFAULT_BLUR_SIZE, min_val=1, max_val=50)
|
|
479
|
-
self.builder.add_field('file_name', FIELD_TEXT, 'File name', required=False,
|
|
480
|
-
default=constants.DEFAULT_NOISE_MAP_FILENAME,
|
|
481
|
-
placeholder=constants.DEFAULT_NOISE_MAP_FILENAME)
|
|
482
|
-
self.add_bold_label("Miscellanea:")
|
|
483
|
-
self.builder.add_field('plot_histograms', FIELD_BOOL, 'Plot histograms', required=False,
|
|
484
|
-
default=False)
|
|
485
|
-
self.builder.add_field('plot_path', FIELD_REL_PATH, 'Plots path', required=False,
|
|
486
|
-
default=constants.DEFAULT_PLOTS_PATH,
|
|
487
|
-
placeholder='relative to working path')
|
|
488
|
-
self.builder.add_field('plot_range', FIELD_INT_TUPLE, 'Plot range', required=False,
|
|
489
|
-
size=2, default=constants.DEFAULT_NOISE_PLOT_RANGE,
|
|
490
|
-
labels=['min', 'max'], min_val=[0] * 2, max_val=[1000] * 2)
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
class FocusStackBaseConfigurator(DefaultActionConfigurator):
|
|
494
|
-
ENERGY_OPTIONS = ['Laplacian', 'Sobel']
|
|
495
|
-
MAP_TYPE_OPTIONS = ['Average', 'Maximum']
|
|
496
|
-
FLOAT_OPTIONS = ['float 32 bits', 'float 64 bits']
|
|
497
|
-
|
|
498
|
-
def create_form(self, layout, action):
|
|
499
|
-
super().create_form(layout, action)
|
|
500
|
-
if self.expert:
|
|
501
|
-
self.builder.add_field('working_path', FIELD_ABS_PATH, 'Working path', required=False)
|
|
502
|
-
self.builder.add_field('input_path', FIELD_REL_PATH, 'Input path', required=False,
|
|
503
|
-
placeholder='relative to working path')
|
|
504
|
-
self.builder.add_field('output_path', FIELD_REL_PATH, 'Output path', required=False,
|
|
505
|
-
placeholder='relative to working path')
|
|
506
|
-
self.builder.add_field('scratch_output_dir', FIELD_BOOL, 'Scratch output dir.',
|
|
507
|
-
required=False, default=True)
|
|
508
|
-
|
|
509
|
-
def common_fields(self, layout):
|
|
510
|
-
self.builder.add_field('denoise_amount', FIELD_FLOAT, 'Denoise', required=False,
|
|
511
|
-
default=0, min_val=0, max_val=10)
|
|
512
|
-
self.add_bold_label("Stacking algorithm:")
|
|
513
|
-
combo = self.builder.add_field('stacker', FIELD_COMBO, 'Stacking algorithm', required=True,
|
|
514
|
-
options=constants.STACK_ALGO_OPTIONS,
|
|
515
|
-
default=constants.STACK_ALGO_DEFAULT)
|
|
516
|
-
q_pyramid, q_depthmap = QWidget(), QWidget()
|
|
517
|
-
for q in [q_pyramid, q_depthmap]:
|
|
518
|
-
layout = QFormLayout()
|
|
519
|
-
layout.setFieldGrowthPolicy(QFormLayout.AllNonFixedFieldsGrow)
|
|
520
|
-
layout.setRowWrapPolicy(QFormLayout.DontWrapRows)
|
|
521
|
-
layout.setFormAlignment(Qt.AlignLeft | Qt.AlignTop)
|
|
522
|
-
layout.setLabelAlignment(Qt.AlignLeft)
|
|
523
|
-
q.setLayout(layout)
|
|
524
|
-
stacked = QStackedWidget()
|
|
525
|
-
stacked.addWidget(q_pyramid)
|
|
526
|
-
stacked.addWidget(q_depthmap)
|
|
527
|
-
|
|
528
|
-
def change():
|
|
529
|
-
text = combo.currentText()
|
|
530
|
-
if text == 'Pyramid':
|
|
531
|
-
stacked.setCurrentWidget(q_pyramid)
|
|
532
|
-
elif text == 'Depth map':
|
|
533
|
-
stacked.setCurrentWidget(q_depthmap)
|
|
534
|
-
change()
|
|
535
|
-
if self.expert:
|
|
536
|
-
self.builder.add_field('pyramid_min_size', FIELD_INT, 'Minimum size (px)',
|
|
537
|
-
required=False, add_to_layout=q_pyramid.layout(),
|
|
538
|
-
default=constants.DEFAULT_PY_MIN_SIZE, min_val=2, max_val=256)
|
|
539
|
-
self.builder.add_field('pyramid_kernel_size', FIELD_INT, 'Kernel size (px)',
|
|
540
|
-
required=False, add_to_layout=q_pyramid.layout(),
|
|
541
|
-
default=constants.DEFAULT_PY_KERNEL_SIZE, min_val=3, max_val=21)
|
|
542
|
-
self.builder.add_field('pyramid_gen_kernel', FIELD_FLOAT, 'Gen. kernel',
|
|
543
|
-
required=False, add_to_layout=q_pyramid.layout(),
|
|
544
|
-
default=constants.DEFAULT_PY_GEN_KERNEL,
|
|
545
|
-
min_val=0.0, max_val=2.0)
|
|
546
|
-
self.builder.add_field('pyramid_float_type', FIELD_COMBO, 'Precision', required=False,
|
|
547
|
-
add_to_layout=q_pyramid.layout(),
|
|
548
|
-
options=self.FLOAT_OPTIONS, values=constants.VALID_FLOATS,
|
|
549
|
-
default=dict(zip(constants.VALID_FLOATS,
|
|
550
|
-
self.FLOAT_OPTIONS))[constants.DEFAULT_PY_FLOAT])
|
|
551
|
-
self.builder.add_field('depthmap_energy', FIELD_COMBO, 'Energy', required=False,
|
|
552
|
-
add_to_layout=q_depthmap.layout(),
|
|
553
|
-
options=self.ENERGY_OPTIONS, values=constants.VALID_DM_ENERGY,
|
|
554
|
-
default=dict(zip(constants.VALID_DM_ENERGY,
|
|
555
|
-
self.ENERGY_OPTIONS))[constants.DEFAULT_DM_ENERGY])
|
|
556
|
-
self.builder.add_field('map_type', FIELD_COMBO, 'Map type', required=False,
|
|
557
|
-
add_to_layout=q_depthmap.layout(),
|
|
558
|
-
options=self.MAP_TYPE_OPTIONS, values=constants.VALID_DM_MAP,
|
|
559
|
-
default=dict(zip(constants.VALID_DM_MAP,
|
|
560
|
-
self.MAP_TYPE_OPTIONS))[constants.DEFAULT_DM_MAP])
|
|
561
|
-
if self.expert:
|
|
562
|
-
self.builder.add_field('depthmap_kernel_size', FIELD_INT, 'Kernel size (px)',
|
|
563
|
-
required=False, add_to_layout=q_depthmap.layout(),
|
|
564
|
-
default=constants.DEFAULT_DM_KERNEL_SIZE, min_val=3, max_val=21)
|
|
565
|
-
self.builder.add_field('depthmap_blur_size', FIELD_INT, 'Blurl size (px)',
|
|
566
|
-
required=False, add_to_layout=q_depthmap.layout(),
|
|
567
|
-
default=constants.DEFAULT_DM_BLUR_SIZE, min_val=1, max_val=21)
|
|
568
|
-
self.builder.add_field('depthmap_smooth_size', FIELD_INT, 'Smooth size (px)',
|
|
569
|
-
required=False, add_to_layout=q_depthmap.layout(),
|
|
570
|
-
default=constants.DEFAULT_DM_SMOOTH_SIZE, min_val=0, max_val=256)
|
|
571
|
-
self.builder.add_field('depthmap_temperature', FIELD_FLOAT, 'Temperature',
|
|
572
|
-
required=False,
|
|
573
|
-
add_to_layout=q_depthmap.layout(),
|
|
574
|
-
default=constants.DEFAULT_DM_TEMPERATURE,
|
|
575
|
-
min_val=0, max_val=1, step=0.05)
|
|
576
|
-
self.builder.add_field('depthmap_levels', FIELD_INT, 'Levels', required=False,
|
|
577
|
-
add_to_layout=q_depthmap.layout(),
|
|
578
|
-
default=constants.DEFAULT_DM_LEVELS, min_val=2, max_val=6)
|
|
579
|
-
self.builder.add_field('depthmap_float_type', FIELD_COMBO, 'Precision', required=False,
|
|
580
|
-
add_to_layout=q_depthmap.layout(), options=self.FLOAT_OPTIONS,
|
|
581
|
-
values=constants.VALID_FLOATS,
|
|
582
|
-
default=dict(zip(constants.VALID_FLOATS,
|
|
583
|
-
self.FLOAT_OPTIONS))[constants.DEFAULT_DM_FLOAT])
|
|
584
|
-
self.builder.layout.addRow(stacked)
|
|
585
|
-
combo.currentIndexChanged.connect(change)
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
class FocusStackConfigurator(FocusStackBaseConfigurator):
|
|
589
|
-
def create_form(self, layout, action):
|
|
590
|
-
super().create_form(layout, action)
|
|
591
|
-
if self.expert:
|
|
592
|
-
self.builder.add_field('exif_path', FIELD_REL_PATH, 'Exif data path', required=False,
|
|
593
|
-
placeholder='relative to working path')
|
|
594
|
-
self.builder.add_field('prefix', FIELD_TEXT, 'Ouptut filename prefix', required=False,
|
|
595
|
-
default=constants.DEFAULT_STACK_PREFIX,
|
|
596
|
-
placeholder=constants.DEFAULT_STACK_PREFIX)
|
|
597
|
-
self.builder.add_field('plot_stack', FIELD_BOOL, 'Plot stack', required=False,
|
|
598
|
-
default=constants.DEFAULT_PLOT_STACK)
|
|
599
|
-
super().common_fields(layout)
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
class FocusStackBunchConfigurator(FocusStackBaseConfigurator):
|
|
603
|
-
def create_form(self, layout, action):
|
|
604
|
-
super().create_form(layout, action)
|
|
605
|
-
self.builder.add_field('frames', FIELD_INT, 'Frames', required=False,
|
|
606
|
-
default=constants.DEFAULT_FRAMES, min_val=1, max_val=100)
|
|
607
|
-
self.builder.add_field('overlap', FIELD_INT, 'Overlapping frames', required=False,
|
|
608
|
-
default=constants.DEFAULT_OVERLAP, min_val=0, max_val=100)
|
|
609
|
-
self.builder.add_field('plot_stack', FIELD_BOOL, 'Plot stack', required=False,
|
|
610
|
-
default=constants.DEFAULT_PLOT_STACK_BUNCH)
|
|
611
|
-
super().common_fields(layout)
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
class MultiLayerConfigurator(DefaultActionConfigurator):
|
|
615
|
-
def create_form(self, layout, action):
|
|
616
|
-
super().create_form(layout, action)
|
|
617
|
-
if self.expert:
|
|
618
|
-
self.builder.add_field('working_path', FIELD_ABS_PATH, 'Working path', required=False)
|
|
619
|
-
self.builder.add_field('input_path', FIELD_REL_PATH,
|
|
620
|
-
f'Input path (separate by {constants.PATH_SEPARATOR})',
|
|
621
|
-
required=False, multiple_entries=True,
|
|
622
|
-
placeholder='relative to working path')
|
|
623
|
-
if self.expert:
|
|
624
|
-
self.builder.add_field('output_path', FIELD_REL_PATH, 'Output path', required=False,
|
|
625
|
-
placeholder='relative to working path')
|
|
626
|
-
self.builder.add_field('exif_path', FIELD_REL_PATH, 'Exif data path', required=False,
|
|
627
|
-
placeholder='relative to working path')
|
|
628
|
-
self.builder.add_field('scratch_output_dir', FIELD_BOOL, 'Scratch output dir.',
|
|
629
|
-
required=False, default=True)
|
|
630
|
-
self.builder.add_field('reverse_order', FIELD_BOOL, 'Reverse file order',
|
|
631
|
-
required=False,
|
|
632
|
-
default=constants.DEFAULT_MULTILAYER_FILE_REVERSE_ORDER)
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
class CombinedActionsConfigurator(DefaultActionConfigurator):
|
|
636
|
-
def create_form(self, layout, action):
|
|
637
|
-
super().create_form(layout, action)
|
|
638
|
-
if self.expert:
|
|
639
|
-
self.builder.add_field('working_path', FIELD_ABS_PATH, 'Working path', required=False)
|
|
640
|
-
self.builder.add_field('input_path', FIELD_REL_PATH, 'Input path', required=False,
|
|
641
|
-
must_exist=True, placeholder='relative to working path')
|
|
642
|
-
self.builder.add_field('output_path', FIELD_REL_PATH, 'Output path', required=False,
|
|
643
|
-
placeholder='relative to working path')
|
|
644
|
-
self.builder.add_field('scratch_output_dir', FIELD_BOOL, 'Scratch output dir.',
|
|
645
|
-
required=False, default=True)
|
|
646
|
-
if self.expert:
|
|
647
|
-
self.builder.add_field('plot_path', FIELD_REL_PATH, 'Plots path', required=False,
|
|
648
|
-
default="plots", placeholder='relative to working path')
|
|
649
|
-
self.builder.add_field('resample', FIELD_INT, 'Resample frame stack', required=False,
|
|
650
|
-
default=1, min_val=1, max_val=100)
|
|
651
|
-
self.builder.add_field('ref_idx', FIELD_INT, 'Reference frame index', required=False,
|
|
652
|
-
default=-1, min_val=-1, max_val=1000)
|
|
653
|
-
self.builder.add_field('step_process', FIELD_BOOL, 'Step process', required=False,
|
|
654
|
-
default=True)
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
class MaskNoiseConfigurator(DefaultActionConfigurator):
|
|
658
|
-
def create_form(self, layout, action):
|
|
659
|
-
super().create_form(layout, action)
|
|
660
|
-
self.builder.add_field('noise_mask', FIELD_REL_PATH, 'Noise mask file', required=False,
|
|
661
|
-
path_type='file', must_exist=True,
|
|
662
|
-
default=constants.DEFAULT_NOISE_MAP_FILENAME,
|
|
663
|
-
placeholder=constants.DEFAULT_NOISE_MAP_FILENAME)
|
|
664
|
-
if self.expert:
|
|
665
|
-
self.builder.add_field('kernel_size', FIELD_INT, 'Kernel size', required=False,
|
|
666
|
-
default=constants.DEFAULT_MN_KERNEL_SIZE, min_va=1, max_val=10)
|
|
667
|
-
self.builder.add_field('method', FIELD_COMBO, 'Interpolation method', required=False,
|
|
668
|
-
options=['Mean', 'Median'], default='Mean')
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
class VignettingConfigurator(DefaultActionConfigurator):
|
|
672
|
-
def create_form(self, layout, action):
|
|
673
|
-
super().create_form(layout, action)
|
|
674
|
-
if self.expert:
|
|
675
|
-
self.builder.add_field('r_steps', FIELD_INT, 'Radial steps', required=False,
|
|
676
|
-
default=constants.DEFAULT_R_STEPS, min_val=1, max_val=1000)
|
|
677
|
-
self.builder.add_field('black_threshold', FIELD_INT, 'Black intensity threshold',
|
|
678
|
-
required=False, default=constants.DEFAULT_BLACK_THRESHOLD,
|
|
679
|
-
min_val=0, max_val=1000)
|
|
680
|
-
self.builder.add_field('max_correction', FIELD_FLOAT, 'Max. correction', required=False,
|
|
681
|
-
default=constants.DEFAULT_MAX_CORRECTION,
|
|
682
|
-
min_val=0, max_val=1, step=0.05)
|
|
683
|
-
self.add_bold_label("Miscellanea:")
|
|
684
|
-
self.builder.add_field('plot_correction', FIELD_BOOL, 'Plot correction', required=False,
|
|
685
|
-
default=False)
|
|
686
|
-
self.builder.add_field('plot_summary', FIELD_BOOL, 'Plot summary', required=False,
|
|
687
|
-
default=False)
|
|
688
|
-
self.builder.add_field('apply_correction', FIELD_BOOL, 'Apply correction', required=False,
|
|
689
|
-
default=True)
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
class AlignFramesConfigurator(DefaultActionConfigurator):
|
|
693
|
-
BORDER_MODE_OPTIONS = ['Constant', 'Replicate', 'Replicate and blur']
|
|
694
|
-
TRANSFORM_OPTIONS = ['Rigid', 'Homography']
|
|
695
|
-
METHOD_OPTIONS = ['Random Sample Consensus (RANSAC)', 'Least Median (LMEDS)']
|
|
696
|
-
MATCHING_METHOD_OPTIONS = ['K-nearest neighbors', 'Hamming distance']
|
|
697
|
-
|
|
698
|
-
def __init__(self, expert, current_wd):
|
|
699
|
-
super().__init__(expert, current_wd)
|
|
700
|
-
self.matching_method_field = None
|
|
701
|
-
self.info_label = None
|
|
702
|
-
self.detector_field = None
|
|
703
|
-
self.descriptor_field = None
|
|
704
|
-
self.matching_method_field = None
|
|
705
|
-
|
|
706
|
-
def show_info(self, message, timeout=3000):
|
|
707
|
-
self.info_label.setText(message)
|
|
708
|
-
self.info_label.setVisible(True)
|
|
709
|
-
timer = QTimer(self.info_label)
|
|
710
|
-
timer.setSingleShot(True)
|
|
711
|
-
timer.timeout.connect(self.info_label.hide)
|
|
712
|
-
timer.start(timeout)
|
|
713
|
-
|
|
714
|
-
def change_match_config(self):
|
|
715
|
-
detector = self.detector_field.currentText()
|
|
716
|
-
descriptor = self.descriptor_field.currentText()
|
|
717
|
-
match_method = dict(
|
|
718
|
-
zip(self.MATCHING_METHOD_OPTIONS,
|
|
719
|
-
constants.VALID_MATCHING_METHODS))[self.matching_method_field.currentText()]
|
|
720
|
-
try:
|
|
721
|
-
validate_align_config(detector, descriptor, match_method)
|
|
722
|
-
except Exception as e:
|
|
723
|
-
self.show_info(str(e))
|
|
724
|
-
if descriptor == constants.DETECTOR_SIFT and \
|
|
725
|
-
match_method == constants.MATCHING_NORM_HAMMING:
|
|
726
|
-
self.matching_method_field.setCurrentText(self.MATCHING_METHOD_OPTIONS[0])
|
|
727
|
-
if detector == constants.DETECTOR_ORB and descriptor == constants.DESCRIPTOR_AKAZE and \
|
|
728
|
-
match_method == constants.MATCHING_NORM_HAMMING:
|
|
729
|
-
self.matching_method_field.setCurrentText(constants.MATCHING_NORM_HAMMING)
|
|
730
|
-
if detector == constants.DETECTOR_BRISK and descriptor == constants.DESCRIPTOR_AKAZE:
|
|
731
|
-
self.descriptor_field.setCurrentText('BRISK')
|
|
732
|
-
if detector == constants.DETECTOR_SURF and descriptor == constants.DESCRIPTOR_AKAZE:
|
|
733
|
-
self.descriptor_field.setCurrentText('SIFT')
|
|
734
|
-
if detector == constants.DETECTOR_SIFT and descriptor != constants.DESCRIPTOR_SIFT:
|
|
735
|
-
self.descriptor_field.setCurrentText('SIFT')
|
|
736
|
-
if detector in constants.NOKNN_METHODS['detectors'] and \
|
|
737
|
-
descriptor in constants.NOKNN_METHODS['descriptors']:
|
|
738
|
-
if match_method == constants.MATCHING_KNN:
|
|
739
|
-
self.matching_method_field.setCurrentText(self.MATCHING_METHOD_OPTIONS[1])
|
|
740
|
-
|
|
741
|
-
def create_form(self, layout, action):
|
|
742
|
-
super().create_form(layout, action)
|
|
743
|
-
self.detector_field = None
|
|
744
|
-
self.descriptor_field = None
|
|
745
|
-
self.matching_method_field = None
|
|
746
|
-
if self.expert:
|
|
747
|
-
self.add_bold_label("Feature identification:")
|
|
748
|
-
|
|
749
|
-
self.info_label = QLabel()
|
|
750
|
-
self.info_label.setStyleSheet("color: orange; font-style: italic;")
|
|
751
|
-
self.info_label.setVisible(False)
|
|
752
|
-
layout.addRow(self.info_label)
|
|
753
|
-
|
|
754
|
-
self.detector_field = self.builder.add_field(
|
|
755
|
-
'detector', FIELD_COMBO, 'Detector', required=False,
|
|
756
|
-
options=constants.VALID_DETECTORS, default=constants.DEFAULT_DETECTOR)
|
|
757
|
-
self.descriptor_field = self.builder.add_field(
|
|
758
|
-
'descriptor', FIELD_COMBO, 'Descriptor', required=False,
|
|
759
|
-
options=constants.VALID_DESCRIPTORS, default=constants.DEFAULT_DESCRIPTOR)
|
|
760
|
-
|
|
761
|
-
self.add_bold_label("Feature matching:")
|
|
762
|
-
self.matching_method_field = self.builder.add_field(
|
|
763
|
-
'match_method', FIELD_COMBO, 'Match method', required=False,
|
|
764
|
-
options=self.MATCHING_METHOD_OPTIONS, values=constants.VALID_MATCHING_METHODS,
|
|
765
|
-
default=constants.DEFAULT_MATCHING_METHOD)
|
|
766
|
-
self.detector_field.setToolTip(
|
|
767
|
-
"SIFT: Requires SIFT descriptor and K-NN matching\n"
|
|
768
|
-
"ORB/AKAZE: Work best with Hamming distance"
|
|
769
|
-
)
|
|
770
|
-
|
|
771
|
-
self.descriptor_field.setToolTip(
|
|
772
|
-
"SIFT: Requires K-NN matching\n"
|
|
773
|
-
"ORB/AKAZE: Require Hamming distance with ORB/AKAZE detectors"
|
|
774
|
-
)
|
|
775
|
-
|
|
776
|
-
self.matching_method_field.setToolTip(
|
|
777
|
-
"Automatically selected based on detector/descriptor combination"
|
|
778
|
-
)
|
|
779
|
-
|
|
780
|
-
self.detector_field.currentIndexChanged.connect(self.change_match_config)
|
|
781
|
-
self.descriptor_field.currentIndexChanged.connect(self.change_match_config)
|
|
782
|
-
self.matching_method_field.currentIndexChanged.connect(self.change_match_config)
|
|
783
|
-
self.builder.add_field('flann_idx_kdtree', FIELD_INT, 'Flann idx kdtree',
|
|
784
|
-
required=False,
|
|
785
|
-
default=constants.DEFAULT_FLANN_IDX_KDTREE,
|
|
786
|
-
min_val=0, max_val=10)
|
|
787
|
-
self.builder.add_field('flann_trees', FIELD_INT, 'Flann trees', required=False,
|
|
788
|
-
default=constants.DEFAULT_FLANN_TREES,
|
|
789
|
-
min_val=0, max_val=10)
|
|
790
|
-
self.builder.add_field('flann_checks', FIELD_INT, 'Flann checks', required=False,
|
|
791
|
-
default=constants.DEFAULT_FLANN_CHECKS,
|
|
792
|
-
min_val=0, max_val=1000)
|
|
793
|
-
self.builder.add_field('threshold', FIELD_FLOAT, 'Threshold', required=False,
|
|
794
|
-
default=constants.DEFAULT_ALIGN_THRESHOLD,
|
|
795
|
-
min_val=0, max_val=1, step=0.05)
|
|
796
|
-
|
|
797
|
-
self.add_bold_label("Transform:")
|
|
798
|
-
transform = self.builder.add_field(
|
|
799
|
-
'transform', FIELD_COMBO, 'Transform', required=False,
|
|
800
|
-
options=self.TRANSFORM_OPTIONS, values=constants.VALID_TRANSFORMS,
|
|
801
|
-
default=constants.DEFAULT_TRANSFORM)
|
|
802
|
-
method = self.builder.add_field(
|
|
803
|
-
'align_method', FIELD_COMBO, 'Align method', required=False,
|
|
804
|
-
options=self.METHOD_OPTIONS, values=constants.VALID_ALIGN_METHODS,
|
|
805
|
-
default=constants.DEFAULT_ALIGN_METHOD)
|
|
806
|
-
rans_threshold = self.builder.add_field(
|
|
807
|
-
'rans_threshold', FIELD_FLOAT, 'RANSAC threshold (px)', required=False,
|
|
808
|
-
default=constants.DEFAULT_RANS_THRESHOLD, min_val=0, max_val=20, step=0.1)
|
|
809
|
-
self.builder.add_field(
|
|
810
|
-
'min_good_matches', FIELD_INT, "Min. good matches", required=False,
|
|
811
|
-
default=constants.DEFAULT_ALIGN_MIN_GOOD_MATCHES, min_val=0, max_val=500)
|
|
812
|
-
|
|
813
|
-
def change_method():
|
|
814
|
-
text = method.currentText()
|
|
815
|
-
if text == self.METHOD_OPTIONS[0]:
|
|
816
|
-
rans_threshold.setEnabled(True)
|
|
817
|
-
elif text == self.METHOD_OPTIONS[1]:
|
|
818
|
-
rans_threshold.setEnabled(False)
|
|
819
|
-
method.currentIndexChanged.connect(change_method)
|
|
820
|
-
change_method()
|
|
821
|
-
self.builder.add_field('align_confidence', FIELD_FLOAT, 'Confidence (%)',
|
|
822
|
-
required=False, decimals=1,
|
|
823
|
-
default=constants.DEFAULT_ALIGN_CONFIDENCE,
|
|
824
|
-
min_val=70.0, max_val=100.0, step=0.1)
|
|
825
|
-
|
|
826
|
-
refine_iters = self.builder.add_field(
|
|
827
|
-
'refine_iters', FIELD_INT, 'Refinement iterations (Rigid)', required=False,
|
|
828
|
-
default=constants.DEFAULT_REFINE_ITERS, min_val=0, max_val=1000)
|
|
829
|
-
max_iters = self.builder.add_field(
|
|
830
|
-
'max_iters', FIELD_INT, 'Max. iterations (Homography)', required=False,
|
|
831
|
-
default=constants.DEFAULT_ALIGN_MAX_ITERS, min_val=0, max_val=5000)
|
|
832
|
-
|
|
833
|
-
def change_transform():
|
|
834
|
-
text = transform.currentText()
|
|
835
|
-
if text == self.TRANSFORM_OPTIONS[0]:
|
|
836
|
-
refine_iters.setEnabled(True)
|
|
837
|
-
max_iters.setEnabled(False)
|
|
838
|
-
elif text == self.TRANSFORM_OPTIONS[1]:
|
|
839
|
-
refine_iters.setEnabled(False)
|
|
840
|
-
max_iters.setEnabled(True)
|
|
841
|
-
transform.currentIndexChanged.connect(change_transform)
|
|
842
|
-
change_transform()
|
|
843
|
-
subsample = self.builder.add_field(
|
|
844
|
-
'subsample', FIELD_INT, 'Subsample factor', required=False,
|
|
845
|
-
default=constants.DEFAULT_ALIGN_SUBSAMPLE, min_val=1, max_val=256)
|
|
846
|
-
fast_subsampling = self.builder.add_field(
|
|
847
|
-
'fast_subsampling', FIELD_BOOL, 'Fast subsampling', required=False,
|
|
848
|
-
default=constants.DEFAULT_ALIGN_FAST_SUBSAMPLING)
|
|
849
|
-
|
|
850
|
-
def change_subsample():
|
|
851
|
-
fast_subsampling.setEnabled(subsample.value() > 1)
|
|
852
|
-
subsample.valueChanged.connect(change_subsample)
|
|
853
|
-
change_subsample()
|
|
854
|
-
self.add_bold_label("Border:")
|
|
855
|
-
self.builder.add_field('border_mode', FIELD_COMBO, 'Border mode', required=False,
|
|
856
|
-
options=self.BORDER_MODE_OPTIONS,
|
|
857
|
-
values=constants.VALID_BORDER_MODES,
|
|
858
|
-
default=constants.DEFAULT_BORDER_MODE)
|
|
859
|
-
self.builder.add_field('border_value', FIELD_INT_TUPLE,
|
|
860
|
-
'Border value (if constant)', required=False, size=4,
|
|
861
|
-
default=constants.DEFAULT_BORDER_VALUE,
|
|
862
|
-
labels=constants.RGBA_LABELS,
|
|
863
|
-
min_val=constants.DEFAULT_BORDER_VALUE, max_val=[255] * 4)
|
|
864
|
-
self.builder.add_field('border_blur', FIELD_FLOAT, 'Border blur', required=False,
|
|
865
|
-
default=constants.DEFAULT_BORDER_BLUR,
|
|
866
|
-
min_val=0, max_val=1000, step=1)
|
|
867
|
-
self.add_bold_label("Miscellanea:")
|
|
868
|
-
self.builder.add_field('plot_summary', FIELD_BOOL, 'Plot summary',
|
|
869
|
-
required=False, default=False)
|
|
870
|
-
self.builder.add_field('plot_matches', FIELD_BOOL, 'Plot matches',
|
|
871
|
-
required=False, default=False)
|
|
872
|
-
|
|
873
|
-
def update_params(self, params: Dict[str, Any]) -> bool:
|
|
874
|
-
if self.detector_field and self.descriptor_field and self.matching_method_field:
|
|
875
|
-
try:
|
|
876
|
-
detector = self.detector_field.currentText()
|
|
877
|
-
descriptor = self.descriptor_field.currentText()
|
|
878
|
-
match_method = dict(
|
|
879
|
-
zip(self.MATCHING_METHOD_OPTIONS,
|
|
880
|
-
constants.VALID_MATCHING_METHODS))[
|
|
881
|
-
self.matching_method_field.currentText()]
|
|
882
|
-
validate_align_config(detector, descriptor, match_method)
|
|
883
|
-
return super().update_params(params)
|
|
884
|
-
except Exception as e:
|
|
885
|
-
traceback.print_tb(e.__traceback__)
|
|
886
|
-
QMessageBox.warning(None, "Error", f"{str(e)}")
|
|
887
|
-
return False
|
|
888
|
-
return super().update_params(params)
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
class BalanceFramesConfigurator(DefaultActionConfigurator):
|
|
892
|
-
CORRECTION_MAP_OPTIONS = ['Linear', 'Gamma', 'Match histograms']
|
|
893
|
-
CHANNEL_OPTIONS = ['Luminosity', 'RGB', 'HSV', 'HLS']
|
|
894
|
-
|
|
895
|
-
def create_form(self, layout, action):
|
|
896
|
-
super().create_form(layout, action)
|
|
897
|
-
if self.expert:
|
|
898
|
-
self.builder.add_field('mask_size', FIELD_FLOAT, 'Mask size', required=False,
|
|
899
|
-
default=0, min_val=0, max_val=5, step=0.1)
|
|
900
|
-
self.builder.add_field('intensity_interval', FIELD_INT_TUPLE, 'Intensity range',
|
|
901
|
-
required=False, size=2,
|
|
902
|
-
default=[v for k, v in
|
|
903
|
-
constants.DEFAULT_INTENSITY_INTERVAL.items()],
|
|
904
|
-
labels=['min', 'max'], min_val=[-1] * 2, max_val=[65536] * 2)
|
|
905
|
-
self.builder.add_field('subsample', FIELD_INT, 'Subsample factor', required=False,
|
|
906
|
-
default=constants.DEFAULT_BALANCE_SUBSAMPLE,
|
|
907
|
-
min_val=1, max_val=256)
|
|
908
|
-
self.builder.add_field('corr_map', FIELD_COMBO, 'Correction map', required=False,
|
|
909
|
-
options=self.CORRECTION_MAP_OPTIONS, values=constants.VALID_BALANCE,
|
|
910
|
-
default='Linear')
|
|
911
|
-
self.builder.add_field('channel', FIELD_COMBO, 'Channel', required=False,
|
|
912
|
-
options=self.CHANNEL_OPTIONS,
|
|
913
|
-
values=constants.VALID_BALANCE_CHANNELS,
|
|
914
|
-
default='Luminosity')
|
|
915
|
-
self.add_bold_label("Miscellanea:")
|
|
916
|
-
self.builder.add_field('plot_summary', FIELD_BOOL, 'Plot summary',
|
|
917
|
-
required=False, default=False)
|
|
918
|
-
self.builder.add_field('plot_histograms', FIELD_BOOL, 'Plot histograms',
|
|
919
|
-
required=False, default=False)
|