diff --git a/CHANGELOG.md b/CHANGELOG.md index f908dd25f..d43a0eece 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,9 @@ # Change Log -## 1.22.5 +## 1.23.0 + +### Features +- Add tile source bandCount attribute ([#1214](../../pull/1214)) ### Improvements - Harden the nd2 source to allow it to read more files ([#1207](../../pull/1207)) diff --git a/large_image/tilesource/base.py b/large_image/tilesource/base.py index 2e9465ea9..d0b3ae73c 100644 --- a/large_image/tilesource/base.py +++ b/large_image/tilesource/base.py @@ -141,6 +141,7 @@ def __init__(self, encoding='JPEG', jpegQuality=95, jpegSubsampling=0, self.sizeY = None self._styleLock = threading.RLock() self._dtype = None + self._bandCount = None if encoding not in TileOutputMimeTypes: raise ValueError('Invalid encoding "%s"' % encoding) @@ -214,7 +215,7 @@ def getBounds(self, *args, **kwargs): } def getCenter(self, *args, **kwargs): - """Retruns (Y, X) center location.""" + """Returns (Y, X) center location.""" if self.geospatial: bounds = self.getBounds(*args, **kwargs) return ( @@ -239,6 +240,7 @@ def dtype(self): region=dict(left=0, top=0, width=1, height=1), format=TILE_FORMAT_NUMPY) self._dtype = sample.dtype + self._bandCount = sample.shape[-1] if len(sample.shape) == 3 else 1 finally: self._setSkipStyle(False) else: @@ -246,6 +248,13 @@ def dtype(self): return self._dtype + @property + def bandCount(self): + if not self._bandCount: + if not self.dtype: + return None + return self._bandCount + @staticmethod def getLRUHash(*args, **kwargs): """ @@ -1604,10 +1613,14 @@ def _outputTile(self, tile, tileEncoding, x, y, z, pilImageAllowed=False, if self._dtype is None: if tileEncoding == TILE_FORMAT_NUMPY: self._dtype = tile.dtype + self._bandCount = tile.shape[-1] if len(tile.shape) == 3 else 1 elif tileEncoding == TILE_FORMAT_PIL: self._dtype = numpy.uint8 if ';16' not in tile.mode else numpy.uint16 + self._bandCount = len(tile.mode) else: - self._dtype = _imageToNumpy(tile)[0].dtype + _img = _imageToNumpy(tile)[0] + self._dtype = _img.dtype + self._bandCount = _img.shape[-1] if len(_img.shape) == 3 else 1 mode = None if (numpyAllowed == 'always' or tileEncoding == TILE_FORMAT_NUMPY or @@ -1717,6 +1730,7 @@ def getMetadata(self): 'mm_x': mag['mm_x'], 'mm_y': mag['mm_y'], 'dtype': str(self.dtype), + 'bandCount': self.bandCount, }) @property diff --git a/sources/gdal/large_image_source_gdal/__init__.py b/sources/gdal/large_image_source_gdal/__init__.py index 3d412e697..9c302d4f4 100644 --- a/sources/gdal/large_image_source_gdal/__init__.py +++ b/sources/gdal/large_image_source_gdal/__init__.py @@ -580,22 +580,17 @@ def geospatial(self): hasattr(self, '_netcdf')) def getMetadata(self): + metadata = super().getMetadata() with self._getDatasetLock: - metadata = JSONDict({ + metadata.update({ 'geospatial': self.geospatial, - 'levels': self.levels, - 'sizeX': self.sizeX, - 'sizeY': self.sizeY, 'sourceLevels': self.sourceLevels, 'sourceSizeX': self.sourceSizeX, 'sourceSizeY': self.sourceSizeY, - 'tileWidth': self.tileWidth, - 'tileHeight': self.tileHeight, 'bounds': self.getBounds(self.projection), 'sourceBounds': self.getBounds(), 'bands': self.getBandInformation(), }) - metadata.update(self.getNativeMagnification()) if hasattr(self, '_netcdf'): # To ensure all band information from all subdatasets in netcdf, # we could do the following: diff --git a/sources/rasterio/large_image_source_rasterio/__init__.py b/sources/rasterio/large_image_source_rasterio/__init__.py index ab005ad50..d0aa5a278 100644 --- a/sources/rasterio/large_image_source_rasterio/__init__.py +++ b/sources/rasterio/large_image_source_rasterio/__init__.py @@ -493,30 +493,22 @@ def getBandInformation(self, statistics=True, dataset=None, **kwargs): return infoSet def getMetadata(self): + metadata = super().getMetadata() with self._getDatasetLock: # check if the file is geospatial has_projection = self.dataset.crs has_gcps = len(self.dataset.gcps[0]) != 0 and self.dataset.gcps[1] has_affine = self.dataset.transform - metadata = JSONDict({ + metadata.update({ 'geospatial': bool(has_projection or has_gcps or has_affine), - 'levels': self.levels, - 'sizeX': self.sizeX, - 'sizeY': self.sizeY, 'sourceLevels': self.sourceLevels, 'sourceSizeX': self.sourceSizeX, 'sourceSizeY': self.sourceSizeY, - 'tileWidth': self.tileWidth, - 'tileHeight': self.tileHeight, 'bounds': self.getBounds(self.projection), 'sourceBounds': self.getBounds(), 'bands': self.getBandInformation(), }) - - # magnification is computed elswhere - metadata.update(self.getNativeMagnification()) - return metadata def getInternalMetadata(self, **kwargs): diff --git a/sources/tiff/large_image_source_tiff/__init__.py b/sources/tiff/large_image_source_tiff/__init__.py index ace70480b..1551ff709 100644 --- a/sources/tiff/large_image_source_tiff/__init__.py +++ b/sources/tiff/large_image_source_tiff/__init__.py @@ -165,6 +165,7 @@ def __init__(self, path, **kwargs): # noqa tifftools.constants.SampleFormat[sampleformat or 1].name, bitspersample )) + self._bandCount = highest._tiffInfo.get('samplesperpixel') # Sort the directories so that the highest resolution is the last one; # if a level is missing, put a None value in its place. self._tiffDirectories = [directories.get(key) for key in @@ -298,6 +299,7 @@ def _initWithTiffTools(self): # noqa tifftools.constants.SampleFormat[sampleformat or 1].name, bitspersample )) + self._bandCount = dir0._tiffInfo.get('samplesperpixel') info = _cached_read_tiff(self._largeImagePath) frames = [] associated = [] # for now, a list of directories diff --git a/sources/tiff/large_image_source_tiff/tiff_reader.py b/sources/tiff/large_image_source_tiff/tiff_reader.py index 2cf4e8fca..9124ea29a 100644 --- a/sources/tiff/large_image_source_tiff/tiff_reader.py +++ b/sources/tiff/large_image_source_tiff/tiff_reader.py @@ -787,7 +787,10 @@ def getTile(self, x, y): # a form we expect. If this isn't done, then PIL can load the image # multiple times, which sometimes throws an exception in PIL's JPEG # 2000 module. - image = image.convert('RGB') + if image.mode != 'L': + image = image.convert('RGB') + else: + image.load() return image def parse_image_description(self, meta=None): # noqa diff --git a/sources/tifffile/large_image_source_tifffile/__init__.py b/sources/tifffile/large_image_source_tifffile/__init__.py index 316aadd7c..64ce77100 100644 --- a/sources/tifffile/large_image_source_tifffile/__init__.py +++ b/sources/tifffile/large_image_source_tifffile/__init__.py @@ -309,7 +309,8 @@ def _handle_scn(self): # noqa self._channelInfo = channels try: self._channels = [ - channels.get(idx)['name'] for idx in range(len(channels))] + channels.get(idx)['name'].split('|')[0] + for idx in range(len(channels))] except Exception: pass diff --git a/test/test_source_base.py b/test/test_source_base.py index 70aa63a35..423be0d03 100644 --- a/test/test_source_base.py +++ b/test/test_source_base.py @@ -196,6 +196,7 @@ def testSourcesTilesAndMethods(source, filename): assert ts.getInternalMetadata() is not None assert ts.getOneBandInformation(1) is not None assert len(ts.getBandInformation()) >= 1 + assert len(ts.getBandInformation()) == tileMetadata['bandCount'] assert ts.getPixel(region=dict(left=0, top=0)) is not None # Histograms are too slow to test in this way # assert len(ts.histogram()['histogram']) >= 1