movie.py 13.2 KB
Newer Older
1
#!/ur/bin/env python
2
3
# -*- coding: utf-8 -*-

4
import numpy as np
5
6
import pandas as pd

7
from pathlib import Path  # TODO wie kann ich third-party module nach außen verstecken
8
import pickle
9
from subprocess import Popen, PIPE, STDOUT
10
11

# TODO Ist der import aus dem selben Pakte so korrekt?
12
from . import views
13
from . import visuals
14
from . import helpers
15

16

17
18
# zum Problem mit privaten und öffentlichen Eigenschaften http://www.python-course.eu/python3_properties.php und 'Fluent Python' relativ weit vorne
# numpy gibt beim Versuch zB. size zu schreiben auch ein AttributeError() aus.
19
20
class Movie(object):
    """main class to interact with the colorspace of movieframes"""
21
    def __init__(self, prefix, folder='./', fps=4):
22
23
24
25
26
27
28
29
        """creates an video object holding the frames of the video
        
        Arguments:
            object {self} -- the video object itself
            prefix {str} -- part of the frames filenames representing the movie
        
        Keyword Arguments:
            folder {str} -- folder containing the frames (default: {'./'})
30
            fps {int} -- number of frames per second that were extracted from the movie (default: {4})  # before this were 1
31
32
        """

33
        self._frames = Frames(folder, prefix)
34
        self.fps = fps
35
        self.fsize = self._frames.frm_cnt
36

37
    def showf(self, start, end=0, type='time', mltp=1, viewer='eog'):
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
        """Opens frames within the given timespan in an image viewer

           start : timestamp of the start position [hh:]mm:ss or
                  frame number as a string
           end   : timestamp of the end position [hh:]mm:ss or
                  frame number as a string
           type : measure type of from and to. Values 'time' or 'frame'
                  or list
           mltp : use a different multiplyer to multiply frame number
                  than the frame step of the Movie instance
           viewer : image view of choiche (defaults to sxiv)
        """

        # transform time to frames if type is time
        if type is 'time':
            start = helpers.time2framenr(start, self.fps)
            end = helpers.time2framenr(end, self.fps)
        elif type is 'frame':
            start = int(start) * mltp
            end = int(end) * mltp

59
60
61
62
63
64
65
        # funktioniert nur in zsh
        # glob = self._frames.folder + self._frames.prefix + '{' + '{0:05d}'.format(start) + '..' + '{0:05d}'.format(end) + '}' + '.png'
        glob_lst = []
        for frm in range(start, end+1):
            glob_lst.append(self._frames.folder + self._frames.prefix + '{0:05d}'.format(frm) + '.png')

        cmd = viewer + ' ' + ' '.join(glob_lst)
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
104
105
106
107
108
109
110
111
112
113
114
115
116
        Popen(cmd, stdout=PIPE, stderr=STDOUT, shell=True)

        return True

    def visualize(self, ctrst, meth, vtype, start=1, end=0, stp=5,
                  save=False, desc=False):
        """Creates a visualization for a specific contrast in the movie

        Parameters
        ----------
        ctrst: the contrast to be plotted
        method: the method which should be used by the contrast class in order
                to calculate the contrast
        vtype: type of the visualization (distribution, seqmean, seqmad, etc)
        start: Int which defines the start frame for the description
        end: Int which defines the end frame for the description (0 defines
             the unknown end frame)
        stp: the frequency in sec by which frames are used to calculate the
             contrast
        save: wether the plot should be saved to a file or not TODO
        desc: a description which is used as a title and filename for the
              visualization

        Returns
        -------
        a tuple containing a view and a matplotlib object
        """
        # set default plot description if no description is provided
        if not(desc):
            desc = 'Frame ' + str(start) + ' bis ' + str(end)

        data = self.analyze(ctrst, meth, vtype, start, end, stp)

        # build visualization for the selected visualization type
        if vtype == 'distribution':
            vis = visuals.MultivariatePlot(data)
        elif vtype in ['seqmean', 'seqmad', 'seqvar', 'seqper']:
            vis = visuals.UnivariatePlot(data)

        vis.plot(data)

        return data, vis

        # export plot to file
        if save:
            filename = self._frames.prefix + vtype + 'plot' + '_' + ctrst + \
                       '_' + desc
            header = self._frames.prefix + vtype + 'plot' + ' for ' + \
                ctrst + ' - ' + desc
            vis.saveplt(fname=filename, title=header)

