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
8
import pickle
9
from subprocess import Popen, PIPE, STDOUT
10

11
from . import views
12
from . import visuals
13
from . import helpers
14

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.
19
# numpy gibt beim Versuch zB. size zu schreiben auch ein AttributeError() aus.
20
21
class Movie(object):
    """main class to interact with the colorspace of movieframes"""
22
    def __init__(self, prefix, folder='./', fps=4):
23
        """creates an video object holding the frames of the video
24

25
26
27
        Arguments:
            object {self} -- the video object itself
            prefix {str} -- part of the frames filenames representing the movie
28

29
30
        Keyword Arguments:
            folder {str} -- folder containing the frames (default: {'./'})
31
32
            fps {int} -- number of frames per second that were extracted from
                the movie (default: {4})  # before this were 1
33
34
        """

35
        self._frames = Frames(folder, prefix)
36
        self.fps = fps
37
        self.fsize = self._frames.frm_cnt
38

39
    def showf(self, start, end=0, type='time', mltp=1, viewer='eog'):
40
41
42
43
44
45
46
47
48
49
50
51
52
53
        """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
54
        if type == 'time':
55
56
            start = helpers.time2framenr(start, self.fps)
            end = helpers.time2framenr(end, self.fps)
57
        elif type == 'frame':
58
59
60
            start = int(start) * mltp
            end = int(end) * mltp

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

        cmd = viewer + ' ' + ' '.join(glob_lst)
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
        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)

121
    def analyze(self, ctrst, meth, view, start=1, end=0, stp=5):
122
123
124
125
126
        """Calculates contrast data for a specific contrast in the movie

        Parameters
        ----------
        ctrst: the contrast to be plotted
127
128
129
130
131
132
133
134
135
136
137
138
139
            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

140
        start: Int which defines the start frame for the description
141

142
143
        end: Int which defines the end frame for the description (0 defines
             the unknown end frame)
144

145
146
        stp: the frequency in frame number by which frames are used to
            calculate the contrast
147
148
149

        Returns
        -------
150
151
        TODO: a pandas DataFrame containing the frame number, timestamp, bin
            and number of pixels contained in the bin
152
153
154
155
156
157
        """

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

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

167
168
            return data

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

            return data

175
    def illustrate(self):
176
177
178
        """Visualizes the value of a contrast on frame images
        """
        pass
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
205
206
207
208
    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
209
        - visualize und derive integrieren und dann entsprechend aussortieren
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
        """
        # 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)
232
            # frm_stp wird um 2 erhöht weil es sonst scheiße aussieht
233
            multivariate.populate(ctrst=ctrst, frm_stp=stp+2)
234
235
236
237
238
239
240

            # TODO: pickle instance instead of just deleating it!

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

241
242
            header = prefix[:-1] + ' MultivariatePlot for ' + ctrst + ' - '
            header += desc
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
            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
259
260
            # TODO Varianz funktioniert so nicht
            summarizations = ['seqmean', 'seqmad']
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283

            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
284
285
286
287
            header = prefix[:-1] + ' UnivariatePlot for ' + ctrst + ' - '
            header += desc
            filename = self._frames.prefix + 'univariateplot_' + ctrst + '_'
            filename += desc
288
289
            vis.saveplt(fname=filename, title=header)

290
291
292
        with open(prefix[:-1] + '_summary.pickle', 'wb') as f:
            pickle.dump(summary, f)

293
294
        return summary

295
296

class Frames(object):
297
    # TODO getters und setters für start und end setzen
298
    # TODO frm_stp sollte definitiv Teil der Frames Klasse werden
299
300
301
302
303
304
305
    """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
306
307
308
        self.end = end
        self.frm_cnt = 0

309
310
311
312
    # TODO start als property erzeugt bisher eine Endlosschleife wei
    # count_frames sart und end brauchen und so gegenseitig sart und end nicht
    # gesetzt wird

313
314
315
316
317
318
319
320
    @property
    def end(self):
        return self.__end

    @end.setter
    def end(self, nr):
        if nr == 0:
            self.__end = self.frm_length
321
            self.frm_cnt = self.count_frames()
322
323
        elif nr > self.frm_length:
            self.__end = self.frm_length - 1
324
            self.frm_cnt = self.count_frames()
325
326
        elif nr < self.start:
            self.__end = self.start + 1
327
            self.frm_cnt = self.count_frames()
328
329
        else:
            self.__end = nr
330
            self.frm_cnt = self.count_frames()
331
332
333
334
335
336
337
338
339

    @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
340
341

    def get_frame_list(self):
342
        """returns the list of paths for all frame files in the current folder
343

344
        Returns:
345
346
            list -- returns the list of paths for all frame files in the
                current folder
347
        """
348
        frm_path = Path(self.folder)
349
        return list(sorted(frm_path.glob('*.png')))
350
351
352
353
354
355
356
357
358
359
360
361

    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