views.py 17.6 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
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
21

22
23
24
25
26
27
28
29
30
31
32
       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
    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:
55
            obj = np.asarray(input_array, dtype=np.uint32).view(cls).copy()
56
        else:
57
            input_array = np.empty((0, 3), dtype=np.uint32)
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
            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__
103
class MultivariateSequence(View):
104
105
106
107
108
109
110
111
112
113
114
    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
           -------
115
           Object : MultivariateSequence
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
               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
141
142
        # selef._frames.start = start
        # self._frames.end = end
143

144
        contrast_points = np.empty((0, 3), dtype=np.uint32)
145

146
147
148
149
150
151
152
153
        # 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
154

155
156
157
158
            shape = ctrst_img.shape
            ctrst_img = np.reshape(ctrst_img, (shape[0] * shape[1]))

            hist_value, _ = np.histogram(ctrst_img, bins=self._bins,
159
                                         range=(0, 256))
160
161

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

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

        return deepcopy(self)  # TODO does not create a new object
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


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
199
    def populate(self, variant='peak', ctrst='light_dark',
200
                 method='luminance', frm_stp=10, bins=256):
201
202
        """Creates a scatterplot for the dynamic of contrast across movie frames

203
204
           variant: peak or bin (one of max/min peak; uper/lower mean)
           threshold: for the peak variant TODO
205
206
207
208
           smooth: smooth plot TODO
        """

        # set class properties
209
        self._variant = variant
210
211
212
213
214
215
216
217
218
        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

219
        contrast_points = np.empty((0, 2), dtype=np.uint32)
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))

                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 variant
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
        contrast_points = np.asarray(contrast_points, np.uint32)
309
310
311
312
        shape = contrast_points.shape
        self.resize(shape, refcheck=False)
        self[:, :] = contrast_points
        contrast_points = None
313
314


315
316
317
318
319
320
321
322
323
324
325
326
327
class UnivariateSequence(View):
    def __new__(cls, frames, input_array=None):
        """Represents a movie contrast in terms of a single feature

           Parameters
           ----------

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

        obj = View.__new__(cls, frames, input_array=input_array)
328
        obj.feature = None
329
330
331
332
        return obj

    def __array_finalize__(self, obj):
        if obj is None: return
333
        self._threshold = getattr(obj, 'feature', None)
334
335
336
337
338
        View.__array_finalize__(self, obj)

    # TODO später ersetzen durch numpy mean/deviation in Kombination mit anderen Möglichkeiten
    @staticmethod
    def meanmad(values):
339

340
341
342
343
344
345
346
347
348
349
350
351
352
        values = values.flatten()
        length = values.size
        points = np.sum(values)
        # range nicht array und histogram verschachtelt (siehe slice)
        bin_values = np.apply_along_axis(lambda x: x * values[x-1], 0,
                                         range(1, length+1)) 
        mean = np.sum(bin_values) / points
        absolutes = np.apply_along_axis(lambda x: abs(x - mean) * values[x-1],
                                        0, range(1, length+1))
        mad = np.sum(absolutes) / points
        return mean, mad

    # TODO: Kurve muß unbeding von cuts bereinigt werde und interpolation funktioniert da nicht
353
354
    def seqmean(self, ctrst='light_dark',
                method='luminance', frm_stp=10, bins=256):
355
356
357
358
359
360
361
362
363
        """Creates a scatterplot for the dynamic of contrast across movie frames

           frm_fld: path to folder with movie images
           frm_pref: file nave in fron of the count value
           frm_step: take every x frame
           channel: channel in the HSV color space
           save: save plot also to disk
        """
        # set class properties
364
        self.feature = 'mean'
365
366
367
368
369
370
371
372
373
        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

374
        contrast_points = np.empty((0), dtype=np.uint32)
375
376
377
378
379
380
381
382
383
384
385
386
387
388

        # 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)

            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))

            hist_value = hist_value.flatten()
389
            hist_value = hist_value.astype(np.uint32, copy=False)
390
391
392

            # eigenes mean und devi durch numpy ersetzt
            # TODO Ergebnisse der unterschiedlichen Verfahrenstimmt nicht überein
393
394
            # contrast = hist_value.mean()
            # deviation = hist_value.std()
395
            contrast, _ = UnivariateSequence.meanmad(hist_value)
396

397
398
            contrast_points = np.hstack((contrast_points,
                                         contrast))
399

400
        contrast_points = np.asarray(contrast_points, np.uint32)
401
402
        shape = contrast_points.shape
        self.resize(shape, refcheck=False)
403
404
405
406
        self[:] = contrast_points
        contrast_points = None

    # TODO: Kurve muß unbeding von cuts bereinigt werde und interpolation funktioniert da nicht
407
408
    def seqmad(self, ctrst='light_dark',
               method='luminance', frm_stp=10, bins=256):
409
410
411
412
413
414
415
416
417
        """Creates a scatterplot for the dynamic of contrast across movie frames

           frm_fld: path to folder with movie images
           frm_pref: file nave in fron of the count value
           frm_step: take every x frame
           channel: channel in the HSV color space
           save: save plot also to disk
        """
        # set class properties
418
        self.feature = 'absolute_deviation'
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
        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

        contrast_points = np.empty((0), dtype=np.uint32)

        # 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)

            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))

            hist_value = hist_value.flatten()
            hist_value = hist_value.astype(np.uint32, copy=False)

            # eigenes mean und devi durch numpy ersetzt
            # TODO Ergebnisse der unterschiedlichen Verfahrenstimmt nicht überein
            # contrast = hist_value.mean()
            # deviation = hist_value.std()
            _, deviation = UnivariateSequence.meanmad(hist_value)

            contrast_points = np.hstack((contrast_points,
                                         deviation))

        contrast_points = np.asarray(contrast_points, np.uint32)
        shape = contrast_points.shape
        self.resize(shape, refcheck=False)
        self[:] = contrast_points
458
        contrast_points = None