-
-
Notifications
You must be signed in to change notification settings - Fork 7.9k
Import JSAnimation into the animation module. (Fixes #4703) #4821
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
lib/matplotlib/animation.py
Outdated
**mode_dict)) | ||
|
||
|
||
def anim_to_jshtml(anim, fps=None, embed_frames=True, default_mode=None): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a specific reason why this is not a method on the animation class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Because the JSAnimation package had it this way
- Because it calls a specific backend, and none of the other backends get a method
- Because.....
I don't have a strong preference here. Anyone have an opinion?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't have any strong preference. I was just comparing it to the HTLM5 method. I realise that is different now because it's not really a new backend. Either is fine with me.
Nice. Seems to work great. It should get a whats new entry and I would like to make sure that we probably credit the work of @jakevdp I guess there is no easy way of retaining the original commit history? At the least it should be mentioned clearly in the whats new entry. It probably also makes sense to get a reference added to the original repository mentioning that this is now part of Matplotlib when we get close to a release with this in. In the long term I think we should replace the present icons with Fort Awesome http://fortawesome.github.io/Font-Awesome/ which we already use in the nbagg backend and which IPython/Jupyter uses internally but that can be done in a separate PR and we don't need to hold this up on that. The tests failures are pep8 and what looks like random failures on Python 2.7 and 3.4 |
I absolutely want to credit @jakevdp. I briefly tried to turn the JSAnimation history for |
I think you are right that applying the original commits as patches is near impossible. I think the most important is that the comments, documentation and whats new correctly reflects the origin of the module |
56fba88
to
74e6175
Compare
I went ahead and used the Font Awesome icons, to avoid having the original icons in the history. It also removed some code and decreased the size of the generated HTML. |
Awesome :) I think this is ready to go but I will leave it a little bit to see if anyone else have comments. |
I suspect that we are going to have be blacklist the animation module from the pep8 tests (unless you want to linewrap html), figure out how make it skip block quotes, or have a data file with the html blob. |
can't you do On Fri, Jul 31, 2015 at 9:09 AM, Thomas A Caswell notifications@github.com
|
I was planning to go ahead and wrap it when I get a chance today. I don't want anyone to later pep8 my file :) |
The reason I never submitted this to mpl is that it's a pretty hackish solution to the goal: it embeds raw png binary data as base64 strings directly in the resulting HTML string. This works OK for animations consisting of a few frames, but as soon as you try to do something marginally bigger the file size blows up. I've had reports from people who crashed their IPython session by trying to make a simple animation. With these limitations in mind, I deliberately chose not to put JSAnimation on PyPI, and not to actually cut a code release, because I didn't feel like dealing with the inevitable flow of issue reports from users who don't understand the quirks of the code. That said, I'm fine with this being in matplotlib, but do keep in mind what it would mean to maintain and support this kind of partial & limited solution. |
@jakevdp I think the HTML5 video solution I just added fills the need for larger animations. I do think the JSAnimation support is useful due to the better controls you can get--provided you don't have too many frames. (These kinds of issues are why it defaults to off.) |
371e7bb
to
0657c02
Compare
I can't look at the PR now; but if it is accepted, will the
limitations be abundantly clear to the user? Jake's message seems to
me like a pretty strong warning that we might regret including
JSAnimation in mpl. We have difficulty keeping up with bugs and user
queries as it is. I don't think "useful" is sufficient justification
at this point.
|
I'm willing to document, but the problem is no different than creating too many image objects in a Python session with the standalone animation support. At some point users who do too much will run into problems--I can bring the notebook to a standstill with a print statement and a stupid loop. I have an animation with 360 images open using JSAnimation right now, with no problems whatsoever--so we're not exactly talking about something really delicate. My opinion is that we will continue to have far more problems reliably running |
The difference is that the HTML output can be persistent. Say someone is using IPython and accidentally creates too many image objects in a conventional animation, bogging down their kernel. They can restart the kernel and try again. No harm done. Say, however, that someone is using the notebook and accidentally embeds gigabytes of pngs into the HTML output, crashing their browser. The only way to recover the notebook is to somehow strip that output, but you can't do that via any of the IPython tools that try to first open the notebook. Unless the user is comfortable editing JSON by hand to remove the offending lines (and has a program that can handle editing a very large file), the notebook is basically lost. I'm not saying that JSAnimation shouldn't be added to mpl; I'm just saying it's a hacked solution and we should be well aware of that before committing it. |
How about adding a limit to size of embedded (base64 encoded) content? This could be another rcparam, working for both this and the html5 video stuff. Default to....250 MB? |
I like that! Rather than an rcparam, though, it could be an argument to the converter. Then if the user tries something too big. Maybe set it purposely low, like ~10MB, and have a very informative early error message (maybe based on the size of the first frame * number of frames) then the user can explicitly allow a huge animation if that's what they want. |
My only problem with no rcparam is how to set it for the IPython display hook. |
Anything holding up this PR? This PR happens to fix a seg-fault for me as mentioned in #4967. |
@jakevdp 's concern (which I am not sure if @dopplershift addressed well enough) and understanding why this fixes the segfault. |
There's the question of whether it should be an rcparam or an explicit argument to the converter (I implemented the former), but otherwise the concerns are addressed IMO. |
lib/matplotlib/animation.py
Outdated
options=' '.join(options)) | ||
return VIDEO_TAG.format(video=self._base64_video, | ||
size=self._video_size, | ||
options=' '.join(options)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should not be indented, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ping. I also think this was not intentional?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No, it was indented so that we only return the tag if the base64-encoded video data was created (i.e. not too large). The mistake was not to have an alternative return if things go wrong. Will fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not clear if this has been addressed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There's an else
right below this comment now. :)
Looks pretty good – I noted a few things. Have you run tests on this branch? That indented code should have broken some things. Also, there is a package that has forked JSAnimation and had to add some script to fix filenames which had spaces in them. I can't say I fully understand the source of the error, but this PR has their fix to the issue. |
@dopplershift I broke this by cherry picking one of your commits into #4995 |
attn @choldgraf @NelleV This is probably the right representation to try to grab to put into sphinx-gallery output. |
Can the js and template code be moved to a private module (just to keep |
9f3b4d1
to
957cd3c
Compare
Done. |
lib/matplotlib/animation.py
Outdated
def new_id(cls): | ||
#return '%16x' % cls.rng.getrandbits(64) | ||
return ''.join(cls.rng.choice(string.ascii_uppercase) | ||
for x in range(16)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just use https://docs.python.org/3.6/library/uuid.html (whichever version you like) and drop rng? (at least I don't think .rng should be exposed)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea, done.
lib/matplotlib/animation.py
Outdated
# Can't open a second time while opened on windows. So we avoid | ||
# deleting when closed, and delete manually later. | ||
with tempfile.NamedTemporaryFile(suffix='.html', delete=False) as f: | ||
anim.save(f.name, writer=HTMLWriter(fps=fps, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't we just write to a BytesIO? (when using a subprocess to encode this would be tricky due to the need to use pipes but here it should be straightforward, no?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd love to, and it makes sense, but I think that's a more invasive a change to the original code (which I didn't write) than I'm comfortable with. Animation itself currently assumes a string filename since it relies heavily on command line tools for writing.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Alternatively, in order to make things work on Windows more easily (I have to deal with that all the time :-() you can create a TemporaryDirectory and use whatever filename you want in it -- then you still get proper cleanup at contextmanager exit. Although that probably means depending on https://github.com/pjdelport/backports.tempfile on AntiquePython (or https://github.com/minrk/backports.temporarydirectory but that doesn't appear to be on PyPI)
TBH I still prefer the BytesIO option, but heh.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It just requires adding an in-memory option alongside the existing file option, and using that for the jshtml converter. Just a little late in the game to be adding that, especially given that I didn't write the HTMLWriter
.
lib/matplotlib/animation.py
Outdated
if vid_len >= embed_limit: | ||
import warnings | ||
warnings.warn('Animation movie is {0} bytes, exceeding ' | ||
'the limit of {1}. If you\'re sure you want a ' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use double quotes around and you won't need to escape single quotes.
No need to number the "{}"'s (here and throughout the PR).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
lib/matplotlib/animation.py
Outdated
|
||
def _repr_html_(self): | ||
'''IPython display hook for rendering.''' | ||
fmt = rcParams['animation.html'] | ||
if fmt == 'html5': | ||
return self.to_html5_video() | ||
elif fmt == 'jshtml': | ||
return anim_to_jshtml(self) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
any reason why anim_to_jshtml is a freestanding function and not a method like to_html5_video? (in which case it should be to_jshtml)
(FTR I don't have anything against freestanding functions, it's more an API consistency thing)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think because this started as an external code, so the function made sense. I've changed to make things more consistent.
lib/matplotlib/_animation.py
Outdated
@@ -0,0 +1,210 @@ | |||
# Javascript template for HTMLWriter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Perhaps _animation_data.py
? (like _color_data.py
)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like it.
957cd3c
to
abf8cc5
Compare
I think I resolved @anntzer 's comments. |
This pulls http://github.com/jakevdp/JSAnimation into the code. Most of this is in the HTMLWriter class. This also adds the `jshtml` option for the animation.html setting.
abf8cc5
to
268d7a0
Compare
Grrr. Fixed pep8 line length issues. |
Not sure if I want to kick AppVeyor again or not, there seems to be some sporadic problem while it runs |
lib/matplotlib/animation.py
Outdated
imgdata64 = encodebytes(f.read()).decode('ascii') | ||
self._total_bytes += len(imgdata64) | ||
if self._total_bytes >= self._bytes_limit: | ||
import warnings |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
move the import to top?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Heh, it's already there. Removed a few instances of the import lower down.
lib/matplotlib/animation.py
Outdated
reflect_checked='') | ||
mode_dict[self.default_mode + '_checked'] = 'checked' | ||
|
||
interval = int(1000. / self.fps) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
1000, no dot (in a py3 world "/" definitely means float div :-))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tends to happen in 2 year old PRs... 😉
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually should probably just use //
since it's casting to int anyway.
lib/matplotlib/animation.py
Outdated
import warnings | ||
warnings.warn("unrecognized default_mode: using 'loop'") | ||
|
||
self._saved_frames = list() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"= []"
lib/matplotlib/animation.py
Outdated
"""Generate HTML representation of the animation""" | ||
if fps is None and hasattr(self, '_interval'): | ||
# Convert interval in ms to frames per second | ||
fps = 1000. / self._interval |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"1000"
lib/matplotlib/animation.py
Outdated
self.fig.savefig(f, format=self.frame_format, | ||
dpi=self.dpi, **savefig_kwargs) | ||
f.seek(0) | ||
imgdata64 = encodebytes(f.read()).decode('ascii') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
f.getvalue(), then you don't need to seek first
shouldn't you just use bytes (and open the final file in binary mode) throughout?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too much template processing with format
to use bytes. Then I'd just have to be using encode
everywhere, which doesn't feel like a win.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Used getvalue
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The argument regarding format sounds reasonable.
This makes both the HTMLWriter and the HTML5 video support check the amount of data being saved into HTML against the rc parameter "animation.embed_limit".
268d7a0
to
ffb2ccd
Compare
Alright, one more time. |
This pulls http://github.com/jakevdp/JSAnimation into the code. Most of
this is in the HTMLWriter class. This also adds the
jshtml
option forthe animation.html setting.