From 5d191893f70bba2ec05d85b7c33d94c702129280 Mon Sep 17 00:00:00 2001 From: Mauricio Del Razo Date: Fri, 26 Jul 2013 16:23:39 -0700 Subject: [PATCH 1/4] Modified class for additional functionality --- JSAnimation/html_writer.py | 117 ++++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 54 deletions(-) diff --git a/JSAnimation/html_writer.py b/JSAnimation/html_writer.py index c85d995..c33760b 100644 --- a/JSAnimation/html_writer.py +++ b/JSAnimation/html_writer.py @@ -1,8 +1,7 @@ import os -import warnings -import random -import cStringIO from matplotlib.animation import writers, FileMovieWriter +import tempfile +import random ICON_DIR = os.path.join(os.path.dirname(__file__), 'icons') @@ -22,14 +21,18 @@ def _load_base64(self, filename): data = open(os.path.join(self.icon_dir, filename), 'rb').read() return 'data:image/{0};base64,{1}'.format(self.extension, data.encode('base64')) - + +PREV_INCLUDE = """ +{add_html} +""" JS_INCLUDE = """ """ - DISPLAY_TEMPLATE = """
- +

@@ -179,9 +182,10 @@ def _load_base64(self, filename):
- Once - Loop - Reflect + + Once + Loop + Reflect
@@ -191,6 +195,7 @@ def _load_base64(self, filename): /* The IDs given should match those used in the template above. */ var img_id = "_anim_img{id}"; var slider_id = "_anim_slider{id}"; + var frame_id = "_frameno_id" var loop_select_id = "_anim_loop_select{id}"; var frames = new Array({Nframes}); {fill_frames} @@ -198,22 +203,19 @@ def _load_base64(self, filename): /* set a timeout to make sure all the above elements are created before the object is initialized. */ setTimeout(function() {{ - anim{id} = new Animation(frames, img_id, slider_id, loop_select_id); + anim{id} = new Animation(frames, img_id, slider_id, frame_id, loop_select_id); }}, 0); """ INCLUDED_FRAMES = """ - for (var i=0; i<{Nframes}; i++){{ - frames[i] = "{frame_dir}/frame" + ("0000000" + i).slice(-7) + ".{frame_format}"; - }} + frames = {frame_list} """ -def _included_frames(frame_list, frame_format): +def _included_frames(frame_fullnames): """frame_list should be a list of filenames""" - return INCLUDED_FRAMES.format(Nframes=len(frame_list), - frame_dir=os.path.dirname(frame_list[0]), - frame_format=frame_format) + return INCLUDED_FRAMES.format(Nframes=len(frame_fullnames), + frame_list=frame_fullnames) def _embedded_frames(frame_list, frame_format): """frame_list should be a list of base64-encoded png files""" @@ -224,7 +226,6 @@ def _embedded_frames(frame_list, frame_format): frame_data.replace('\n', '\\\n')) return embedded - @writers.register('html') class HTMLWriter(FileMovieWriter): # we start the animation id count at a random number: this way, if two @@ -235,43 +236,56 @@ class HTMLWriter(FileMovieWriter): args_key = 'animation.ffmpeg_args' supported_formats = ['png', 'jpeg', 'tiff', 'svg'] - def __init__(self, fps=30, codec=None, bitrate=None, extra_args=None, - metadata=None, embed_frames=False, default_mode='loop'): + + def __init__(self, fps=30, codec=None, bitrate=None, extra_args=None, metadata=None, + embed_frames=False, frame_dir=None, add_html='',frame_width=650,interval=30): self.embed_frames = embed_frames - self.default_mode = default_mode.lower() - - if self.default_mode not in ['loop', 'once', 'reflect']: - self.default_mode = 'loop' - warnings.warn("unrecognized default_mode: using 'loop'") - self._saved_frames = list() - super(HTMLWriter, self).__init__(fps, codec, bitrate, - extra_args, metadata) - - def setup(self, fig, outfile, dpi, frame_dir=None): + self.frame_dir = frame_dir + self.add_html = add_html + self.frame_width = frame_width + self.interval = interval + super(HTMLWriter, self).__init__(fps=fps, codec=codec, + bitrate=bitrate, + extra_args=extra_args, + metadata=metadata) + + + def setup(self, fig, outfile, dpi): if os.path.splitext(outfile)[-1] not in ['.html', '.htm']: raise ValueError("outfile must be *.htm or *.html") if not self.embed_frames: - if frame_dir is None: - frame_dir= outfile.rstrip('.html') + '_frames' - if not os.path.exists(frame_dir): - os.makedirs(frame_dir) - frame_prefix = os.path.join(frame_dir, 'frame') + if self.frame_dir is None: + self.frame_dir= outfile.rstrip('.html') + '_frames' + if not os.path.exists(self.frame_dir): + os.makedirs(self.frame_dir) + frame_prefix = os.path.join(self.frame_dir, 'frame') + else: frame_prefix = None super(HTMLWriter, self).setup(fig, outfile, dpi, frame_prefix, clear_temp=False) - + + # Set frame fullname; it can be replaced in child class + def set_framename(self): + frame_fullname = self._temp_names + for i in range(len(self._temp_names)): + frame_name = 'frame' + str(i).zfill(4) + "." + self.frame_format + frame_fullname[i] = os.path.join(self.frame_dir, frame_name) + return frame_fullname + + #def set_framenum(self): + #frame_num = self._temp_names + def grab_frame(self, **savefig_kwargs): if self.embed_frames: - suffix = '.' + self.frame_format - f = cStringIO.StringIO() - self.fig.savefig(f, format=self.frame_format, - dpi=self.dpi, **savefig_kwargs) - f.reset() - self._saved_frames.append(f.read().encode('base64')) + with tempfile.NamedTemporaryFile(suffix='.png') as f: + self.fig.savefig(f.name, format=self.frame_format, + dpi=self.dpi, **savefig_kwargs) + self._saved_frames.append( + open(f.name, 'rb').read().encode('base64')) else: return super(HTMLWriter, self).grab_frame(**savefig_kwargs) @@ -290,23 +304,18 @@ def communicate(self): self.frame_format) else: # temp names is filled by FileMovieWriter - fill_frames = _included_frames(self._temp_names, - self.frame_format) - - mode_dict = dict(once_checked='', - loop_checked='', - reflect_checked='') - mode_dict[self.default_mode + '_checked'] = 'checked' - - interval = int(1000. / self.fps) + frame_fullname = self.set_framename() + fill_frames = _included_frames(frame_fullname) with open(self.outfile, 'w') as of: - of.write(JS_INCLUDE.format(interval=interval)) + if (self.add_html != ''): + of.write(PREV_INCLUDE.format(add_html=self.add_html)) + of.write(JS_INCLUDE.format(interval=self.interval)) of.write(DISPLAY_TEMPLATE.format(id=self.anim_id, Nframes=len(self._temp_names), fill_frames=fill_frames, icons=_Icons(), - **mode_dict)) + frame_width=self.frame_width)) # Increment the counter, so that if multiple animations are made the # variables and element ids won't conflict. From b4f06ab69689d5f44de73b03015930b116e7be10 Mon Sep 17 00:00:00 2001 From: Mauricio Del Razo Date: Tue, 6 Aug 2013 12:21:57 -0700 Subject: [PATCH 2/4] @maojrs modifications under updated version --- JSAnimation/IPython_display.py | 78 +++++++++-- JSAnimation/html_writer.py | 230 ++++++++++++++++++--------------- 2 files changed, 189 insertions(+), 119 deletions(-) diff --git a/JSAnimation/IPython_display.py b/JSAnimation/IPython_display.py index 5f6bf18..70dcb86 100644 --- a/JSAnimation/IPython_display.py +++ b/JSAnimation/IPython_display.py @@ -2,8 +2,63 @@ from matplotlib.animation import Animation import matplotlib.pyplot as plt import tempfile +import random +import os + + +__all__ = ['anim_to_html', 'display_animation'] + + +class _NameOnlyTemporaryFile(object): + """A context-managed temporary file which is not opened. + + The file should be accessible by name on any system. + + Parameters + ---------- + suffix : string + The suffix of the temporary file (default = '') + prefix : string + The prefix of the temporary file (default = '_tmp_') + hash_length : string + The length of the random hash. The size of the hash space will + be 16 ** hash_length (default=8) + seed : integer + the seed for the random number generator. If not specified, the + system time will be used as a seed. + absolute : boolean + If true, return an absolute path to a temporary file in the current + working directory. + + Example + ------- + + >>> with _NameOnlyTemporaryFile(seed=0, absolute=False) as f: + ... print(f) + ... + _tmp_d82c07cd + >>> os.path.exists('_tmp_d82c07cd') # file removed after context + False + + """ + def __init__(self, prefix='_tmp_', suffix='', hash_length=8, + seed=None, absolute=True): + rng = random.Random(seed) + self.name = '%s%0*x%s' % (prefix, hash_length, + rng.getrandbits(4 * hash_length), suffix) + if absolute: + self.name = os.path.abspath(self.name) + + def __enter__(self): + return self + + def __exit__(self, *exc_info): + if os.path.exists(self.name): + os.remove(self.name) + def anim_to_html(anim, fps=None, embed_frames=True, default_mode='loop'): + """Generate HTML representation of the animation""" if fps is None and hasattr(anim, '_interval'): # Convert interval in ms to frames per second fps = 1000. / anim._interval @@ -13,26 +68,25 @@ def anim_to_html(anim, fps=None, embed_frames=True, default_mode='loop'): return anim._html_representation else: # tempfile can't be used here: we need a filename, and this - # fails on windows - + # fails on windows. Instead, we use a custom filename generator #with tempfile.NamedTemporaryFile(suffix='.html') as f: - # anim.save(f.name, writer=HTMLWriter(fps=fps, - # embed_frames=embed_frames, - # default_mode=default_mode)) - # html = open(f.name).read() - - filename = './tmp_output_fdsahkrkdkskrt.html' # poor-man's temp file - anim.save(filename, writer=HTMLWriter(fps=fps, - embed_frames=embed_frames, - default_mode=default_mode)) - html = open(filename).read() + with _NameOnlyTemporaryFile(suffix='.html') as f: + anim.save(f.name, writer=HTMLWriter(fps=fps, + embed_frames=embed_frames, + default_mode=default_mode)) + html = open(f.name).read() + anim._html_representation = html return html def display_animation(anim, **kwargs): + """Display the animation with an IPython HTML object""" from IPython.display import HTML return HTML(anim_to_html(anim, **kwargs)) +# This is the magic that makes animations display automatically in the +# IPython notebook. The _repr_html_ method is a special method recognized +# by IPython. Animation._repr_html_ = anim_to_html diff --git a/JSAnimation/html_writer.py b/JSAnimation/html_writer.py index c33760b..680c38d 100644 --- a/JSAnimation/html_writer.py +++ b/JSAnimation/html_writer.py @@ -1,11 +1,14 @@ import os +import warnings +import random +import cStringIO from matplotlib.animation import writers, FileMovieWriter -import tempfile import random ICON_DIR = os.path.join(os.path.dirname(__file__), 'icons') + class _Icons(object): """This class is a container for base64 representations of the icons""" icons = ['first', 'prev', 'reverse', 'pause', 'play', 'next', 'last'] @@ -21,151 +24,152 @@ def _load_base64(self, filename): data = open(os.path.join(self.icon_dir, filename), 'rb').read() return 'data:image/{0};base64,{1}'.format(self.extension, data.encode('base64')) - + PREV_INCLUDE = """ {add_html} -""" +""" JS_INCLUDE = """ """ + DISPLAY_TEMPLATE = """
@@ -182,10 +186,10 @@ def _load_base64(self, filename):
- - Once - Loop - Reflect + + Once + Loop + Reflect
@@ -193,18 +197,20 @@ def _load_base64(self, filename): """ @@ -212,11 +218,13 @@ def _load_base64(self, filename): frames = {frame_list} """ + def _included_frames(frame_fullnames): """frame_list should be a list of filenames""" return INCLUDED_FRAMES.format(Nframes=len(frame_fullnames), frame_list=frame_fullnames) + def _embedded_frames(frame_list, frame_format): """frame_list should be a list of base64-encoded png files""" template = ' frames[{0}] = "data:image/{1};base64,{2}"\n' @@ -226,30 +234,37 @@ def _embedded_frames(frame_list, frame_format): frame_data.replace('\n', '\\\n')) return embedded + @writers.register('html') class HTMLWriter(FileMovieWriter): # we start the animation id count at a random number: this way, if two # animations are meant to be included on one HTML page, there is a # very small chance of conflict. - anim_id = random.randint(0, 10000000) + rng = random.Random() exec_key = 'animation.ffmpeg_path' args_key = 'animation.ffmpeg_args' supported_formats = ['png', 'jpeg', 'tiff', 'svg'] - - - def __init__(self, fps=30, codec=None, bitrate=None, extra_args=None, metadata=None, - embed_frames=False, frame_dir=None, add_html='',frame_width=650,interval=30): + + @classmethod + def new_id(cls): + return '%16x' % cls.rng.getrandbits(64) + + def __init__(self, fps=30, codec=None, bitrate=None, extra_args=None, + metadata=None, embed_frames=False, frame_dir=None, add_html='', + frame_width=650,default_mode='loop'): self.embed_frames = embed_frames - self._saved_frames = list() self.frame_dir = frame_dir self.add_html = add_html self.frame_width = frame_width - self.interval = interval - super(HTMLWriter, self).__init__(fps=fps, codec=codec, - bitrate=bitrate, - extra_args=extra_args, - metadata=metadata) - + self.default_mode = default_mode.lower() + + if self.default_mode not in ['loop', 'once', 'reflect']: + self.default_mode = 'loop' + warnings.warn("unrecognized default_mode: using 'loop'") + + self._saved_frames = list() + super(HTMLWriter, self).__init__(fps, codec, bitrate, + extra_args, metadata) def setup(self, fig, outfile, dpi): if os.path.splitext(outfile)[-1] not in ['.html', '.htm']: @@ -261,7 +276,6 @@ def setup(self, fig, outfile, dpi): if not os.path.exists(self.frame_dir): os.makedirs(self.frame_dir) frame_prefix = os.path.join(self.frame_dir, 'frame') - else: frame_prefix = None @@ -275,17 +289,15 @@ def set_framename(self): frame_name = 'frame' + str(i).zfill(4) + "." + self.frame_format frame_fullname[i] = os.path.join(self.frame_dir, frame_name) return frame_fullname - - #def set_framenum(self): - #frame_num = self._temp_names - + def grab_frame(self, **savefig_kwargs): if self.embed_frames: - with tempfile.NamedTemporaryFile(suffix='.png') as f: - self.fig.savefig(f.name, format=self.frame_format, - dpi=self.dpi, **savefig_kwargs) - self._saved_frames.append( - open(f.name, 'rb').read().encode('base64')) + suffix = '.' + self.frame_format + f = cStringIO.StringIO() + self.fig.savefig(f, format=self.frame_format, + dpi=self.dpi, **savefig_kwargs) + f.reset() + self._saved_frames.append(f.read().encode('base64')) else: return super(HTMLWriter, self).grab_frame(**savefig_kwargs) @@ -307,17 +319,21 @@ def communicate(self): frame_fullname = self.set_framename() fill_frames = _included_frames(frame_fullname) + mode_dict = dict(once_checked='', + loop_checked='', + reflect_checked='') + mode_dict[self.default_mode + '_checked'] = 'checked' + + interval = int(1000. / self.fps) + with open(self.outfile, 'w') as of: if (self.add_html != ''): - of.write(PREV_INCLUDE.format(add_html=self.add_html)) - of.write(JS_INCLUDE.format(interval=self.interval)) - of.write(DISPLAY_TEMPLATE.format(id=self.anim_id, + of.write(PREV_INCLUDE.format(add_html=self.add_html)) + of.write(JS_INCLUDE) + of.write(DISPLAY_TEMPLATE.format(id=self.new_id(), Nframes=len(self._temp_names), fill_frames=fill_frames, + interval=interval, icons=_Icons(), - frame_width=self.frame_width)) - - # Increment the counter, so that if multiple animations are made the - # variables and element ids won't conflict. - # (this is most useful in IPython notebook) - self.__class__.anim_id += 1 + frame_width=self.frame_width, + **mode_dict)) From c5d7f955c0e0d19e5c02aca7e03cc90579cceef1 Mon Sep 17 00:00:00 2001 From: Mauricio Del Razo Date: Tue, 6 Aug 2013 12:40:28 -0700 Subject: [PATCH 3/4] Fiexed indentation --- JSAnimation/html_writer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/JSAnimation/html_writer.py b/JSAnimation/html_writer.py index 19bba06..046b11b 100644 --- a/JSAnimation/html_writer.py +++ b/JSAnimation/html_writer.py @@ -287,7 +287,7 @@ def setup(self, fig, outfile, dpi): def set_framename(self): frame_fullname = self._temp_names for i in range(len(self._temp_names)): - frame_name = 'frame' + str(i).zfill(4) + "." + self.frame_format + frame_name = 'frame' + str(i).zfill(4) + "." + self.frame_format frame_fullname[i] = os.path.join(self.frame_dir, frame_name) return frame_fullname From be3be0b6275b95db0dd3649acf1eebffddc8e4bb Mon Sep 17 00:00:00 2001 From: Mauricio Del Razo Date: Tue, 6 Aug 2013 12:41:42 -0700 Subject: [PATCH 4/4] Fiexed indentation 2 --- JSAnimation/html_writer.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/JSAnimation/html_writer.py b/JSAnimation/html_writer.py index 046b11b..3da6a51 100644 --- a/JSAnimation/html_writer.py +++ b/JSAnimation/html_writer.py @@ -328,8 +328,8 @@ def communicate(self): interval = int(1000. / self.fps) with open(self.outfile, 'w') as of: - if (self.add_html != ''): - of.write(PREV_INCLUDE.format(add_html=self.add_html)) + if (self.add_html != ''): + of.write(PREV_INCLUDE.format(add_html=self.add_html)) of.write(JS_INCLUDE) of.write(DISPLAY_TEMPLATE.format(id=self.new_id(), Nframes=len(self._temp_names),