shinestacker 1.0.4.post2__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.

Files changed (37) hide show
  1. shinestacker/_version.py +1 -1
  2. shinestacker/algorithms/__init__.py +4 -1
  3. shinestacker/algorithms/align.py +128 -14
  4. shinestacker/algorithms/balance.py +362 -163
  5. shinestacker/algorithms/base_stack_algo.py +33 -4
  6. shinestacker/algorithms/depth_map.py +9 -12
  7. shinestacker/algorithms/multilayer.py +12 -2
  8. shinestacker/algorithms/noise_detection.py +8 -3
  9. shinestacker/algorithms/pyramid.py +57 -42
  10. shinestacker/algorithms/pyramid_auto.py +141 -0
  11. shinestacker/algorithms/pyramid_tiles.py +264 -0
  12. shinestacker/algorithms/stack.py +14 -11
  13. shinestacker/algorithms/stack_framework.py +17 -11
  14. shinestacker/algorithms/utils.py +180 -1
  15. shinestacker/algorithms/vignetting.py +23 -5
  16. shinestacker/config/constants.py +31 -5
  17. shinestacker/gui/action_config.py +6 -7
  18. shinestacker/gui/action_config_dialog.py +425 -258
  19. shinestacker/gui/base_form_dialog.py +11 -6
  20. shinestacker/gui/flow_layout.py +105 -0
  21. shinestacker/gui/gui_run.py +24 -19
  22. shinestacker/gui/main_window.py +4 -3
  23. shinestacker/gui/menu_manager.py +12 -2
  24. shinestacker/gui/new_project.py +28 -22
  25. shinestacker/gui/project_controller.py +40 -23
  26. shinestacker/gui/project_converter.py +6 -6
  27. shinestacker/gui/project_editor.py +21 -7
  28. shinestacker/gui/time_progress_bar.py +2 -2
  29. shinestacker/retouch/exif_data.py +5 -5
  30. shinestacker/retouch/shortcuts_help.py +4 -4
  31. shinestacker/retouch/vignetting_filter.py +12 -8
  32. {shinestacker-1.0.4.post2.dist-info → shinestacker-1.2.0.dist-info}/METADATA +20 -1
  33. {shinestacker-1.0.4.post2.dist-info → shinestacker-1.2.0.dist-info}/RECORD +37 -34
  34. {shinestacker-1.0.4.post2.dist-info → shinestacker-1.2.0.dist-info}/WHEEL +0 -0
  35. {shinestacker-1.0.4.post2.dist-info → shinestacker-1.2.0.dist-info}/entry_points.txt +0 -0
  36. {shinestacker-1.0.4.post2.dist-info → shinestacker-1.2.0.dist-info}/licenses/LICENSE +0 -0
  37. {shinestacker-1.0.4.post2.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
- CorrectionMapBase.__init__(self, dtype, ref_hist, intensity_interval)
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
- CorrectionMapBase.__init__(self, dtype, ref_hist, intensity_interval)
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=-1, fast_subsampling=constants.DEFAULT_BALANCE_FAST_SUBSAMPLING,
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.plot_histograms = plot_histograms
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. max_pixel_value = None
139
- self.corrections = None
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 if ref_image.dtype == np.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.corr_map = LinearMap(self.dtype, hist, self.intensity_interval)
324
+ self.corr_map_obj = LinearMap(self.dtype, hist, self.intensity_interval)
150
325
  elif self.corr_map == constants.BALANCE_GAMMA:
151
- self.corr_map = GammaMap(self.dtype, hist, self.intensity_interval)
326
+ self.corr_map_obj = GammaMap(self.dtype, hist, self.intensity_interval)
152
327
  elif self.corr_map == constants.BALANCE_MATCH_HIST:
153
- self.corr_map = MatchHist(self.dtype, hist, self.intensity_interval)
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.corrections = np.ones((size, self.channels))
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, self.subsample, self.fast_subsampling)
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
- 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
- )
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.corr_map.correction(self.get_hist(image, idx))
182
- return correction, self.corr_map.adjust(image, correction)
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.corrections[idx] = self.corr_map.correction_size(correction)
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
- Correction.__init__(self, 1, **kwargs)
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
- 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)
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
- 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()
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
- Correction.__init__(self, 3, **kwargs)
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
- 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)
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
- 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()
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
- Correction.__init__(self, 2, **kwargs)
461
+ super().__init__(2, **kwargs)
462
+ self.labels = None
463
+ self.colors = None
307
464
 
308
465
  def preprocess(self, image):
309
- assert False, 'abstract method'
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
- _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)
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
- 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()
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
- Ch2Correction.__init__(self, **kwargs)
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 cv2.cvtColor(image, cv2.COLOR_BGR2HSV)
498
+ return bgr_to_hsv(image)
348
499
 
349
500
  def postprocess(self, image):
350
- return cv2.cvtColor(image, cv2.COLOR_HSV2BGR)
501
+ return hsv_to_bgr(image)
351
502
 
352
503
 
353
504
  class LSCorrection(Ch2Correction):
354
505
  def __init__(self, **kwargs):
355
- Ch2Correction.__init__(self, **kwargs)
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 cv2.cvtColor(image, cv2.COLOR_BGR2HLS)
548
+ return bgr_to_lab(image)
361
549
 
362
550
  def postprocess(self, image):
363
- return cv2.cvtColor(image, cv2.COLOR_HLS2BGR)
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('corr_map', constants.DEFAULT_CORR_MAP)
372
- subsample = kwargs.get('subsample', constants.DEFAULT_BALANCE_SUBSAMPLE)
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.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
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
- 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)
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
- image = self.correction.apply_correction(idx, image)
614
+ image = self.correction.apply_correction(idx, image)
416
615
  return image