views.py 17.8 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
9
# import peakutils
# from .helpers import luminance
10
11
from copy import deepcopy
from . import contrasts
12
# from . import movie
13
14
15
16
17
18
19

# 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."
20
21


22
23
class View(np.ndarray):
    """Core class for the representation of colour contrasts in Movies
24

25
       View is the base class for specific ways to represent colour contrasts.
26
27
28
29
30
31
32
33
34
35
       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
       """
36

37
38
39
40
41
42
43
44
45
    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
46
47
           input_array : itten.contrasts.View  # TODO wie schaffte np lower
                         case object
48
49
50
51
52
53
54
55
56
57
58
           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:
59
            obj = np.asarray(input_array, dtype=np.uint32).view(cls).copy()
60
        else:
61
            input_array = np.empty((0, 3), dtype=np.uint32)
62
63
64
            obj = np.asarray(input_array).view(cls).copy()
        obj._frames = frames
        obj._contrast = 2
65
        obj._frame_step = 4
66
67
68
69
        obj._bins = 256
        return obj

    def __array_finalize__(self, obj):
70
71
        if obj is None:
            return
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
        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
        """

95
        cls_names = {'monochromatic': 'Monochromatic',
96
97
98
99
100
101
102
103
104
                     'saturation': 'Saturation',
                     'hue': 'Hue',
                     'cold-warm': 'ColdWarm',
                     'complementary': 'Complementary'}

        cls = getattr(contrasts, cls_names[param])

        return cls

105
    # scheint niemals verwendet zu werden
106
107
108
109
110
111
    def frms_from_indcs(self, indices):
        """results frame numbers from a list of indices"""
        indices = np.add(indices, 1)
        frm_nrs = np.multiply(indices, self._frame_step)
        frm_nrs = np.subtract(frm_nrs, self._frame_step)
        return frm_nrs.flatten()
112

113
114
115
116
117
118
# subclassing subclass of numpyhttp://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__


119
class MultivariateSequence(View):
120
121
122
123
124
125
126
127
    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
           ----------
128
            TODO threshold : the minimum number of pixels that need to appear
129
                in a certain bin so that the bin will be included in the output
130
131
132
133
                data. (Needs to be modifiable in relation to the total number
                of pixels in the frame, that means on the frame size 240p60
                etc.) Oder Threshold ganz raus, weil das eigentlich eine Sache
                der Visualisierung ist auszusortieren.
134
135
136

           Returns
           -------
137
           Object : MultivariateSequence
138
139
140
141
142
143
               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)
144
        obj._threshold = 6000
145
146
147
        return obj

    def __array_finalize__(self, obj):
148
149
        if obj is None:
            return
150
151
152
        View.__array_finalize__(self, obj)
        self._threshold = getattr(obj, '_threshold', None)

153
    def populate(self, ctrst='monochromatic', method='luminance', frm_stp=4,
154
                 bins=16, thrsh=6000, start=1, end=0):
155
156
157
158
159
160
161
162
163
        """doc (aus __new__ zusammentragen)
        """

        # set class properties
        self._contrast = ctrst
        self._method = method
        self._frame_step = frm_stp
        self._bins = bins
        self._threshold = thrsh
164
165
        # TODO dafür müssen erst getters und setters in movie.frames definiert
        # werden
166
        # self._frames.start = start
167
        # self._frames.end = end
168

169
        contrast_points = np.empty((0, 3), dtype=np.uint32)
170

171
        # pwd list sollte in Frames sein und hier nur durchlaufen werden
172
173
        for frm_nr in range(self._frames.start, self._frames.end,
                            self._frame_step):
174
175
176
177
178
179

            # FIX for legacy numbering scheme of files 1 instead of 0001
            # TODO remove this ridiculous fix and the whole pwd building which
            # now solved by having implemented sorted(Path()…) in Frames
            pwd = ""
            if '_0' in str(self._frames.frames[0]):
180
181
                pwd = self._frames.folder + self._frames.prefix
                pwd += '{0:05d}'.format(frm_nr) + '.png'
182
            else:
183
184
                pwd = self._frames.folder + self._frames.prefix + str(frm_nr)
                pwd += '.png'
185
186
187

            img = cv2.imread(pwd)  # BGR

188
            # Kontrast Parameternamen auf Klassennamen mappen
189
            ctrst_cls = self._get_ctrst_cls_name(self._contrast)
190
            ctrst_img = ctrst_cls(img, self._method).ctrst
191

192
193
194
195
            shape = ctrst_img.shape
            ctrst_img = np.reshape(ctrst_img, (shape[0] * shape[1]))

            hist_value, _ = np.histogram(ctrst_img, bins=self._bins,
196
                                         range=(0, 256))
197
198

            for bin_index, point in enumerate(hist_value):
199
                if int(point) > self._threshold:
200
201
                    entry = np.array([[frm_nr, bin_index, point]],
                                     dtype=np.uint32)
202
                    contrast_points = np.vstack((contrast_points, entry))
203

204
205
        # irgendwie prüfen, ob ich contrast_points insgesamt durch self
        # ersetzen kann
206
        contrast_points = np.asarray(contrast_points, np.uint32)
207
208
209
        shape = contrast_points.shape
        self.resize(shape, refcheck=False)
        self[:, :] = contrast_points
210
        contrast_points = None
211
212

        return deepcopy(self)  # TODO does not create a new object
213
214


215
216
217
218
219
220
221
222
223
224
225
226
class UnivariateSequence(View):
    def __new__(cls, frames, input_array=None):
        """Represents a movie contrast in terms of a single feature

           Parameters
           ----------

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

