From 9b56008ea174ba4449566142f436eb6aea83a30d Mon Sep 17 00:00:00 2001 From: David Manthey Date: Wed, 22 Jan 2025 11:28:17 -0500 Subject: [PATCH] Speed up style composition. The speed up is achieved by have a special case for palettes that have two entries with the first entry 0 and for conditions where no mask is required. Slightly speed up source comparison --- CHANGELOG.md | 1 + docs/tilesource_options.rst | 2 +- large_image/tilesource/base.py | 26 +++++++++++++++----------- test/lisource_compare.py | 4 ++-- 4 files changed, 19 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index eacbd8ded..b0c200bb9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ - Default to not caching source in notebooks ([#1776](../../pull/1776)) - Automatically set the JUPYTER_PROXY value ([#1781](../../pull/1781)) - Add a general channelNames property to tile sources ([#1783](../../pull/1783)) +- Speed up compositing styles ([#1784](../../pull/1784)) ### Changes diff --git a/docs/tilesource_options.rst b/docs/tilesource_options.rst index 6689a7ca7..5f70b109b 100644 --- a/docs/tilesource_options.rst +++ b/docs/tilesource_options.rst @@ -111,7 +111,7 @@ A band definition is an object which can contain the following keys: - ``band``: the band numpy image in a band stage. - - ``mask``: a mask numpy image to use when applying the band. + - ``mask``: a mask numpy image to use when applying the band. None for no mask (this is the equivalent of the mask being all True). - ``palette``: the normalized palette for a band. diff --git a/large_image/tilesource/base.py b/large_image/tilesource/base.py index c78d74664..ab89b973c 100644 --- a/large_image/tilesource/base.py +++ b/large_image/tilesource/base.py @@ -1070,9 +1070,11 @@ def _applyStyle( # noqa if sc.nodata is not None: sc.mask = sc.band != float(sc.nodata) else: - sc.mask = np.full(image.shape[:2], True) + sc.mask = None sc.band = (sc.band - sc.min) / delta if not sc.clamp: + if sc.mask is None: + sc.mask = np.full(image.shape[:2], True) sc.mask = sc.mask & (sc.band >= 0) & (sc.band <= 1) sc.band = self._applyStyleFunction(sc.band, sc, 'band') # To implement anything other multiply or lighten, we should mimic @@ -1097,25 +1099,27 @@ def _applyStyle( # noqa if not channel or np.any( sc.palette[:, channel] != sc.palette[:, channel - 1]): if not sc.discrete: - clrs = np.interp(sc.band, sc.palettebase, sc.palette[:, channel]) + if len(sc.palette) == 2 and sc.palette[0, channel] == 0: + clrs = sc.band * sc.palette[1, channel] + else: + clrs = np.interp(sc.band, sc.palettebase, sc.palette[:, channel]) else: clrs = sc.palette[ np.floor(sc.band * len(sc.palette)).astype(int).clip( 0, len(sc.palette) - 1), channel] if sc.composite == 'multiply': if eidx: - sc.output[:sc.mask.shape[0], :sc.mask.shape[1], channel] = np.multiply( - sc.output[:sc.mask.shape[0], :sc.mask.shape[1], channel], - np.where(sc.mask, clrs / 255, 1)) + sc.output[:clrs.shape[0], :clrs.shape[1], channel] = np.multiply( + sc.output[:clrs.shape[0], :clrs.shape[1], channel], + (clrs / 255) if sc.mask is None else np.where(sc.mask, clrs / 255, 1)) else: if not eidx: - sc.output[:sc.mask.shape[0], - :sc.mask.shape[1], - channel] = np.where(sc.mask, clrs, 0) + sc.output[:clrs.shape[0], :clrs.shape[1], channel] = ( + clrs if sc.mask is None else np.where(sc.mask, clrs, 0)) else: - sc.output[:sc.mask.shape[0], :sc.mask.shape[1], channel] = np.maximum( - sc.output[:sc.mask.shape[0], :sc.mask.shape[1], channel], - np.where(sc.mask, clrs, 0)) + sc.output[:clrs.shape[0], :clrs.shape[1], channel] = np.maximum( + sc.output[:clrs.shape[0], :clrs.shape[1], channel], + clrs if sc.mask is None else np.where(sc.mask, clrs, 0)) sc.output = self._applyStyleFunction(sc.output, sc, 'postband') if hasattr(sc, 'styleIndex'): del sc.styleIndex diff --git a/test/lisource_compare.py b/test/lisource_compare.py index 9dc3d5f76..19bfe7cce 100755 --- a/test/lisource_compare.py +++ b/test/lisource_compare.py @@ -146,7 +146,6 @@ def source_compare(sourcePath, opts): # noqa canread = large_image.canReadList(sourcePath, availableSources=sublist) if opts.can_read and not len([cr for cr in canread if cr[1]]): return None - large_image.cache_util.cachesClear() slen = max([len(source) for source, _ in canread] + [10]) sys.stdout.write('Source' + ' ' * (slen - 6)) sys.stdout.write(' Width Height') @@ -208,7 +207,8 @@ def source_compare(sourcePath, opts): # noqa '_geospatial_source', None): continue result = results['styles'][-1]['sources'][source] = {} - large_image.cache_util.cachesClear() + if couldread: + large_image.cache_util.cachesClear() try: t = time.time() ts = large_image.tilesource.AvailableTileSources[source](sourcePath, **kwargs)