diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d4da4953..a02fe321d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ - Store the id of job results for easier post-job work ([887](../../pull/887)) - Harden style compositing of partial tiles ([889](../../pull/889)) - Cache read_tiff calls to speed up restyling ([891](../../pull/891)) +- Speed up styling by doing less ([892](../../pull/892)) ### Changes - Be more consistent in source class name attribute assignment ([884](../../pull/884)) diff --git a/large_image/tilesource/base.py b/large_image/tilesource/base.py index 1c171e475..72d2a2635 100644 --- a/large_image/tilesource/base.py +++ b/large_image/tilesource/base.py @@ -1128,7 +1128,7 @@ def _applyStyle(self, image, style, x, y, z, frame=None): # noqa output = numpy.zeros((image.shape[0], image.shape[1], 4), float) mainImage = image mainFrame = frame - for entry in style: + for eidx, entry in enumerate(style): dtype = dtype if dtype is not None else entry.get('dtype') axis = axis if axis is not None else entry.get('axis') bandidx = 0 if image.shape[2] <= 2 else 1 @@ -1213,13 +1213,17 @@ def _applyStyle(self, image, style, x, y, z, frame=None): # noqa clrs = palette[numpy.floor(band * len(palette)).astype(int).clip( 0, len(palette) - 1), channel] if composite == 'multiply': - output[:keep.shape[0], :keep.shape[1], channel] = numpy.multiply( - output[:keep.shape[0], :keep.shape[1], channel], - numpy.where(keep, clrs / 255, 1)) + if eidx: + output[:keep.shape[0], :keep.shape[1], channel] = numpy.multiply( + output[:keep.shape[0], :keep.shape[1], channel], + numpy.where(keep, clrs / 255, 1)) else: - output[:keep.shape[0], :keep.shape[1], channel] = numpy.maximum( - output[:keep.shape[0], :keep.shape[1], channel], - numpy.where(keep, clrs, 0)) + if not eidx: + output[:keep.shape[0], :keep.shape[1], channel] = numpy.where(keep, clrs, 0) + else: + output[:keep.shape[0], :keep.shape[1], channel] = numpy.maximum( + output[:keep.shape[0], :keep.shape[1], channel], + numpy.where(keep, clrs, 0)) if dtype == 'uint16': output = (output * 65535 / 255).astype(numpy.uint16) elif dtype == 'float': @@ -1287,7 +1291,8 @@ def _outputTile(self, tile, tileEncoding, x, y, z, pilImageAllowed=False, mode = None if (numpyAllowed == 'always' or tileEncoding == TILE_FORMAT_NUMPY or (applyStyle and getattr(self, 'style', None)) or isEdge): - tile, mode = self._outputTileNumpyStyle(tile, applyStyle, x, y, z, kwargs.get('frame')) + tile, mode = self._outputTileNumpyStyle( + tile, applyStyle, x, y, z, self._getFrame(**kwargs)) if isEdge: contentWidth = min(self.tileWidth, sizeX - (maxX - self.tileWidth)) @@ -1491,6 +1496,21 @@ def getBandInformation(self, statistics=False, **kwargs): self._bandInfo = bandInfo return self._bandInfo + def _getFrame(self, frame=None, **kwargs): + """ + Get the current frame number. If a style is used that completely + specified the frame, use that value instead. + + :param frame: an integer or string with the frame number. + :returns: an integer frame number. + """ + frame = int(frame or 0) + if (hasattr(self, 'style') and 'bands' in self.style and + len(self.style['bands']) and + all(entry.get('frame') is not None for entry in self.style['bands'])): + frame = int(self.style['bands'][0]['frame']) + return frame + def _xyzInRange(self, x, y, z, frame=None, numFrames=None): """ Check if a tile at x, y, z is in range based on self.levels, diff --git a/sources/bioformats/large_image_source_bioformats/__init__.py b/sources/bioformats/large_image_source_bioformats/__init__.py index 69a2f55a6..743d0c39c 100644 --- a/sources/bioformats/large_image_source_bioformats/__init__.py +++ b/sources/bioformats/large_image_source_bioformats/__init__.py @@ -478,7 +478,7 @@ def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, **kwargs): ft = fc = fz = 0 fseries = self._metadata['frameSeries'][0] if kwargs.get('frame') is not None: - frame = int(kwargs.get('frame')) + frame = self._getFrame(**kwargs) fc = frame % self._metadata['sizeC'] fz = (frame // self._metadata['sizeC']) % self._metadata['sizeZ'] ft = (frame // self._metadata['sizeC'] // diff --git a/sources/multi/large_image_source_multi/__init__.py b/sources/multi/large_image_source_multi/__init__.py index 391f1cc1e..cb55ee0cc 100644 --- a/sources/multi/large_image_source_multi/__init__.py +++ b/sources/multi/large_image_source_multi/__init__.py @@ -906,7 +906,7 @@ def _addSourceToTile(self, tile, sourceEntry, corners, scale): @methodcache() def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, **kwargs): - frame = int(kwargs.get('frame') or 0) + frame = self._getFrame(**kwargs) self._xyzInRange(x, y, z, frame, len(self._frames) if hasattr(self, '_frames') else None) scale = 2 ** (self.levels - 1 - z) corners = [[ diff --git a/sources/nd2/large_image_source_nd2/__init__.py b/sources/nd2/large_image_source_nd2/__init__.py index 44a35130e..973755025 100644 --- a/sources/nd2/large_image_source_nd2/__init__.py +++ b/sources/nd2/large_image_source_nd2/__init__.py @@ -254,7 +254,7 @@ def getInternalMetadata(self, **kwargs): @methodcache() def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, **kwargs): - frame = int(kwargs.get('frame') or 0) + frame = self._getFrame(**kwargs) self._xyzInRange(x, y, z, frame, self._framecount) x0, y0, x1, y1, step = self._xyzToCorners(x, y, z) tileframe = self._nd2array diff --git a/sources/ometiff/large_image_source_ometiff/__init__.py b/sources/ometiff/large_image_source_ometiff/__init__.py index 111e6e563..fc7d9356e 100644 --- a/sources/ometiff/large_image_source_ometiff/__init__.py +++ b/sources/ometiff/large_image_source_ometiff/__init__.py @@ -324,7 +324,7 @@ def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, x, y, z, pilImageAllowed=pilImageAllowed, numpyAllowed=numpyAllowed, sparseFallback=sparseFallback, **kwargs) - frame = int(kwargs.get('frame') or 0) + frame = self._getFrame(**kwargs) if frame < 0 or frame >= len(self._omebase['TiffData']): raise TileSourceError('Frame does not exist') subdir = None diff --git a/sources/test/large_image_source_test/__init__.py b/sources/test/large_image_source_test/__init__.py index 907b98cb6..4834d1e91 100644 --- a/sources/test/large_image_source_test/__init__.py +++ b/sources/test/large_image_source_test/__init__.py @@ -170,7 +170,7 @@ def getInternalMetadata(self, **kwargs): @methodcache() def getTile(self, x, y, z, *args, **kwargs): - frame = int(kwargs.get('frame') or 0) + frame = self._getFrame(**kwargs) self._xyzInRange(x, y, z, frame, len(self._frames) if hasattr(self, '_frames') else None) if not (self.minLevel <= z <= self.maxLevel): diff --git a/sources/tiff/large_image_source_tiff/__init__.py b/sources/tiff/large_image_source_tiff/__init__.py index 992b993cc..1c38bf186 100644 --- a/sources/tiff/large_image_source_tiff/__init__.py +++ b/sources/tiff/large_image_source_tiff/__init__.py @@ -544,7 +544,7 @@ def getInternalMetadata(self, **kwargs): @methodcache() def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, sparseFallback=False, **kwargs): - frame = int(kwargs.get('frame') or 0) + frame = self._getFrame(**kwargs) self._xyzInRange(x, y, z, frame, len(self._frames) if hasattr(self, '_frames') else None) if frame > 0: if self._frames[frame]['dirs'][z] is not None: @@ -641,7 +641,7 @@ def getTileFromEmptyDirectory(self, x, y, z, **kwargs): basez = z scale = 1 dirlist = self._tiffDirectories - frame = int(kwargs.get('frame') or 0) + frame = self._getFrame(**kwargs) if frame > 0: dirlist = self._frames[frame]['dirs'] while dirlist[z] is None: @@ -662,7 +662,7 @@ def getTileFromEmptyDirectory(self, x, y, z, **kwargs): subtile = self.getTile( x * scale + newX, y * scale + newY, z, pilImageAllowed=True, numpyAllowed=False, - sparseFallback=True, edge=False, frame=kwargs.get('frame')) + sparseFallback=True, edge=False, frame=frame) if not isinstance(subtile, PIL.Image.Image): subtile = PIL.Image.open(io.BytesIO(subtile)) tile.paste(subtile, (newX * self.tileWidth, diff --git a/sources/tifffile/large_image_source_tifffile/__init__.py b/sources/tifffile/large_image_source_tifffile/__init__.py index 8d5c67e43..dc50db9bd 100644 --- a/sources/tifffile/large_image_source_tifffile/__init__.py +++ b/sources/tifffile/large_image_source_tifffile/__init__.py @@ -365,7 +365,7 @@ def _getAssociatedImage(self, imageKey): @methodcache() def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, **kwargs): - frame = int(kwargs.get('frame') or 0) + frame = self._getFrame(**kwargs) self._xyzInRange(x, y, z, frame, self._framecount) x0, y0, x1, y1, step = self._xyzToCorners(x, y, z) if len(self._series) > 1: diff --git a/sources/vips/large_image_source_vips/__init__.py b/sources/vips/large_image_source_vips/__init__.py index 59fd77090..4dfbf63fc 100644 --- a/sources/vips/large_image_source_vips/__init__.py +++ b/sources/vips/large_image_source_vips/__init__.py @@ -184,7 +184,7 @@ def getNativeMagnification(self): @methodcache() def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, **kwargs): - frame = int(kwargs.get('frame') or 0) + frame = self._getFrame(**kwargs) self._xyzInRange(x, y, z, frame, len(self._frames)) img = self._getFrameImage(frame) x0, y0, x1, y1, step = self._xyzToCorners(x, y, z)