Skip to content

Commit

Permalink
BUG: unpin Pillow and allow versions >=10.1.0 (#1045)
Browse files Browse the repository at this point in the history
  • Loading branch information
FirefoxMetzger committed Nov 7, 2023
1 parent d24944f commit f58379c
Show file tree
Hide file tree
Showing 5 changed files with 37 additions and 28 deletions.
23 changes: 13 additions & 10 deletions imageio/plugins/pillow.py
Expand Up @@ -34,13 +34,18 @@
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union, cast

import numpy as np
from PIL import ExifTags, GifImagePlugin, Image, ImageSequence, UnidentifiedImageError # type: ignore
from PIL import ExifTags, GifImagePlugin, Image, ImageSequence, UnidentifiedImageError
from PIL import __version__ as pil_version # type: ignore

from ..core.request import URI_BYTES, InitializationError, IOMode, Request
from ..core.v3_plugin_api import ImageProperties, PluginV3
from ..typing import ArrayLike


def pillow_version() -> Tuple[int]:
return tuple(int(x) for x in pil_version.split("."))


def _exif_orientation_transform(orientation: int, mode: str) -> Callable:
# get transformation that transforms an image from a
# given EXIF orientation into the standard orientation
Expand Down Expand Up @@ -303,29 +308,27 @@ def _apply_transforms(
# see: https://github.com/python-pillow/Pillow/issues/5929
image = image.convert(image.palette.mode)
elif image.format == "PNG" and image.mode == "I":
# By default, pillows unpacks 16-bit grayscale PNG into 32-bit
# integers due to limited 16-bit support in pillow itself. However,
# recent versions can directly unpack into a 16-bit buffer, which is
# more correct (and more efficient) in our scenario.
major, minor, patch = pillow_version()

if sys.byteorder == "little":
desired_mode = "I;16"
else: # pragma: no cover
# can't test big-endian in GH-Actions
desired_mode = "I;16B"

try:
Image._getdecoder(desired_mode, "raw", "I;16B")
except ValueError:
if major < 10: # pragma: no cover
warnings.warn(
"Loading 16-bit (uint16) PNG as int32 due to limitations "
"in pillow's PNG decoder. This will be fixed in a future "
"version of pillow which will make this warning dissapear.",
UserWarning,
)
else: # pragma: no cover
# Let pillow know that it is okay to return 16-bit
elif minor < 1: # pragma: no cover
# pillow<10.1.0 can directly decode into 16-bit grayscale
image.mode = desired_mode
else:
# pillow >= 10.1.0
image = image.convert(desired_mode)

image = np.asarray(image)

Expand Down
17 changes: 13 additions & 4 deletions imageio/plugins/pillowmulti.py
Expand Up @@ -6,8 +6,7 @@

import numpy as np

from .pillow_legacy import PillowFormat, ndarray_to_pil, image_as_uint

from .pillow_legacy import PillowFormat, image_as_uint, ndarray_to_pil

logger = logging.getLogger(__name__)

Expand All @@ -27,7 +26,7 @@ class GIFFormat(PillowFormat):

# GIF reader needs no modifications compared to base pillow reader

class Writer(PillowFormat.Writer):
class Writer(PillowFormat.Writer): # pragma: no cover
def _open(
self,
loop=0,
Expand All @@ -37,6 +36,16 @@ def _open(
quantizer=0,
subrectangles=False,
):
from PIL import __version__ as pillow_version

major, minor, patch = tuple(int(x) for x in pillow_version.split("."))
if major == 10 and minor >= 1:
raise ImportError(
f"Pillow v{pillow_version} is not supported by ImageIO's legacy "
"pillow plugin when writing GIF. Consider using to the new "
"plugin or downgrade to `pillow<10.1.0`."
)

# Check palettesize
palettesize = int(palettesize)
if palettesize < 2 or palettesize > 256:
Expand Down Expand Up @@ -89,7 +98,7 @@ def intToBin(i):
return i.to_bytes(2, byteorder="little")


class GifWriter:
class GifWriter: # pragma: no cover
"""Class that for helping write the animated GIF file. This is based on
code from images2gif.py (part of visvis). The version here is modified
to allow streamed writing.
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -83,7 +83,7 @@

# pinned to > 8.3.2 due to security vulnerability
# See: https://github.com/advisories/GHSA-98vv-pw6r-q6q4
install_requires = ["numpy", "pillow>= 8.3.2,<10.1.0"]
install_requires = ["numpy", "pillow>= 8.3.2"]

plugins = {
"bsdf": [],
Expand Down
11 changes: 0 additions & 11 deletions tests/test_legacy_plugin_wrapper.py
@@ -1,6 +1,5 @@
import imageio as iio
import pytest
import numpy as np


def test_exception_message_bytes():
Expand Down Expand Up @@ -42,16 +41,6 @@ def test_ellipsis_index(test_images):
assert metadata == {}


def test_list_writing(test_images, tmp_path):
expected = iio.v3.imread(test_images / "newtonscradle.gif", index=...)
expected = [*expected]

iio.v3.imwrite(tmp_path / "test.gif", expected, plugin="GIF-PIL")
actual = iio.v3.imread(tmp_path / "test.gif", index=...)

assert np.allclose(actual, expected)


def test_properties(test_images):
p = iio.v3.improps(test_images / "newtonscradle.gif", plugin="GIF-PIL", index=...)
assert p.shape == (36, 150, 200, 4)
Expand Down
12 changes: 10 additions & 2 deletions tests/test_pillow_legacy.py
Expand Up @@ -277,7 +277,12 @@ def test_gif(tmp_path):
continue # quantize fails, see also png
fname = fnamebase + "%i.%i.%i.gif" % (isfloat, crop, colors)
rim = get_ref_im(colors, crop, isfloat)
imageio.imsave(fname, rim, format="GIF-PIL")

try:
imageio.imsave(fname, rim, format="GIF-PIL")
except ImportError:
pytest.xfail("New pillow version is no longer supported.")

im = imageio.imread(fname, format="GIF-PIL")
mul = 255 if isfloat else 1
if colors not in (0, 1):
Expand Down Expand Up @@ -321,7 +326,10 @@ def test_animated_gif(test_images, tmp_path):
ims1 = [x.astype(np.float32) / 256 for x in ims1]
ims1 = [x[:, :, :colors] for x in ims1]
fname = fnamebase + ".animated.%i.gif" % colors
imageio.mimsave(fname, ims1, duration=0.2, format="GIF-PIL")
try:
imageio.mimsave(fname, ims1, duration=0.2, format="GIF-PIL")
except ImportError:
pytest.xfail("Pillow version no longer supported.")
# Retrieve
print("fooo", fname, isfloat, colors)
ims2 = imageio.mimread(fname, format="GIF-PIL")
Expand Down

0 comments on commit f58379c

Please sign in to comment.