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