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
|
-
# pylint: disable=C0114, C0115, C0116, E1101, R0902, E1128, E0606, W0640, R0913, R0917
|
|
1
|
+
# pylint: disable=C0114, C0115, C0116, E1101, R0902, E1128, E0606, W0640, R0913, R0917, R0914
|
|
2
|
+
import math
|
|
2
3
|
import numpy as np
|
|
3
4
|
import cv2
|
|
4
5
|
import matplotlib.pyplot as plt
|
|
@@ -7,10 +8,187 @@ from scipy.interpolate import interp1d
|
|
|
7
8
|
from .. config.constants import constants
|
|
8
9
|
from .. core.exceptions import InvalidOptionError
|
|
9
10
|
from .. core.colors import color_str
|
|
10
|
-
from .utils import read_img, save_plot, img_subsample
|
|
11
|
+
from .utils import (read_img, save_plot, img_subsample, bgr_to_hsv, bgr_to_hls,
|
|
12
|
+
hsv_to_bgr, hls_to_bgr, bgr_to_lab, lab_to_bgr)
|
|
11
13
|
from .stack_framework import SubAction
|
|
12
14
|
|
|
13
15
|
|
|
16
|
+
class BaseHistogrammer:
|
|
17
|
+
def __init__(self, dtype, num_pixel_values, max_pixel_value, channels,
|
|
18
|
+
plot_histograms, plot_summary, process=None):
|
|
19
|
+
self.dtype = dtype
|
|
20
|
+
self.num_pixel_values = num_pixel_values
|
|
21
|
+
self.max_pixel_value = max_pixel_value
|
|
22
|
+
self.channels = channels
|
|
23
|
+
self.plot_histograms = plot_histograms
|
|
24
|
+
self.plot_summary = plot_summary
|
|
25
|
+
self.process = process
|
|
26
|
+
self.corrections = None
|
|
27
|
+
|
|
28
|
+
def begin(self, size):
|
|
29
|
+
self.corrections = np.ones((size, self.channels))
|
|
30
|
+
|
|
31
|
+
def add_correction(self, idx, correction):
|
|
32
|
+
if idx != self.process.ref_idx:
|
|
33
|
+
self.corrections[idx] = correction
|
|
34
|
+
|
|
35
|
+
def histo_plot(self, ax, hist, x_label, color, alpha=1):
|
|
36
|
+
ax.set_ylabel("# of pixels")
|
|
37
|
+
ax.set_xlabel(x_label)
|
|
38
|
+
ax.set_xlim([0, self.max_pixel_value])
|
|
39
|
+
ax.set_yscale('log')
|
|
40
|
+
x_values = np.linspace(0, self.max_pixel_value, len(hist))
|
|
41
|
+
ax.plot(x_values, hist, color=color, alpha=alpha)
|
|
42
|
+
|
|
43
|
+
def save_plot(self, idx):
|
|
44
|
+
idx_str = f"{idx:04d}"
|
|
45
|
+
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
46
|
+
f"{self.process.name}-hist-{idx_str}.pdf"
|
|
47
|
+
save_plot(plot_path)
|
|
48
|
+
plt.close('all')
|
|
49
|
+
self.process.callback(
|
|
50
|
+
'save_plot',
|
|
51
|
+
self.process.id, f"{self.process.name}: balance\nframe {idx_str}",
|
|
52
|
+
plot_path
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
def save_summary_plot(self, name='balance'):
|
|
56
|
+
plot_path = f"{self.process.working_path}/{self.process.plot_path}/" \
|
|
57
|
+
f"{self.process.name}-{name}.pdf"
|
|
58
|
+
save_plot(plot_path)
|
|
59
|
+
plt.close('all')
|
|
60
|
+
self.process.callback(
|
|
61
|
+
'save_plot', self.process.id,
|
|
62
|
+
f"{self.process.name}: {name}", plot_path
|
|
63
|
+
)
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
class LumiHistogrammer(BaseHistogrammer):
|
|
67
|
+
def __init__(self, *args, **kwargs):
|
|
68
|
+
super().__init__(*args, **kwargs)
|
|
69
|
+
self.colors = ("r", "g", "b")
|
|
70
|
+
|
|
71
|
+
def generate_frame_plot(self, idx, hist, chans, calc_hist_func):
|
|
72
|
+
_fig, axs = plt.subplots(1, 2, figsize=constants.PLT_FIG_SIZE, sharey=True)
|
|
73
|
+
self.histo_plot(axs[0], hist, "pixel luminosity", 'black')
|
|
74
|
+
for (chan, color) in zip(chans, self.colors):
|
|
75
|
+
hist_col = calc_hist_func(chan)
|
|
76
|
+
self.histo_plot(axs[1], hist_col, "R, G, B intensity", color, alpha=0.5)
|
|
77
|
+
plt.xlim(0, self.max_pixel_value)
|
|
78
|
+
self.save_plot(idx)
|
|
79
|
+
|
|
80
|
+
def generate_summary_plot(self, ref_idx):
|
|
81
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
82
|
+
x = np.arange(0, len(self.corrections), dtype=int)
|
|
83
|
+
y = self.corrections
|
|
84
|
+
plt.plot([ref_idx, ref_idx], [0, np.max(y)], color='cornflowerblue',
|
|
85
|
+
linestyle='--', label='reference frame')
|
|
86
|
+
plt.plot([x[0], x[-1]], [1, 1], color='lightgray', linestyle='--',
|
|
87
|
+
label='no correction')
|
|
88
|
+
plt.plot(x, y, color='navy', label='luminosity correction')
|
|
89
|
+
plt.xlabel('frame')
|
|
90
|
+
plt.ylabel('correction')
|
|
91
|
+
plt.legend()
|
|
92
|
+
plt.xlim(x[0], x[-1])
|
|
93
|
+
plt.ylim(0, np.max(y) * 1.1)
|
|
94
|
+
self.save_summary_plot()
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
class RGBHistogrammer(BaseHistogrammer):
|
|
98
|
+
def __init__(self, *args, **kwargs):
|
|
99
|
+
super().__init__(*args, **kwargs)
|
|
100
|
+
self.colors = ("r", "g", "b")
|
|
101
|
+
|
|
102
|
+
def generate_frame_plot(self, idx, hists):
|
|
103
|
+
_fig, axs = plt.subplots(1, 3, figsize=constants.PLT_FIG_SIZE, sharey=True)
|
|
104
|
+
for c in [2, 1, 0]:
|
|
105
|
+
self.histo_plot(axs[c], hists[c], self.colors[c] + " luminosity", self.colors[c])
|
|
106
|
+
plt.xlim(0, self.max_pixel_value)
|
|
107
|
+
self.save_plot(idx)
|
|
108
|
+
|
|
109
|
+
def generate_summary_plot(self, ref_idx):
|
|
110
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
111
|
+
x = np.arange(0, len(self.corrections), dtype=int)
|
|
112
|
+
y = self.corrections
|
|
113
|
+
max_val = np.max(y) if np.any(y) else 1.0
|
|
114
|
+
plt.plot([ref_idx, ref_idx], [0, max_val], color='cornflowerblue',
|
|
115
|
+
linestyle='--', label='reference frame')
|
|
116
|
+
plt.plot([x[0], x[-1]], [1, 1], color='lightgray', linestyle='--',
|
|
117
|
+
label='no correction')
|
|
118
|
+
plt.plot(x, y[:, 0], color='r', label='R correction')
|
|
119
|
+
plt.plot(x, y[:, 1], color='g', label='G correction')
|
|
120
|
+
plt.plot(x, y[:, 2], color='b', label='B correction')
|
|
121
|
+
plt.xlabel('frame')
|
|
122
|
+
plt.ylabel('correction')
|
|
123
|
+
plt.legend()
|
|
124
|
+
plt.xlim(x[0], x[-1])
|
|
125
|
+
plt.ylim(0, max_val * 1.1)
|
|
126
|
+
self.save_summary_plot()
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
class Ch1Histogrammer(BaseHistogrammer):
|
|
130
|
+
def __init__(self, labels, colors, *args, **kwargs):
|
|
131
|
+
super().__init__(*args, **kwargs)
|
|
132
|
+
self.labels = labels
|
|
133
|
+
self.colors = colors
|
|
134
|
+
|
|
135
|
+
def generate_frame_plot(self, idx, hists):
|
|
136
|
+
_fig, axs = plt.subplots(1, 3, figsize=constants.PLT_FIG_SIZE, sharey=True)
|
|
137
|
+
for c in range(3):
|
|
138
|
+
self.histo_plot(axs[c], hists[c], self.labels[c], self.colors[c])
|
|
139
|
+
plt.xlim(0, self.max_pixel_value)
|
|
140
|
+
self.save_plot(idx)
|
|
141
|
+
|
|
142
|
+
def generate_summary_plot(self, ref_idx):
|
|
143
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
144
|
+
x = np.arange(0, len(self.corrections), dtype=int)
|
|
145
|
+
y = self.corrections
|
|
146
|
+
max_val = np.max(y) if np.any(y) else 1.0
|
|
147
|
+
plt.plot([ref_idx, ref_idx], [0, max_val], color='cornflowerblue',
|
|
148
|
+
linestyle='--', label='reference frame')
|
|
149
|
+
plt.plot([x[0], x[-1]], [1, 1], color='lightgray', linestyle='--',
|
|
150
|
+
label='no correction')
|
|
151
|
+
plt.plot(x, y[:, 0], color=self.colors[0], label=self.labels[0] + ' correction')
|
|
152
|
+
plt.xlabel('frame')
|
|
153
|
+
plt.ylabel('correction')
|
|
154
|
+
plt.legend()
|
|
155
|
+
plt.xlim(x[0], x[-1])
|
|
156
|
+
plt.ylim(0, max_val * 1.1)
|
|
157
|
+
self.save_summary_plot()
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
class Ch2Histogrammer(BaseHistogrammer):
|
|
161
|
+
def __init__(self, labels, colors, *args, **kwargs):
|
|
162
|
+
super().__init__(*args, **kwargs)
|
|
163
|
+
self.labels = labels
|
|
164
|
+
self.colors = colors
|
|
165
|
+
|
|
166
|
+
def generate_frame_plot(self, idx, hists):
|
|
167
|
+
_fig, axs = plt.subplots(1, 3, figsize=constants.PLT_FIG_SIZE, sharey=True)
|
|
168
|
+
for c in range(3):
|
|
169
|
+
self.histo_plot(axs[c], hists[c], self.labels[c], self.colors[c])
|
|
170
|
+
plt.xlim(0, self.max_pixel_value)
|
|
171
|
+
self.save_plot(idx)
|
|
172
|
+
|
|
173
|
+
def generate_summary_plot(self, ref_idx):
|
|
174
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
175
|
+
x = np.arange(0, len(self.corrections), dtype=int)
|
|
176
|
+
y = self.corrections
|
|
177
|
+
max_val = np.max(y) if np.any(y) else 1.0
|
|
178
|
+
plt.plot([ref_idx, ref_idx], [0, max_val], color='cornflowerblue',
|
|
179
|
+
linestyle='--', label='reference frame')
|
|
180
|
+
plt.plot([x[0], x[-1]], [1, 1], color='lightgray', linestyle='--',
|
|
181
|
+
label='no correction')
|
|
182
|
+
plt.plot(x, y[:, 0], color=self.colors[1], label=self.labels[1] + ' correction')
|
|
183
|
+
plt.plot(x, y[:, 1], color=self.colors[2], label=self.labels[2] + ' correction')
|
|
184
|
+
plt.xlabel('frame')
|
|
185
|
+
plt.ylabel('correction')
|
|
186
|
+
plt.legend()
|
|
187
|
+
plt.xlim(x[0], x[-1])
|
|
188
|
+
plt.ylim(0, max_val * 1.1)
|
|
189
|
+
self.save_summary_plot()
|
|
190
|
+
|
|
191
|
+
|
|
14
192
|
class CorrectionMapBase:
|
|
15
193
|
def __init__(self, dtype, ref_hist, intensity_interval=None):
|
|
16
194
|
intensity_interval = {**constants.DEFAULT_INTENSITY_INTERVAL, **(intensity_interval or {})}
|
|
@@ -37,13 +215,11 @@ class CorrectionMapBase:
|
|
|
37
215
|
chans = cv2.split(image)
|
|
38
216
|
if self.channels == 2:
|
|
39
217
|
ch_out = [chans[0]] + [self.apply_lut(
|
|
40
|
-
correction[c - 1],
|
|
41
|
-
self.reference[c - 1], chans[c]
|
|
218
|
+
correction[c - 1], self.reference[c - 1], chans[c]
|
|
42
219
|
) for c in range(1, 3)]
|
|
43
220
|
elif self.channels == 3:
|
|
44
221
|
ch_out = [self.apply_lut(
|
|
45
|
-
correction[c],
|
|
46
|
-
self.reference[c], chans[c]
|
|
222
|
+
correction[c], self.reference[c], chans[c]
|
|
47
223
|
) for c in range(3)]
|
|
48
224
|
return cv2.merge(ch_out)
|
|
49
225
|
|
|
@@ -53,7 +229,7 @@ class CorrectionMapBase:
|
|
|
53
229
|
|
|
54
230
|
class MatchHist(CorrectionMapBase):
|
|
55
231
|
def __init__(self, dtype, ref_hist, intensity_interval=None):
|
|
56
|
-
|
|
232
|
+
super().__init__(dtype, ref_hist, intensity_interval)
|
|
57
233
|
self.reference = self.cumsum(ref_hist)
|
|
58
234
|
self.reference_mean = [r.mean() for r in self.reference]
|
|
59
235
|
self.values = [*range(self.num_pixel_values)]
|
|
@@ -86,7 +262,7 @@ class MatchHist(CorrectionMapBase):
|
|
|
86
262
|
|
|
87
263
|
class CorrectionMap(CorrectionMapBase):
|
|
88
264
|
def __init__(self, dtype, ref_hist, intensity_interval=None):
|
|
89
|
-
|
|
265
|
+
super().__init__(dtype, ref_hist, intensity_interval)
|
|
90
266
|
self.reference = [self.mid_val(self.id_lut, h) for h in ref_hist]
|
|
91
267
|
|
|
92
268
|
def mid_val(self, lut, h):
|
|
@@ -94,9 +270,6 @@ class CorrectionMap(CorrectionMapBase):
|
|
|
94
270
|
|
|
95
271
|
|
|
96
272
|
class GammaMap(CorrectionMap):
|
|
97
|
-
def __init__(self, dtype, ref_hist, intensity_interval=None):
|
|
98
|
-
CorrectionMap.__init__(self, dtype, ref_hist, intensity_interval)
|
|
99
|
-
|
|
100
273
|
def correction(self, hist):
|
|
101
274
|
return [bisect(lambda x: self.mid_val(self.lut(x), h) - r, 0.1, 5)
|
|
102
275
|
for h, r in zip(hist, self.reference)]
|
|
@@ -109,9 +282,6 @@ class GammaMap(CorrectionMap):
|
|
|
109
282
|
|
|
110
283
|
|
|
111
284
|
class LinearMap(CorrectionMap):
|
|
112
|
-
def __init__(self, dtype, ref_hist, intensity_interval=None):
|
|
113
|
-
CorrectionMap.__init__(self, dtype, ref_hist, intensity_interval)
|
|
114
|
-
|
|
115
285
|
def lut(self, correction, _reference=None):
|
|
116
286
|
ar = np.arange(0, self.num_pixel_values)
|
|
117
287
|
return np.clip(ar * correction, 0, self.max_pixel_value).astype(self.dtype)
|
|
@@ -122,42 +292,56 @@ class LinearMap(CorrectionMap):
|
|
|
122
292
|
|
|
123
293
|
class Correction:
|
|
124
294
|
def __init__(self, channels, mask_size=0, intensity_interval=None,
|
|
125
|
-
subsample
|
|
295
|
+
subsample=constants.DEFAULT_BALANCE_SUBSAMPLE,
|
|
296
|
+
fast_subsampling=constants.DEFAULT_BALANCE_FAST_SUBSAMPLING,
|
|
126
297
|
corr_map=constants.DEFAULT_CORR_MAP,
|
|
127
298
|
plot_histograms=False, plot_summary=False):
|
|
128
299
|
self.mask_size = mask_size
|
|
129
300
|
self.intensity_interval = intensity_interval
|
|
130
|
-
self.
|
|
131
|
-
self.plot_summary = plot_summary
|
|
132
|
-
self.subsample = constants.DEFAULT_BALANCE_SUBSAMPLE if subsample == -1 else subsample
|
|
301
|
+
self.subsample = subsample
|
|
133
302
|
self.fast_subsampling = fast_subsampling
|
|
134
303
|
self.corr_map = corr_map
|
|
135
304
|
self.channels = channels
|
|
305
|
+
self.plot_histograms = plot_histograms
|
|
306
|
+
self.plot_summary = plot_summary
|
|
136
307
|
self.dtype = None
|
|
137
308
|
self.num_pixel_values = None
|
|
138
|
-
self.
|
|
139
|
-
self.
|
|
309
|
+
self.max_pixel_value = None
|
|
310
|
+
self.corr_map_obj = None
|
|
140
311
|
self.process = None
|
|
312
|
+
self.histogrammer = None
|
|
141
313
|
|
|
142
314
|
def begin(self, ref_image, size, ref_idx):
|
|
143
315
|
self.dtype = ref_image.dtype
|
|
144
|
-
self.num_pixel_values = constants.NUM_UINT8
|
|
145
|
-
else constants.NUM_UINT16
|
|
316
|
+
self.num_pixel_values = constants.NUM_UINT8 \
|
|
317
|
+
if ref_image.dtype == np.uint8 else constants.NUM_UINT16
|
|
146
318
|
self.max_pixel_value = self.num_pixel_values - 1
|
|
319
|
+
self._create_histogrammer()
|
|
320
|
+
self.histogrammer.process = self.process
|
|
147
321
|
hist = self.get_hist(self.preprocess(ref_image), ref_idx)
|
|
148
322
|
if self.corr_map == constants.BALANCE_LINEAR:
|
|
149
|
-
self.
|
|
323
|
+
self.corr_map_obj = LinearMap(self.dtype, hist, self.intensity_interval)
|
|
150
324
|
elif self.corr_map == constants.BALANCE_GAMMA:
|
|
151
|
-
self.
|
|
325
|
+
self.corr_map_obj = GammaMap(self.dtype, hist, self.intensity_interval)
|
|
152
326
|
elif self.corr_map == constants.BALANCE_MATCH_HIST:
|
|
153
|
-
self.
|
|
327
|
+
self.corr_map_obj = MatchHist(self.dtype, hist, self.intensity_interval)
|
|
154
328
|
else:
|
|
155
329
|
raise InvalidOptionError("corr_map", self.corr_map)
|
|
156
|
-
self.
|
|
330
|
+
self.histogrammer.begin(size)
|
|
331
|
+
|
|
332
|
+
def _create_histogrammer(self):
|
|
333
|
+
raise NotImplementedError("Subclasses must implement _create_histogrammer")
|
|
157
334
|
|
|
158
335
|
def calc_hist_1ch(self, image):
|
|
336
|
+
if self.subsample > 0:
|
|
337
|
+
subsample = self.subsample
|
|
338
|
+
else:
|
|
339
|
+
h, w = image.shape[:2]
|
|
340
|
+
img_res = float(h) * float(w) / constants.ONE_MEGA
|
|
341
|
+
target_res = constants.DEFAULT_BALANCE_RES_TARGET_MPX
|
|
342
|
+
subsample = int(1 + math.floor(img_res / target_res))
|
|
159
343
|
img_sub = image if self.subsample == 1 \
|
|
160
|
-
else img_subsample(image,
|
|
344
|
+
else img_subsample(image, subsample, self.fast_subsampling)
|
|
161
345
|
if self.mask_size == 0:
|
|
162
346
|
image_sel = img_sub
|
|
163
347
|
else:
|
|
@@ -170,16 +354,13 @@ class Correction:
|
|
|
170
354
|
image_sel = img_sub[
|
|
171
355
|
(xv - width / 2) ** 2 + (yv - height / 2) ** 2 <= mask_radius ** 2
|
|
172
356
|
]
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
bins=np.linspace(-0.5, self.num_pixel_values - 0.5,
|
|
176
|
-
self.num_pixel_values + 1)
|
|
177
|
-
)
|
|
357
|
+
bins = np.linspace(-0.5, self.num_pixel_values - 0.5, self.num_pixel_values + 1)
|
|
358
|
+
hist, _bins = np.histogram(image_sel, bins=bins)
|
|
178
359
|
return hist
|
|
179
360
|
|
|
180
361
|
def balance(self, image, idx):
|
|
181
|
-
correction = self.
|
|
182
|
-
return correction, self.
|
|
362
|
+
correction = self.corr_map_obj.correction(self.get_hist(image, idx))
|
|
363
|
+
return correction, self.corr_map_obj.adjust(image, correction)
|
|
183
364
|
|
|
184
365
|
def get_hist(self, _image, _idx):
|
|
185
366
|
return None
|
|
@@ -188,10 +369,12 @@ class Correction:
|
|
|
188
369
|
pass
|
|
189
370
|
|
|
190
371
|
def apply_correction(self, idx, image):
|
|
372
|
+
if idx == self.process.ref_idx:
|
|
373
|
+
return image
|
|
191
374
|
image = self.preprocess(image)
|
|
192
375
|
correction, image = self.balance(image, idx)
|
|
193
376
|
image = self.postprocess(image)
|
|
194
|
-
self.
|
|
377
|
+
self.histogrammer.add_correction(idx, self.corr_map_obj.correction_size(correction))
|
|
195
378
|
return image
|
|
196
379
|
|
|
197
380
|
def preprocess(self, image):
|
|
@@ -200,167 +383,171 @@ class Correction:
|
|
|
200
383
|
def postprocess(self, image):
|
|
201
384
|
return image
|
|
202
385
|
|
|
203
|
-
def histo_plot(self, ax, hist, x_label, color, alpha=1):
|
|
204
|
-
ax.set_ylabel("# of pixels")
|
|
205
|
-
ax.set_xlabel(x_label)
|
|
206
|
-
ax.set_xlim([0, self.num_pixel_values])
|
|
207
|
-
ax.set_yscale('log')
|
|
208
|
-
ax.plot(hist, color=color, alpha=alpha)
|
|
209
|
-
|
|
210
|
-
def save_plot(self, idx):
|
|
211
|
-
idx_str = f"{idx:04d}"
|
|
212
|
-
plot_path = f"{self.process.working_path}/" \
|
|
213
|
-
f"{self.process.plot_path}/{self.process.name}-hist-{idx_str}.pdf"
|
|
214
|
-
save_plot(plot_path)
|
|
215
|
-
plt.close('all')
|
|
216
|
-
self.process.callback(
|
|
217
|
-
'save_plot',
|
|
218
|
-
self.process.id, f"{self.process.name}: balance\nframe {idx_str}",
|
|
219
|
-
plot_path
|
|
220
|
-
)
|
|
221
|
-
|
|
222
|
-
def save_summary_plot(self, name='balance'):
|
|
223
|
-
plot_path = f"{self.process.working_path}/" \
|
|
224
|
-
f"{self.process.plot_path}/{self.process.name}-{name}.pdf"
|
|
225
|
-
save_plot(plot_path)
|
|
226
|
-
plt.close('all')
|
|
227
|
-
self.process.callback(
|
|
228
|
-
'save_plot', self.process.id,
|
|
229
|
-
f"{self.process.name}: {name}", plot_path
|
|
230
|
-
)
|
|
231
|
-
|
|
232
386
|
|
|
233
387
|
class LumiCorrection(Correction):
|
|
234
388
|
def __init__(self, **kwargs):
|
|
235
|
-
|
|
389
|
+
super().__init__(1, **kwargs)
|
|
390
|
+
|
|
391
|
+
def _create_histogrammer(self):
|
|
392
|
+
self.histogrammer = LumiHistogrammer(
|
|
393
|
+
dtype=self.dtype,
|
|
394
|
+
num_pixel_values=self.num_pixel_values,
|
|
395
|
+
max_pixel_value=self.max_pixel_value,
|
|
396
|
+
channels=1,
|
|
397
|
+
plot_histograms=self.plot_histograms,
|
|
398
|
+
plot_summary=self.plot_summary
|
|
399
|
+
)
|
|
236
400
|
|
|
237
401
|
def get_hist(self, image, idx):
|
|
238
402
|
hist = self.calc_hist_1ch(cv2.cvtColor(image, cv2.COLOR_BGR2GRAY))
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
_fig, axs = plt.subplots(1, 2, figsize=(10, 5), sharey=True)
|
|
243
|
-
self.histo_plot(axs[0], hist, "pixel luminosity", 'black')
|
|
244
|
-
for (chan, color) in zip(chans, colors):
|
|
245
|
-
hist_col = self.calc_hist_1ch(chan)
|
|
246
|
-
self.histo_plot(axs[1], hist_col, "r,g,b luminosity", color, alpha=0.5)
|
|
247
|
-
plt.xlim(0, self.max_pixel_value)
|
|
248
|
-
self.save_plot(idx)
|
|
403
|
+
if self.histogrammer.plot_histograms:
|
|
404
|
+
chans = cv2.split(image)
|
|
405
|
+
self.histogrammer.generate_frame_plot(idx, hist, chans, self.calc_hist_1ch)
|
|
249
406
|
return [hist]
|
|
250
407
|
|
|
251
408
|
def end(self, ref_idx):
|
|
252
|
-
if self.plot_summary:
|
|
253
|
-
|
|
254
|
-
x = np.arange(1, len(self.corrections) + 1, dtype=int)
|
|
255
|
-
y = self.corrections
|
|
256
|
-
plt.plot([ref_idx + 1, ref_idx + 1], [0, 1], color='cornflowerblue',
|
|
257
|
-
linestyle='--', label='reference frame')
|
|
258
|
-
plt.plot([x[0], x[-1]], [1, 1], color='lightgray', linestyle='--',
|
|
259
|
-
label='no correction')
|
|
260
|
-
plt.plot(x, y, color='navy', label='luminosity correction')
|
|
261
|
-
plt.xlabel('frame')
|
|
262
|
-
plt.ylabel('correction')
|
|
263
|
-
plt.legend()
|
|
264
|
-
plt.xlim(x[0], x[-1])
|
|
265
|
-
plt.ylim(0)
|
|
266
|
-
self.save_summary_plot()
|
|
409
|
+
if self.histogrammer.plot_summary:
|
|
410
|
+
self.histogrammer.generate_summary_plot(ref_idx)
|
|
267
411
|
|
|
268
412
|
|
|
269
413
|
class RGBCorrection(Correction):
|
|
270
414
|
def __init__(self, **kwargs):
|
|
271
|
-
|
|
415
|
+
super().__init__(3, **kwargs)
|
|
416
|
+
|
|
417
|
+
def _create_histogrammer(self):
|
|
418
|
+
self.histogrammer = RGBHistogrammer(
|
|
419
|
+
dtype=self.dtype,
|
|
420
|
+
num_pixel_values=self.num_pixel_values,
|
|
421
|
+
max_pixel_value=self.max_pixel_value,
|
|
422
|
+
channels=3,
|
|
423
|
+
plot_histograms=self.plot_histograms,
|
|
424
|
+
plot_summary=self.plot_summary
|
|
425
|
+
)
|
|
272
426
|
|
|
273
427
|
def get_hist(self, image, idx):
|
|
274
428
|
hist = [self.calc_hist_1ch(chan) for chan in cv2.split(image)]
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
_fig, axs = plt.subplots(1, 3, figsize=(10, 5), sharey=True)
|
|
278
|
-
for c in [2, 1, 0]:
|
|
279
|
-
self.histo_plot(axs[c], hist[c], colors[c] + " luminosity", colors[c])
|
|
280
|
-
plt.xlim(0, self.max_pixel_value)
|
|
281
|
-
self.save_plot(idx)
|
|
429
|
+
if self.histogrammer.plot_histograms:
|
|
430
|
+
self.histogrammer.generate_frame_plot(idx, hist)
|
|
282
431
|
return hist
|
|
283
432
|
|
|
284
433
|
def end(self, ref_idx):
|
|
285
|
-
if self.plot_summary:
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
self.
|
|
434
|
+
if self.histogrammer.plot_summary:
|
|
435
|
+
self.histogrammer.generate_summary_plot(ref_idx)
|
|
436
|
+
|
|
437
|
+
|
|
438
|
+
class Ch1Correction(Correction):
|
|
439
|
+
def __init__(self, **kwargs):
|
|
440
|
+
super().__init__(1, **kwargs)
|
|
441
|
+
self.labels = None
|
|
442
|
+
self.colors = None
|
|
443
|
+
|
|
444
|
+
def preprocess(self, image):
|
|
445
|
+
raise NotImplementedError('abstract method')
|
|
446
|
+
|
|
447
|
+
def get_hist(self, image, idx):
|
|
448
|
+
hist = [self.calc_hist_1ch(chan) for chan in cv2.split(image)]
|
|
449
|
+
if self.histogrammer.plot_histograms:
|
|
450
|
+
self.histogrammer.generate_frame_plot(idx, hist)
|
|
451
|
+
return [hist[0]]
|
|
452
|
+
|
|
453
|
+
def end(self, ref_idx):
|
|
454
|
+
if self.histogrammer.plot_summary:
|
|
455
|
+
self.histogrammer.generate_summary_plot(ref_idx)
|
|
302
456
|
|
|
303
457
|
|
|
304
458
|
class Ch2Correction(Correction):
|
|
305
459
|
def __init__(self, **kwargs):
|
|
306
|
-
|
|
460
|
+
super().__init__(2, **kwargs)
|
|
461
|
+
self.labels = None
|
|
462
|
+
self.colors = None
|
|
307
463
|
|
|
308
464
|
def preprocess(self, image):
|
|
309
|
-
|
|
465
|
+
raise NotImplementedError('abstract method')
|
|
310
466
|
|
|
311
467
|
def get_hist(self, image, idx):
|
|
312
468
|
hist = [self.calc_hist_1ch(chan) for chan in cv2.split(image)]
|
|
313
|
-
if self.plot_histograms:
|
|
314
|
-
|
|
315
|
-
for c in range(3):
|
|
316
|
-
self.histo_plot(axs[c], hist[c], self.labels[c], self.colors[c])
|
|
317
|
-
plt.xlim(0, self.max_pixel_value)
|
|
318
|
-
self.save_plot(idx)
|
|
469
|
+
if self.histogrammer.plot_histograms:
|
|
470
|
+
self.histogrammer.generate_frame_plot(idx, hist)
|
|
319
471
|
return hist[1:]
|
|
320
472
|
|
|
321
473
|
def end(self, ref_idx):
|
|
322
|
-
if self.plot_summary:
|
|
323
|
-
|
|
324
|
-
x = np.arange(1, len(self.corrections) + 1, dtype=int)
|
|
325
|
-
y = self.corrections
|
|
326
|
-
plt.plot([ref_idx + 1, ref_idx + 1], [0, 1], color='cornflowerblue',
|
|
327
|
-
linestyle='--', label='reference frame')
|
|
328
|
-
plt.plot([x[0], x[-1]], [1, 1], color='lightgray', linestyle='--',
|
|
329
|
-
label='no correction')
|
|
330
|
-
plt.plot(x, y[:, 0], color=self.colors[1], label=self.labels[1] + ' correction')
|
|
331
|
-
plt.plot(x, y[:, 1], color=self.colors[2], label=self.labels[2] + ' correction')
|
|
332
|
-
plt.xlabel('frame')
|
|
333
|
-
plt.ylabel('correction')
|
|
334
|
-
plt.legend()
|
|
335
|
-
plt.xlim(x[0], x[-1])
|
|
336
|
-
plt.ylim(0)
|
|
337
|
-
self.save_summary_plot()
|
|
474
|
+
if self.histogrammer.plot_summary:
|
|
475
|
+
self.histogrammer.generate_summary_plot(ref_idx)
|
|
338
476
|
|
|
339
477
|
|
|
340
478
|
class SVCorrection(Ch2Correction):
|
|
341
479
|
def __init__(self, **kwargs):
|
|
342
|
-
|
|
480
|
+
super().__init__(**kwargs)
|
|
343
481
|
self.labels = ("H", "S", "V")
|
|
344
482
|
self.colors = ("hotpink", "orange", "navy")
|
|
345
483
|
|
|
484
|
+
def _create_histogrammer(self):
|
|
485
|
+
self.histogrammer = Ch2Histogrammer(
|
|
486
|
+
dtype=self.dtype,
|
|
487
|
+
num_pixel_values=self.num_pixel_values,
|
|
488
|
+
max_pixel_value=self.max_pixel_value,
|
|
489
|
+
channels=2,
|
|
490
|
+
plot_histograms=self.plot_histograms,
|
|
491
|
+
plot_summary=self.plot_summary,
|
|
492
|
+
labels=self.labels,
|
|
493
|
+
colors=self.colors
|
|
494
|
+
)
|
|
495
|
+
|
|
346
496
|
def preprocess(self, image):
|
|
347
|
-
return
|
|
497
|
+
return bgr_to_hsv(image)
|
|
348
498
|
|
|
349
499
|
def postprocess(self, image):
|
|
350
|
-
return
|
|
500
|
+
return hsv_to_bgr(image)
|
|
351
501
|
|
|
352
502
|
|
|
353
503
|
class LSCorrection(Ch2Correction):
|
|
354
504
|
def __init__(self, **kwargs):
|
|
355
|
-
|
|
505
|
+
super().__init__(**kwargs)
|
|
356
506
|
self.labels = ("H", "L", "S")
|
|
357
507
|
self.colors = ("hotpink", "navy", "orange")
|
|
358
508
|
|
|
509
|
+
def _create_histogrammer(self):
|
|
510
|
+
self.histogrammer = Ch2Histogrammer(
|
|
511
|
+
dtype=self.dtype,
|
|
512
|
+
num_pixel_values=self.num_pixel_values,
|
|
513
|
+
max_pixel_value=self.max_pixel_value,
|
|
514
|
+
channels=2,
|
|
515
|
+
plot_histograms=self.plot_histograms,
|
|
516
|
+
plot_summary=self.plot_summary,
|
|
517
|
+
labels=self.labels,
|
|
518
|
+
colors=self.colors
|
|
519
|
+
)
|
|
520
|
+
|
|
521
|
+
def preprocess(self, image):
|
|
522
|
+
return bgr_to_hls(image)
|
|
523
|
+
|
|
524
|
+
def postprocess(self, image):
|
|
525
|
+
return hls_to_bgr(image)
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
class LABCorrection(Ch1Correction):
|
|
529
|
+
def __init__(self, **kwargs):
|
|
530
|
+
super().__init__(**kwargs)
|
|
531
|
+
self.labels = ("L", "A", "B")
|
|
532
|
+
self.colors = ("black", "yellow", "red")
|
|
533
|
+
|
|
534
|
+
def _create_histogrammer(self):
|
|
535
|
+
self.histogrammer = Ch1Histogrammer(
|
|
536
|
+
dtype=self.dtype,
|
|
537
|
+
num_pixel_values=self.num_pixel_values,
|
|
538
|
+
max_pixel_value=self.max_pixel_value,
|
|
539
|
+
channels=2,
|
|
540
|
+
plot_histograms=self.plot_histograms,
|
|
541
|
+
plot_summary=self.plot_summary,
|
|
542
|
+
labels=self.labels,
|
|
543
|
+
colors=self.colors
|
|
544
|
+
)
|
|
545
|
+
|
|
359
546
|
def preprocess(self, image):
|
|
360
|
-
return
|
|
547
|
+
return bgr_to_lab(image)
|
|
361
548
|
|
|
362
549
|
def postprocess(self, image):
|
|
363
|
-
return
|
|
550
|
+
return lab_to_bgr(image)
|
|
364
551
|
|
|
365
552
|
|
|
366
553
|
class BalanceFrames(SubAction):
|
|
@@ -368,33 +555,44 @@ class BalanceFrames(SubAction):
|
|
|
368
555
|
super().__init__(enabled=enabled)
|
|
369
556
|
self.process = None
|
|
370
557
|
self.shape = None
|
|
371
|
-
corr_map = kwargs.get(
|
|
372
|
-
|
|
558
|
+
self.corr_map = kwargs.get(
|
|
559
|
+
'corr_map', constants.DEFAULT_CORR_MAP)
|
|
560
|
+
self.subsample = kwargs.get(
|
|
561
|
+
'subsample', constants.DEFAULT_BALANCE_SUBSAMPLE)
|
|
373
562
|
self.fast_subsampling = kwargs.get(
|
|
374
563
|
'fast_subsampling', constants.DEFAULT_BALANCE_FAST_SUBSAMPLING)
|
|
375
|
-
channel = kwargs.
|
|
376
|
-
|
|
377
|
-
1 if corr_map == constants.BALANCE_MATCH_HIST
|
|
378
|
-
else constants.DEFAULT_BALANCE_SUBSAMPLE) if subsample == -1 else subsample
|
|
564
|
+
self.channel = kwargs.get(
|
|
565
|
+
'channel', constants.DEFAULT_CHANNEL)
|
|
379
566
|
self.mask_size = kwargs.get('mask_size', 0)
|
|
380
567
|
self.plot_summary = kwargs.get('plot_summary', False)
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
568
|
+
self.plot_histograms = kwargs.get('plot_histograms', False)
|
|
569
|
+
if self.subsample == -1:
|
|
570
|
+
self.subsample = (1 if self.corr_map == constants.BALANCE_MATCH_HIST
|
|
571
|
+
else constants.DEFAULT_BALANCE_SUBSAMPLE)
|
|
572
|
+
correction_class = {
|
|
573
|
+
constants.BALANCE_LUMI: LumiCorrection,
|
|
574
|
+
constants.BALANCE_RGB: RGBCorrection,
|
|
575
|
+
constants.BALANCE_HSV: SVCorrection,
|
|
576
|
+
constants.BALANCE_HLS: LSCorrection,
|
|
577
|
+
constants.BALANCE_LAB: LABCorrection,
|
|
578
|
+
}.get(self.channel, None)
|
|
579
|
+
if correction_class is None:
|
|
580
|
+
raise InvalidOptionError("channel", self.channel)
|
|
581
|
+
self.correction = correction_class(
|
|
582
|
+
mask_size=self.mask_size,
|
|
583
|
+
subsample=self.subsample,
|
|
584
|
+
fast_subsampling=self.fast_subsampling,
|
|
585
|
+
corr_map=self.corr_map,
|
|
586
|
+
plot_histograms=self.plot_histograms,
|
|
587
|
+
plot_summary=self.plot_summary
|
|
588
|
+
)
|
|
391
589
|
|
|
392
590
|
def begin(self, process):
|
|
393
591
|
self.process = process
|
|
394
592
|
self.correction.process = process
|
|
395
|
-
img = read_img(self.process.
|
|
593
|
+
img = read_img(self.process.input_filepath(process.ref_idx))
|
|
396
594
|
self.shape = img.shape
|
|
397
|
-
self.correction.begin(img, self.process.
|
|
595
|
+
self.correction.begin(img, self.process.total_action_counts, process.ref_idx)
|
|
398
596
|
|
|
399
597
|
def end(self):
|
|
400
598
|
self.process.print_message(' ' * 60)
|
|
@@ -404,13 +602,13 @@ class BalanceFrames(SubAction):
|
|
|
404
602
|
img = np.zeros(shape)
|
|
405
603
|
mask_radius = int(min(*shape) * self.mask_size / 2)
|
|
406
604
|
cv2.circle(img, (shape[1] // 2, shape[0] // 2), mask_radius, 255, -1)
|
|
407
|
-
plt.figure(figsize=
|
|
605
|
+
plt.figure(figsize=constants.PLT_FIG_SIZE)
|
|
408
606
|
plt.title('Mask')
|
|
409
607
|
plt.imshow(img, 'gray')
|
|
410
|
-
self.correction.save_summary_plot("mask")
|
|
608
|
+
self.correction.histogrammer.save_summary_plot("mask")
|
|
411
609
|
|
|
412
610
|
def run_frame(self, idx, _ref_idx, image):
|
|
413
611
|
if idx != self.process.ref_idx:
|
|
414
612
|
self.process.sub_message_r(color_str(': balance image', constants.LOG_COLOR_LEVEL_3))
|
|
415
|
-
|
|
613
|
+
image = self.correction.apply_correction(idx, image)
|
|
416
614
|
return image
|