117
    def analyze(self, ctrst, meth, view, start=1, end=0, stp=5):
118
119
120
121
122
        """Calculates contrast data for a specific contrast in the movie

        Parameters
        ----------
        ctrst: the contrast to be plotted
123
124
125
126
127
128
129
130
131
132
133
134
135
            possible values are: monochromatic, saturation, hue, cold-warm,
            complementary

        method: the method by which the peculiar contrast is calculated.
            Possible values depend on the contrast type and include:

            luminance, value, lightness, redniss_lab (monochromatic)
            saturation, colourfulness (saturation)
            hsV, labHcl (hue)

        view: the way contrast is modeled in the data
            possible values are: distribution, seqmean, seqmad, seqvar, seqper

136
        start: Int which defines the start frame for the description
137

138
139
        end: Int which defines the end frame for the description (0 defines
             the unknown end frame)
140
141
142

        stp: the frequency in frame number by which frames are used to calculate
            the contrast
143
144
145

        Returns
        -------
146
147
        TODO: a pandas DataFrame containing the frame number, timestamp, bin and
            number of pixels contained in the bin
148
149
150
151
152
153
        """

        # set movie start and end postion for the visualization
        self._frames.start = start
        self._frames.end = end

154
        if view == 'distribution':
155
156
157
            # CTRL funktioniert start/end hier wenn ich es nicht noch einmal
            # mit übergebe ?
            data = views.MultivariateSequence(self._frames,)
158
            data.populate(ctrst=ctrst, method=meth, frm_stp=stp)
159
160
            data = pd.DataFrame(data, columns=['frame', 'bin', 'pixels'],
                dtype=np.int64)
161
162
            data['time'] = data['frame'].apply(helpers.frame2time, fps=4)

163
164
            return data

165
        elif view in ['seqmean', 'seqmad', 'seqvar', 'seqper']:
166
            data = views.UnivariateSequence(self._frames,)
167
            getattr(data, view)(ctrst=ctrst, method=meth, frm_stp=stp)
168
169
170
171
172
173
174

            return data

    def illustrate():
        """Visualizes the value of a contrast on frame images
        """
        pass
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
    def describe(self, desc=False, start=1, end=0, stp=5):
        """Calculates each feature provided by Itten for the movie instance

        Summary values will be returned in a python dictionary. Contrast
        sequences will not be returned due to save memory. The method which
        will be used to compute each contrast is the contrast class' default
        method. Instead the will be plotted using Itten Sequence Plots. More
        precisely MultivariatePlot and UnivariatePlot will be stored on disk
        for each contrast type.  The filename follows the Pattern prefix +
        contrast.

        Parameters
        ----------
        start: Int which defines the start frame for the description
        end: Int which defines the end frame for the description (0 defines
             the unknown end frame)

        Returns
        -------
        Dictionary: Dictionary which contains summarizing statistics The
                    Dictionary keys carry the names of the type of
                    summarization. For each contrast it contains: min, max,
                    mean, median, 25, 75, standard deviation, variance. And for
                    each of this values the same set of values are computed.
        ToDo
        ----
        - outsource summary/feature loop to function which can be accessed
          by the view class
        - pickle view data
205
        - visualize und derive integrieren und dann entsprechend aussortieren
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
        """
        # set properties of the current function call
        self._frames.start = start
        self._frames.end = end
        prefix = self._frames.prefix
        if not(desc):
            desc = 'Frame ' + str(start) + ' bis ' + str(end)

        # Summary Dictionary
        title = prefix[:-1]
        summary = {title: {}}

        # available contrasts
        contrasts = ['saturation', 'light_dark']

        # compute all available statistics for each contrast
        for ctrst in contrasts:

            summary[title][ctrst] = {}

            # compute multivariatee contrast representation
            multivariate = views.MultivariateSequence(self._frames)
228
            # frm_stp wird um 2 erhöht weil es sonst scheiße aussieht
