Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add tile source bandCount attribute #1214

Merged
merged 1 commit into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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))
Expand Down
18 changes: 16 additions & 2 deletions large_image/tilesource/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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 (
Expand All @@ -239,13 +240,21 @@ 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:
return None

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):
"""
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 2 additions & 7 deletions sources/gdal/large_image_source_gdal/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
12 changes: 2 additions & 10 deletions sources/rasterio/large_image_source_rasterio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
2 changes: 2 additions & 0 deletions sources/tiff/large_image_source_tiff/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
5 changes: 4 additions & 1 deletion sources/tiff/large_image_source_tiff/tiff_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion sources/tifffile/large_image_source_tifffile/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions test/test_source_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down