Skip to content
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

JPG compression for inline pylab #4679

Merged
merged 9 commits into from Jan 27, 2014
Merged

JPG compression for inline pylab #4679

merged 9 commits into from Jan 27, 2014

Conversation

dbarbeau
Copy link
Contributor

This is a follow up to #4448

It adds JPG compression and quality options to render inline figures. This is useful for blogs where PNG isn't compact enough and SVG overly-verbose.

@Carreau
Copy link
Member

Carreau commented Dec 15, 2013

This looks good to me.

I'll just re-ask the same question that I did when the retina key has appeard : what if you want jpeg double res for retina ?

@Carreau
Copy link
Member

Carreau commented Dec 15, 2013

Would you like to drop a line in the what's new and the doc at the same time ?

@dbarbeau
Copy link
Contributor Author

I don't know the history behind the retina key but just now, I'd say it doesn't fit well in the "figure_format" flag. It is some sort of extra-qualifier. Maybe a syntax like png:2x for the figure_format option could give more flexibility:

  • jpg:2x for JPG Retina
  • png:4x for PNG Super-Retina

The :2x part would be optional and if absent it would be synonym of :1x. The retina key would be kept as synonym of png:2x but deprecated. This also avoids adding an extra flag and preserves the current behaviour so users aren't lost.

Will look into docs and what's new.

@dbarbeau
Copy link
Contributor Author

I added some lines in the what's new pr directory. I didn't find a convenient place to add extra doc and found the options page was autogenerated. Any tips to where I could put more information?

@dbarbeau
Copy link
Contributor Author

Oh, I didn't implement the proposal above (png:2x syntax) because I think it belongs to another PR and I don't know the code well enough to be sure such a syntax wouldn't have side-effects in places expecting a simple format and receiving that sort of composite string.


if has_pil:
# If we have PIL using jpeg as inline image format can save some bytes.
fmts.append('jpg')
Copy link
Member

Choose a reason for hiding this comment

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

I'm not convinced about conditionally including options for config - it could lead to unclear failures if someone sets the option to jpg and later removes PIL, because the error message will just say that jpg is not a valid value. It also means that we import PIL, which AFAIK is a relatively heavy library, even when it's not going to be used. I'd rather have jpg as an option all the time, and check for PIL when jpg is specified.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I agree about the weight of the import. However, I feel uncomfortable about advertising jpg and then, when it is about to be used, fail. It is an un-kept promise and a late failure. Documenting it in the help string can help, but won't prevent late failure during Notebook execution. What is the general policy?

Copy link
Member

Choose a reason for hiding this comment

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

If late failure is a concern, I think it should be possible to have a _figure_format_changed function which, if you select jpg, tries to import PIL, and warns you if it can't. But I'd rather the error message said "can't import PIL" or similar, rather than "jpg is not a valid option".

I don't think we have a general policy for this kind of thing.

@damianavila
Copy link
Member

I suggest using Pillow instead of PIL... it is a fork of PIL but a lot of better and it will probably replace PIL after all...
If I remember correctly, you import Image in the same way... so you would need to changes only some references which I commented in the code above 😉

@minrk
Copy link
Member

minrk commented Dec 16, 2013

Just a note: -1 to @damianavila's proposed additions of pillow to comments / docstrings.

@takluyver
Copy link
Member

I'd mention Pillow in docstrings, but possibly as "PIL/Pillow"

@damianavila
Copy link
Member

Just a note: -1 to @damianavila's proposed additions of pillow to comments / docstrings.

and because ?

I'd mention Pillow in docstrings, but possibly as "PIL/Pillow"

I can live with this proposal...

@dbarbeau
Copy link
Contributor Author

I think the PIL/Pillow import method is the same, and I'm actually using Pillow :) I can document it as PIL/Pillow, and be generic in the code by referring to it as "python_imaging". i really don't mind.

@damianavila
Copy link
Member

I'm actually using Pillow :) I can document it as PIL/Pillow, and be generic in the code by referring to it as "python_imaging".

I think that mention of Pillow will save a lot of headaches to future users who don't know about this better fork of PIL... but it would be better to wait for @minrk comments about his -1 to make some of these changes, before actually doing the changes...

