shinestacker 1.1.0__py3-none-any.whl → 1.2.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of shinestacker might be problematic. Click here for more details.
- shinestacker/_version.py +1 -1
- shinestacker/algorithms/__init__.py +4 -1
- shinestacker/algorithms/align.py +149 -34
- shinestacker/algorithms/balance.py +364 -166
- shinestacker/algorithms/base_stack_algo.py +6 -0
- shinestacker/algorithms/depth_map.py +1 -1
- shinestacker/algorithms/multilayer.py +22 -13
- shinestacker/algorithms/noise_detection.py +7 -8
- shinestacker/algorithms/pyramid.py +3 -2
- shinestacker/algorithms/pyramid_auto.py +141 -0
- shinestacker/algorithms/pyramid_tiles.py +199 -44
- shinestacker/algorithms/stack.py +20 -20
- shinestacker/algorithms/stack_framework.py +136 -156
- shinestacker/algorithms/utils.py +175 -1
- shinestacker/algorithms/vignetting.py +26 -8
- shinestacker/config/constants.py +31 -6
- shinestacker/core/framework.py +12 -12
- shinestacker/gui/action_config.py +59 -7
- shinestacker/gui/action_config_dialog.py +427 -283
- shinestacker/gui/base_form_dialog.py +11 -6
- shinestacker/gui/gui_images.py +10 -10
- shinestacker/gui/gui_run.py +1 -1
- shinestacker/gui/main_window.py +6 -5
- shinestacker/gui/menu_manager.py +16 -2
- shinestacker/gui/new_project.py +26 -22
- shinestacker/gui/project_controller.py +43 -27
- shinestacker/gui/project_converter.py +2 -8
- shinestacker/gui/project_editor.py +50 -27
- shinestacker/gui/tab_widget.py +3 -3
- shinestacker/retouch/exif_data.py +5 -5
- shinestacker/retouch/shortcuts_help.py +4 -4
- shinestacker/retouch/vignetting_filter.py +12 -8
- {shinestacker-1.1.0.dist-info → shinestacker-1.2.1.dist-info}/METADATA +1 -1
- {shinestacker-1.1.0.dist-info → shinestacker-1.2.1.dist-info}/RECORD +38 -37
- {shinestacker-1.1.0.dist-info → shinestacker-1.2.1.dist-info}/WHEEL +0 -0
- {shinestacker-1.1.0.dist-info → shinestacker-1.2.1.dist-info}/entry_points.txt +0 -0
- {shinestacker-1.1.0.dist-info → shinestacker-1.2.1.dist-info}/licenses/LICENSE +0 -0
- {shinestacker-1.1.0.dist-info → shinestacker-1.2.1.dist-info}/top_level.txt +0 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, R0902, E1101, W0718, W0640, R0913, R0917, R0914
|
|
2
|
+
import math
|
|
2
3
|
import traceback
|
|
3
4
|
import logging
|
|
4
5
|
import numpy as np
|
|
@@ -49,16 +50,30 @@ def fit_sigmoid(radii, intensities):
|
|
|
49
50
|
return res
|
|
50
51
|
|
|
51
52
|
|
|
53
|
+
def subsample_factor(subsample, image):
|
|
54
|
+
if subsample == 0:
|
|
55
|
+
h, w = image.shape[:2]
|
|
56
|
+
img_res = (float(h) / 1000) * (float(w) / 1000)
|
|
57
|
+
target_res = constants.DEFAULT_BALANCE_RES_TARGET_MPX
|
|
58
|
+
subsample = int(1 + math.floor(img_res / target_res))
|
|
59
|
+
return subsample
|
|
60
|
+
|
|
61
|
+
|
|
52
62
|
def img_subsampled(image, subsample=constants.DEFAULT_VIGN_SUBSAMPLE,
|
|
53
63
|
fast_subsampling=constants.DEFAULT_VIGN_FAST_SUBSAMPLING):
|
|
54
64
|
image_bw = cv2.cvtColor(img_8bit(image), cv2.COLOR_BGR2GRAY)
|
|
55
|
-
|
|
65
|
+
if subsample == 0:
|
|
66
|
+
subsample = subsample_factor(subsample, image)
|
|
67
|
+
img_sub = image_bw if subsample == 1 else img_subsample(image_bw, subsample, fast_subsampling)
|
|
68
|
+
return img_sub
|
|
56
69
|
|
|
57
70
|
|
|
58
71
|
def compute_fit_parameters(
|
|
59
72
|
image, r_steps, radii=None, intensities=None,
|
|
60
73
|
subsample=constants.DEFAULT_VIGN_SUBSAMPLE,
|
|
61
74
|
fast_subsampling=constants.DEFAULT_VIGN_FAST_SUBSAMPLING):
|
|
75
|
+
if subsample == 0:
|
|
76
|
+
subsample = subsample_factor(subsample, image)
|
|
62
77
|
image_sub = img_subsampled(image, subsample, fast_subsampling)
|
|
63
78
|
if radii is None and intensities is None:
|
|
64
79
|
radii, intensities = radial_mean_intensity(image_sub, r_steps)
|
|
@@ -77,6 +92,8 @@ def correct_vignetting(
|
|
|
77
92
|
if params is None:
|
|
78
93
|
if r_steps is None:
|
|
79
94
|
raise RuntimeError("Either r_steps or pars must not be None")
|
|
95
|
+
if subsample == 0:
|
|
96
|
+
subsample = subsample_factor(subsample, image)
|
|
80
97
|
params = compute_fit_parameters(
|
|
81
98
|
image, r_steps, subsample=subsample, fast_subsampling=fast_subsampling)
|
|
82
99
|
if v0 is None:
|
|
@@ -121,11 +138,12 @@ class Vignetting(SubAction):
|
|
|
121
138
|
h, w = img_0.shape[:2]
|
|
122
139
|
self.w_2, self.h_2 = w / 2, h / 2
|
|
123
140
|
self.r_max = np.sqrt((w / 2)**2 + (h / 2)**2)
|
|
124
|
-
|
|
141
|
+
subsample = subsample_factor(self.subsample, img_0)
|
|
142
|
+
image_sub = img_subsampled(img_0, subsample, self.fast_subsampling)
|
|
125
143
|
radii, intensities = radial_mean_intensity(image_sub, self.r_steps)
|
|
126
144
|
try:
|
|
127
145
|
params = compute_fit_parameters(
|
|
128
|
-
img_0, self.r_steps, radii, intensities,
|
|
146
|
+
img_0, self.r_steps, radii, intensities, subsample, self.fast_subsampling)
|
|
129
147
|
except Exception as e:
|
|
130
148
|
traceback.print_tb(e.__traceback__)
|
|
131
149
|
self.process.sub_message(
|
|
@@ -142,9 +160,9 @@ class Vignetting(SubAction):
|
|
|
142
160
|
"light_blue"),
|
|
143
161
|
level=logging.DEBUG)
|
|
144
162
|
if self.plot_correction:
|
|
145
|
-
plt.figure(figsize=
|
|
163
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
146
164
|
plt.plot(radii, intensities, label="image mean intensity")
|
|
147
|
-
plt.plot(radii, sigmoid_model(radii *
|
|
165
|
+
plt.plot(radii, sigmoid_model(radii * subsample, *params), label="sigmoid fit")
|
|
148
166
|
plt.xlabel('radius (pixels)')
|
|
149
167
|
plt.ylabel('mean intensity')
|
|
150
168
|
plt.legend()
|
|
@@ -165,16 +183,16 @@ class Vignetting(SubAction):
|
|
|
165
183
|
self.process.sub_message_r(color_str(": correct vignetting", "cyan"))
|
|
166
184
|
return correct_vignetting(
|
|
167
185
|
img_0, self.max_correction, self.black_threshold, None, params, self.v0,
|
|
168
|
-
|
|
186
|
+
subsample, self.fast_subsampling)
|
|
169
187
|
|
|
170
188
|
def begin(self, process):
|
|
171
189
|
self.process = process
|
|
172
|
-
self.corrections = [np.full(self.process.
|
|
190
|
+
self.corrections = [np.full(self.process.total_action_counts, None, dtype=float)
|
|
173
191
|
for p in self.percentiles]
|
|
174
192
|
|
|
175
193
|
def end(self):
|
|
176
194
|
if self.plot_summary:
|
|
177
|
-
plt.figure(figsize=
|
|
195
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
178
196
|
xs = np.arange(1, len(self.corrections[0]) + 1, dtype=int)
|
|
179
197
|
for i, p in enumerate(self.percentiles):
|
|
180
198
|
linestyle = 'solid'
|
shinestacker/config/constants.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# pylint: disable=C0114, C0115, C0116, C0103, R0903
|
|
2
2
|
import sys
|
|
3
3
|
import re
|
|
4
|
+
import os
|
|
4
5
|
|
|
5
6
|
|
|
6
7
|
class _Constants:
|
|
@@ -12,12 +13,17 @@ class _Constants:
|
|
|
12
13
|
NUM_UINT16 = 65536
|
|
13
14
|
MAX_UINT8 = 255
|
|
14
15
|
MAX_UINT16 = 65535
|
|
16
|
+
ONE_KILO = 1024
|
|
17
|
+
ONE_MEGA = ONE_KILO**2
|
|
18
|
+
ONE_GIGA = ONE_KILO**3
|
|
15
19
|
|
|
16
20
|
LOG_FONTS = ['Monaco', 'Menlo', ' Lucida Console', 'Courier New', 'Courier', 'monospace']
|
|
17
21
|
LOG_FONTS_STR = ", ".join(LOG_FONTS)
|
|
18
22
|
|
|
19
23
|
ANSI_ESCAPE = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
|
|
20
24
|
|
|
25
|
+
PLT_FIG_SIZE = (10, 5)
|
|
26
|
+
|
|
21
27
|
ACTION_JOB = "Job"
|
|
22
28
|
ACTION_COMBO = "CombinedActions"
|
|
23
29
|
ACTION_NOISEDETECTION = "NoiseDetection"
|
|
@@ -34,12 +40,17 @@ class _Constants:
|
|
|
34
40
|
SUB_ACTION_TYPES = [ACTION_MASKNOISE, ACTION_VIGNETTING, ACTION_ALIGNFRAMES,
|
|
35
41
|
ACTION_BALANCEFRAMES]
|
|
36
42
|
STACK_ALGO_PYRAMID = 'Pyramid'
|
|
37
|
-
STACK_ALGO_PYRAMID_TILES = 'Pyramid Tiles'
|
|
38
43
|
STACK_ALGO_DEPTH_MAP = 'Depth map'
|
|
39
|
-
STACK_ALGO_OPTIONS = [STACK_ALGO_PYRAMID,
|
|
44
|
+
STACK_ALGO_OPTIONS = [STACK_ALGO_PYRAMID, STACK_ALGO_DEPTH_MAP]
|
|
40
45
|
STACK_ALGO_DEFAULT = STACK_ALGO_PYRAMID
|
|
41
46
|
DEFAULT_PLOTS_PATH = 'plots'
|
|
42
47
|
|
|
48
|
+
FIELD_SUBSAMPLE_VALUES_1 = [2, 3, 4, 6, 8, 12, 16, 24, 32]
|
|
49
|
+
FIELD_SUBSAMPLE_OPTIONS_1 = [f"1/{n} × 1/{n}" for n in FIELD_SUBSAMPLE_VALUES_1]
|
|
50
|
+
FIELD_SUBSAMPLE_VALUES = [0, 1] + FIELD_SUBSAMPLE_VALUES_1
|
|
51
|
+
FIELD_SUBSAMPLE_OPTIONS = ['Auto', 'Full resolution'] + FIELD_SUBSAMPLE_OPTIONS_1
|
|
52
|
+
FIELD_SUBSAMPLE_DEFAULT = FIELD_SUBSAMPLE_VALUES[0]
|
|
53
|
+
|
|
43
54
|
PATH_SEPARATOR = ';'
|
|
44
55
|
|
|
45
56
|
LOG_COLOR_ALERT = 'red'
|
|
@@ -51,8 +62,10 @@ class _Constants:
|
|
|
51
62
|
|
|
52
63
|
DEFAULT_FILE_REVERSE_ORDER = False
|
|
53
64
|
DEFAULT_MULTILAYER_FILE_REVERSE_ORDER = True
|
|
65
|
+
MULTILAYER_WARNING_MEM_GB = 1
|
|
54
66
|
|
|
55
67
|
DEFAULT_NOISE_MAP_FILENAME = "noise-map/hot_pixels.png"
|
|
68
|
+
DEFAULT_NOISE_MAX_FRAMES = 10
|
|
56
69
|
DEFAULT_MN_KERNEL_SIZE = 3
|
|
57
70
|
INTERPOLATE_MEAN = 'MEAN'
|
|
58
71
|
INTERPOLATE_MEDIAN = 'MEDIAN'
|
|
@@ -105,9 +118,11 @@ class _Constants:
|
|
|
105
118
|
DEFAULT_REFINE_ITERS = 100
|
|
106
119
|
DEFAULT_ALIGN_CONFIDENCE = 99.9
|
|
107
120
|
DEFAULT_ALIGN_MAX_ITERS = 2000
|
|
121
|
+
DEFAULT_ALIGN_ABORT_ABNORMAL = False
|
|
108
122
|
DEFAULT_BORDER_VALUE = [0] * 4
|
|
109
123
|
DEFAULT_BORDER_BLUR = 50
|
|
110
|
-
DEFAULT_ALIGN_SUBSAMPLE =
|
|
124
|
+
DEFAULT_ALIGN_SUBSAMPLE = 0
|
|
125
|
+
DEFAULT_ALIGN_RES_TARGET_MPX = 2
|
|
111
126
|
DEFAULT_ALIGN_FAST_SUBSAMPLING = False
|
|
112
127
|
DEFAULT_ALIGN_MIN_GOOD_MATCHES = 50
|
|
113
128
|
|
|
@@ -120,9 +135,12 @@ class _Constants:
|
|
|
120
135
|
BALANCE_RGB = "RGB"
|
|
121
136
|
BALANCE_HSV = "HSV"
|
|
122
137
|
BALANCE_HLS = "HLS"
|
|
123
|
-
|
|
138
|
+
BALANCE_LAB = "LAB"
|
|
139
|
+
VALID_BALANCE_CHANNELS = [BALANCE_LUMI, BALANCE_RGB, BALANCE_HSV, BALANCE_HLS,
|
|
140
|
+
BALANCE_LAB]
|
|
124
141
|
|
|
125
|
-
DEFAULT_BALANCE_SUBSAMPLE =
|
|
142
|
+
DEFAULT_BALANCE_SUBSAMPLE = 0
|
|
143
|
+
DEFAULT_BALANCE_RES_TARGET_MPX = 2
|
|
126
144
|
DEFAULT_BALANCE_FAST_SUBSAMPLING = False
|
|
127
145
|
DEFAULT_CORR_MAP = BALANCE_LINEAR
|
|
128
146
|
DEFAULT_CHANNEL = BALANCE_LUMI
|
|
@@ -131,7 +149,8 @@ class _Constants:
|
|
|
131
149
|
DEFAULT_R_STEPS = 100
|
|
132
150
|
DEFAULT_BLACK_THRESHOLD = 1.0
|
|
133
151
|
DEFAULT_MAX_CORRECTION = 1
|
|
134
|
-
DEFAULT_VIGN_SUBSAMPLE =
|
|
152
|
+
DEFAULT_VIGN_SUBSAMPLE = 0
|
|
153
|
+
DEFAULT_VIGN_RES_TARGET_MPX = 2
|
|
135
154
|
DEFAULT_VIGN_FAST_SUBSAMPLING = False
|
|
136
155
|
|
|
137
156
|
FLOAT_32 = 'float-32'
|
|
@@ -163,6 +182,12 @@ class _Constants:
|
|
|
163
182
|
DEFAULT_PY_KERNEL_SIZE = 5
|
|
164
183
|
DEFAULT_PY_GEN_KERNEL = 0.4
|
|
165
184
|
DEFAULT_PY_TILE_SIZE = 512
|
|
185
|
+
DEFAULT_PY_N_TILED_LAYERS = 2
|
|
186
|
+
DEFAULT_PY_MEMORY_LIMIT_GB = 8
|
|
187
|
+
DEFAULT_PY_MAX_THREADS = min(os.cpu_count() or 4, 8)
|
|
188
|
+
DEFAULT_PY_MODE = 'auto'
|
|
189
|
+
PY_VALID_MODES = ['auto', 'memory', 'tiled']
|
|
190
|
+
MIN_PY_TILE_SIZE = 256
|
|
166
191
|
|
|
167
192
|
DEFAULT_PLOT_STACK_BUNCH = False
|
|
168
193
|
DEFAULT_PLOT_STACK = True
|
shinestacker/core/framework.py
CHANGED
|
@@ -24,7 +24,7 @@ class TqdmCallbacks:
|
|
|
24
24
|
|
|
25
25
|
def __init__(self):
|
|
26
26
|
self.tbar = None
|
|
27
|
-
self.
|
|
27
|
+
self.total_action_counts = -1
|
|
28
28
|
|
|
29
29
|
@classmethod
|
|
30
30
|
def instance(cls):
|
|
@@ -33,8 +33,8 @@ class TqdmCallbacks:
|
|
|
33
33
|
return cls._instance
|
|
34
34
|
|
|
35
35
|
def step_counts(self, name, counts):
|
|
36
|
-
self.
|
|
37
|
-
self.tbar = make_tqdm_bar(name, self.
|
|
36
|
+
self.total_action_counts = counts
|
|
37
|
+
self.tbar = make_tqdm_bar(name, self.total_action_counts)
|
|
38
38
|
|
|
39
39
|
def begin_steps(self, name):
|
|
40
40
|
pass
|
|
@@ -191,12 +191,12 @@ class Job(JobBase):
|
|
|
191
191
|
class ActionList(JobBase):
|
|
192
192
|
def __init__(self, name, enabled=True, **kwargs):
|
|
193
193
|
JobBase.__init__(self, name, enabled, **kwargs)
|
|
194
|
-
self.
|
|
195
|
-
self.
|
|
194
|
+
self.total_action_counts = None
|
|
195
|
+
self.current_action_count = None
|
|
196
196
|
|
|
197
197
|
def set_counts(self, counts):
|
|
198
|
-
self.
|
|
199
|
-
self.callback('step_counts', self.id, self.name, self.
|
|
198
|
+
self.total_action_counts = counts
|
|
199
|
+
self.callback('step_counts', self.id, self.name, self.total_action_counts)
|
|
200
200
|
|
|
201
201
|
def begin(self):
|
|
202
202
|
self.callback('begin_steps', self.id, self.name)
|
|
@@ -205,17 +205,17 @@ class ActionList(JobBase):
|
|
|
205
205
|
self.callback('end_steps', self.id, self.name)
|
|
206
206
|
|
|
207
207
|
def __iter__(self):
|
|
208
|
-
self.
|
|
208
|
+
self.current_action_count = 0
|
|
209
209
|
return self
|
|
210
210
|
|
|
211
211
|
def run_step(self):
|
|
212
212
|
pass
|
|
213
213
|
|
|
214
214
|
def __next__(self):
|
|
215
|
-
if self.
|
|
215
|
+
if self.current_action_count < self.total_action_counts:
|
|
216
216
|
self.run_step()
|
|
217
|
-
x = self.
|
|
218
|
-
self.
|
|
217
|
+
x = self.current_action_count
|
|
218
|
+
self.current_action_count += 1
|
|
219
219
|
return x
|
|
220
220
|
raise StopIteration
|
|
221
221
|
|
|
@@ -223,7 +223,7 @@ class ActionList(JobBase):
|
|
|
223
223
|
self.print_message(color_str('begin run', constants.LOG_COLOR_LEVEL_2), end='\n')
|
|
224
224
|
self.begin()
|
|
225
225
|
for _ in iter(self):
|
|
226
|
-
self.callback('after_step', self.id, self.name, self.
|
|
226
|
+
self.callback('after_step', self.id, self.name, self.current_action_count)
|
|
227
227
|
if self.callback('check_running', self.id, self.name) is False:
|
|
228
228
|
raise RunStopException(self.name)
|
|
229
229
|
self.end()
|
|
@@ -10,19 +10,22 @@ from PySide6.QtWidgets import (QPushButton, QHBoxLayout, QFileDialog, QLabel, QC
|
|
|
10
10
|
from .. config.constants import constants
|
|
11
11
|
from .select_path_widget import (create_select_file_paths_widget, create_layout_widget_no_margins,
|
|
12
12
|
create_layout_widget_and_connect)
|
|
13
|
-
from .project_model import ActionConfig
|
|
14
13
|
|
|
15
14
|
FIELD_TEXT = 'text'
|
|
16
15
|
FIELD_ABS_PATH = 'abs_path'
|
|
17
16
|
FIELD_REL_PATH = 'rel_path'
|
|
18
17
|
FIELD_FLOAT = 'float'
|
|
19
18
|
FIELD_INT = 'int'
|
|
19
|
+
FIELD_REF_IDX = 'ref_idx'
|
|
20
20
|
FIELD_INT_TUPLE = 'int_tuple'
|
|
21
21
|
FIELD_BOOL = 'bool'
|
|
22
22
|
FIELD_COMBO = 'combo'
|
|
23
23
|
FIELD_TYPES = [FIELD_TEXT, FIELD_ABS_PATH, FIELD_REL_PATH, FIELD_FLOAT,
|
|
24
24
|
FIELD_INT, FIELD_INT_TUPLE, FIELD_BOOL, FIELD_COMBO]
|
|
25
25
|
|
|
26
|
+
FIELD_REF_IDX_OPTIONS = ['Median frame', 'First frame', 'Last frame', 'Specify index']
|
|
27
|
+
FIELD_REF_IDX_MAX = 1000
|
|
28
|
+
|
|
26
29
|
|
|
27
30
|
class ActionConfigurator(ABC):
|
|
28
31
|
def __init__(self, expert, current_wd):
|
|
@@ -30,23 +33,23 @@ class ActionConfigurator(ABC):
|
|
|
30
33
|
self.current_wd = current_wd
|
|
31
34
|
|
|
32
35
|
@abstractmethod
|
|
33
|
-
def create_form(self, layout, action
|
|
36
|
+
def create_form(self, layout, action, tag="Action"):
|
|
34
37
|
pass
|
|
35
38
|
|
|
36
39
|
@abstractmethod
|
|
37
|
-
def update_params(self, params
|
|
40
|
+
def update_params(self, params):
|
|
38
41
|
pass
|
|
39
42
|
|
|
40
43
|
|
|
41
44
|
class FieldBuilder:
|
|
42
45
|
def __init__(self, layout, action, current_wd):
|
|
43
|
-
self.
|
|
46
|
+
self.main_layout = layout
|
|
44
47
|
self.action = action
|
|
45
48
|
self.current_wd = current_wd
|
|
46
49
|
self.fields = {}
|
|
47
50
|
|
|
48
|
-
def add_field(self, tag
|
|
49
|
-
required
|
|
51
|
+
def add_field(self, tag, field_type, label,
|
|
52
|
+
required=False, add_to_layout=None, **kwargs):
|
|
50
53
|
if field_type == FIELD_TEXT:
|
|
51
54
|
widget = self.create_text_field(tag, **kwargs)
|
|
52
55
|
elif field_type == FIELD_ABS_PATH:
|
|
@@ -57,6 +60,8 @@ class FieldBuilder:
|
|
|
57
60
|
widget = self.create_float_field(tag, **kwargs)
|
|
58
61
|
elif field_type == FIELD_INT:
|
|
59
62
|
widget = self.create_int_field(tag, **kwargs)
|
|
63
|
+
elif field_type == FIELD_REF_IDX:
|
|
64
|
+
widget = self.create_ref_idx_field(tag, **kwargs)
|
|
60
65
|
elif field_type == FIELD_INT_TUPLE:
|
|
61
66
|
widget = self.create_int_tuple_field(tag, **kwargs)
|
|
62
67
|
elif field_type == FIELD_BOOL:
|
|
@@ -76,6 +81,8 @@ class FieldBuilder:
|
|
|
76
81
|
default_value = kwargs.get('default', 0.0)
|
|
77
82
|
elif field_type == FIELD_INT:
|
|
78
83
|
default_value = kwargs.get('default', 0)
|
|
84
|
+
elif field_type == FIELD_REF_IDX:
|
|
85
|
+
default_value = kwargs.get('default', 0)
|
|
79
86
|
elif field_type == FIELD_INT_TUPLE:
|
|
80
87
|
default_value = kwargs.get('default', [0] * kwargs.get('size', 1))
|
|
81
88
|
elif field_type == FIELD_BOOL:
|
|
@@ -93,7 +100,7 @@ class FieldBuilder:
|
|
|
93
100
|
**kwargs
|
|
94
101
|
}
|
|
95
102
|
if add_to_layout is None:
|
|
96
|
-
add_to_layout = self.
|
|
103
|
+
add_to_layout = self.main_layout
|
|
97
104
|
add_to_layout.addRow(f"{label}:", widget)
|
|
98
105
|
return widget
|
|
99
106
|
|
|
@@ -113,6 +120,9 @@ class FieldBuilder:
|
|
|
113
120
|
widget.setChecked(default)
|
|
114
121
|
elif field['type'] == FIELD_INT:
|
|
115
122
|
widget.setValue(default)
|
|
123
|
+
elif field['type'] == FIELD_REF_IDX:
|
|
124
|
+
widget.layout().itemAt(2).widget().setValue(default)
|
|
125
|
+
widget.layout().itemAt(0).widget().setCurrentText(FIELD_REF_IDX_OPTIONS[0])
|
|
116
126
|
elif field['type'] == FIELD_INT_TUPLE:
|
|
117
127
|
for i in range(field['size']):
|
|
118
128
|
spinbox = widget.layout().itemAt(1 + i * 2).widget()
|
|
@@ -147,6 +157,17 @@ class FieldBuilder:
|
|
|
147
157
|
params[tag] = field['widget'].isChecked()
|
|
148
158
|
elif field['type'] == FIELD_INT:
|
|
149
159
|
params[tag] = field['widget'].value()
|
|
160
|
+
elif field['type'] == FIELD_REF_IDX:
|
|
161
|
+
wl = field['widget'].layout()
|
|
162
|
+
txt = wl.itemAt(0).widget().currentText()
|
|
163
|
+
if txt == FIELD_REF_IDX_OPTIONS[0]:
|
|
164
|
+
params[tag] = 0
|
|
165
|
+
elif txt == FIELD_REF_IDX_OPTIONS[1]:
|
|
166
|
+
params[tag] = 1
|
|
167
|
+
elif txt == FIELD_REF_IDX_OPTIONS[2]:
|
|
168
|
+
params[tag] = -1
|
|
169
|
+
else:
|
|
170
|
+
params[tag] = wl.itemAt(2).widget().value()
|
|
150
171
|
elif field['type'] == FIELD_INT_TUPLE:
|
|
151
172
|
params[tag] = [field['widget'].layout().itemAt(1 + i * 2).widget().value()
|
|
152
173
|
for i in range(field['size'])]
|
|
@@ -328,6 +349,37 @@ class FieldBuilder:
|
|
|
328
349
|
spin.setValue(self.action.params.get(tag, default))
|
|
329
350
|
return spin
|
|
330
351
|
|
|
352
|
+
def create_ref_idx_field(self, tag, default=0):
|
|
353
|
+
layout = QHBoxLayout()
|
|
354
|
+
combo = QComboBox()
|
|
355
|
+
combo.addItems(FIELD_REF_IDX_OPTIONS)
|
|
356
|
+
label = QLabel("index [1, ..., N]: ")
|
|
357
|
+
spin = QSpinBox()
|
|
358
|
+
spin.setRange(1, FIELD_REF_IDX_MAX)
|
|
359
|
+
value = self.action.params.get(tag, default)
|
|
360
|
+
if value == 0:
|
|
361
|
+
combo.setCurrentText(FIELD_REF_IDX_OPTIONS[0])
|
|
362
|
+
spin.setValue(1)
|
|
363
|
+
elif value == 1:
|
|
364
|
+
combo.setCurrentText(FIELD_REF_IDX_OPTIONS[1])
|
|
365
|
+
spin.setValue(1)
|
|
366
|
+
elif value == -1:
|
|
367
|
+
combo.setCurrentText(FIELD_REF_IDX_OPTIONS[2])
|
|
368
|
+
spin.setValue(1)
|
|
369
|
+
else:
|
|
370
|
+
combo.setCurrentText(FIELD_REF_IDX_OPTIONS[3])
|
|
371
|
+
spin.setValue(value)
|
|
372
|
+
|
|
373
|
+
def set_enabled():
|
|
374
|
+
spin.setEnabled(combo.currentText() == FIELD_REF_IDX_OPTIONS[-1])
|
|
375
|
+
|
|
376
|
+
combo.currentTextChanged.connect(set_enabled)
|
|
377
|
+
set_enabled()
|
|
378
|
+
layout.addWidget(combo)
|
|
379
|
+
layout.addWidget(label)
|
|
380
|
+
layout.addWidget(spin)
|
|
381
|
+
return create_layout_widget_no_margins(layout)
|
|
382
|
+
|
|
331
383
|
def create_int_tuple_field(self, tag, size=1,
|
|
332
384
|
default=[0] * 100, min_val=[0] * 100, max_val=[100] * 100,
|
|
333
385
|
**kwargs):
|