-
-
Notifications
You must be signed in to change notification settings - Fork 8k
endless looping GIFs with PillowWriter #11789
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
Checklist 1: I have no idea how I would create a pytest for this.
Checklist 3,4: I want to add the loop parameter to the PillowWrite.__init__
docstring, but I believe that I would have to copy the entire docstring from MovieWriter.__init__
, and then add the extra parameter, so that the sphinx-autodoc will work properly. That's that best way I can think of doing it, is it?
Checklist 5: It's a minor new feature. So, I shouldn't add this?
Checklist 6: It is an api change, but it shouldn't break anything? Like who relied on a gif that only looped once?
- can you interrogate the gif header for the loop parameter? Probably not so important for a test here.
3/4 Maybe just in the MovieWriter doc string say that loop exists if the writer is Pillow. For triple bonus points you could look into the other writers and see if there is a loop method for those.
5/6 I think a whats new entry is good, but no need for an API entry - thats generally only if API is changed, not if some is added.
Oh, well in that case it’d be good to quickly go through the writers and document their behaviour.
Warning, treated as error:
/home/circleci/project/doc/users/next_whats_new/endless_gif.rst:document isn't included in any toctree
I think this is just since the next_whats_new toctree is commented out in whats_new.rst
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.
anyone can dismiss once comments handled
lib/matplotlib/animation.py
Outdated
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.
__init__(self, *args, loop=False, **kwargs)
or at least make it a boolean.
Argument needs to be documented in some docstring.
Please check for API consistency across writers; if the behavior of other writers is to loop then the default should be changed here.
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 Pillow image.save function takes the loop
parameter as an integer, so a gif could be make to loop a set number of times. Also, loop=False
might make sense in the docstring, but loop=self.loop
where self.loop
is False
is an endless loop since False==0
. I suppose I could change it to loop=not self.loop
, but should we not allow setting to loop say 5 times?
I'll change it to a Boolean if you still want. (I suppose no one needs a gif to loop exactly 137 times!)
I like the move to the loop
kwarg in the __init__
function definition. I'll do that for sure.
HTMLWriter and ImageMagick both default to looping I believe, but the ffmpegwriter writing to a .mp4 wouldn't loop. Whether defaulting to loop or not really depends on the format. Gif's should loop, mp4's can't. Different formats have different looping properties (HTMLWriter has a 'reflect' option, so it has a default_mode
parameter that takes a string instead of a loop
parameter.
I'll default it to looping (unless you object).
I know that I need to put it in a docstring, but I'm having difficulty figuring out where. I'm not familiar with sphinx-autodoc. I really could use help here. I could write an entire new docstring based off the base class and add the parameter. Or I could do as @jklymak suggested, but then other writers would have a "loop parameter only available in PillowWriter".
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.
Something went wrong with the formatting.
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 the file has windows line endings. I will change that. Other than that I don't know.
I think you could accept a Boolean and convert to an integer internally; just check if a Boolean. OTOH it’s a bit confusing that 0 is loop and 1 is loop only once. Agree that specifying the number loops is desiarable option
I suppose I could do
self.loop = not loop if isintance(loop, bool) else loop
then set the default to loop=True
. Then in whatever docstring say that it accepts a Boolean or an integer that specifies number of loops. This way it is clear that True is endless, False is once, and an integer >=1 is an integer.
What do people think?
More that I think about it maybe just make it an integer and value error on anything else so the Boolean doesn’t get passed by accident. Maybe just lay out the options here and folks can vote 🗳 (I’d do it but am on phone). Sorry to drag out a simple change but may as well get it right.
Option 1:
Booleans only.
Pros:
- Simple.
Cons:
- can't set a finite >= 1 integer of loops to display.
Implemented as
self.loop = not loop
Option 2:
ints (this is what it currently is, but without the ValueError)
Pros:
- Can set a finite >= 1 integer of loops to display.
Cons:
- 0 == infinity
Implemented as
# can't use isinstance since bools are ints if type(loop) == int: self.loop = loop else: raise ValueError("loop must be an int")
Option 3:
Both bools and ints
Pros:
- simple True is endless and False plays once
- finite number of loops is possible
Cons:
- We have to explain that both are excepted.
Implemented as
self.loop = not loop if isinstance(loop, bool) else loop
Documentation will be updated to reflect whatever choice.
I'm in favour of option 2 or 3.
I vote 2 + ValueError, just because I think loop=False will actually pass loop=0 if its not caught, but I may be wrong.
I added the ValueError. If we did want to support both bools and ints in the future, then it is easier to add that, then it is to remove support for bools after the fact.
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.
👍 on the functionality. Minor tweak to the int
check if you're interested.
lib/matplotlib/animation.py
Outdated
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.
So much easier to do this now that we're Python 3 only.
lib/matplotlib/animation.py
Outdated
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.
if not isinstance(loop, int): raise ValueError(...) self.loop = loop
?
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.
bools are ints. So not isinstance(True, int)
will return false. Which will result in passing a value of True
aka 1
to loop which won't loop, but one would expect loop=True to loop. See my option 3 for a way to implement support for both bools and ints, but this will force just ints
.
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.
Though I suppose
if type(loop) != int: raise ValueError(...) self.loop=loop
But it only saves one of code, and I don't really feel like pushing another commit to this unless I have too 😛.
One other question on further thought: since this is not a boolean flag, but a number of loops, should the parameter be loops
rather than loop
?
That's not a bad point. But I think that the plural loops
makes the fact that a value of 0 results in endless looping make even less sense. Pillow uses the parameter name loop
, maybe we should just stick with that.
I am looking through the animation module at the moment and based on the available options in all writers at the moment I would say that we could just add unlimited looping also in the pillow writer without any option to change it. Most people want it anyway (but I am not against the option).
I think that a refractoring or at least change in documentation about kwargs should be done in the animation module. At the moment the parameters to the pillow writer are something like,
codec, fps, bitrate, extra_args, metadata
but the only one that are not ignored is fps
.
So a new API or documentation that clearly show what options that are used in a writer and adding new relevant options should be good.
I have started to change some of the documentation to show which kwargs that are actually used and how.
I would suggest to just add the unlimited looping functionality in a pr and the rest in other prs. Do this really need a test?
lib/matplotlib/animation.py
Outdated
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 rest of the options are just ignored in the pillow writer so I don't think that they should be documented.
I thought that I'd take a crack a setting the number of loops with ImageMagickWriter. I attempted to modify this line, but the gif still looped infinitely despite this doc. I'll stick with just modifying PillowWriter.
Changing that line worked for me. I think that both the pillow- and imagemagick writer API should be the same.
One possibility that are not perfect but is in accordance with the current implementation is to put the new kwarg in moviewriter
and just put better documentation in the subclasses which kwargs that are used in that writer.
When both imagemagick and pillow seems to use ints for number of loops with 0 as infinity I think that we should stick to that notation.
Edit:
I would even say that this pr and the extra kwargs in the HTMLWriter breaks the API. It is with those changes not possible to get the same functionality from anim.save(name, writer=writer_obj, ...)
and anim.save(name, writer=string_name_of_writer, ...
).
So I believe that all new kwargs should be in MovieWriter
and animation.save
Sorry it's been a while.
In the interest in getting this bug fix done (and leaving me time for my thousand other projects), I've removed all API changes. This PR now does 2 things:
- Makes gifs with PillowWriter loop endlessly. Consistent with
ImageMagickWriter
andImageMagickFileWriter
. (削除) Adds a simple docstring to PillowWriter. (削除ここまで)
I'll leave any refractoring/API changes to someone more familiar with this API than me. This PR is now the minimum needed to fix #11789. The suggested loop
keyword argument can be added later.
lib/matplotlib/animation.py
Outdated
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.
kill the docstring as well for now?
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.
Sure.
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.
typo (animatation)
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.
Silly me. Fixed.
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.
modulo typo in whatsnew
@dopplershift I dismissed your review as the scope of the PR has changed; can you check it again?
Ok, I fixed the typo. I believe the doc failures are due to sphinx 1.8.0.
We should have fixed the doc builds now; can you rebase to see if everything's working again?
Last time I tried to rebase a fork, I really messed up the commit history. Any advice?
You should have branched your fork before submitting this PR. Right now your PR is based on your master branch, and that will make your life hard.
After that, you'd best give https://matplotlib.org/devel/gitwash/index.html a thorough look. In particular pay attention to backing up your branch.
With your setup, I would do:
git checkout master
git checkout -b backup-of-master # this will save your bacon if you mess up.
git checkout master # get back to the changes you made in this PR
git fetch upstream # if you haven't set your 'upstream' remote, then you need to do that first.
git rebase upstream/master # you don't have any conflicts so this should be smooth
git push origin master --force
If you mess up:
git checkout master
git reset --hard backup-of-master
will get you locally back to where you were.
This is some redundant branch switching:
git checkout -b backup-of-master # this will save your bacon if you mess up. git checkout master # get back to the changes you made in this PR
git branch backup-of-master
will create a branch and not switch to it.
Adds an optional parameter to PillowWriter that will set the number of times that a gif should loop for. Defaults to 0 meaning endless
@jklymak Your advice seems to have worked. Thank you. I've rebased.
Can whoever merges this squash-merge it?
* master: (51 commits) Disable sticky edge accumulation if no autoscaling. Avoid quadratic behavior when accumulating stickies. Rectified plot error (matplotlib#12501) endless looping GIFs with PillowWriter (matplotlib#11789) TST: add test for re-normalizing image FIX: colorbar re-check norm before draw for autolabels Fix search for sphinx >=1.8 (matplotlib#12216) Fix some flake8 issues Don't handle impossible values for `align` in hist() DOC: add section about test / doc dependencies DOC: clarify time windows for support TST: test colorbar tick labels correct FIX: make minor ticks formatted with science formatter as well DOC: clarify what 'minor version' means Adjust the widths of the messages during the build. Simplify radar_chart example. Fix duplicate condition in pathpatch3d example (matplotlib#12495) Fix typo in documentation FIX: make unused spines invisible Kill FontManager.update_fonts. ...
Uh oh!
There was an error while loading. Please reload this page.
PR Summary
Closes #11787 .
(削除) Adds an optional parameter to PillowWriter that will set the number of times that a GIF will loop for. Defaults to 0 meaning endless looping. Previously defaulted to 1 (never looping) without a way to change it. (削除ここまで)API change removed.Makes PillowWriter produce endless looping gifs like ImageMagickWriter and ImageMagickFileWriter.
PR Checklist
(削除) [ ] Has Pytest style unit tests (削除ここまで)(削除) [ ] Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way (削除ここまで)