@minrk
Copy link
Member

minrk commented Dec 16, 2013

I think it's okay to mention just 'PIL' in the comments, since the import is the same, and we don't need to add clutter to every comment. You need the library, it doesn't matter how you install it. Mentioning Pillow in the what's new snippet is fine, though.

@damianavila
Copy link
Member

I think it's okay to mention just 'PIL' in the comments, since the import is the same, and we don't need to add clutter to every comment. You need the library, it doesn't matter how you install it. Mentioning Pillow in the what's new snippet is fine, though.

OK, but I still would like to see a reference in the docstrings... maybe with the suggestion from @takluyver: "PIL/Pillow" :trollface:
The people read what's new once, but the source is read it a lot of times... so aiding the user with just a few words in docstrings do not seem to much to add to me... :trollface:

@minrk
Copy link
Member

minrk commented Dec 17, 2013

I just think adding PIL/Pillow to every instance is useless clutter. At most one location seems like plenty.

@damianavila
Copy link
Member

At most one location seems like plenty.

Yes, but I do not think the what's new was the proper location... but, at the end, it is a little detail... and not a reason to stop the flow of this PR... ▶️ 😉

@minrk
Copy link
Member

minrk commented Dec 17, 2013

Sorry - by one location I meant in the code (not counting what's new). Mentioning it in the print_figure docstring and/or the traitlet help string is fine, but renaming variables as you proposed is a bit too far.

@@ -338,5 +342,5 @@ def configure_inline_support(shell, backend):
del shell._saved_rcParams
Copy link
Member

Choose a reason for hiding this comment

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

This won't quite toggle correctly when moving from png to jpg, or jpg to anything - let's add [ f.type_printers.pop(Figure, None) for f in svg,png,jpg... ] to the top, then only turn them on in the if/elif/elif branch. That will be more consistent.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I must admit I don't totally understand what's going on there and I proceeded by copy/pasting. I see types are [un]registered so that formatters know what to handle and how to handle them. I don't get why SVG formatter was altered in each branch of the if/elif... If you can provide some background I'll be very grateful!

Thanks!

Copy link
Member

Choose a reason for hiding this comment

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

We only want one formatter registered at a time, otherwise every format will be published. This is saying "forget what we were publishing before, just publish the selected format." So, when png is selected, stop publishing svg and vice versa.

Copy link
Member

Choose a reason for hiding this comment

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

We only want one formatter registered at a time, otherwise every format will be published

I'll rebring this issue later, but I feel like we should also allow many formater no ?

@damianavila
Copy link
Member

Mentioning it in the print_figure docstring and/or the traitlet help string is fine, but renaming variables as you proposed is a bit too far.

I agree that is maybe too far to rename variables... I will be happy with

in the print_figure docstring and/or the traitlet help string

Great we have achieved a consensus, hehe... 👍

@@ -53,7 +59,20 @@ def _config_changed(self, name, old, new):
inline backend."""
)

figure_format = CaselessStrEnum(['svg', 'png', 'retina'], default_value='png', config=True,
fmts = ['svg', 'png', 'retina']
Copy link
Member

Choose a reason for hiding this comment

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

add jpg to the formats, and then add a check for has_pil in _figure_format_changed, such as:

def _figure_format_changed(self, name, old, new):
    if new == 'jpg' and not has_pil:
        raise TraitError("Require PIL/Pillow for jpg figures")

Copy link
Member

Choose a reason for hiding this comment

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

And if we're doing this, may as well delay the import of PIL until jpg is set.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, I think I can live with having the PIL import inside this function. Will do!

@minrk
Copy link
Member

minrk commented Dec 17, 2013

Actually, let's back up for a second - why do we check for PIL anyway? matplotlib jpeg output seems to work fine without PIL.

@minrk
Copy link
Member

minrk commented Dec 17, 2013

Nevermind - I was testing a non-agg backend. PIL is needed for jpg output with agg. Ignore me, I'm going home :).

@damianavila
Copy link
Member

No problem, it is late here too ;-)

@dbarbeau
Copy link
Contributor Author

Ok, I think the latest commit addresses most (if not all) of your comments. I tried to test it the best I could but I have a few concerns :

  • its quite hard to make ipython notebook print out warn() or logging.debug(...) or even print()! I had to attach another frontend to the kernel and call %debug to obtain feedback upon exceptions.
  • is calling nosetests IPython/kernel/zmq/tests the way to run tests locally?

As always any feedback is welcome!

@dbarbeau
Copy link
Contributor Author

If you do %matplotlib inline, warn statements will be captured in the cell (I would recommend against ever starting the notebook with --matplotlib/pylab, I assume that's why you can't see your messages). logging.debug on the other hand, will only show up (in the terminal) if you have set the log-level to debug, e.g. with ipython notebook --debug

I was indeed doing notebook --pylab=inline and was worried because I didn't get any errors! I tried %pylab inline which seems to work and now I do get exceptions in my cells! Thanks, and thanks for the iptest info too.

@@ -0,0 +1,2 @@
* The InlineBackend.figure_format flag now supports JPEG output if PIL is available.
* The new InlineBackend.quality flag controls the amount of compression (currently JPEG only)
Copy link
Member

Choose a reason for hiding this comment

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

InlineBackend.quality between backticks ?

@Carreau
Copy link
Member

Carreau commented Dec 18, 2013

Looks good and test are passing. I upvote.

@damianavila
Copy link
Member

Me too 👍

@minrk
Copy link
Member

minrk commented Dec 18, 2013

I'll rebring this issue later, but I feel like we should also allow many formater, no?

Yes, I think so, and it's not hard to actually do. Right now, you can:

for fmt, mime in [('svg', 'image/svg+xml'), ('png', 'image/png'), ('jpg', 'image/jpeg')]:
    f = ip.display_formatter.formatters[mime]
    f.for_type(Figure, lambda fig: print_figure(fig, fmt)

It's just a question of API, and the default behavior makes the most sense with only one format published. I think you proposed a 'formats' configurable, which would be a list rather than the current single string. But that's separate from this issue, which I agree is ready to go.

@dbarbeau
Copy link
Contributor Author

Hello guys!

I was wondering if you'd like me to rebase this. I ask because I've read that rebasing can be dangerous, but since it hasn't been merged yet I don't think it should be an issue. However, I'd like to know your opinion!

Thanks

@Carreau
Copy link
Member

Carreau commented Dec 20, 2013

It merge cleanly so there is no need to rebase for now. We usually rebase only when there is a merge conflict.

@dbarbeau
Copy link
Contributor Author

OK!

I'm looking into adding unit tests but I don't really get how to do this for such a function. I'd like to create a code cell which creates a pylab figure and just test that the server returns a jpg if I asked it to. I don't see well how to proceed? Should start IPython notebook and talk HTTP directly to it?

@takluyver
Copy link
Member

There's a test IPython.core.tests.test_pylabtools.test_figure_to_svg that you could base it on, and check the magic number for JPEG files. I think testing a separate process for this is probably overkill.

@dbarbeau
Copy link
Contributor Author

Ah right, thanks I was looking in the kernel package, not the core package. Will further test my test and commit.

try:
from PIL import Image
def test_figure_to_jpg():
# simple check for at least svg-looking output
Copy link
Member

Choose a reason for hiding this comment

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

svg-looking? I think is jpg 😉

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right :)

@minrk
Copy link
Member

minrk commented Jan 24, 2014

Sorry, looks like this needs a rebase.

@dbarbeau
Copy link
Contributor Author

Oh noooo! ^^ Will do!

@ellisonbg
Copy link
Member

Looks like this has been rebased - is it ready for merge?

@minrk
Copy link
Member

minrk commented Jan 27, 2014

Yup. Thanks, @dbarbeau!

minrk added a commit that referenced this pull request Jan 27, 2014
JPG compression for inline pylab
@minrk minrk merged commit 6def8ea into ipython:master Jan 27, 2014
mattvonrocketstein pushed a commit to mattvonrocketstein/ipython that referenced this pull request Nov 3, 2014
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

8 participants