Skip to content

Switch to using pillow for png as well. #15193

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
merged 1 commit into from
Oct 4, 2019
Merged

Conversation

anntzer
Copy link
Contributor

@anntzer anntzer commented Sep 5, 2019

PR Summary

Closes #15165.

Probably needs discussion as to whether the loss of precision for reading 16-bit RGB(A) pngs is a blocking issue or not.


The version check in _has_pil (pillow>=3.4) was dropped as the oldest
Pillow that supports Py3.6 (the oldest Python supported by Matplotlib)
is 4.0 anyways.

Some complications arise from an unfortunate design decision that
imread() returns pngs in 0-1 float dtype but others formats in integer
dtype (via Pillow), and from Pillow having not-so-great support for
16-bit images (e.g. Pillow#3041).

This makes reading of 16-bit RGB(A) PNGs (which are exposed as 0-1
floats by imread()) slightly less accurate, because Pillow coarsens them
to 8-bit first (by truncation); hence the slight change in the
pngsuite.png baseline image. (Note that the renderers only have 8-bit
precision anyways (unless using mplcairo with cairo master...), but the
discrepancy comes from differences in rounding between Pillow and
Matplotlib.)


I have a followup PR that makes it possible to build Matplotlib on Windows on a clean python.org python with just MSVC installed (by setting MPLLOCALFREETYPE=1).

Edit: This previously depended on #15175, but is now independent of it.

PR Checklist

  • Has Pytest style unit tests
  • Code is Flake 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

Copy link
Member

@tacaswell tacaswell left a comment

Choose a reason for hiding this comment

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

Modulo the 16bit read-back being document as an API change.

@efiring
Copy link
Member

efiring commented Sep 24, 2019

Looks good to me, in general terms.

@anntzer
Copy link
Contributor Author

anntzer commented Sep 24, 2019

API change note about precision loss added.

return self._print_image(filename, wx.BITMAP_TYPE_JPEG,
*args, **kwargs)
print_jpg = print_jpeg
def print_jpeg(self, filename, *args, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

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

Doesn't the condition mean this function should be removed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

no, the wx backend (not wxagg!) has always relied on wx's native handling of png/tiff/jpeg/...

Copy link
Member

Choose a reason for hiding this comment

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

That's not what the removed code says.

Copy link
Member

Choose a reason for hiding this comment

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

If I understand this correctly, @QuLogic is right, but the API change is now documented.

return self._print_image(filename, wx.BITMAP_TYPE_TIF,
*args, **kwargs)
print_tif = print_tiff
def print_tiff(self, filename, *args, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

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

And this one too.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

as above

return pil_to_array(image)
from matplotlib import _png
img_open = (
PIL.PngImagePlugin.PngImageFile if ext == 'png' else PIL.Image.open)
Copy link
Member

Choose a reason for hiding this comment

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

Do we even need this, or can we just do everything through plain open? (I understand that the _pil_png_to_float_array bit is for backwards compatibility.)

Copy link
Contributor Author

@anntzer anntzer Oct 1, 2019

Choose a reason for hiding this comment

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

No, the point is to force pillow to open the image as png if ext == "png", regardless of what pillow may think the image is.

@anntzer
Copy link
Contributor Author

anntzer commented Oct 3, 2019

rebased

@anntzer anntzer force-pushed the pillowpng branch 2 times, most recently from 3a7407b to f0bcdff Compare October 3, 2019 14:53
buf = BytesIO()
(Image.fromarray(
output.view(dtype=np.uint8).reshape((*output.shape, 4))
.save(buf, format="png")))
Copy link
Member

Choose a reason for hiding this comment

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

Just style but rather:

data = output.view(dtype=np.uint8).reshape((*output.shape, 4))
Image.fromarray(data).save(buf, format="png")

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

return self._print_image(filename, wx.BITMAP_TYPE_JPEG,
*args, **kwargs)
print_jpg = print_jpeg
def print_jpeg(self, filename, *args, **kwargs):
Copy link
Member

Choose a reason for hiding this comment

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

If I understand this correctly, @QuLogic is right, but the API change is now documented.

@@ -0,0 +1,10 @@
Matplotlib now uses Pillow to save and read pngs
Copy link
Member

Choose a reason for hiding this comment

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

Please adapt to the new way of documentation API changes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

The version check in _has_pil (pillow>=3.4) was dropped as the oldest
Pillow that supports Py3.6 (the oldest Python supported by Matplotlib)
is 4.0 anyways.

Some complications arise from an unfortunate design decision that
imread() returns pngs in 0-1 float dtype but others formats in integer
dtype (via Pillow), and from Pillow having not-so-great support for
16-bit images (e.g. Pillow#3041).

This makes reading of 16-bit RGB(A) PNGs (which are exposed as 0-1
floats by imread()) slightly less accurate, because Pillow coarsens them
to 8-bit first (by truncation); hence the slight change in the
pngsuite.png baseline image.  (Note that the *renderers* only have 8-bit
precision anyways (unless using mplcairo with cairo master...), but the
discrepancy comes from differences in rounding between Pillow and
Matplotlib).
Copy link
Member

@timhoffm timhoffm left a comment

Choose a reason for hiding this comment

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

Anybody can merge after CI pass.

@anntzer
Copy link
Contributor Author

anntzer commented Oct 4, 2019

self-merging per the above.

@anntzer anntzer merged commit be978a3 into matplotlib:master Oct 4, 2019
@anntzer anntzer deleted the pillowpng branch October 4, 2019 08:34
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.

Switch to requiring Pillow rather than having our own png wrapper?
5 participants