From fe62446b3f2f5725201680c2c4f213895add4c61 Mon Sep 17 00:00:00 2001 From: David Manthey Date: Fri, 15 Dec 2023 09:16:06 -0500 Subject: [PATCH] Speed up tile output. Skip a decode step if possible; use simplejpeg for decoding when available and appropriate. --- CHANGELOG.md | 2 ++ large_image/tilesource/tiledict.py | 9 +++++---- large_image/tilesource/utilities.py | 17 +++++++++++++++-- .../multi/large_image_source_multi/__init__.py | 1 + 4 files changed, 23 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2860c524a..1df6192f9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,8 @@ - Add a utility function for getting loggers ([#1403](../../pull/1403)) - Get bioformats jar version if bioformats was started ([#1405](../../pull/1405)) - Improve thread safety of concurrent first reads using bioformats ([#1406](../../pull/1406)) +- Read more metadata from DICOMweb ([#1378](../../pull/1378)) +- Speed up tile output ([#1407](../../pull/1407)) ### Changes - Use an enum for priority constants ([#1400](../../pull/1400)) diff --git a/large_image/tilesource/tiledict.py b/large_image/tilesource/tiledict.py index 9c1c9afed..f85a535b0 100644 --- a/large_image/tilesource/tiledict.py +++ b/large_image/tilesource/tiledict.py @@ -173,10 +173,11 @@ def __getitem__(self, key, *args, **kwargs): else: tileData = self._retileTile() - pilData = _imageToPIL(tileData) - + pilData = None # resample if needed if self.resample not in (False, None) and self.requestedScale: + pilData = _imageToPIL(tileData) + self['width'] = max(1, int( pilData.size[0] / self.requestedScale)) self['height'] = max(1, int( @@ -206,7 +207,7 @@ def __getitem__(self, key, *args, **kwargs): tileData, _ = _imageToNumpy(tileData) tileFormat = TILE_FORMAT_NUMPY elif TILE_FORMAT_PIL in self.format: - tileData = pilData + tileData = pilData if pilData is not None else _imageToPIL(tileData) tileFormat = TILE_FORMAT_PIL elif TILE_FORMAT_IMAGE in self.format: tileData, mimeType = _encodeImage( @@ -217,7 +218,7 @@ def __getitem__(self, key, *args, **kwargs): 'Cannot yield tiles in desired format %r' % ( self.format, )) else: - tileData = pilData + tileData = pilData if pilData is not None else _imageToPIL(tileData) tileFormat = TILE_FORMAT_PIL self['tile'] = tileData diff --git a/large_image/tilesource/utilities.py b/large_image/tilesource/utilities.py index f025aa766..20e70bc93 100644 --- a/large_image/tilesource/utilities.py +++ b/large_image/tilesource/utilities.py @@ -32,6 +32,7 @@ 'GREEN': '#00ff00', 'BLUE': '#0000ff', } +modesBySize = ['L', 'LA', 'RGB', 'RGBA'] class ImageBytes(bytes): @@ -187,7 +188,7 @@ def _imageToPIL(image, setMode=None): # Fallback for hyperspectral data to just use the first three bands if image.shape[2] > 4: image = image[:, :, :3] - mode = ['L', 'LA', 'RGB', 'RGBA'][image.shape[2] - 1] + mode = modesBySize[image.shape[2] - 1] if len(image.shape) == 3 and image.shape[2] == 1: image = np.resize(image, image.shape[:2]) if image.dtype == np.uint32: @@ -220,6 +221,17 @@ def _imageToNumpy(image): :param image: input image. :returns: a numpy array and a target PIL image mode. """ + if isinstance(image, np.ndarray) and len(image.shape) == 3 and 1 <= image.shape[2] <= 4: + return image, modesBySize[image.shape[2] - 1] + if (simplejpeg and isinstance(image, bytes) and image[:3] == b'\xff\xd8\xff' and + b'\xff\xc0' in image[:1024]): + idx = image.index(b'\xff\xc0') + if image[idx + 9:idx + 10] in {b'\x01', b'\x03'}: + try: + image = simplejpeg.decode_jpeg( + image, colorspace='GRAY' if image[idx + 9:idx + 10] == b'\x01' else 'RGB') + except Exception: + pass if not isinstance(image, np.ndarray): if not isinstance(image, PIL.Image.Image): image = PIL.Image.open(io.BytesIO(image)) @@ -232,7 +244,8 @@ def _imageToNumpy(image): image = np.asarray(image) else: if len(image.shape) == 3: - mode = ['L', 'LA', 'RGB', 'RGBA'][(image.shape[2] - 1) if image.shape[2] <= 4 else 3] + mode = modesBySize[(image.shape[2] - 1) if image.shape[2] <= 4 else 3] + return image, mode else: mode = 'L' if len(image.shape) == 2: diff --git a/sources/multi/large_image_source_multi/__init__.py b/sources/multi/large_image_source_multi/__init__.py index 56f3f649f..b9483f380 100644 --- a/sources/multi/large_image_source_multi/__init__.py +++ b/sources/multi/large_image_source_multi/__init__.py @@ -970,6 +970,7 @@ def _addSourceToTile(self, tile, sourceEntry, corners, scale): x = y = 0 # If there is no transform or the diagonals are positive and there is # no sheer, use getRegion with an appropriate size (be wary of edges) + # TODO: when affine is generalized, only use this if scale is 1 if (transform is None or transform[0][0] > 0 and transform[0][1] == 0 and transform[1][0] == 0 and transform[1][1] > 0):