Ticket #12827: trac_12827_animate3d_save_image.patch
| File trac_12827_animate3d_save_image.patch, 24.3 KB (added by niles, 14 months ago) |
|---|
-
sage/plot/all.py
# HG changeset patch # User Niles Johnson # Date 1334691602 14400 # Node ID 5d73e5413c6fe84d03e265308f75955e2cebdefd # Parent c239be1054e01526a1b0b62da6691061b9dd5587 Trac ticket 12827: implements save_image method for major graphics types to allow animating of those types diff --git a/sage/plot/all.py b/sage/plot/all.py
a b 26 26 27 27 from arc import arc 28 28 29 from animate import Animation asanimate29 from animate import animate 30 30 31 31 from plot3d.tachyon import Tachyon 32 32 -
sage/plot/animate.py
diff --git a/sage/plot/animate.py b/sage/plot/animate.py
a b 1 """1 r""" 2 2 Animated plots 3 3 4 Animations are generated from a list (or other iteratable) of graphics 5 objects. Images are produced by calling the ``save_image`` method on 6 each input object, and using ImageMagick's ``convert`` program [IM] or 7 ``ffmpeg`` [FF] to generate an animation. The output format is GIF by 8 default, but can be any of the formats supported by ``convert`` or 9 ``ffmpeg``. 10 11 .. Warning:: 12 13 Note that ImageMagick and FFmpeg are not included with Sage, and 14 must be installed by the user. On unix systems, type ``which 15 convert`` at a command prompt to see if ``convert`` is installed. 16 If it is, you will be given its location. See [IM] or [FF] for 17 installation instructions. 18 19 4 20 EXAMPLES: 5 We plot a circle shooting up to the right::6 21 7 sage: a = animate([circle((i,i), 1-1/(i+1), hue=i/10) for i in srange(0,2,0.2)], 22 The sine function:: 23 24 sage: sines = [plot(c*sin(x), (-2*pi,2*pi), color=Color(c,0,0), ymin=-1,ymax=1) for c in sxrange(0,1,.2)] 25 sage: a = animate(sines) 26 sage: a 27 Animation with 5 frames 28 sage: a.show() # not tested 29 30 An animated :class:`sage.plot.plot.GraphicsArray` of rotating ellipses:: 31 32 sage: ellipses = (graphics_array([[ellipse((0,0),a,b,angle=t,xmin=-3,xmax=3)+circle((0,0),3,color='blue') for a in range(1,3)] for b in range(2,4)]) for t in sxrange(0,pi/4,.15)) 33 sage: E = animate(ellipses) 34 sage: E # animations produced from a generator do not have a known length 35 Animation with unknown number of frames 36 sage: E.show() # not tested 37 38 A simple animation of a circle shooting up to the right:: 39 40 sage: shoot = animate([circle((i,i), 1-1/(i+1), hue=i/10) for i in srange(0,2,0.2)], 8 41 ... xmin=0,ymin=0,xmax=2,ymax=2,figsize=[2,2]) 9 sage: a.show() # optional -- ImageMagick 42 sage: shoot.show() # optional -- ImageMagick 43 44 An animation of 3d objects:: 45 46 sage: var('s,t') 47 (s, t) 48 sage: def sphere_and_plane(x): 49 ... return sphere((0,0,0),1,color='red',opacity=.5)+parametric_plot3d([t,x,s],(s,-1,1),(t,-1,1),color='green',opacity=.7) 50 ... 51 sage: sp = animate([sphere_and_plane(x) for x in sxrange(-1,1,.3)]) 52 sage: sp[0] # first frame 53 sage: sp[-1] # last frame 54 sage: sp.show() # not tested 55 56 If the input objects do not have a ``save_image`` method, then the 57 animation object attempts to make an image by calling its internal 58 method :meth:`sage.plot.animate.Animation.make_image`. This is 59 illustrated by the following example:: 60 61 62 sage: t = var('t') 63 sage: a = animate((sin(c*pi*t) for c in sxrange(1,2,.2))) 64 sage: a.show() # optional -- ImageMagick 65 66 67 68 REFERENCES: 69 70 .. [IM] http://www.imagemagick.org 71 72 .. [FF] http://www.ffmpeg.org 73 10 74 """ 11 75 12 76 ############################################################################ … … 23 87 import sage.misc.misc 24 88 import sage.misc.viewer 25 89 90 91 def animate(frames, **kwds): 92 r""" 93 Animate a list of frames by creating a 94 :class:`sage.plot.animate.Animation` object. 95 96 EXAMPLES:: 97 98 sage: t = var('t') 99 sage: a = animate((cos(c*pi*t) for c in sxrange(1,2,.2))) 100 sage: a.show() # optional -- ImageMagick 101 """ 102 return Animation(frames, **kwds) 103 26 104 class Animation(SageObject): 27 105 r""" 28 106 Return an animation of a sequence of plots of objects. 29 107 30 108 INPUT: 31 109 32 33 - ``v`` - list of Sage objects. These should 34 preferably be graphics objects, but if they aren't then plot is 110 - ``v`` - iterable of Sage objects. These should preferably be 111 graphics objects, but if they aren't then :meth:`make_image` is 35 112 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 113 114 - ``xmin, xmax, ymin, ymax`` - the ranges of the x and y axes. 115 116 - ``**kwds`` - all additional inputs are passed onto the rendering 117 command. E.g., use figsize to adjust the resolution and aspect 118 ratio. 44 119 45 120 EXAMPLES:: 46 121 … … 53 128 sage: a.show() # optional -- ImageMagick 54 129 sage: a[:5].show() # optional -- ImageMagick 55 130 56 The ``show`` function takes arguments to specify the131 The :meth:`show` function takes arguments to specify the 57 132 delay between frames (measured in hundredths of a second, default 58 133 value 20) and the number of iterations (default value 0, which 59 134 means to iterate forever). To iterate 4 times with half a second … … 72 147 sage: a = animate(v, xmin=0, ymin=0) 73 148 sage: a.show() # optional -- ImageMagick 74 149 sage: show(L) 150 151 152 TESTS: 75 153 76 T ESTS: This illustrates that ticket #2066 is fixed (setting axes77 rangeswhen an endpoint is 0)::154 This illustrates that ticket #2066 is fixed (setting axes ranges 155 when an endpoint is 0):: 78 156 79 157 sage: animate([plot(sin, -1,1)], xmin=0, ymin=0)._Animation__kwds['xmin'] 80 158 0 … … 85 163 ... for k in srange(0,2*pi,0.3)]) 86 164 sage: a.show() # optional -- ImageMagick 87 165 """ 88 def __init__(self, v , **kwds):166 def __init__(self, v=None, **kwds): 89 167 r""" 90 Return an animation of a sequence of plots of objects. 91 92 See documentation of ``animate`` for more details and examples. 168 Return an animation of a sequence of plots of objects. See 169 documentation of :func:`animate` for more details and examples. 93 170 94 171 EXAMPLES:: 95 172 96 173 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]) 174 ... xmin=0, xmax=2*pi, figsize=[2,1]) # indirect doctest 98 175 sage: a 99 176 Animation with 21 frames 100 177 """ 101 w = [] 102 for x in v: 103 if not isinstance(x, plot.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 = [plot.Graphics()] 108 self.__frames = w 178 # if typecheck_input: 179 # w = [] 180 # for x in v: 181 # if not is_graphic(x): 182 # x = plot.plot(x, (kwds.get('xmin',0), kwds.get('xmax', 1))) 183 # w.append(x) 184 # if len(w) == 0: 185 # w = [plot.Graphics()] 186 # else: 187 # w = v 188 self.__frames = v 109 189 self.__kwds = kwds 110 190 111 191 def _combine_kwds(self, *kwds_tuple): 112 192 """ 113 193 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 194 dictionaries in kwds_tuple. This also does the appropriate 195 thing for taking the mins and maxes of all of the x/y 196 mins/maxes. 197 117 198 EXAMPLES:: 118 199 119 200 sage: a = animate([plot(sin, -1,1)], xmin=0, ymin=0) … … 143 224 EXAMPLES:: 144 225 145 226 sage: a = animate([x, x^2, x^3, x^4]) 146 sage: a[2].show() # optional -- ImageMagick 227 sage: frame2 = a[2] # indirect doctest 228 sage: frame2.show() # optional -- ImageMagick 147 229 """ 148 230 return self.__frames[i] 149 231 … … 158 240 sage: a 159 241 Animation with 10 frames 160 242 sage: a.show() # optional -- ImageMagick 161 sage: a[3:7] 243 sage: a[3:7] # indirect doctest 162 244 Animation with 4 frames 163 245 sage: a[3:7].show() # optional -- ImageMagick 164 246 """ … … 177 259 sage: a._repr_() 178 260 'Animation with 10 frames' 179 261 """ 180 return "Animation with %s frames"%(len(self.__frames)) 262 try: 263 num = len(self) 264 except TypeError: 265 num = "unknown number of" 266 return "Animation with %s frames"%num 267 181 268 182 269 def __add__(self, other): 183 270 """ 184 Add two animations. This has the effect of superimposing the two185 animations frame-by-frame.271 Add two animations. This has the effect of superimposing the 272 two animations frame-by-frame. 186 273 187 EXAMPLES: We add and multiply two animations. 188 189 :: 274 EXAMPLES:: 190 275 191 276 sage: a = animate([circle((i,0),1) for i in srange(0,2,0.4)], 192 277 ... xmin=0, ymin=-1, xmax=3, ymax=1, figsize=[2,1]) … … 194 279 sage: b = animate([circle((0,i),1,hue=0) for i in srange(0,2,0.4)], 195 280 ... xmin=0, ymin=-1, xmax=1, ymax=3, figsize=[1,2]) 196 281 sage: b.show() # optional 197 sage: (a*b).show() # optional -- ImageMagick 198 sage: (a+b).show() # optional -- ImageMagick 282 sage: s = a+b # indirect doctest 283 sage: len(a), len(b) 284 (5, 5) 285 sage: len(s) 286 5 287 sage: s.show() # optional -- ImageMagick 199 288 """ 200 289 if not isinstance(other, Animation): 201 290 other = Animation(other) … … 211 300 212 301 def __mul__(self, other): 213 302 """ 214 Multiply two animations. This has the effect of appending the two215 animations (the second comes after the first).303 Multiply two animations. This has the effect of appending the 304 two animations (the second comes after the first). 216 305 217 EXAMPLES: We add and multiply two animations. 218 219 :: 306 EXAMPLES:: 220 307 221 308 sage: a = animate([circle((i,0),1,thickness=20*i) for i in srange(0,2,0.4)], 222 309 ... xmin=0, ymin=-1, xmax=3, ymax=1, figsize=[2,1], axes=False) 223 310 sage: a.show() # optional -- ImageMagick 224 311 sage: b = animate([circle((0,i),1,hue=0,thickness=20*i) for i in srange(0,2,0.4)], 225 312 ... xmin=0, ymin=-1, xmax=1, ymax=3, figsize=[1,2], axes=False) 226 sage: b.show() # optional -- ImageMagick 227 sage: (a*b).show() # optional -- ImageMagick 228 sage: (a+b).show() # optional -- ImageMagick 313 sage: p = a*b # indirect doctest 314 sage: len(a), len(b) 315 (5, 5) 316 sage: len(p) 317 10 318 sage: p.show() # optional -- ImageMagick 229 319 """ 230 320 if not isinstance(other, Animation): 231 321 other = Animation(other) … … 234 324 235 325 return Animation(self.__frames + other.__frames, **kwds) 236 326 327 def __len__(self): 328 """ 329 Length of self 330 331 EXAMPLES:: 332 sage: a = animate([circle((i,0),1,thickness=20*i) for i in srange(0,2,0.4)], 333 ... xmin=0, ymin=-1, xmax=3, ymax=1, figsize=[2,1], axes=False) 334 sage: len(a) 335 5 336 """ 337 return len(self.__frames) 338 339 def duration(self): 340 """ 341 This method is not yet implemented. 342 Return duration of this animation as a Python ``timedelta``. 343 344 EXAMPLES:: 345 346 sage: a = animate([x, x^2, x^3, x^4]) 347 sage: a.duration() # not tested 348 """ 349 #from datetime import timedelta 350 raise NotImplementedError('duration is not implemented') 351 352 def make_image(self, frame, filename, **kwds): 353 r""" 354 Given a frame which has no ``save_image()`` method, make a graphics 355 object and save it as an image with the given filename. By default, this is 356 :meth:`sage.plot.plot.plot`. To make animations of other objects, 357 override this method in a subclass. 358 359 EXAMPLES:: 360 361 sage: from sage.plot.animate import Animation 362 sage: class MyAnimation(Animation): 363 ... def make_image(self, frame, filename, **kwds): 364 ... P = parametric_plot(frame[0], frame[1], **frame[2]) 365 ... P.save_image(filename,**kwds) 366 367 sage: t = var('t') 368 sage: x = lambda t: cos(t) 369 sage: y = lambda n,t: sin(t)/n 370 sage: B = MyAnimation([([x(t), y(i+1,t)],(t,0,1), {'color':Color((1,0,i/4)), 'aspect_ratio':1, 'ymax':1}) for i in range(4)]) 371 372 sage: d = B.png() 373 sage: v = os.listdir(d); v.sort(); v 374 ['00000000.png', '00000001.png', '00000002.png', '00000003.png'] 375 sage: B.show() #not tested 376 377 sage: class MyAnimation(Animation): 378 ... def make_image(self, frame, filename, **kwds): 379 ... G = frame.plot() 380 ... G.set_axes_range(floor(G.xmin()),ceil(G.xmax()),floor(G.ymin()),ceil(G.ymax())) 381 ... G.save_image(filename, **kwds) 382 383 sage: B = MyAnimation([graphs.CompleteGraph(n) for n in range(7,11)], figsize=5) 384 sage: d = B.png() 385 sage: v = os.listdir(d); v.sort(); v 386 ['00000000.png', '00000001.png', '00000002.png', '00000003.png'] 387 sage: B.show() #not tested 388 389 """ 390 p = plot.plot(frame) 391 p.save_image(filename, **kwds) 392 237 393 def png(self, dir=None): 238 """ 239 Return the absolute path to a temp directory that contains the 240 rendered PNG's of all the images in this animation. 394 r""" 395 Render PNG images of the frames in this animation, saving them 396 in `dir`. Return the absolute path to that directory. If the 397 frames have been previously rendered, just return the 398 directory in which they are stored. In this case, the input 399 `dir` is ignored. 400 401 INPUT:: 402 403 - dir -- Directory in which to store frames. Default 404 `None`; in this case, a temporary directory will be 405 created for storing the frames. 241 406 242 407 EXAMPLES:: 243 408 … … 250 415 return self.__png_dir 251 416 except AttributeError: 252 417 pass 253 d = sage.misc.misc.tmp_dir() 418 if dir is None: 419 d = sage.misc.misc.tmp_dir() 420 else: 421 d = dir 254 422 G = self.__frames 255 423 for i, frame in enumerate(self.__frames): 424 # if not is_graphic(frame): 425 # frame = self.make_graphic(frame) 256 426 filename = '%s/%s'%(d,sage.misc.misc.pad_zeros(i,8)) 257 frame.save(filename + '.png', **self.__kwds) 427 # frame.save(filename + '.png', **self.__kwds) 428 try: 429 frame.save_image(filename + '.png', **self.__kwds) 430 except AttributeError: 431 self.make_image(frame, filename + '.png', **self.__kwds) 258 432 self.__png_dir = d 259 433 return d 260 434 261 435 def graphics_array(self, ncols=3): 262 436 """ 263 Return a graphics array with the given number of columns with plots 264 of the frames of this animation. 437 Return a :class:`sage.plot.plot.GraphicsArray` with plots of the 438 frames of this animation, using the given number of columns. 439 The frames must be acceptable inputs for 440 :class:`sage.plot.plot.GraphicsArray`. 265 441 266 442 EXAMPLES:: 267 443 … … 285 461 sage: print g 286 462 Graphics Array of size 2 x 2 287 463 sage: g.show('sage.png') # optional 464 465 :: 466 467 sage: T = animate([tetrahedron(size=s)+cube(size=1,opacity=.1) for s in sxrange(.2,.6,.15)]) 468 sage: d = T.png() # long time 469 sage: T.graphics_array() # long time 470 Traceback (most recent call last) 471 ... 472 TypeError: 'Graphics3dGroup' object is not iterable 288 473 """ 289 474 n = len(self.__frames) 290 475 ncols = int(ncols) … … 299 484 This function will only work if either (a) the ImageMagick 300 485 software suite is installed, i.e., you have the ``convert`` 301 486 command or (b) ``ffmpeg`` is installed. See 302 www.imagemagick.org for more about ImageMagic , and see487 www.imagemagick.org for more about ImageMagick, and see 303 488 www.ffmpeg.org for more about ``ffmpeg``. By default, this 304 489 produces the gif using ``convert`` if it is present. If this 305 490 can't find ``convert`` or if ``use_ffmpeg`` is True, then it … … 322 507 - ``use_ffmpeg`` - boolean (default: False); if True, use 323 508 'ffmpeg' by default instead of 'convert'. 324 509 325 If savefileis not specified: in notebook mode, display the510 If ``savefile`` is not specified: in notebook mode, display the 326 511 animation; otherwise, save it to a default file name. 327 512 328 513 EXAMPLES:: … … 388 573 print "Animation saved to file %s." % savefile 389 574 except (CalledProcessError, OSError): 390 575 msg = """ 391 Error: Neither ImageMagick nor ffmpeg appears to be installed. Saving an 392 animation to a GIF file or displaying an animation requires one of these 393 packages, so please install one of them and try again. 576 Error: Cannot generate GIF animation. Verify that convert (ImageMagick) or ffmpeg is installed, and that the objects passed to the animate command can be saved in PNG image format. 394 577 395 578 See www.imagemagick.org and www.ffmpeg.org for more information.""" 579 396 580 raise OSError, msg 397 581 398 582 def show(self, delay=20, iterations=0): … … 495 679 suffix to use for the video. This may be 'mpg', 'mpeg', 496 680 'avi', 'gif', or any other format that ffmpeg can handle. 497 681 If this is None and the user specifies ``savefile`` with a 498 suffix, say 'savefile=animation.avi', try to determine the682 suffix, say ``savefile=animation.avi``, try to determine the 499 683 format ('avi' in this case) from that file name. If no file 500 684 is specified or if the suffix cannot be determined, 'mpg' is 501 685 used. … … 515 699 - ``verbose`` - boolean (default: False); if True, print 516 700 messages produced by the ffmpeg command. 517 701 518 If savefileis not specified: in notebook mode, display the702 If ``savefile`` is not specified: in notebook mode, display the 519 703 animation; otherwise, save it to a default file name. 520 704 521 705 EXAMPLES:: -
sage/plot/plot.py
diff --git a/sage/plot/plot.py b/sage/plot/plot.py
a b 2385 2385 # free limits autoscale 2386 2386 #subplot.autoscale_view(tight=True) 2387 2387 return figure 2388 2388 2389 2390 def save_image(self, filename=None, *args, **kwds): 2391 r""" 2392 Save an image representation of self. The image type is 2393 determined by the extension of the filename. For example, 2394 this could be ``.png``, ``.jpg``, ``.gif``, ``.pdf``, 2395 ``.svg``. Currently this is implemented by calling the 2396 :meth:`save` method of self, passing along all arguments and 2397 keywords. 2398 2399 .. Note:: 2400 2401 Not all image types are necessarily implemented for all 2402 graphics types. See :meth:`save` for more details. 2403 2404 EXAMPLES:: 2405 2406 sage: c = circle((1,1), 1, color='red') 2407 sage: filename = os.path.join(SAGE_TMP, 'test.png') 2408 sage: c.save_image(filename, xmin=-1, xmax=3, ymin=-1, ymax=3) 2409 """ 2410 self.save(filename, *args, **kwds) 2411 2412 2389 2413 # ALLOWED_EXTENSIONS is the list of recognized formats. 2390 2414 # filename argument is written explicitly so that it can be used as a 2391 2415 # positional one, which is a very likely usage for this function. … … 3817 3841 g.save(filename, dpi=dpi, figure=figure, sub=subplot, 3818 3842 verify=do_verify, axes = axes, **args) 3819 3843 3844 def save_image(self, filename=None, *args, **kwds): 3845 r""" 3846 Save an image representation of self. The image type is 3847 determined by the extension of the filename. For example, 3848 this could be ``.png``, ``.jpg``, ``.gif``, ``.pdf``, 3849 ``.svg``. Currently this is implemented by calling the 3850 :meth:`save` method of self, passing along all arguments and 3851 keywords. 3852 3853 .. Note:: 3854 3855 Not all image types are necessarily implemented for all 3856 graphics types. See :meth:`save` for more details. 3857 3858 EXAMPLES:: 3859 3860 sage: plots = [[plot(m*cos(x + n*pi/4), (x,0, 2*pi)) for n in range(3)] for m in range(1,3)] 3861 sage: G = graphics_array(plots) 3862 sage: G.save_image(tmp_filename()+'.png') 3863 """ 3864 self.save(filename, *args, **kwds) 3865 3820 3866 def save(self, filename=None, dpi=DEFAULT_DPI, figsize=None, 3821 3867 axes = None, **args): 3822 3868 """ 3823 save the ``graphics_array`` to (for now) a png called3869 Save the ``graphics_array`` to (for now) a png called 3824 3870 'filename'. 3871 3872 EXAMPLES:: 3873 3874 sage: plots = [[parametric_plot([(1/m)*cos(x),(1/n)*sin(x)], (x,0, 2*pi)) for n in range(1,3)] for m in range(1,3)] 3875 sage: G = graphics_array(plots) 3876 sage: G.save(tmp_filename()+'.png') 3825 3877 """ 3826 3878 if (figsize is not None): self.__set_figsize__(figsize) 3827 3879 self._render(filename, dpi=dpi, figsize=self._figsize, axes = axes, **args) -
sage/plot/plot3d/base.pyx
diff --git a/sage/plot/plot3d/base.pyx b/sage/plot/plot3d/base.pyx
a b 1148 1148 pipes = "2>/dev/null 1>/dev/null &" 1149 1149 os.system('%s "%s.%s" %s' % (viewer_app, filename, ext, pipes)) 1150 1150 1151 def save_image(self, filename=None, *args, **kwds): 1152 r""" 1153 Save an image representation of self. The image type is 1154 determined by the extension of the filename. For example, 1155 this could be ``.png``, ``.jpg``, ``.gif``, ``.pdf``, 1156 ``.svg``. Currently this is implemented by calling the 1157 :meth:`save` method of self, passing along all arguments and 1158 keywords. 1159 1160 .. Note:: 1161 1162 Not all image types are necessarily implemented for all 1163 graphics types. See :meth:`save` for more details. 1164 1165 EXAMPLES:: 1166 1167 sage: f = tmp_filename() + '.png' 1168 sage: G = sphere() 1169 sage: G.save_image(f) 1170 """ 1171 self.save(filename, *args, **kwds) 1172 1151 1173 def save(self, filename, **kwds): 1152 1174 """ 1153 1175 Save the graphic to an image file (of type: PNG, BMP, GIF, PPM, or TIFF) -
sage/plot/plot3d/tachyon.py
diff --git a/sage/plot/plot3d/tachyon.py b/sage/plot/plot3d/tachyon.py
a b 252 252 """ 253 253 return self.str() 254 254 255 def save_image(self, filename=None, *args, **kwds): 256 r""" 257 Save an image representation of self. The image type is 258 determined by the extension of the filename. For example, 259 this could be ``.png``, ``.jpg``, ``.gif``, ``.pdf``, 260 ``.svg``. Currently this is implemented by calling the 261 :meth:`save` method of self, passing along all arguments and 262 keywords. 263 264 .. Note:: 265 266 Not all image types are necessarily implemented for all 267 graphics types. See :meth:`save` for more details. 268 269 EXAMPLES:: 270 271 sage: q = Tachyon() 272 sage: q.light((1,1,11), 1,(1,1,1)) 273 sage: q.texture('s') 274 sage: q.sphere((0,-1,1),1,'s') 275 sage: tempname = tmp_filename() 276 sage: q.save_image(tempname) 277 """ 278 self.save(filename, *args, **kwds) 279 255 280 def save(self, filename='sage.png', verbose=0, block=True, extra_opts=''): 256 281 r""" 257 282 INPUT:
