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.

Files changed (38) hide show
  1. shinestacker/_version.py +1 -1
  2. shinestacker/algorithms/__init__.py +4 -1
  3. shinestacker/algorithms/align.py +149 -34
  4. shinestacker/algorithms/balance.py +364 -166
  5. shinestacker/algorithms/base_stack_algo.py +6 -0
  6. shinestacker/algorithms/depth_map.py +1 -1
  7. shinestacker/algorithms/multilayer.py +22 -13
  8. shinestacker/algorithms/noise_detection.py +7 -8
  9. shinestacker/algorithms/pyramid.py +3 -2
  10. shinestacker/algorithms/pyramid_auto.py +141 -0
  11. shinestacker/algorithms/pyramid_tiles.py +199 -44
  12. shinestacker/algorithms/stack.py +20 -20
  13. shinestacker/algorithms/stack_framework.py +136 -156
  14. shinestacker/algorithms/utils.py +175 -1
  15. shinestacker/algorithms/vignetting.py +26 -8
  16. shinestacker/config/constants.py +31 -6
  17. shinestacker/core/framework.py +12 -12
  18. shinestacker/gui/action_config.py +59 -7
  19. shinestacker/gui/action_config_dialog.py +427 -283
  20. shinestacker/gui/base_form_dialog.py +11 -6
  21. shinestacker/gui/gui_images.py +10 -10
  22. shinestacker/gui/gui_run.py +1 -1
  23. shinestacker/gui/main_window.py +6 -5
  24. shinestacker/gui/menu_manager.py +16 -2
  25. shinestacker/gui/new_project.py +26 -22
  26. shinestacker/gui/project_controller.py +43 -27
  27. shinestacker/gui/project_converter.py +2 -8
  28. shinestacker/gui/project_editor.py +50 -27
  29. shinestacker/gui/tab_widget.py +3 -3
  30. shinestacker/retouch/exif_data.py +5 -5
  31. shinestacker/retouch/shortcuts_help.py +4 -4
  32. shinestacker/retouch/vignetting_filter.py +12 -8
  33. {shinestacker-1.1.0.dist-info → shinestacker-1.2.1.dist-info}/METADATA +1 -1
  34. {shinestacker-1.1.0.dist-info → shinestacker-1.2.1.dist-info}/RECORD +38 -37
  35. {shinestacker-1.1.0.dist-info → shinestacker-1.2.1.dist-info}/WHEEL +0 -0
  36. {shinestacker-1.1.0.dist-info → shinestacker-1.2.1.dist-info}/entry_points.txt +0 -0
  37. {shinestacker-1.1.0.dist-info → shinestacker-1.2.1.dist-info}/licenses/LICENSE +0 -0
  38. {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
- CorrectionMapBase.__init__(self, dtype, ref_hist, intensity_interval)
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
- CorrectionMapBase.__init__(self, dtype, ref_hist, intensity_interval)
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=-1, fast_subsampling=constants.DEFAULT_BALANCE_FAST_SUBSAMPLING,
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.plot_histograms = plot_histograms
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. max_pixel_value = None
139
- self.corrections = None
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 if ref_image.dtype == np.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.corr_map = LinearMap(self.dtype, hist, self.intensity_interval)
323
+ self.corr_map_obj = LinearMap(self.dtype, hist, self.intensity_interval)
150
324
  elif self.corr_map == constants.BALANCE_GAMMA:
151
- self.corr_map = GammaMap(self.dtype, hist, self.intensity_interval)
325
+ self.corr_map_obj = GammaMap(self.dtype, hist, self.intensity_interval)
152
326
  elif self.corr_map == constants.BALANCE_MATCH_HIST:
153
- self.corr_map = MatchHist(self.dtype, hist, self.intensity_interval)
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.corrections = np.ones((size, self.channels))
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, self.subsample, self.fast_subsampling)
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
- hist, _bins = np.histogram(
174
- image_sel,
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.corr_map.correction(self.get_hist(image, idx))
182
- return correction, self.corr_map.adjust(image, correction)
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.corrections[idx] = self.corr_map.correction_size(correction)
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
- Correction.__init__(self, 1, **kwargs)
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
- chans = cv2.split(image)
240
- colors = ("r", "g", "b")
241
- if self.plot_histograms:
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
- plt.figure(figsize=(10, 5))
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
- Correction.__init__(self, 3, **kwargs)
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
- colors = ("r", "g", "b")
276
- if self.plot_histograms:
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
- plt.figure(figsize=(10, 5))
287
- x = np.arange(1, len(self.corrections) + 1, dtype=int)
288
- y = self.corrections
289
- plt.plot([ref_idx + 1, ref_idx + 1], [0, 1], color='cornflowerblue',
290
- linestyle='--', label='reference frame')
291
- plt.plot([x[0], x[-1]], [1, 1], color='lightgray', linestyle='--',
292
- label='no correction')
293
- plt.plot(x, y[:, 0], color='r', label='R correction')
294
- plt.plot(x, y[:, 1], color='g', label='G correction')
295
- plt.plot(x, y[:, 2], color='b', label='B correction')
296
- plt.xlabel('frame')
297
- plt.ylabel('correction')
298
- plt.legend()
299
- plt.xlim(x[0], x[-1])
300
- plt.ylim(0)
301
- self.save_summary_plot()
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
- Correction.__init__(self, 2, **kwargs)
460
+ super().__init__(2, **kwargs)
461
+ self.labels = None
462
+ self.colors = None
307
463
 
308
464
  def preprocess(self, image):
309
- assert False, 'abstract method'
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
- _fig, axs = plt.subplots(1, 3, figsize=(10, 5), sharey=True)
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
- plt.figure(figsize=(10, 5))
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
- Ch2Correction.__init__(self, **kwargs)
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 cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
497
+ return bgr_to_hsv(image)
348
498
 
349
499
  def postprocess(self, image):
350
- return cv2.cvtColor(image, cv2.COLOR_HSV2BGR)
500
+ return hsv_to_bgr(image)
351
501
 
352
502
 
353
503
  class LSCorrection(Ch2Correction):
354
504
  def __init__(self, **kwargs):
355
- Ch2Correction.__init__(self, **kwargs)
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 cv2.cvtColor(image, cv2.COLOR_BGR2HLS)
547
+ return bgr_to_lab(image)
361
548
 
362
549
  def postprocess(self, image):
363
- return cv2.cvtColor(image, cv2.COLOR_HLS2BGR)
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('corr_map', constants.DEFAULT_CORR_MAP)
372
- subsample = kwargs.get('subsample', constants.DEFAULT_BALANCE_SUBSAMPLE)
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.pop('channel', constants.DEFAULT_CHANNEL)
376
- kwargs['subsample'] = (
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
- if channel == constants.BALANCE_LUMI:
382
- self.correction = LumiCorrection(**kwargs)
383
- elif channel == constants.BALANCE_RGB:
384
- self.correction = RGBCorrection(**kwargs)
385
- elif channel == constants.BALANCE_HSV:
386
- self.correction = SVCorrection(**kwargs)
387
- elif channel == constants.BALANCE_HLS:
388
- self.correction = LSCorrection(**kwargs)
389
- else:
390
- raise InvalidOptionError("channel", channel)
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.input_full_path + "/" + self.process.filenames[process.ref_idx])
593
+ img = read_img(self.process.input_filepath(process.ref_idx))
396
594
  self.shape = img.shape
397
- self.correction.begin(img, self.process.counts, process.ref_idx)
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=(10, 5))
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
- image = self.correction.apply_correction(idx, image)
613
+ image = self.correction.apply_correction(idx, image)
416
614
  return image