227
        obj = View.__new__(cls, frames, input_array)
228
        obj.feature = None
229
230
231
        return obj

    def __array_finalize__(self, obj):
232
233
        if obj is None:
            return
234
        self._threshold = getattr(obj, 'feature', None)
235
236
        View.__array_finalize__(self, obj)

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

242
243
244
245
246
        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,
247
                                         range(1, length+1))
248
249
250
251
252
253
        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

254
255
    # TODO: Kurve muß unbeding von cuts bereinigt werde und interpolation
    # funktioniert da nicht
256
    def seqmean(self, ctrst='monochromatic',
257
                method='luminance', frm_stp=4, bins=256):
258
259
260
261
262
263
264
265
266
        """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
267
        self.feature = 'mean'
268
269
270
271
272
273
274
275
276
        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

277
        contrast_points = np.empty((0), dtype=np.uint32)
278
279
280
281

        # 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):
282
283
            pwd = ""
            if '_0' in str(self._frames.frames[0]):
284
285
                pwd = self._frames.folder + self._frames.prefix
                pwd += '{0:05d}'.format(frm_nr) + '.png'
286
            else:
287
288
                pwd = self._frames.folder + self._frames.prefix + str(frm_nr)
                pwd += '.png'
289

290
291
292
            img = cv2.imread(pwd)

            ctrst_cls = self._get_ctrst_cls_name(self._contrast)
293
            ctrst_img = ctrst_cls(img, method=self._method).ctrst
294
295
296
297
298

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

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

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

308
309
            contrast_points = np.hstack((contrast_points,
                                         contrast))
310

311
        contrast_points = np.asarray(contrast_points, np.uint32)
312
313
        shape = contrast_points.shape
        self.resize(shape, refcheck=False)
314
315
316
        self[:] = contrast_points
        contrast_points = None

317
318
    # TODO: Kurve muß unbeding von cuts bereinigt werde und interpolation
    # funktioniert da nicht
319
    def seqmad(self, ctrst='monochromatic',
320
               method='luminance', frm_stp=10, bins=256):
321
322
323
324
325
326
327
328
329
        """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
330
        self.feature = 'absolute_deviation'
331
332
333
334
335
336
337
338
339
340
341
342
343
344
        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):
345
346
            pwd = ""
            if '_0' in str(self._frames.frames[0]):
347
348
                pwd = self._frames.folder + self._frames.prefix
                pwd += '{0:05d}'.format(frm_nr) + '.png'
349
            else:
350
351
                pwd = self._frames.folder + self._frames.prefix + str(frm_nr)
                pwd += '.png'
352

353
354
355
356
357
358
359
360
361
362
363
364
            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
365
366
            # TODO Ergebnisse der unterschiedlichen Verfahrenstimmt nicht
            # überein
367
368
369
370
371
372
373
374
375
376
377
            # 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
378
        contrast_points = None
379

380
    def seqper(self, ctrst='monochromatic',
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
               method='luminance', perc=50, frm_stp=10, bins=256):
        """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
        self.feature = 'percentile ' + str(perc)
        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):
406
407
            pwd = ""
            if '_0' in str(self._frames.frames[0]):
408
409
                pwd = self._frames.folder + self._frames.prefix
                pwd += '{0:05d}'.format(frm_nr) + '.png'
410
            else:
411
412
                pwd = self._frames.folder + self._frames.prefix + str(frm_nr)
                pwd += '.png'
413

414
415
416
            img = cv2.imread(pwd)

            ctrst_cls = self._get_ctrst_cls_name(self._contrast)
417
            ctrst_img = ctrst_cls(img, method=self._method).ctrst
418
419
420
421
422
423
424
425
426
427
428
429

            percentile = np.percentile(ctrst_img.flatten(), perc)

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

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

430
    def seqvar(self, ctrst='monochromatic',
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
               method='luminance', perc=50, frm_stp=10, bins=256):
        """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
        self.feature = 'variance'
        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):
456
457
            pwd = ""
            if '_0' in str(self._frames.frames[0]):
458
459
                pwd = self._frames.folder + self._frames.prefix
                pwd += '{0:05d}'.format(frm_nr) + '.png'
460
            else:
461
462
                pwd = self._frames.folder + self._frames.prefix + str(frm_nr)
                pwd += '.png'
463

464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
            img = cv2.imread(pwd)

            ctrst_cls = self._get_ctrst_cls_name(self._contrast)
            ctrst_img = ctrst_cls(img).ctrst

            variance = np.var(ctrst_img.flatten())

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

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