Skip to content

Commit

Permalink
FEAT: Decode gray 16-bit PNG as uint16 (not int32) if pillow allows (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
FirefoxMetzger committed May 31, 2023
1 parent b0f9f3b commit 95bb89d
Show file tree
Hide file tree
Showing 3 changed files with 41 additions and 11 deletions.
38 changes: 33 additions & 5 deletions imageio/plugins/pillow.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,16 @@
"""

import sys
import warnings
from io import BytesIO
from typing import Callable, Optional, Dict, Any, Tuple, cast, Iterator, Union, List
from typing import Any, Callable, Dict, Iterator, List, Optional, Tuple, Union, cast

import numpy as np
from PIL import Image, UnidentifiedImageError, ImageSequence, ExifTags # type: ignore
from ..core.request import Request, IOMode, InitializationError, URI_BYTES
from ..core.v3_plugin_api import PluginV3, ImageProperties
import warnings
from PIL import ExifTags, Image, ImageSequence, UnidentifiedImageError # type: ignore

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


Expand Down Expand Up @@ -275,6 +278,31 @@ def _apply_transforms(
# adjust for pillow9 changes
# 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.

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:
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
image.mode = desired_mode

image = np.asarray(image)

meta = self.metadata(index=self._image.tell(), exclude_applied=False)
Expand Down
4 changes: 3 additions & 1 deletion tests/test_pillow.py
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,9 @@ def test_png_16bit(test_images, tmp_path):
im2 = iio.imread(tmp_path / "2.png", plugin="pillow")
assert im2.dtype == np.uint8

im3 = iio.imread(tmp_path / "1.png", plugin="pillow")
with pytest.warns(UserWarning):
im3 = iio.imread(tmp_path / "1.png", plugin="pillow")

assert im3.dtype == np.int32


Expand Down
10 changes: 5 additions & 5 deletions tests/test_pyav.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@

IS_AV_10_0_0 = tuple(int(x) for x in av.__version__.split(".")) == (10, 0, 0)

# the maintainer of pyAV hasn't responded to my bug reports in over 4 months so
# I am disabling test on pypy to stay sane.
if IS_PYPY:
pytest.skip("pyAV sometimes causes segfaults on Pypy.", allow_module_level=True)


def test_mp4_read(test_images: Path):
with av.open(str(test_images / "cockatoo.mp4"), "r") as container:
Expand Down Expand Up @@ -575,11 +580,6 @@ def test_keyframe_intervals(test_images):
assert np.max(np.diff(key_dist)) <= 5


# the maintainer of pyAV hasn't responded to my bug reports in over 4 months so
# I am disabling this test on pypy to stay sane.
@pytest.mark.skipif(
IS_PYPY, reason="Using the trim filter in pyAV sometimes causes segfaults on Pypy."
)
def test_trim_filter(test_images):
# this is a regression test for:
# https://github.com/imageio/imageio/issues/951
Expand Down

0 comments on commit 95bb89d

Please sign in to comment.