Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/plot/animate.py
4034 views
1
"""
2
Animated plots
3
4
EXAMPLES:
5
We plot a circle shooting up to the right::
6
7
sage: a = animate([circle((i,i), 1-1/(i+1), hue=i/10) for i in srange(0,2,0.2)],
8
... xmin=0,ymin=0,xmax=2,ymax=2,figsize=[2,2])
9
sage: a.show() # optional -- ImageMagick
10
"""
11
12
############################################################################
13
# Copyright (C) 2007 William Stein <[email protected]>
14
# Distributed under the terms of the GNU General Public License (GPL)
15
# http://www.gnu.org/licenses/
16
############################################################################
17
18
import os
19
20
from sage.structure.sage_object import SageObject
21
22
import plot
23
import sage.misc.misc
24
import sage.misc.viewer
25
26
class Animation(SageObject):
27
r"""
28
Return an animation of a sequence of plots of objects.
29
30
INPUT:
31
32
33
- ``v`` - list of Sage objects. These should
34
preferably be graphics objects, but if they aren't then plot is
35
called on them.
36
37
- ``xmin, xmax, ymin, ymax`` - the ranges of the x and
38
y axes.
39
40
- ``**kwds`` - all additional inputs are passed onto
41
the rendering command. E.g., use figsize to adjust the resolution
42
and aspect ratio.
43
44
45
EXAMPLES::
46
47
sage: a = animate([sin(x + float(k)) for k in srange(0,2*pi,0.3)],
48
... xmin=0, xmax=2*pi, figsize=[2,1])
49
sage: a
50
Animation with 21 frames
51
sage: a[:5]
52
Animation with 5 frames
53
sage: a.show() # optional -- ImageMagick
54
sage: a[:5].show() # optional -- ImageMagick
55
56
The ``show`` function takes arguments to specify the
57
delay between frames (measured in hundredths of a second, default
58
value 20) and the number of iterations (default value 0, which
59
means to iterate forever). To iterate 4 times with half a second
60
between each frame::
61
62
sage: a.show(delay=50, iterations=4) # optional -- ImageMagick
63
64
An animation of drawing a parabola::
65
66
sage: step = 0.1
67
sage: L = Graphics()
68
sage: v = []
69
sage: for i in srange(0,1,step):
70
... L += line([(i,i^2),(i+step,(i+step)^2)], rgbcolor=(1,0,0), thickness=2)
71
... v.append(L)
72
sage: a = animate(v, xmin=0, ymin=0)
73
sage: a.show() # optional -- ImageMagick
74
sage: show(L)
75
76
TESTS: This illustrates that ticket #2066 is fixed (setting axes
77
ranges when an endpoint is 0)::
78
79
sage: animate([plot(sin, -1,1)], xmin=0, ymin=0)._Animation__kwds['xmin']
80
0
81
82
We check that Trac #7981 is fixed::
83
84
sage: a = animate([plot(sin(x + float(k)), (0, 2*pi), ymin=-5, ymax=5)
85
... for k in srange(0,2*pi,0.3)])
86
sage: a.show() # optional -- ImageMagick
87
"""
88
def __init__(self, v, **kwds):
89
r"""
90
Return an animation of a sequence of plots of objects.
91
92
See documentation of ``animate`` for more details and examples.
93
94
EXAMPLES::
95
96
sage: a = animate([sin(x + float(k)) for k in srange(0,2*pi,0.3)],
97
... xmin=0, xmax=2*pi, figsize=[2,1])
98
sage: a
99
Animation with 21 frames
100
"""
101
w = []
102
for x in v:
103
if not isinstance(x, sage.plot.graphics.Graphics):
104
x = plot.plot(x, (kwds.get('xmin',0), kwds.get('xmax', 1)))
105
w.append(x)
106
if len(w) == 0:
107
w = [sage.plot.graphics.Graphics()]
108
self.__frames = w
109
self.__kwds = kwds
110
111
def _combine_kwds(self, *kwds_tuple):
112
"""
113
Returns a dictionary which is a combination of the all the
114
dictionaries in kwds_tuple. This also does the appropriate thing
115
for taking the mins and maxes of all of the x/y mins/maxes.
116
117
EXAMPLES::
118
119
sage: a = animate([plot(sin, -1,1)], xmin=0, ymin=0)
120
sage: kwds1 = {'a':1, 'b':2, 'xmin':2, 'xmax':5}
121
sage: kwds2 = {'b':3, 'xmin':0, 'xmax':4}
122
sage: kwds = a._combine_kwds(kwds1, kwds2)
123
sage: list(sorted(kwds.items()))
124
[('a', 1), ('b', 3), ('xmax', 5), ('xmin', 0)]
125
126
Test that the bug reported in ticket #12107 has been fixed::
127
128
sage: kwds3 = {}
129
sage: kwds4 = {'b':3, 'xmin':0, 'xmax':4}
130
sage: a._combine_kwds(kwds3, kwds4)['xmin']
131
0
132
"""
133
new_kwds = {}
134
135
for kwds in kwds_tuple:
136
new_kwds.update(kwds)
137
138
import __builtin__
139
for name in ['xmin', 'xmax', 'ymin', 'ymax']:
140
values = [v for v in [kwds.get(name, None) for kwds in kwds_tuple] if v is not None]
141
if values:
142
new_kwds[name] = getattr(__builtin__, name[1:])(values)
143
return new_kwds
144
145
146
def __getitem__(self, i):
147
"""
148
Get a frame from an animation or
149
slice this animation returning a subanimation.
150
151
EXAMPLES::
152
153
sage: a = animate([x, x^2, x^3, x^4])
154
sage: a[2].show() # optional -- ImageMagick
155
sage: a = animate([circle((i,-i), 1-1/(i+1), hue=i/10) for i in srange(0,2,0.2)],
156
... xmin=0,ymin=-2,xmax=2,ymax=0,figsize=[2,2])
157
sage: a
158
Animation with 10 frames
159
sage: a.show() # optional -- ImageMagick
160
sage: a[3:7]
161
Animation with 4 frames
162
sage: a[3:7].show() # optional -- ImageMagick
163
"""
164
if isinstance(i, slice):
165
return Animation(self.__frames[i], **self.__kwds)
166
else:
167
return self.__frames[i]
168
169
def _repr_(self):
170
"""
171
Print representation for an animation.
172
173
EXAMPLES::
174
175
sage: a = animate([circle((i,-i), 1-1/(i+1), hue=i/10) for i in srange(0,2,0.2)],
176
... xmin=0,ymin=-2,xmax=2,ymax=0,figsize=[2,2])
177
sage: a
178
Animation with 10 frames
179
sage: a._repr_()
180
'Animation with 10 frames'
181
"""
182
return "Animation with %s frames"%(len(self.__frames))
183
184
def __add__(self, other):
185
"""
186
Add two animations. This has the effect of superimposing the two
187
animations frame-by-frame.
188
189
EXAMPLES: We add and multiply two animations.
190
191
::
192
193
sage: a = animate([circle((i,0),1) for i in srange(0,2,0.4)],
194
... xmin=0, ymin=-1, xmax=3, ymax=1, figsize=[2,1])
195
sage: a.show() # optional -- ImageMagick
196
sage: b = animate([circle((0,i),1,hue=0) for i in srange(0,2,0.4)],
197
... xmin=0, ymin=-1, xmax=1, ymax=3, figsize=[1,2])
198
sage: b.show() # optional -- ImageMagick
199
sage: (a*b).show() # optional -- ImageMagick
200
sage: (a+b).show() # optional -- ImageMagick
201
"""
202
if not isinstance(other, Animation):
203
other = Animation(other)
204
205
kwds = self._combine_kwds(self.__kwds, other.__kwds)
206
207
#Combine the frames
208
m = max(len(self.__frames), len(other.__frames))
209
frames = [a+b for a,b in zip(self.__frames, other.__frames)]
210
frames += self.__frames[m:] + other.__frames[m:]
211
212
return Animation(frames, **kwds)
213
214
def __mul__(self, other):
215
"""
216
Multiply two animations. This has the effect of appending the two
217
animations (the second comes after the first).
218
219
EXAMPLES: We add and multiply two animations.
220
221
::
222
223
sage: a = animate([circle((i,0),1,thickness=20*i) for i in srange(0,2,0.4)],
224
... xmin=0, ymin=-1, xmax=3, ymax=1, figsize=[2,1], axes=False)
225
sage: a.show() # optional -- ImageMagick
226
sage: b = animate([circle((0,i),1,hue=0,thickness=20*i) for i in srange(0,2,0.4)],
227
... xmin=0, ymin=-1, xmax=1, ymax=3, figsize=[1,2], axes=False)
228
sage: b.show() # optional -- ImageMagick
229
sage: (a*b).show() # optional -- ImageMagick
230
sage: (a+b).show() # optional -- ImageMagick
231
"""
232
if not isinstance(other, Animation):
233
other = Animation(other)
234
235
kwds = self._combine_kwds(self.__kwds, other.__kwds)
236
237
return Animation(self.__frames + other.__frames, **kwds)
238
239
def png(self, dir=None):
240
"""
241
Return the absolute path to a temp directory that contains the
242
rendered PNG's of all the images in this animation.
243
244
EXAMPLES::
245
246
sage: a = animate([plot(x^2 + n) for n in range(4)])
247
sage: d = a.png()
248
sage: v = os.listdir(d); v.sort(); v
249
['00000000.png', '00000001.png', '00000002.png', '00000003.png']
250
"""
251
try:
252
return self.__png_dir
253
except AttributeError:
254
pass
255
d = sage.misc.misc.tmp_dir()
256
G = self.__frames
257
for i, frame in enumerate(self.__frames):
258
filename = '%s/%s'%(d,sage.misc.misc.pad_zeros(i,8))
259
frame.save(filename + '.png', **self.__kwds)
260
self.__png_dir = d
261
return d
262
263
def graphics_array(self, ncols=3):
264
"""
265
Return a graphics array with the given number of columns with plots
266
of the frames of this animation.
267
268
EXAMPLES::
269
270
sage: E = EllipticCurve('37a')
271
sage: v = [E.change_ring(GF(p)).plot(pointsize=30) for p in [97, 101, 103, 107]]
272
sage: a = animate(v, xmin=0, ymin=0)
273
sage: a
274
Animation with 4 frames
275
sage: a.show() # optional -- ImageMagick
276
277
::
278
279
sage: g = a.graphics_array()
280
sage: print g
281
Graphics Array of size 1 x 3
282
sage: g.show(figsize=[4,1]) # optional
283
284
::
285
286
sage: g = a.graphics_array(ncols=2)
287
sage: print g
288
Graphics Array of size 2 x 2
289
sage: g.show('sage.png') # optional
290
"""
291
n = len(self.__frames)
292
ncols = int(ncols)
293
return plot.graphics_array(self.__frames, int(n/ncols), ncols)
294
295
def gif(self, delay=20, savefile=None, iterations=0, show_path=False,
296
use_ffmpeg=False):
297
r"""
298
Returns an animated gif composed from rendering the graphics
299
objects in self.
300
301
This function will only work if either (a) the ImageMagick
302
software suite is installed, i.e., you have the ``convert``
303
command or (b) ``ffmpeg`` is installed. See
304
www.imagemagick.org for more about ImageMagic, and see
305
www.ffmpeg.org for more about ``ffmpeg``. By default, this
306
produces the gif using ``convert`` if it is present. If this
307
can't find ``convert`` or if ``use_ffmpeg`` is True, then it
308
uses ``ffmpeg`` instead.
309
310
INPUT:
311
312
- ``delay`` - (default: 20) delay in hundredths of a
313
second between frames
314
315
- ``savefile`` - file that the animated gif gets saved
316
to
317
318
- ``iterations`` - integer (default: 0); number of
319
iterations of animation. If 0, loop forever.
320
321
- ``show_path`` - boolean (default: False); if True,
322
print the path to the saved file
323
324
- ``use_ffmpeg`` - boolean (default: False); if True, use
325
'ffmpeg' by default instead of 'convert'.
326
327
If savefile is not specified: in notebook mode, display the
328
animation; otherwise, save it to a default file name.
329
330
EXAMPLES::
331
332
sage: a = animate([sin(x + float(k)) for k in srange(0,2*pi,0.7)],
333
... xmin=0, xmax=2*pi, figsize=[2,1])
334
sage: dir = tmp_dir() + '/'
335
sage: a.gif() # not tested
336
sage: a.gif(savefile=dir + 'my_animation.gif', delay=35, iterations=3) # optional -- ImageMagick
337
sage: a.gif(savefile=dir + 'my_animation.gif', show_path=True) # optional -- ImageMagick
338
Animation saved to .../my_animation.gif.
339
sage: a.gif(savefile=dir + 'my_animation.gif', show_path=True, use_ffmpeg=True) # optional -- ffmpeg
340
Animation saved to .../my_animation.gif.
341
342
.. note::
343
344
If neither ffmpeg nor ImageMagick is installed, you will
345
get an error message like this::
346
347
Error: Neither ImageMagick nor ffmpeg appears to be installed. Saving an
348
animation to a GIF file or displaying an animation requires one of these
349
packages, so please install one of them and try again.
350
351
See www.imagemagick.org and www.ffmpeg.org for more information.
352
353
AUTHORS:
354
355
- William Stein
356
"""
357
from sage.misc.sage_ostools import have_program
358
have_convert = have_program('convert')
359
have_ffmpeg = self._have_ffmpeg()
360
if use_ffmpeg or not have_convert:
361
if have_ffmpeg:
362
self.ffmpeg(savefile=savefile, show_path=show_path,
363
output_format='gif', delay=delay,
364
iterations=iterations)
365
else:
366
if not have_convert:
367
msg = """
368
Error: Neither ImageMagick nor ffmpeg appears to be installed. Saving an
369
animation to a GIF file or displaying an animation requires one of these
370
packages, so please install one of them and try again.
371
372
See www.imagemagick.org and www.ffmpeg.org for more information."""
373
else:
374
msg = """
375
Error: ffmpeg does not appear to be installed. Download it from
376
www.ffmpeg.org, or use 'convert' to produce gifs instead."""
377
raise OSError, msg
378
else:
379
if not savefile:
380
savefile = sage.misc.misc.graphics_filename(ext='gif')
381
if not savefile.endswith('.gif'):
382
savefile += '.gif'
383
savefile = os.path.abspath(savefile)
384
d = self.png()
385
cmd = ( 'cd "%s"; sage-native-execute convert -dispose Background '
386
'-delay %s -loop %s *.png "%s"' ) % ( d, int(delay),
387
int(iterations), savefile )
388
from subprocess import check_call, CalledProcessError
389
try:
390
check_call(cmd, shell=True)
391
if show_path:
392
print "Animation saved to file %s." % savefile
393
except (CalledProcessError, OSError):
394
msg = """
395
Error: Neither ImageMagick nor ffmpeg appears to be installed. Saving an
396
animation to a GIF file or displaying an animation requires one of these
397
packages, so please install one of them and try again.
398
399
See www.imagemagick.org and www.ffmpeg.org for more information."""
400
raise OSError, msg
401
402
def show(self, delay=20, iterations=0):
403
r"""
404
Show this animation.
405
406
INPUT:
407
408
409
- ``delay`` - (default: 20) delay in hundredths of a
410
second between frames
411
412
- ``iterations`` - integer (default: 0); number of
413
iterations of animation. If 0, loop forever.
414
415
416
.. note::
417
418
Currently this is done using an animated gif, though this
419
could change in the future. This requires that either
420
ffmpeg or the ImageMagick suite (in particular, the
421
``convert`` command) is installed.
422
423
See also the :meth:`ffmpeg` method.
424
425
EXAMPLES::
426
427
sage: a = animate([sin(x + float(k)) for k in srange(0,2*pi,0.7)],
428
... xmin=0, xmax=2*pi, figsize=[2,1])
429
sage: a.show() # optional -- ImageMagick
430
431
The preceding will loop the animation forever. If you want to show
432
only three iterations instead::
433
434
sage: a.show(iterations=3) # optional -- ImageMagick
435
436
To put a half-second delay between frames::
437
438
sage: a.show(delay=50) # optional -- ImageMagick
439
440
.. note::
441
442
If you don't have ffmpeg or ImageMagick installed, you will
443
get an error message like this::
444
445
Error: Neither ImageMagick nor ffmpeg appears to be installed. Saving an
446
animation to a GIF file or displaying an animation requires one of these
447
packages, so please install one of them and try again.
448
449
See www.imagemagick.org and www.ffmpeg.org for more information.
450
"""
451
if plot.DOCTEST_MODE:
452
filename = sage.misc.misc.tmp_filename() + '.gif'
453
self.gif(savefile=filename, delay=delay, iterations=iterations)
454
return
455
456
if plot.EMBEDDED_MODE:
457
self.gif(delay = delay, iterations = iterations)
458
else:
459
filename = sage.misc.misc.tmp_filename() + '.gif'
460
self.gif(delay=delay, savefile=filename, iterations=iterations)
461
os.system('%s %s 2>/dev/null 1>/dev/null &'%(
462
sage.misc.viewer.browser(), filename))
463
464
def _have_ffmpeg(self):
465
"""
466
Return True if the program 'ffmpeg' is installed. See
467
www.ffmpeg.org to download ffmpeg.
468
469
EXAMPLES::
470
471
sage: a = animate([plot(sin, -1,1)], xmin=0, ymin=0)
472
sage: a._have_ffmpeg() # random: depends on whether ffmpeg is installed
473
False
474
"""
475
from sage.misc.sage_ostools import have_program
476
return have_program('ffmpeg')
477
478
def ffmpeg(self, savefile=None, show_path=False, output_format=None,
479
ffmpeg_options='', delay=None, iterations=0, verbose=False):
480
"""
481
Returns a movie showing an animation composed from rendering
482
the graphics objects in self.
483
484
This function will only work if ffmpeg is installed. See
485
http://www.ffmpeg.org for information about ffmpeg.
486
487
INPUT:
488
489
- ``savefile`` - file that the mpeg gets saved to.
490
491
.. warning:
492
493
This will overwrite ``savefile`` if it already exists.
494
495
- ``show_path`` - boolean (default: False); if True, print the
496
path to the saved file
497
498
- ``output_format`` - string (default: None); format and
499
suffix to use for the video. This may be 'mpg', 'mpeg',
500
'avi', 'gif', or any other format that ffmpeg can handle.
501
If this is None and the user specifies ``savefile`` with a
502
suffix, say 'savefile=animation.avi', try to determine the
503
format ('avi' in this case) from that file name. If no file
504
is specified or if the suffix cannot be determined, 'mpg' is
505
used.
506
507
- ``ffmpeg_options`` - string (default: ''); this string is
508
passed directly to ffmpeg.
509
510
- ``delay`` - integer (default: None) delay in hundredths of a
511
second between frames; i.e., the framerate is 100/delay.
512
This is not supported for mpeg files: for mpegs, the frame
513
rate is always 25 fps.
514
515
- ``iterations`` - integer (default: 0); number of iterations
516
of animation. If 0, loop forever. This is only supported
517
for animated gif output.
518
519
- ``verbose`` - boolean (default: False); if True, print
520
messages produced by the ffmpeg command.
521
522
If savefile is not specified: in notebook mode, display the
523
animation; otherwise, save it to a default file name.
524
525
EXAMPLES::
526
527
sage: a = animate([sin(x + float(k)) for k in srange(0,2*pi,0.7)],
528
... xmin=0, xmax=2*pi, figsize=[2,1])
529
sage: dir = tmp_dir() + '/'
530
sage: a.ffmpeg(savefile=dir + 'new.mpg') # optional -- ffmpeg
531
sage: a.ffmpeg(savefile=dir + 'new.avi') # optional -- ffmpeg
532
sage: a.ffmpeg(savefile=dir + 'new.gif') # optional -- ffmpeg
533
sage: a.ffmpeg(savefile=dir + 'new.mpg', show_path=True) # optional -- ffmpeg
534
Animation saved to .../new.mpg.
535
536
.. note::
537
538
If ffmpeg is not installed, you will get an error message
539
like this::
540
541
Error: ffmpeg does not appear to be installed. Saving an animation to
542
a movie file in any format other than GIF requires this software, so
543
please install it and try again.
544
545
See www.ffmpeg.org for more information.
546
"""
547
if not self._have_ffmpeg():
548
msg = """Error: ffmpeg does not appear to be installed. Saving an animation to
549
a movie file in any format other than GIF requires this software, so
550
please install it and try again."""
551
raise OSError, msg
552
else:
553
if not savefile:
554
if output_format is None:
555
output_format = 'mpg'
556
savefile = sage.misc.misc.graphics_filename(ext=output_format)
557
else:
558
if output_format is None:
559
suffix = os.path.splitext(savefile)[1]
560
if len(suffix) > 0:
561
suffix = suffix.lstrip('.')
562
output_format = suffix
563
else:
564
output_format = 'mpg'
565
if not savefile.endswith('.' + output_format):
566
savefile += '.' + output_format
567
early_options = ''
568
if output_format == 'gif':
569
ffmpeg_options += ' -pix_fmt rgb24 -loop_output %s ' % iterations
570
if delay is not None and output_format != 'mpeg' and output_format != 'mpg':
571
early_options += ' -r %s -g 3 ' % int(100/delay)
572
savefile = os.path.abspath(savefile)
573
pngdir = self.png()
574
pngs = os.path.join(pngdir, "%08d.png")
575
# For ffmpeg, it seems that some options, like '-g ... -r
576
# ...', need to come before the input file names, while
577
# some options, like '-pix_fmt rgb24', need to come
578
# afterwards. Hence 'early_options' and 'ffmpeg_options'
579
cmd = 'cd "%s"; sage-native-execute ffmpeg -y -f image2 %s -i %s %s %s' % (pngdir, early_options, pngs, ffmpeg_options, savefile)
580
from subprocess import check_call, CalledProcessError, PIPE
581
try:
582
if verbose:
583
print "Executing %s " % cmd
584
check_call(cmd, shell=True)
585
else:
586
check_call(cmd, shell=True, stderr=PIPE)
587
if show_path:
588
print "Animation saved to file %s." % savefile
589
except (CalledProcessError, OSError):
590
print "Error running ffmpeg."
591
raise
592
593
def save(self, filename=None, show_path=False, use_ffmpeg=False):
594
"""
595
Save this animation.
596
597
INPUT:
598
599
- ``filename`` - (default: None) name of save file
600
601
- ``show_path`` - boolean (default: False); if True,
602
print the path to the saved file
603
604
- ``use_ffmpeg`` - boolean (default: False); if True, use
605
'ffmpeg' by default instead of 'convert' when creating GIF
606
files.
607
608
If filename is None, then in notebook mode, display the
609
animation; otherwise, save the animation to a GIF file. If
610
filename ends in '.sobj', save to an sobj file. Otherwise,
611
try to determine the format from the filename extension
612
('.mpg', '.gif', '.avi', etc.). If the format cannot be
613
determined, default to gif.
614
615
For GIF files, either ffmpeg or the ImageMagick suite must be
616
installed. For other movie formats, ffmpeg must be installed.
617
An sobj file can be saved with no extra software installed.
618
619
EXAMPLES::
620
621
sage: a = animate([sin(x + float(k)) for k in srange(0,2*pi,0.7)],
622
... xmin=0, xmax=2*pi, figsize=[2,1])
623
sage: dir = tmp_dir() + '/'
624
sage: a.save() # not tested
625
sage: a.save(dir + 'wave.gif') # optional -- ImageMagick
626
sage: a.save(dir + 'wave.gif', show_path=True) # optional -- ImageMagick
627
Animation saved to file .../wave.gif.
628
sage: a.save(dir + 'wave.avi', show_path=True) # optional -- ffmpeg
629
Animation saved to file .../wave.avi.
630
sage: a.save(dir + 'wave0.sobj')
631
sage: a.save(dir + 'wave1.sobj', show_path=True)
632
Animation saved to file .../wave1.sobj.
633
"""
634
if filename is None:
635
suffix = '.gif'
636
else:
637
suffix = os.path.splitext(filename)[1]
638
if len(suffix) == 0:
639
suffix = '.gif'
640
641
if filename is None or suffix == '.gif':
642
self.gif(savefile=filename, show_path=show_path,
643
use_ffmpeg=use_ffmpeg)
644
return
645
elif suffix == '.sobj':
646
SageObject.save(self, filename)
647
if show_path:
648
print "Animation saved to file %s." % filename
649
return
650
else:
651
self.ffmpeg(savefile=filename, show_path=show_path)
652
return
653
654