229
            multivariate.populate(ctrst=ctrst, frm_stp=stp+2)
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253

            # TODO: pickle instance instead of just deleating it!

            # plot multivariate view
            vis = visuals.MultivariatePlot(multivariate)
            vis.plot(multivariate)

            header = prefix[:-1] + ' MultivariatePlot for ' + ctrst + ' - ' + desc
            filename = prefix + 'multivariateplot_' + ctrst + '_' + desc
            vis.saveplt(fname=filename, title=header)

            # compute summarizations for current contrast
            univariate = views.UnivariateSequence(self._frames)

            # TODO Workaround, becaus frm_stp is not part of Frames classbut
            # view class so when I instantiate vis below it defaults to the
            # __init__ value 10 and not the value set by the call of describe.
            # This leads to a matplotlib error
            univariate._frame_step = stp

            # instantiate plot for all univariate summarizations
            vis = visuals.UnivariatePlot(univariate)

            # summarizing methods of univariate class
254
            summarizations = ['seqmean', 'seqmad']  # TODO Varianz funktioniert so nicht
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281

            for feature in summarizations:
                # compute summarizations for given univariate value
                getattr(univariate, feature)(ctrst=ctrst, frm_stp=stp)

                # TODO Pickle current instance state

                # describe current feature by summarizing statistics
                summary[title][ctrst][feature[3:]] = {
                        'minv': int(univariate.min()),
                        'maxv': int(univariate.max()),
                        'mean': int(univariate.mean()),
                        'median': int(np.median(univariate)),
                        'perc25': int(np.percentile(univariate, 25)),
                        'perc75': int(np.percentile(univariate, 75)),
                        'std': int(univariate.std()),
                        'var': int(univariate.var())
                        }

                # plot current summarization current univariate contrast plot
                vis.plot(univariate)

            # save univariate plot
            header = prefix[:-1] + ' UnivariatePlot for ' + ctrst + ' - ' + desc
            filename = self._frames.prefix + 'univariateplot_' + ctrst + '_' + desc
            vis.saveplt(fname=filename, title=header)

282
283
284
        with open(prefix[:-1] + '_summary.pickle', 'wb') as f:
            pickle.dump(summary, f)

285
286
        return summary

287
288

class Frames(object):
289
    # TODO getters und setters für start und end setzen
290
    # TODO frm_stp sollte definitiv Teil der Frames Klasse werden
291
292
293
294
295
296
297
    """Parses movie frames properties"""
    def __init__(self, folder, prefix, start=1, end=0):
        self.folder = folder
        self.prefix = prefix
        self.frames = self.get_frame_list()
        self.frm_length = self.count_total_frames()
        self.start = start
298
299
300
        self.end = end
        self.frm_cnt = 0

301
302
303
304
    # TODO start als property erzeugt bisher eine Endlosschleife wei
    # count_frames sart und end brauchen und so gegenseitig sart und end nicht
    # gesetzt wird

305
306
307
308
309
310
311
312
    @property
    def end(self):
        return self.__end

    @end.setter
    def end(self, nr):
        if nr == 0:
            self.__end = self.frm_length
313
            self.frm_cnt = self.count_frames()
314
315
        elif nr > self.frm_length:
            self.__end = self.frm_length - 1
316
            self.frm_cnt = self.count_frames()
317
318
        elif nr < self.start:
            self.__end = self.start + 1
319
            self.frm_cnt = self.count_frames()
320
321
        else:
            self.__end = nr
322
            self.frm_cnt = self.count_frames()
323
324
325
326
327
328
329
330
331

    @property
    def frm_cnt(self):
        return self.__frm_cnt

    @frm_cnt.setter
    def frm_cnt(self, nr):
        count = self.count_frames()
        self.__frm_cnt = count
332
333

    def get_frame_list(self):
334
335
336
337
338
        """returns the list of paths for all frame files in the current folder
        
        Returns:
            list -- returns the list of paths for all frame files in the current folder
        """
339
        frm_path = Path(self.folder)
340
        return list(sorted(frm_path.glob('*.png')))
341
342
343
344
345
346
347
348
349
350
351
352

    def count_total_frames(self):
        return len(self.frames)

    def count_frames(self):
        return self.end - (self.start - 1)

    def _get_end_frame(self, end):
        if end == 0:
            return self.frm_length
        else:
            return end