views.py 12 KB
Newer Older
1
2
3
4
5
6
7
#!/usr/bin/env python
# -*- coding: utf-8 -*-


# TODO import as cv
import cv2
import numpy as np
8
import peakutils
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
from .helpers import luminance
from copy import deepcopy
from . import contrasts

# subclassing numpy ndarray
# Vorgehen: https://docs.scipy.org/doc/numpy/user/basics.subclassing.html
# resize Probleme https://sourceforge.net/p/numpy/mailman/message/12594801/
# andere ownership Probleme könne angeblich mit out= gelöst werden
# "Use __new__ when you need to control the creation of a new instance.
# Use __init__ when you need to control initialization of a new instance."
class View(np.ndarray):
    """Core class for the representation of colour contrasts in Movies
       
       View is the basis class for specific ways to represent colour contrasts.
       It does hold the definitions of contrasts itself. Objects of class View
       subclass the numpy array class and hence inherit numpy methods. However,
       it is not recommended to use functions which manipulate the array in
       terms of structure. In this case some of the additional functions which
       are implemented by this class and its subclasses might not lead to
       reasonable results.

       Attributes:
       TODO Docstring komplettieren und Verfahren überprüfen
       """
33

34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
    def __new__(cls, frames, input_array=None):
        """instantiates the view class

           instantiation complies with the recommendation for subclassing
           numpy.ndarray

           Parameters
           ----------
           frames : itten.movie.Frames
           input_array : itten.contrasts.View  # TODO wie schaffte np lower case object
           contrast : String  # Modifizieren Channel Integer to String
           frame_step : Int
           savefig : Boolean

           Returns
           -------
           Object : View
               Empty numpy.ndarray of type View
        """
        obj = input_array
        if type(obj) == np.ndarray:
            obj = np.asarray(input_array, dtype=np.uint8).view(cls).copy()
        else:
57
            input_array = np.empty((0, 3), dtype=np.uint8)
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
            obj = np.asarray(input_array).view(cls).copy()
        obj._frames = frames
        obj._contrast = 2
        obj._frame_step = 10
        obj._bins = 256
        return obj

    def __array_finalize__(self, obj):
        if obj is None: return
        self._frames = getattr(obj, '_frames', None)
        self._contrast = getattr(obj, '_contrast', None)
        self._frame_step = getattr(obj, '_frame_step', None)
        self._bins = getattr(obj, '_bins', None)

    def __array_wrap__(self, out_arr, context=None):
        return np.ndarray.__array_wrap__(self, out_arr, context)

    def _get_ctrst_cls_name(self, param):
        """Returns corresponding class name for the contrast parameter value

           Parameters
           ----------
           param : String
               String provided by the ctrst parameter in a call to the
               populate method

           Returns
           -------
           cls_name : Object
               Class object which corresponds to the parameter
        """

        cls_names = {'light_dark': 'LightDark',
                     'saturation': 'Saturation',
                     'hue': 'Hue',
                     'cold-warm': 'ColdWarm',
                     'complementary': 'Complementary'}

        cls = getattr(contrasts, cls_names[param])

        return cls



# subclassing subclass of numpy http://stackoverflow.com/questions/7342637/how-to-subclass-a-subclass-of-numpy-ndarray
# TODO es gibt noch das Problem, dass numpy nach mehreren Berechnungen von drive eine max recursion Warnung ausgiebt, warum? Brauche ich __del__
104
class MultivariateSequence(View):
105
106
107
108
109
110
111
112
113
114
115
    def __new__(cls, frames, input_array=None):
        """Represents a movie contrast in terms of stacked histograms

           For each defined frame a histogram is calculated and each bin that
           exceeds threshold is considered in the output array

           Parameters
           ----------

           Returns
           -------
116
           Object : MultivariateSequence
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
               VHistStack is a 2-dimensional numpy array which contains
               the frame number, the bin number and a quantifier which
               represents the relative weight of the bin in the frame
        """

        obj = View.__new__(cls, frames, input_array=input_array)
        obj._threshold = 60000
        return obj

    def __array_finalize__(self, obj):
        if obj is None: return
        View.__array_finalize__(self, obj)
        self._threshold = getattr(obj, '_threshold', None)

    def populate(self, ctrst='light_dark', method='luminance', frm_stp=10, bins=16, thrsh=60000, start=1, end=0):
        """doc (aus __new__ zusammentragen)
        """

        # set class properties
        self._contrast = ctrst
        self._method = method
        self._frame_step = frm_stp
        self._bins = bins
        self._threshold = thrsh
        # TODO dafür müssen erst getters und setters in movie.frames definiert werden
142
143
        # selef._frames.start = start
        # self._frames.end = end
144

145
        contrast_points = np.empty((0, 3), dtype=np.uint8)
146

147
148
149
150
151
152
153
154
155
        # pwd list sollte in Frames sein und hier nur durchlaufen werden
        for frm_nr in range(self._frames.start, self._frames.end, self._frame_step):
            pwd = self._frames.folder + self._frames.prefix + str(frm_nr) + '.png'

            img = cv2.imread(pwd)  # BGR

            ctrst_cls = self._get_ctrst_cls_name(self._contrast)
            ctrst_img = ctrst_cls(img).ctrst
            
156
157
            hist_value, _ = np.histogram(ctrst_img.flatten(), bins=self._bins,
                                         range=(0, 256))
158
159
160

            for bin_index, point in enumerate(hist_value):
                if point > self._threshold:
161
162
                    entry = np.array([[frm_nr, bin_index, int(point)]], dtype=np.uint8)
                    contrast_points = np.vstack((contrast_points, entry))
