diff --git a/CHANGELOG.md b/CHANGELOG.md index c16aed6df..65f42bb89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Change Log -## 1.28.3 +## 1.29.0 ### Features - Add redis as cache backend option ([#1469](../../pull/1469)) @@ -10,6 +10,7 @@ - Reject the promise from a canceled annotation ([#1535](../../pull/1535)) - Disallow certain names from being read by any but specific sources ([#1537](../../pull/1537)) - Add the ability to specify the dtype of a multi-source file ([#1542](../../pull/1542)) +- Handle bounds on openslide sources to handle sparse sources ([#1543](../../pull/1543)) ### Changes - Log more when saving annotations ([#1525](../../pull/1525)) diff --git a/sources/openslide/large_image_source_openslide/__init__.py b/sources/openslide/large_image_source_openslide/__init__.py index d975d228a..f6a987133 100644 --- a/sources/openslide/large_image_source_openslide/__init__.py +++ b/sources/openslide/large_image_source_openslide/__init__.py @@ -154,6 +154,30 @@ def __init__(self, path, **kwargs): # noqa 'svslevel': bestlevel, 'scale': scale, }) + self._bounds = None + try: + bounds = { + 'x': int(self._openslide.properties[openslide.PROPERTY_NAME_BOUNDS_X]), + 'y': int(self._openslide.properties[openslide.PROPERTY_NAME_BOUNDS_Y]), + 'width': int(self._openslide.properties[openslide.PROPERTY_NAME_BOUNDS_WIDTH]), + 'height': int(self._openslide.properties[openslide.PROPERTY_NAME_BOUNDS_HEIGHT]), + } + if ( + bounds['x'] >= 0 and bounds['width'] > 0 and + bounds['x'] + bounds['width'] <= self.sizeX and + bounds['y'] >= 0 and bounds['height'] > 0 and + bounds['y'] + bounds['height'] <= self.sizeY and + (bounds['width'] < self.sizeX or bounds['height'] < self.sizeY) + ): + self._bounds = bounds + self.sizeX, self.sizeY = bounds['width'], bounds['height'] + prevlevels = self.levels + self.levels = int(math.ceil(max( + math.log(float(self.sizeX) / self.tileWidth), + math.log(float(self.sizeY) / self.tileHeight)) / math.log(2))) + 1 + self._svslevels = self._svslevels[prevlevels - self.levels:] + except Exception: + pass self._populatedLevels = len({l['svslevel'] for l in self._svslevels}) def _getTileSize(self): @@ -292,6 +316,9 @@ def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, **kwargs): scale = 2 ** (self.levels - 1 - z) offsetx = x * self.tileWidth * scale offsety = y * self.tileHeight * scale + if self._bounds is not None: + offsetx += self._bounds['x'] // svslevel['scale'] + offsety += self._bounds['y'] // svslevel['scale'] # We ask to read an area that will cover the tile at the z level. The # scale we computed in the __init__ process for this svs level tells # how much larger a region we need to read.