Closed banesullivan closed 2 years ago
django-larege-image
is failing for these formats:
FAILED example/core/tests/test_data.py::test_thumbnail[pcx] - ValueError: Can...
FAILED example/core/tests/test_data.py::test_thumbnail[eps] - ValueError: ima...
FAILED example/core/tests/test_data.py::test_thumbnail[mpo] - OSError: cannot...
FAILED example/core/tests/test_data.py::test_thumbnail[palm] - OSError: canno...
FAILED example/core/tests/test_data.py::test_thumbnail[pdf] - ValueError: can...
FAILED example/core/tests/test_data.py::test_thumbnail[xbm] - OSError: cannot...
Curious. Some formats require certain image modes (e.g., based on the error above, palm MUST be black and white, not greyscale or color). I wonder if there is a way to either coerce the image mode to satisfy the format or determine when they are incompatible and not list them.
I wonder if there is a way to either coerce the image mode to satisfy the format
This would be preferred IMO
At the very least, I would expect opening the image with the same encoding to work. E.g.:
src = large_image.open('/Users/bane/Desktop/rasters/converted.tif', encoding='PALM')
thumb_data, mime_type = src.getThumbnail(encoding='PALM')
_________________________________________________ test_thumbnail[PALM] _________________________________________________
encoding = 'PALM'
@pytest.mark.parametrize('encoding', TileOutputMimeTypes.keys())
def test_thumbnail(encoding):
src = large_image.open('/Users/bane/Desktop/rasters/converted.tif', encoding=encoding)
> thumb_data, mime_type = src.getThumbnail(encoding=encoding)
test_thumbnail.py:14:
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
../kw/large_image/large_image/cache_util/cache.py:90: in wrapper
v = func(self, *args, **kwargs)
../kw/large_image/sources/gdal/large_image_source_gdal/__init__.py:1022: in getThumbnail
return super().getThumbnail(width, height, **kwargs)
../kw/large_image/large_image/cache_util/cache.py:90: in wrapper
v = func(self, *args, **kwargs)
../kw/large_image/large_image/tilesource/base.py:1619: in getThumbnail
return self.getRegion(**params)
../kw/large_image/sources/gdal/large_image_source_gdal/__init__.py:1181: in getRegion
return super().getRegion(format, **kwargs)
../kw/large_image/large_image/tilesource/base.py:1789: in getRegion
return _encodeImage(image, format=format, **kwargs)
../kw/large_image/large_image/tilesource/utilities.py:144: in _encodeImage
imageData = _encodeImageBinary(
../kw/large_image/large_image/tilesource/utilities.py:102: in _encodeImageBinary
image.save(output, encoding, **params)
../../anaconda3/envs/dli-dev/lib/python3.9/site-packages/PIL/Image.py:2300: in save
save_handler(self, fp, filename)
_ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _ _
im = <PIL.Image.Image image mode=RGB size=256x256 at 0x1971F0880>, fp = <_io.BytesIO object at 0x1974d8a90>
filename = ''
def _save(im, fp, filename):
if im.mode == "P":
# we assume this is a color Palm image with the standard colormap,
# unless the "info" dict has a "custom-colormap" field
rawmode = "P"
bpp = 8
version = 1
elif im.mode == "L":
if im.encoderinfo.get("bpp") in (1, 2, 4):
# this is 8-bit grayscale, so we shift it to get the high-order bits,
# and invert it because
# Palm does greyscale from white (0) to black (1)
bpp = im.encoderinfo["bpp"]
im = im.point(
lambda x, shift=8 - bpp, maxval=(1 << bpp) - 1: maxval - (x >> shift)
)
elif im.info.get("bpp") in (1, 2, 4):
# here we assume that even though the inherent mode is 8-bit grayscale,
# only the lower bpp bits are significant.
# We invert them to match the Palm.
bpp = im.info["bpp"]
im = im.point(lambda x, maxval=(1 << bpp) - 1: maxval - (x & maxval))
else:
raise OSError(f"cannot write mode {im.mode} as Palm")
# we ignore the palette here
im.mode = "P"
rawmode = "P;" + str(bpp)
version = 1
elif im.mode == "1":
# monochrome -- write it inverted, as is the Palm standard
rawmode = "1;I"
bpp = 1
version = 0
else:
> raise OSError(f"cannot write mode {im.mode} as Palm")
E OSError: cannot write mode RGB as Palm
../../anaconda3/envs/dli-dev/lib/python3.9/site-packages/PIL/PalmImagePlugin.py:157: OSError
So we can test which modes/formats works and be more intelligent on how we handle them, but at a cost of ~90ms startup time on my test machine:
def test():
s = time.time()
modes = ['RGBA', 'RGB', 'L', '1']
allowed = {}
for format in PIL.Image.MIME.keys():
for mode in modes:
image = PIL.Image.new(mode, (32, 32))
output = io.BytesIO()
try:
image.save(output, format)
allowed.setdefault(format, [])
allowed[format].append(image.mode)
except Exception:
pass
print(time.time() - s)
print(allowed)
Could we implement a SafeTileOutputMimeTypes
value that computes that lazily?
See https://github.com/girder/large_image/pull/951 for a fix. This has a blacklist for a few formats -- the icon formats output fixed sizes, so they don't work as general output formats. And, some formats aren't readable with PIL (at least not from a byte string), so they are harder to test.
This will gracefully fallback to lower color modes as needed for formats where that is needed.
The following test and note it fails for some encodings listed as supported
Note that this is causing failures for
django-large-image
: https://github.com/girder/django-large-image/runs/8082224710?check_suite_focus=true