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

        # Popen sxiv with Path and image count
        if '_0' in str(self._frames.frames[0]):
            glob = self._frames.folder + self._frames.prefix + '{' + '{0:04d}'.format(start) + '..'\
                    + '{0:04d}'.format(end) + '}' + '.png'
        else:
            glob = self._frames.folder + self._frames.prefix + '{' + str(start) + '..'\
                + str(end) + '}' + '.png'
        cmd = viewer + ' ' + glob
        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
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
187
    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
188
        - visualize und derive integrieren und dann entsprechend aussortieren
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
        """
        # 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)
211
            # frm_stp wird um 2 erhöht weil es sonst scheiße aussieht
212
            multivariate.populate(ctrst=ctrst, frm_stp=stp+2)
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236

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

            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)

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

268
269
        return summary

270
271

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

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

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

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

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

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

    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