From 8f8eaf9f5dfb75ad48348c12fa7554323a6c7670 Mon Sep 17 00:00:00 2001 From: David Manthey Date: Mon, 1 Aug 2022 10:06:24 -0400 Subject: [PATCH] Register the tile output formats from PIL. --- docs/tilesource_options.rst | 4 +++- girder/girder_large_image/rest/tiles.py | 16 +++++++++------- large_image/constants.py | 10 +++++----- large_image/tilesource/utilities.py | 20 ++++++++++++++++++++ test/test_source_tiff.py | 9 +++++++++ 5 files changed, 46 insertions(+), 13 deletions(-) diff --git a/docs/tilesource_options.rst b/docs/tilesource_options.rst index dbcc98640..608de963a 100644 --- a/docs/tilesource_options.rst +++ b/docs/tilesource_options.rst @@ -11,7 +11,9 @@ Python tile functions can return tile data as images, numpy arrays, or PIL Image Encoding -------- -The ``encoding`` parameter can be one of ``JPEG``, ``PNG``, ``TIFF``, or ``JFIF``. When the tile is output as an image, this is the preferred format. Note that ``JFIF`` is a specific variant of ``JPEG`` that will always use either the Y or YCbCr color space as well as constraining other options. +The ``encoding`` parameter can be one of ``JPEG``, ``PNG``, ``TIFF``, ``JFIF``, or ``TILED``. When the tile is output as an image, this is the preferred format. Note that ``JFIF`` is a specific variant of ``JPEG`` that will always use either the Y or YCbCr color space as well as constraining other options. ``TILED`` will output a tiled tiff file; this is slower than ``TIFF`` but can support images of arbitrary size. + +Additional options are available based on the PIL.Image registered encoders. The ``encoding`` only affects output when ``format`` is ``TILE_FORMAT_IMAGE``. diff --git a/girder/girder_large_image/rest/tiles.py b/girder/girder_large_image/rest/tiles.py index 226ec864a..b5b11f4be 100644 --- a/girder/girder_large_image/rest/tiles.py +++ b/girder/girder_large_image/rest/tiles.py @@ -35,7 +35,7 @@ from girder.models.item import Item from girder.utility.progress import setResponseTimeLimit from large_image.cache_util import strhash -from large_image.constants import TileInputUnits +from large_image.constants import TileInputUnits, TileOutputMimeTypes from large_image.exceptions import TileGeneralError from .. import loadmodelcache @@ -46,7 +46,12 @@ 'image/png': 'png', 'image/tiff': 'tiff', } +for key, value in TileOutputMimeTypes.items(): + if value not in MimeTypeExtensions: + MimeTypeExtensions[value] = key.lower() ImageMimeTypes = list(MimeTypeExtensions) +EncodingTypes = list(TileOutputMimeTypes.keys()) + [ + 'pickle', 'pickle:3', 'pickle:4', 'pickle:5'] def _adjustParams(params): @@ -697,8 +702,7 @@ def deleteTiles(self, item, params): 'have. For geospatial sources, TILED will also have ' 'appropriate tagging. Pickle emits python pickle data with an ' 'optional specific protocol', required=False, - enum=['JPEG', 'PNG', 'TIFF', 'TILED', 'pickle', 'pickle:3', - 'pickle:4', 'pickle:5'], default='JPEG') + enum=EncodingTypes, default='JPEG') .param('contentDisposition', 'Specify the Content-Disposition response ' 'header disposition-type value.', required=False, enum=['inline', 'attachment']) @@ -807,8 +811,7 @@ def getTilesThumbnail(self, item, params): 'have. For geospatial sources, TILED will also have ' 'appropriate tagging. Pickle emits python pickle data with an ' 'optional specific protocol', required=False, - enum=['JPEG', 'PNG', 'TIFF', 'TILED', 'pickle', 'pickle:3', - 'pickle:4', 'pickle:5'], default='JPEG') + enum=EncodingTypes, default='JPEG') .param('jpegQuality', 'Quality used for generating JPEG images', required=False, dataType='int', default=95) .param('jpegSubsampling', 'Chroma subsampling used for generating ' @@ -1220,8 +1223,7 @@ def getAssociatedImageMetadata(self, item, image, params): 'have. For geospatial sources, TILED will also have ' 'appropriate tagging. Pickle emits python pickle data with an ' 'optional specific protocol', required=False, - enum=['JPEG', 'PNG', 'TIFF', 'TILED', 'pickle', 'pickle:3', - 'pickle:4', 'pickle:5'], default='JPEG') + enum=EncodingTypes, default='JPEG') .param('jpegQuality', 'Quality used for generating JPEG images', required=False, dataType='int', default=95) .param('jpegSubsampling', 'Chroma subsampling used for generating ' diff --git a/large_image/constants.py b/large_image/constants.py index 25f0d2e1c..59fa5189f 100644 --- a/large_image/constants.py +++ b/large_image/constants.py @@ -37,16 +37,16 @@ class SourcePriority: TileOutputMimeTypes = { - # JFIF forces conversion to JPEG through PIL to ensure the image is in a - # common colorspace. JPEG colorspace is complex: see - # https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/ - # doc-files/jpeg_metadata.html - 'JFIF': 'image/jpeg', 'JPEG': 'image/jpeg', 'PNG': 'image/png', 'TIFF': 'image/tiff', # TILED indicates the region output should be generated as a tiled TIFF 'TILED': 'image/tiff', + # JFIF forces conversion to JPEG through PIL to ensure the image is in a + # common colorspace. JPEG colorspace is complex: see + # https://docs.oracle.com/javase/8/docs/api/javax/imageio/metadata/ + # doc-files/jpeg_metadata.html + 'JFIF': 'image/jpeg', } TileOutputPILFormat = { 'JFIF': 'JPEG' diff --git a/large_image/tilesource/utilities.py b/large_image/tilesource/utilities.py index dfba6acdc..554c210d6 100644 --- a/large_image/tilesource/utilities.py +++ b/large_image/tilesource/utilities.py @@ -892,3 +892,23 @@ def histogramThreshold(histogram, threshold, fromMax=False): return edges[idx] tally += hist[idx] return edges[-1] + + +def addPILFormatsToOutputOptions(): + """ + Check PIL for available formats that be saved and add them to the lists of + of available formats. + """ + # Call this to actual register the extensions + PIL.Image.registered_extensions() + for key, value in PIL.Image.MIME.items(): + if key not in TileOutputMimeTypes and key in PIL.Image.SAVE: + TileOutputMimeTypes[key] = value + for key, value in PIL.Image.registered_extensions().items(): + key = key.lstrip('.') + if (key not in TileOutputMimeTypes and value in TileOutputMimeTypes and + key not in TileOutputPILFormat): + TileOutputPILFormat[key] = value + + +addPILFormatsToOutputOptions() diff --git a/test/test_source_tiff.py b/test/test_source_tiff.py index 477d3d04c..c8f35ca40 100644 --- a/test/test_source_tiff.py +++ b/test/test_source_tiff.py @@ -74,6 +74,15 @@ def testTileIterator(): tileCount += 1 assert tile['tile'][:len(utilities.TIFFHeader)] == utilities.TIFFHeader assert tileCount == 45 + # Ask for WEBPs + tileCount = 0 + for tile in source.tileIterator( + scale={'magnification': 2.5}, + format=constants.TILE_FORMAT_IMAGE, + encoding='WEBP'): + tileCount += 1 + assert tile['tile'][8:12] == b'WEBP' + assert tileCount == 45 def testTileIteratorRetiling():