Source code for large_image_source_pil

#############################################################################
#  Copyright Kitware Inc.
#
#  Licensed under the Apache License, Version 2.0 ( the "License" );
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#    http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.
#############################################################################

import json
import math
import os

import numpy
import PIL.Image

from large_image import config
from large_image.cache_util import LruCacheMetaclass, methodcache, strhash
from large_image.constants import TILE_FORMAT_PIL, SourcePriority
from large_image.exceptions import TileSourceError, TileSourceFileNotFoundError
from large_image.tilesource import FileTileSource

try:
    from importlib.metadata import PackageNotFoundError
    from importlib.metadata import version as _importlib_version
except ImportError:
    from importlib_metadata import PackageNotFoundError
    from importlib_metadata import version as _importlib_version
try:
    __version__ = _importlib_version(__name__)
except PackageNotFoundError:
    # package is not installed
    pass

# Default to ignoring files with some specific extensions.
config.ConfigValues['source_pil_ignored_names'] = \
    r'(\.mrxs|\.vsi)$'


[docs]def getMaxSize(size=None, maxDefault=4096): """ Get the maximum width and height that we allow for an image. :param size: the requested maximum size. This is either a number to use for both width and height, or an object with {'width': (width), 'height': height} in pixels. If None, the default max size is used. :param maxDefault: a default value to use for width and height. :returns: maxWidth, maxHeight in pixels. 0 means no images are allowed. """ maxWidth = maxHeight = maxDefault if size is not None: if isinstance(size, dict): maxWidth = size.get('width', maxWidth) maxHeight = size.get('height', maxHeight) else: maxWidth = maxHeight = size # We may want to put an upper limit on what is requested so it can't be # completely overridden. return maxWidth, maxHeight
[docs]class PILFileTileSource(FileTileSource, metaclass=LruCacheMetaclass): """ Provides tile access to single image PIL files. """ cacheName = 'tilesource' name = 'pil' # Although PIL is always a fallback source, prefer it to other fallback # sources extensions = { None: SourcePriority.FALLBACK_HIGH } mimeTypes = { None: SourcePriority.FALLBACK_HIGH } def __init__(self, path, maxSize=None, **kwargs): """ Initialize the tile class. See the base class for other available parameters. :param path: the associated file path. :param maxSize: either a number or an object with {'width': (width), 'height': height} in pixels. If None, the default max size is used. """ super().__init__(path, **kwargs) self._maxSize = maxSize if isinstance(maxSize, str): try: maxSize = json.loads(maxSize) except Exception: raise TileSourceError( 'maxSize must be None, an integer, a dictionary, or a ' 'JSON string that converts to one of those.') self.maxSize = maxSize largeImagePath = self._getLargeImagePath() # Some formats shouldn't be read this way, even if they could. For # instances, mirax (mrxs) files look like JPEGs, but opening them as # such misses most of the data. self._ignoreSourceNames('pil', largeImagePath) try: self._pilImage = PIL.Image.open(largeImagePath) except OSError: if not os.path.isfile(largeImagePath): raise TileSourceFileNotFoundError(largeImagePath) from None raise TileSourceError('File cannot be opened via PIL.') minwh = min(self._pilImage.width, self._pilImage.height) maxwh = max(self._pilImage.width, self._pilImage.height) # Throw an exception if too small or big before processing further if minwh <= 0: raise TileSourceError('PIL tile size is invalid.') maxWidth, maxHeight = getMaxSize(maxSize, self.defaultMaxSize()) if maxwh > max(maxWidth, maxHeight): raise TileSourceError('PIL tile size is too large.') # If the rotation flag exists, loading the image may change the width # and height if getattr(self._pilImage, '_tile_orientation', None) not in {None, 1}: self._pilImage.load() # If this is encoded as a 32-bit integer or a 32-bit float, convert it # to an 8-bit integer. This expects the source value to either have a # maximum of 1, 2^8-1, 2^16-1, 2^24-1, or 2^32-1, and scales it to # [0, 255] pilImageMode = self._pilImage.mode.split(';')[0] if pilImageMode in ('I', 'F'): imgdata = numpy.asarray(self._pilImage) maxval = 256 ** math.ceil(math.log(numpy.max(imgdata) + 1, 256)) - 1 self._pilImage = PIL.Image.fromarray(numpy.uint8(numpy.multiply( imgdata, 255.0 / maxval))) self.sizeX = self._pilImage.width self.sizeY = self._pilImage.height # We have just one tile which is the entire image. self.tileWidth = self.sizeX self.tileHeight = self.sizeY self.levels = 1 # Throw an exception if too big after processing if self.tileWidth > maxWidth or self.tileHeight > maxHeight: raise TileSourceError('PIL tile size is too large.')
[docs] def defaultMaxSize(self): """ Get the default max size from the config settings. :returns: the default max size. """ return int(config.getConfig('max_small_image_size', 4096))
[docs] @staticmethod def getLRUHash(*args, **kwargs): return strhash( super(PILFileTileSource, PILFileTileSource).getLRUHash( *args, **kwargs), kwargs.get('maxSize'))
[docs] def getState(self): return super().getState() + ',' + str( self._maxSize)
[docs] def getInternalMetadata(self, **kwargs): """ Return additional known metadata about the tile source. Data returned from this method is not guaranteed to be in any particular format or have specific values. :returns: a dictionary of data or None. """ results = {'pil': {}} for key in ('filename', 'format', 'mode', 'size', 'width', 'height', 'palette', 'info'): try: results['pil'][key] = getattr(self._pilImage, key) except Exception: pass return results
[docs] @methodcache() def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, mayRedirect=False, **kwargs): self._xyzInRange(x, y, z) return self._outputTile(self._pilImage, TILE_FORMAT_PIL, x, y, z, pilImageAllowed, numpyAllowed, **kwargs)
[docs]def open(*args, **kwargs): """ Create an instance of the module class. """ return PILFileTileSource(*args, **kwargs)
[docs]def canRead(*args, **kwargs): """ Check if an input can be read by the module class. """ return PILFileTileSource.canRead(*args, **kwargs)