From 7b9e679a04109821c610a11c857dc8140d76fb6d Mon Sep 17 00:00:00 2001 From: David Manthey Date: Tue, 8 Oct 2019 10:47:27 -0400 Subject: [PATCH] Limit the range of tile sizes the openjpeg source can produce. Tiles that are too large or small are inefficient. For the Leaflet and SlideAtlas viewers, show an error if the tile size is larger than 256, as neither of those viewers works properly in that case. --- server/tilesource/openjpeg.py | 25 ++++++++++++++++++- web_client/views/imageViewerWidget/leaflet.js | 11 +++++++- .../views/imageViewerWidget/slideatlas.js | 11 +++++++- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/server/tilesource/openjpeg.py b/server/tilesource/openjpeg.py index 1124c203b..1ca0b37a7 100644 --- a/server/tilesource/openjpeg.py +++ b/server/tilesource/openjpeg.py @@ -75,6 +75,9 @@ class OpenjpegFileTileSource(FileTileSource): } _xmlTag = b'mxl ' + _minTileSize = 256 + _maxTileSize = 512 + def __init__(self, path, **kwargs): """ Initialize the tile class. See the base class for other available @@ -99,10 +102,24 @@ def __init__(self, path, **kwargs): except IndexError: raise TileSourceException('File cannot be opened via Glymur and OpenJPEG.') self.levels = self._openjpeg.codestream.segment[2].num_res + 1 + self._minlevel = 0 self.tileWidth = self.tileHeight = 2 ** int(math.ceil(max( math.log(float(self.sizeX)) / math.log(2) - self.levels + 1, math.log(float(self.sizeY)) / math.log(2) - self.levels + 1))) - # read associated images and metadata from boxes + # Small and large tiles are both inefficient. Large tiles don't work + # with some viewers (leaflet and Slide Atlas, for instance) + if self.tileWidth < self._minTileSize or self.tileWidth > self._maxTileSize: + self.tileWidth = self.tileHeight = min( + self._maxTileSize, max(self._minTileSize, self.tileWidth)) + self.levels = math.ceil(math.log(float(max( + self.sizeX, self.sizeY)) / self.tileWidth) / math.log(2)) + 1 + self._minlevel = self.levels - self._openjpeg.codestream.segment[2].num_res - 1 + self._getAssociatedImages() + + def _getAssociatedImages(self): + """ + Read associated images and metadata from boxes. + """ self._associatedImages = {} for box in self._openjpeg.box: if box.box_id == self._xmlTag or box.box_id in self._boxToTag: @@ -203,6 +220,10 @@ def getTile(self, x, y, z, pilImageAllowed=False, **kwargs): raise TileSourceException('x is outside layer') if y < 0 or y0 >= self.sizeY: raise TileSourceException('y is outside layer') + scale = None + if z < self._minlevel: + scale = 2 ** (self._minlevel - z) + step = 2 ** (self.levels - 1 - self._minlevel) # possible open the file multiple times so multiple threads can access # it concurrently. with self._openjpegLock: @@ -218,6 +239,8 @@ def getTile(self, x, y, z, pilImageAllowed=False, **kwargs): if len(tile.shape) == 3: mode = ['L', 'LA', 'RGB', 'RGBA'][tile.shape[2] - 1] tile = PIL.Image.frombytes(mode, (tile.shape[1], tile.shape[0]), tile) + if scale: + tile = tile.resize((tile.size[0] // scale, tile.size[1] // scale), PIL.Image.LANCZOS) if tile.size != (self.tileWidth, self.tileHeight): wrap = PIL.Image.new(mode, (self.tileWidth, self.tileHeight)) wrap.paste(tile, (0, 0)) diff --git a/web_client/views/imageViewerWidget/leaflet.js b/web_client/views/imageViewerWidget/leaflet.js index 1858098bd..b416b8562 100644 --- a/web_client/views/imageViewerWidget/leaflet.js +++ b/web_client/views/imageViewerWidget/leaflet.js @@ -23,6 +23,7 @@ var LeafletImageViewerWidget = ImageViewerWidget.extend({ }, render: function () { + var errmsg; // If script or metadata isn't loaded, then abort if (!window.L || !this.tileWidth || !this.tileHeight || this.deleted) { return this; @@ -34,7 +35,15 @@ var LeafletImageViewerWidget = ImageViewerWidget.extend({ } if (this.tileWidth !== this.tileHeight) { - console.error('The Leaflet viewer only supports square tiles.'); + errmsg = 'The Leaflet viewer only supports square tiles.'; + } + if (this.tileWidth > 256) { + errmsg = 'The Leaflet viewer does not support tiles wider than 256 pixels.'; + } + if (errmsg) { + this.viewer = $('
').text(errmsg); + this.$el.append(this.viewer); + console.error(errmsg); return this; } diff --git a/web_client/views/imageViewerWidget/slideatlas.js b/web_client/views/imageViewerWidget/slideatlas.js index a108cb8d3..501b6964b 100644 --- a/web_client/views/imageViewerWidget/slideatlas.js +++ b/web_client/views/imageViewerWidget/slideatlas.js @@ -25,6 +25,7 @@ var SlideAtlasImageViewerWidget = ImageViewerWidget.extend({ }, render: function () { + var errmsg; // If script or metadata isn't loaded, then abort if (!window.SA || !this.tileWidth || !this.tileHeight || this.deleted) { return this; @@ -36,7 +37,15 @@ var SlideAtlasImageViewerWidget = ImageViewerWidget.extend({ } if (this.tileWidth !== this.tileHeight) { - console.error('The SlideAtlas viewer only supports square tiles.'); + errmsg = 'The SlideAtlas viewer only supports square tiles.'; + } + if (this.tileWidth > 256) { + errmsg = 'The SlideAtlas viewer does not support tiles wider than 256 pixels.'; + } + if (errmsg) { + this.viewer = $('
').text(errmsg); + this.$el.append(this.viewer); + console.error(errmsg); return this; }