From 4df4ee1186d74c0f339a59bbe90a280ed0437497 Mon Sep 17 00:00:00 2001 From: David Manthey Date: Tue, 16 Aug 2022 12:46:15 -0400 Subject: [PATCH] Support a bands parameter for the test source. --- CHANGELOG.md | 1 + .../large_image_source_multi/__init__.py | 6 ++++ .../test/large_image_source_test/__init__.py | 31 ++++++++++++++++--- test/test_files/multi_band.yml | 17 ++++++++++ test/test_files/multi_channels.yml | 2 +- test/test_source_multi.py | 10 ++++++ 6 files changed, 61 insertions(+), 6 deletions(-) create mode 100644 test/test_files/multi_band.yml diff --git a/CHANGELOG.md b/CHANGELOG.md index 590d46a9d..fbd43fbc1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ - Reduce error messages and make canRead results more accurate ([918](../../pull/918), [919](../../pull/919), [920](../../pull/920)) - Sort columns in item lists based ([925](../../pull/925), [928](../../pull/928)) - Better handle tiffs with orientation flags in pil source ([924](../../pull/924)) +Support a bands parameter for the test source ([935](../../pull/935)) ### Changes - Remove some extraneous values from metadata responses ([932](../../pull/932)) diff --git a/sources/multi/large_image_source_multi/__init__.py b/sources/multi/large_image_source_multi/__init__.py index 93bbb6056..e924a3bc9 100644 --- a/sources/multi/large_image_source_multi/__init__.py +++ b/sources/multi/large_image_source_multi/__init__.py @@ -676,6 +676,10 @@ def _collectFrames(self, checkAll=False): self._nativeMagnification[key] or tsMag.get(key)) numChecked += 1 tsMeta = ts.getMetadata() + if 'bands' in tsMeta: + if not hasattr(self, '_bands'): + self._bands = {} + self._bands.update(tsMeta['bands']) bbox = self._sourceBoundingBox(source, tsMeta['sizeX'], tsMeta['sizeY']) computedWidth = max(computedWidth, int(math.ceil(bbox['right']))) computedHeight = max(computedHeight, int(math.ceil(bbox['bottom']))) @@ -774,6 +778,8 @@ def getMetadata(self): {k: v for k, v in frame.items() if k.startswith('Index')} for frame in self._frames] self._addMetadataFrameInformation(result, self._channels) + if hasattr(self, '_bands'): + result['bands'] = self._bands.copy() return result def getInternalMetadata(self, **kwargs): diff --git a/sources/test/large_image_source_test/__init__.py b/sources/test/large_image_source_test/__init__.py index 4834d1e91..d890f23cf 100644 --- a/sources/test/large_image_source_test/__init__.py +++ b/sources/test/large_image_source_test/__init__.py @@ -18,12 +18,14 @@ import itertools import math +import numpy from PIL import Image, ImageDraw, ImageFont from large_image.cache_util import LruCacheMetaclass, methodcache, strhash -from large_image.constants import TILE_FORMAT_PIL, SourcePriority +from large_image.constants import TILE_FORMAT_NUMPY, TILE_FORMAT_PIL, SourcePriority from large_image.exceptions import TileSourceError from large_image.tilesource import TileSource +from large_image.tilesource.utilities import _imageToNumpy try: from importlib.metadata import PackageNotFoundError @@ -52,7 +54,8 @@ class TestTileSource(TileSource, metaclass=LruCacheMetaclass): def __init__(self, ignored_path=None, minLevel=0, maxLevel=9, tileWidth=256, tileHeight=256, sizeX=None, sizeY=None, - fractal=False, frames=None, monochrome=False, **kwargs): + fractal=False, frames=None, monochrome=False, bands=None, + **kwargs): """ Initialize the tile class. See the base class for other available parameters. @@ -71,6 +74,8 @@ def __init__(self, ignored_path=None, minLevel=0, maxLevel=9, :param frames: if present, this is either a single number for generic frames, or comma-separated list of c,z,t,xy. :param monochrome: if True, return single channel tiles. + :param bands: if present, a comma-separated list of band names. + Defaults to red,green,blue. """ if not kwargs.get('encoding'): kwargs = kwargs.copy() @@ -93,6 +98,8 @@ def __init__(self, ignored_path=None, minLevel=0, maxLevel=9, self.sizeX / self.tileWidth, self.sizeY / self.tileHeight)))) self.frameSpec = frames or None self.monochrome = bool(monochrome) + self.bandSpec = bands or None + self._bands = bands.split(',') if bands else None # Used for reporting tile information self.levels = self.maxLevel + 1 if frames: @@ -156,6 +163,9 @@ def getMetadata(self): if hasattr(self, '_frames') and len(self._frames) > 1: result['frames'] = self._frames self._addMetadataFrameInformation(result) + if self._bands: + result['bands'] = {n + 1: {'interpretation': val} + for n, val in enumerate(self._bands)} return result def getInternalMetadata(self, **kwargs): @@ -227,7 +237,17 @@ def getTile(self, x, y, z, *args, **kwargs): _counters['tiles'] += 1 if self.monochrome: image = image.convert('L') - return self._outputTile(image, TILE_FORMAT_PIL, x, y, z, **kwargs) + if not self._bands or len(self._bands) == len(image.mode): + return self._outputTile(image, TILE_FORMAT_PIL, x, y, z, **kwargs) + image = _imageToNumpy(image)[0] + newimg = numpy.zeros((image.shape[0], image.shape[1], len(self._bands))) + newimg[:, :, :image.shape[2]] = image + for b in range(image.shape[2], len(self._bands)): + newimg[:, :, b] = (b / len(self._bands) + (1 - b / len(self._bands)) * xFraction) * 255 + newimg[image[:, :, 0] == 0] = 255 if self._bands[b] in {'alpha', 'A'} else 0 + newimg[image[:, :, 0] == 255] = 255 + image = newimg + return self._outputTile(image, TILE_FORMAT_NUMPY, x, y, z, **kwargs) @staticmethod def getLRUHash(*args, **kwargs): @@ -238,13 +258,14 @@ def getLRUHash(*args, **kwargs): kwargs.get('tileWidth'), kwargs.get('tileHeight'), kwargs.get('fractal'), kwargs.get('sizeX'), kwargs.get('sizeY'), kwargs.get('frames'), kwargs.get('monochrome'), + kwargs.get('bands'), ) def getState(self): - return 'test %r %r %r %r %r %r %r %r %r %r' % ( + return 'test %r %r %r %r %r %r %r %r %r %r %r' % ( super().getState(), self.minLevel, self.maxLevel, self.tileWidth, self.tileHeight, self.fractal, self.sizeX, self.sizeY, - self.frameSpec, self.monochrome) + self.frameSpec, self.monochrome, self.bandSpec) def open(*args, **kwargs): diff --git a/test/test_files/multi_band.yml b/test/test_files/multi_band.yml new file mode 100644 index 000000000..8863922c9 --- /dev/null +++ b/test/test_files/multi_band.yml @@ -0,0 +1,17 @@ +--- +sources: + - sourceName: test + path: __none__ + params: + minLevel: 0 + maxLevel: 6 + tileWidth: 256 + tileHeight: 256 + sizeX: 10000 + sizeY: 7500 + fractal: True + # c,z,t,xy OR frames + frames: "4,6,1,1" + monochrome: False + # multiband + bands: "red,green,blue,ir1,ir2" diff --git a/test/test_files/multi_channels.yml b/test/test_files/multi_channels.yml index d525db494..778d21243 100644 --- a/test/test_files/multi_channels.yml +++ b/test/test_files/multi_channels.yml @@ -1,5 +1,5 @@ --- -name: Multi orientationa +name: Multi orientation sources: - path: ./test_orient1.tif channel: CY3 diff --git a/test/test_source_multi.py b/test/test_source_multi.py index d218b2e87..2ce5f5217 100644 --- a/test/test_source_multi.py +++ b/test/test_source_multi.py @@ -146,3 +146,13 @@ def testCanRead(): assert large_image_source_multi.canRead(imagePath) is True imagePath2 = os.path.join(testDir, 'test_files', 'test_orient1.tif') assert large_image_source_multi.canRead(imagePath2) is False + + +def testMultiBand(): + testDir = os.path.dirname(os.path.realpath(__file__)) + imagePath = os.path.join(testDir, 'test_files', 'multi_band.yml') + source = large_image_source_multi.open(imagePath) + metadata = source.getMetadata() + assert len(metadata['bands']) == 5 + image, mimeType = source.getThumbnail(encoding='PNG') + assert image[:len(utilities.PNGHeader)] == utilities.PNGHeader