163

164
        # irgendwie prüfen, ob ich contrast_points insgesamt durch self ersetzen kann
165
166
167
168
        contrast_points = np.asarray(contrast_points, np.uint8)
        shape = contrast_points.shape
        self.resize(shape, refcheck=False)
        self[:, :] = contrast_points
169
        contrast_points = None
170
171

        return deepcopy(self)  # TODO does not create a new object
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216


class BivariateSequence(View):
    def __new__(cls, frames, input_array=None):
        """Represents a movie contrast in terms minbound maxbound values

           For each defined frame a histogram is calculated and each bin that
           exceeds threshold is considered in the output array

           Parameters
           ----------

           Returns
           -------
           Object : BivariateSequence
        """

        obj = View.__new__(cls, frames, input_array=input_array)
        return obj

    def __array_finalize__(self, obj):
        if obj is None: return
        View.__array_finalize__(self, obj)

    # das kann ich jetzt auch mit quantilen machen
    def populate(self, strategy='peak', ctrst='light_dark',
                 method='luminance', frm_stp=10, bins=256, **kwargs):
        """Creates a scatterplot for the dynamic of contrast across movie frames

           strategy: peak or bin (one of max/min peak; uper/lower mean)
           threshold: for the peak strategy TODO
           smooth: smooth plot TODO
        """

        # set class properties
        self._strategy = strategy
        self._contrast = ctrst
        self._method = method
        self._frame_step = frm_stp
        self._bins = bins
        # TODO um start und end in der Methode zu parametrisieren müssen erst
        # getters und setters in movie.frames definiert werden
        # self._frames.start = start
        # self._frames.end = end

217
        contrast_points = np.empty((0, 2), dtype=np.uint8)
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275

        # sofern kein oder nur ein Peak gefunden wird, man könnte dann auch
        # noch einen Durchlauf mit geringeren thres und min_dist versuceh
        lastmin = 0
        lastmax = 256

        # pwd list sollte in Frames sein und hier nur durchlaufen werden
        for frm_nr in range(self._frames.start, self._frames.end,
                            self._frame_step):
            pwd = self._frames.folder + self._frames.prefix + str(frm_nr) + '.png'
            img = cv2.imread(pwd)

            # BREAK: Funktioniert nicht
            if method == 'bin':
                hist_values = cv2.calcHist([img_hsv], [self._channel], None,
                                           [256], [0, 256])
                hist_mins = hist_values[0:127].flatten().tolist()
                hist_maxs = hist_values[128:256].flatten().tolist()
                # Für jeden Frame wird jeder Kontrastwert mit der Anzahl der
                # Pixel für diesen Kontrastwert multipliziert
                # So wird der gesamte Kontrastwert des Frames sowie errechnet
                bin_total = 0
                total_value = 0

                for bin_id, points in enumerate(hist_mins):
                    bin_total = (bin_id + 1) * points
                    total_value += bin_total

                sum_mins = int(sum(hist_mins))
                sum_maxs = int(sum(hist_maxs))

                # print('LOG: {0:8d} min points in frame {1:5d}'.format(int(sum_mins), int(frm_nr)))

                if int(total_value) == 0:
                    total_value = [(bin_nr, value) for bin_nr, value in
                                   enumerate(hist_maxs) if
                                   int(value) > 0][0][0]
                    mins.append(total_value)
                else:
                    mins.append(int(total_value / sum_mins))

                bin_total = 0
                total_value = 0

                for bin_id, points in enumerate(hist_maxs):
                    bin_total = (bin_id + 1) * points
                    total_value += bin_total

                # if there are no values in hist_max take highes bin with value
                # from hist_mins
                if int(total_value) == 0:
                    total_value = [(bin_nr, value) for bin_nr, value in
                                   enumerate(hist_mins) if
                                   int(value) > 0][-1][0]
                    maxs.append(total_value)
                else:
                    maxs.append(int(total_value / sum(hist_maxs)) + 127)

276
            # peak strategy
277
            else:
278
279
280
281
282
                ctrst_cls = self._get_ctrst_cls_name(self._contrast)
                ctrst_img = ctrst_cls(img).ctrst

                hist_value, _ = np.histogram(ctrst_img.flatten(),
                                             bins=self._bins, range=(0, 256))
283
284
285
286
287
288
289
290

                peaks = peakutils.indexes(hist_value.flatten(), thres=0.2,
                                          min_dist=15)

                # Abfangen von von nur 1 oder keinem Peak
                if len(peaks) > 1:
                        lastmin = peaks[0]
                        lastmax = peaks[-1]
291
292
                        contrast_points = np.vstack((contrast_points,
                                                    [lastmin, lastmax]))
293
294
295
296
297
                elif len(peaks) == 1:
                    # je nachdem ob der neue peak dem alten min oder max wert
                    # näher liegt wird er dem einen oder Anderen zugeschlagen
                    if (lastmax - peaks[0]) < (peaks[0] - lastmin):
                        lastmax = peaks[0]
298
299
                        contrast_points = np.vstack((contrast_points,
                                                    [lastmin, lastmax]))
300
301
                    else:
                        lastmin = peaks[0]
302
303
                        contrast_points = np.vstack((contrast_points,
                                                    [lastmin, lastmax]))
304
                else:
305
306
                    contrast_points = np.vstack((contrast_points,
                                                [lastmin, lastmax]))
307

308
309
310
311
312
        contrast_points = np.asarray(contrast_points, np.uint8)
        shape = contrast_points.shape
        self.resize(shape, refcheck=False)
        self[:, :] = contrast_points
        contrast_points = None