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

4
import numpy as np
5
from pathlib import Path  # TODO wie kann ich third-party module nach außen verstecken
6
import pickle
7
from subprocess import Popen, PIPE, STDOUT
8
9

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

14

15
16
# 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.
17
18
class Movie(object):
    """main class to interact with the colorspace of movieframes"""
19
20
21
22
23
24
25
26
27
28
29
30
    def __init__(self, prefix, folder='./', fps=5):
        """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: {'./'})
            fps {int} -- number of frames per second that were extracted from the movie (default: {5})  # before this were 1
        """

31
        self._frames = Frames(folder, prefix)
32
        self.fps = fps
33
        self.fsize = self._frames.frm_cnt
34

35
    def showf(self, start, end=0, type='time', mltp=1, viewer='eog'):
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
        """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

57
58
59
60
61
62
63
        # 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)
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
104
105
106
107
108
109
110
111
112
113
114
115
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
        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)

    def analyze(self, ctrst, meth, dtype, start=1, end=0, stp=5):
        """Calculates contrast data 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
        dtype: 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

        Returns
        -------
        a view object
        """

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

        if dtype == 'distribution':
            # CTRL funktioniert start/end hier wenn ich es nicht noch einmal
            # mit übergebe ?
            data = views.MultivariateSequence(self._frames,)
            data.populate(ctrst=ctrst, method=meth,
                          frm_stp=stp)
            return data

        elif dtype in ['seqmean', 'seqmad', 'seqvar', 'seqper']:
            data = views.UnivariateSequence(self._frames,)
            getattr(data, dtype)(ctrst=ctrst, method=meth, frm_stp=stp)

            return data

    def illustrate():
        """Visualizes the value of a contrast on frame images
        """
        pass
157

158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
    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
187
        - visualize und derive integrieren und dann entsprechend aussortieren
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
        """
        # 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)
210
            # frm_stp wird um 2 erhöht weil es sonst scheiße aussieht
211
            multivariate.populate(ctrst=ctrst, frm_stp=stp+2)
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235

            # 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
236
            summarizations = ['seqmean', 'seqmad']  # TODO Varianz funktioniert so nicht
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

            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)

264
265
266
        with open(prefix[:-1] + '_summary.pickle', 'wb') as f:
            pickle.dump(summary, f)

267
268
        return summary

269
270

class Frames(object):
271
    # TODO getters und setters für start und end setzen
272
    # TODO frm_stp sollte definitiv Teil der Frames Klasse werden
273
274
275
276
277
278
279
    """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
280
281
282
        self.end = end
        self.frm_cnt = 0

283
284
285
286
    # TODO start als property erzeugt bisher eine Endlosschleife wei
    # count_frames sart und end brauchen und so gegenseitig sart und end nicht
    # gesetzt wird

287
288
289
290
291
292
293
294
    @property
    def end(self):
        return self.__end

    @end.setter
    def end(self, nr):
        if nr == 0:
            self.__end = self.frm_length
295
            self.frm_cnt = self.count_frames()
296
297
        elif nr > self.frm_length:
            self.__end = self.frm_length - 1
298
            self.frm_cnt = self.count_frames()
299
300
        elif nr < self.start:
            self.__end = self.start + 1
301
            self.frm_cnt = self.count_frames()
302
303
        else:
            self.__end = nr
304
            self.frm_cnt = self.count_frames()
305
306
307
308
309
310
311
312
313

    @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
314
315

    def get_frame_list(self):
316
317
318
319
320
        """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
        """
321
        frm_path = Path(self.folder)
322
        return list(sorted(frm_path.glob('*.png')))
323
324
325
326
327
328
329
330
331
332
333
334

    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