Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

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

Merged
jklymak merged 8 commits into matplotlib:master from t-makaro:master
Oct 16, 2018
Merged

endless looping GIFs with PillowWriter #11789

jklymak merged 8 commits into matplotlib:master from t-makaro:master
Oct 16, 2018

Conversation

Copy link
Contributor

@t-makaro t-makaro commented Jul 28, 2018
edited
Loading

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 (削除ここまで)
  • Code is PEP 8 compliant
  • New features are documented, with examples if plot related
  • Documentation is sphinx and numpydoc compliant
  • Added an entry to doc/users/next_whats_new/ if major new feature (follow instructions in README.rst there)
  • (削除) [ ] Documented in doc/api/api_changes.rst if API changed in a backward-incompatible way (削除ここまで)
  • API consistency

Copy link
Contributor Author

t-makaro commented Jul 28, 2018
edited
Loading

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?

Copy link
Member

jklymak commented Jul 28, 2018

  1. 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.

Copy link
Member

WeatherGod commented Jul 28, 2018 via email

In some respects, this could be considered a bugfix. The original gif writer that uses ImageMagick produces endless loop GIFs. I don't think there is an API parameter for that writer as well.
...
On Sat, Jul 28, 2018 at 9:34 AM, Jody Klymak ***@***.***> wrote: 1. 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. — You are receiving this because you are subscribed to this thread. Reply to this email directly, view it on GitHub <#11789 (comment)>, or mute the thread <https://github.com/notifications/unsubscribe-auth/AARy-EvtAIRCtbVQ0avD7vZOmx1C2g9Vks5uLGhZgaJpZM4Vk6qM> .

Copy link
Member

jklymak commented Jul 28, 2018

Oh, well in that case it’d be good to quickly go through the writers and document their behaviour.

Copy link
Contributor Author

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.

Copy link
Contributor Author

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

Copy link
Contributor

@anntzer anntzer left a 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

@@ -557,6 +557,7 @@ def isAvailable(cls):
def __init__(self, *args, **kwargs):
Copy link
Contributor

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.

Copy link
Contributor Author

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".

@@ -0,0 +1,7 @@
Looping GIFs with PillowWriter
Copy link
Contributor

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.

Copy link
Contributor Author

@t-makaro t-makaro Jul 28, 2018
edited
Loading

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.

Copy link
Member

jklymak commented Jul 28, 2018

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

Copy link
Contributor Author

t-makaro commented Jul 28, 2018
edited
Loading

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?

Copy link
Member

jklymak commented Jul 28, 2018

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.

Copy link
Contributor Author

t-makaro commented Jul 28, 2018
edited
Loading

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.

@tacaswell tacaswell added this to the v3.1 milestone Jul 28, 2018
Copy link
Member

jklymak commented Jul 28, 2018

I vote 2 + ValueError, just because I think loop=False will actually pass loop=0 if its not caught, but I may be wrong.

Copy link
Contributor Author

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.

Copy link
Contributor

@dopplershift dopplershift left a 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.

@@ -554,7 +554,38 @@ def isAvailable(cls):
return False
return True

def __init__(self, *args, **kwargs):
def __init__(self, *args, loop=0, **kwargs):
Copy link
Contributor

@dopplershift dopplershift Jul 31, 2018

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.

output file. Some keys that may be of use include:
title, artist, genre, subject, copyright, srcform, comment.
'''
if type(loop) == int:
Copy link
Contributor

@dopplershift dopplershift Jul 31, 2018

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

?

Copy link
Contributor Author

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.

Copy link
Contributor Author

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 😛.

Copy link
Contributor

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?

Copy link
Contributor Author

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.

Copy link
Contributor

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?

loop: int
The number of times that the gif will loop.
A value of 0 is endless.
codec: string or None, optional
Copy link
Contributor

@fredrik-1 fredrik-1 Aug 12, 2018

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.

Copy link
Contributor

fredrik-1 commented Aug 12, 2018
edited
Loading

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

Copy link
Contributor Author

t-makaro commented Sep 18, 2018
edited
Loading

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:

  1. Makes gifs with PillowWriter loop endlessly. Consistent with ImageMagickWriter and ImageMagickFileWriter.
  2. (削除) 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.

----------
fps: int
Framerate for the gif.
'''
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure.

Endless Looping GIFs with PillowWriter
--------------------------------------

We acknowledge that most people want to watch a gif more than once. Saving an animatation
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typo (animatation)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silly me. Fixed.

Copy link
Contributor

@anntzer anntzer left a 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

@anntzer anntzer dismissed dopplershift’s stale review September 19, 2018 08:54

@dopplershift I dismissed your review as the scope of the PR has changed; can you check it again?

Copy link
Contributor Author

Ok, I fixed the typo. I believe the doc failures are due to sphinx 1.8.0.

Copy link
Member

QuLogic commented Sep 29, 2018

We should have fixed the doc builds now; can you rebase to see if everything's working again?

Copy link
Contributor Author

t-makaro commented Oct 3, 2018

Last time I tried to rebase a fork, I really messed up the commit history. Any advice?

Copy link
Member

jklymak commented Oct 3, 2018

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.

t-makaro reacted with thumbs up emoji

Copy link
Member

QuLogic commented Oct 3, 2018

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.

jklymak reacted with thumbs up emoji

Copy link
Contributor Author

t-makaro commented Oct 9, 2018

@jklymak Your advice seems to have worked. Thank you. I've rebased.

Copy link
Member

Can whoever merges this squash-merge it?

@jklymak jklymak merged commit 566bd8b into matplotlib:master Oct 16, 2018
thoo added a commit to thoo/matplotlib that referenced this pull request Oct 18, 2018
* 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.
 ...
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Reviewers

@anntzer anntzer anntzer approved these changes

@dstansby dstansby dstansby approved these changes

@dopplershift dopplershift dopplershift left review comments

+1 more reviewer

@fredrik-1 fredrik-1 fredrik-1 left review comments

Reviewers whose approvals may not affect merge requirements
Assignees
No one assigned
Projects
None yet
Milestone
v3.1.0
Development

Successfully merging this pull request may close these issues.

Looping gifs with PillowWriter

AltStyle によって変換されたページ (->オリジナル) /