diff --git a/CHANGELOG.md b/CHANGELOG.md index 9c4031c53..6fc7a8de0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,10 @@ ### Improvements - Harden reading some nc files ([#1218](../../pull/1218)) - Increase the cache used for opening directories in the pylibtiff source ([#1221](../../pull/1221)) +- Refactor style locking to increase parallelism ([#1224](../../pull/1224)) + +### Changes +- Clean up some old slideatlas url references ([#1223](../../pull/1223)) ### Bug Fixes - Pass style to histogram endpoint as json ([#1220](../../pull/1220)) diff --git a/large_image/cache_util/cache.py b/large_image/cache_util/cache.py index 8ed49a6c0..9a02cd070 100644 --- a/large_image/cache_util/cache.py +++ b/large_image/cache_util/cache.py @@ -204,11 +204,12 @@ def __call__(cls, *args, **kwargs): # noqa - N805 subkwargs.pop('style') subresult = cls(*args, **subkwargs) result = subresult.__class__.__new__(subresult.__class__) - with subresult._styleLock: + with subresult._sourceLock: result.__dict__ = subresult.__dict__.copy() - result._styleLock = threading.RLock() + result._sourceLock = threading.RLock() result._setStyle(kwargs['style']) result._classkey = key + result._unstyledInstance = subresult # for pickling result._initValues = (args, kwargs.copy()) with cacheLock: diff --git a/large_image/tilesource/base.py b/large_image/tilesource/base.py index 0a80f0cdb..20dd8c395 100644 --- a/large_image/tilesource/base.py +++ b/large_image/tilesource/base.py @@ -139,7 +139,7 @@ def __init__(self, encoding='JPEG', jpegQuality=95, jpegSubsampling=0, self.levels = None self.sizeX = None self.sizeY = None - self._styleLock = threading.RLock() + self._sourceLock = threading.RLock() self._dtype = None self._bandCount = None @@ -232,27 +232,23 @@ def style(self): @property def dtype(self): - if not self._dtype: - with self._styleLock: - if not hasattr(self, '_skipStyle'): - self._setSkipStyle(True) - try: - sample, format = self.getRegion( - 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 - + with self._sourceLock: + if not self._dtype: + self._dtype = 'check' + sample, _ = getattr(self, '_unstyledInstance', self).getRegion( + region=dict(left=0, top=0, width=1, height=1), + format=TILE_FORMAT_NUMPY) + self._dtype = sample.dtype + self._bandCount = len( + getattr(getattr(self, '_unstyledInstance', self), '_bandInfo', [])) + if not self._bandCount: + self._bandCount = sample.shape[-1] if len(sample.shape) == 3 else 1 return self._dtype @property def bandCount(self): if not self._bandCount: - if not self.dtype: + if not self._dtype or str(self._dtype) == 'check': return None return self._bandCount @@ -1123,21 +1119,17 @@ def _scanForMinMax(self, dtype, frame=None, analysisSize=1024, onlyMinMax=True, :param onlyMinMax: if True, only find the min and max. If False, get the entire histogram. """ - self._setSkipStyle(True) - try: - self._bandRanges[frame] = self.histogram( - dtype=dtype, - onlyMinMax=onlyMinMax, - output={'maxWidth': min(self.sizeX, analysisSize), - 'maxHeight': min(self.sizeY, analysisSize)}, - resample=False, - frame=frame, **kwargs) - if self._bandRanges[frame]: - self.logger.info('Style range is %r' % { - k: v for k, v in self._bandRanges[frame].items() if k in { - 'min', 'max', 'mean', 'stdev'}}) - finally: - self._setSkipStyle(False) + self._bandRanges[frame] = getattr(self, '_unstyledInstance', self).histogram( + dtype=dtype, + onlyMinMax=onlyMinMax, + output={'maxWidth': min(self.sizeX, analysisSize), + 'maxHeight': min(self.sizeY, analysisSize)}, + resample=False, + frame=frame, **kwargs) + if self._bandRanges[frame]: + self.logger.info('Style range is %r' % { + k: v for k, v in self._bandRanges[frame].items() if k in { + 'min', 'max', 'mean', 'stdev'}}) def _validateMinMaxValue(self, value, frame, dtype): """ @@ -1392,21 +1384,6 @@ def _applyICCProfile(self, sc, frame): self.logger.exception('Failed to apply ICC profile') return sc.iccimage - def _setSkipStyle(self, setSkip=False): - if not hasattr(self, '_classkey'): - self._classkey = self.getState() - if setSkip: - self._unlocked_classkey = self._classkey - if hasattr(self, 'cache_lock'): - with self.cache_lock: - self._classkeyLock = self._styleLock - self._skipStyle = True - # Divert the tile cache while querying unstyled tiles - self._classkey = self._unstyledClassKey() - else: - del self._skipStyle - self._classkey = self._unlocked_classkey - def _applyStyle(self, image, style, x, y, z, frame=None): # noqa """ Apply a style to a numpy image. @@ -1450,14 +1427,11 @@ def _applyStyle(self, image, style, x, y, z, frame=None): # noqa else: frame = entry['frame'] if entry.get('frame') is not None else ( sc.mainFrame + entry['framedelta']) - self._setSkipStyle(True) - try: - image = self.getTile(x, y, z, frame=frame, numpyAllowed=True) - image = image[:sc.mainImage.shape[0], - :sc.mainImage.shape[1], - :sc.mainImage.shape[2]] - finally: - self._setSkipStyle(False) + image = getattr(self, '_unstyledInstance', self).getTile( + x, y, z, frame=frame, numpyAllowed=True) + image = image[:sc.mainImage.shape[0], + :sc.mainImage.shape[1], + :sc.mainImage.shape[2]] if (isinstance(entry.get('band'), int) and entry['band'] >= 1 and entry['band'] <= image.shape[2]): sc.bandidx = entry['band'] - 1 @@ -1566,9 +1540,7 @@ def _outputTileNumpyStyle(self, tile, applyStyle, x, y, z, frame=None): """ tile, mode = _imageToNumpy(tile) if applyStyle and (getattr(self, 'style', None) or hasattr(self, '_iccprofiles')): - with self._styleLock: - if not getattr(self, '_skipStyle', False): - tile = self._applyStyle(tile, getattr(self, 'style', None), x, y, z, frame) + tile = self._applyStyle(tile, getattr(self, 'style', None), x, y, z, frame) if tile.shape[0] != self.tileHeight or tile.shape[1] != self.tileWidth: extend = numpy.zeros( (self.tileHeight, self.tileWidth, tile.shape[2]), @@ -1611,7 +1583,7 @@ def _outputTile(self, tile, tileEncoding, x, y, z, pilImageAllowed=False, not isEdge and (not applyStyle or not hasStyle)): return tile - if self._dtype is None: + if self._dtype is None or str(self._dtype) == 'check': if tileEncoding == TILE_FORMAT_NUMPY: self._dtype = tile.dtype self._bandCount = tile.shape[-1] if len(tile.shape) == 3 else 1 @@ -1854,8 +1826,7 @@ def _getFrame(self, frame=None, **kwargs): :returns: an integer frame number. """ frame = int(frame or 0) - if (not getattr(self, '_skipStyle', None) and - hasattr(self, '_style') and 'bands' in self.style and + 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']) diff --git a/sources/rasterio/large_image_source_rasterio/__init__.py b/sources/rasterio/large_image_source_rasterio/__init__.py index d0aa5a278..6a779ebc5 100644 --- a/sources/rasterio/large_image_source_rasterio/__init__.py +++ b/sources/rasterio/large_image_source_rasterio/__init__.py @@ -488,7 +488,8 @@ def getBandInformation(self, statistics=True, dataset=None, **kwargs): infoSet[i] = {k: v for k, v in info.items() if v not in (None, '')} # set the value to cache if needed - cache is False or getattr(self, '_bandInfo', infoSet) + if cache: + self._bandInfo = infoSet return infoSet