def _getLargeImagePath(self): # If self.mayHaveAdjacentFiles is True, we try to use the girder # mount where companion files appear next to each other. largeImageFileId = self.item['largeImage']['fileId'] largeImageFile = File().load(largeImageFileId, force=True) try: largeImagePath = None if (self.mayHaveAdjacentFiles(largeImageFile) and hasattr(File(), 'getGirderMountFilePath')): try: if (largeImageFile.get('imported') and File().getLocalFilePath(largeImageFile) == largeImageFile['path']): largeImagePath = largeImageFile['path'] except Exception: pass if not largeImagePath: try: largeImagePath = File().getGirderMountFilePath( largeImageFile) except FilePathException: pass if not largeImagePath: try: largeImagePath = File().getLocalFilePath(largeImageFile) except AttributeError as e: raise TileSourceException( 'No local file path for this file: %s' % e.args[0]) return largeImagePath except (TileSourceAssetstoreException, FilePathException): raise except (KeyError, ValidationException, TileSourceException) as e: raise TileSourceException('No large image file in this item: %s' % e.args[0])
def getTile(self, x, y, z, pilImageAllowed=False, **kwargs): if z < 0: raise TileSourceException('z layer does not exist') try: svslevel = self._svslevels[z] except IndexError: raise TileSourceException('z layer does not exist') # When we read a region from the SVS, we have to ask for it in the # SVS level 0 coordinate system. Our x and y is in tile space at the # specifed z level, so the offset in SVS level 0 coordinates has to be # scaled by the tile size and by the z level. scale = 2**(self.levels - 1 - z) offsetx = x * self.tileWidth * scale if not (0 <= offsetx < self.sizeX): raise TileSourceException('x is outside layer') offsety = y * self.tileHeight * scale if not (0 <= offsety < self.sizeY): raise TileSourceException('y is outside layer') # 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. try: tile = self._openslide.read_region( (offsetx, offsety), svslevel['svslevel'], (self.tileWidth * svslevel['scale'], self.tileHeight * svslevel['scale'])) except openslide.lowlevel.OpenSlideError as exc: raise TileSourceException('Failed to get OpenSlide region (%r).' % exc) # Always scale to the svs level 0 tile size. if svslevel['scale'] != 1: tile = tile.resize((self.tileWidth, self.tileHeight), PIL.Image.LANCZOS) return self._outputTile(tile, 'PIL', x, y, z, pilImageAllowed, **kwargs)
def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, sparseFallback=False, **kwargs): self._xyzInRange(x, y, z) try: allowStyle = True if self._tiffDirectories[z] is None: try: tile = self.getTileFromEmptyDirectory(x, y, z, **kwargs) except Exception: if sparseFallback: raise IOTiffException('Missing z level %d' % z) else: raise allowStyle = False format = TILE_FORMAT_PIL else: tile = self._tiffDirectories[z].getTile(x, y) format = 'JPEG' if isinstance(tile, PIL.Image.Image): format = TILE_FORMAT_PIL if isinstance(tile, numpy.ndarray): format = TILE_FORMAT_NUMPY return self._outputTile(tile, format, x, y, z, pilImageAllowed, numpyAllowed, applyStyle=allowStyle, **kwargs) except IndexError: raise TileSourceException('z layer does not exist') except InvalidOperationTiffException as e: raise TileSourceException(e.args[0]) except IOTiffException as e: return self.getTileIOTiffException( x, y, z, pilImageAllowed=pilImageAllowed, numpyAllowed=numpyAllowed, sparseFallback=sparseFallback, exception=e, **kwargs)
def getTile(self, x, y, z, pilImageAllowed=False, sparseFallback=False, **kwargs): if z < 0: raise TileSourceException('z layer does not exist') try: if self._tiffDirectories[z] is None: if sparseFallback: raise IOTiffException('Missing z level %d' % z) tile = self.getTileFromEmptyDirectory(x, y, z, **kwargs) format = TILE_FORMAT_PIL else: tile = self._tiffDirectories[z].getTile(x, y) format = 'JPEG' if PIL and isinstance(tile, PIL.Image.Image): format = TILE_FORMAT_PIL return self._outputTile(tile, format, x, y, z, pilImageAllowed, **kwargs) except IndexError: raise TileSourceException('z layer does not exist') except InvalidOperationTiffException as e: raise TileSourceException(e.args[0]) except IOTiffException as e: return self.getTileIOTiffException(x, y, z, pilImageAllowed=pilImageAllowed, sparseFallback=sparseFallback, exception=e, **kwargs)
def getTile(self, x, y, z, pilImageAllowed=False, sparseFallback=False, **kwargs): if (z < 0 or z >= len(self._omeLevels) or self._omeLevels[z] is None or kwargs.get('frame') in (None, 0, '0', '')): return super(OMETiffFileTileSource, self).getTile( x, y, z, pilImageAllowed=pilImageAllowed, sparseFallback=sparseFallback, **kwargs) frame = int(kwargs['frame']) if frame < 0 or frame >= len(self._omebase['TiffData']): raise TileSourceException('Frame does not exist') dirnum = int(self._omeLevels[z]['TiffData'][frame]['IFD']) if dirnum in self._directoryCache: dir = self._directoryCache[dirnum] else: if len(self._directoryCache) >= self._directoryCacheMaxSize: self._directoryCache = {} dir = TiledTiffDirectory(self._getLargeImagePath(), dirnum) self._directoryCache[dirnum] = dir try: tile = dir.getTile(x, y) format = 'JPEG' if PIL and isinstance(tile, PIL.Image.Image): format = TILE_FORMAT_PIL return self._outputTile(tile, format, x, y, z, pilImageAllowed, **kwargs) except InvalidOperationTiffException as e: raise TileSourceException(e.args[0]) except IOTiffException as e: return self.getTileIOTiffException( x, y, z, pilImageAllowed=pilImageAllowed, sparseFallback=sparseFallback, exception=e, **kwargs)
def __init__(self, path, **kwargs): """ Initialize the tile class. See the base class for other available parameters. :param path: a filesystem path for the tile source. """ super(TiffFileTileSource, self).__init__(path, **kwargs) largeImagePath = self._getLargeImagePath() try: alldir = self._scanDirectories() except (ValidationTiffException, TiffException) as exc: alldir = [] lastException = exc # If there are no tiled images, raise an exception. if not len(alldir): msg = "File %s didn't meet requirements for tile source: %s" % ( largeImagePath, lastException) config.getConfig('logger').debug(msg) raise TileSourceException(msg) # Sort the known directories by image area (width * height). Given # equal area, sort by the level. alldir.sort() # The highest resolution image is our preferred image highest = alldir[-1][-1] directories = {} # Discard any images that use a different tiling scheme than our # preferred image for tdir in alldir: td = tdir[-1] level = tdir[2] if (td.tileWidth != highest.tileWidth or td.tileHeight != highest.tileHeight): if not len(self._associatedImages): self._addAssociatedImage(largeImagePath, tdir[-2], True, highest) continue # If a layer's image is not a multiple of the tile size, it should # be near a power of two of the highest resolution image. if (((td.imageWidth % td.tileWidth) and not nearPowerOfTwo(td.imageWidth, highest.imageWidth)) or ((td.imageHeight % td.tileHeight) and not nearPowerOfTwo(td.imageHeight, highest.imageHeight))): continue directories[level] = td if not len(directories) or (len(directories) < 2 and max(directories.keys()) + 1 > 4): raise TileSourceException( 'Tiff image must have at least two levels.') # 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 range(max(directories.keys()) + 1)] self.tileWidth = highest.tileWidth self.tileHeight = highest.tileHeight self.levels = len(self._tiffDirectories) self.sizeX = highest.imageWidth self.sizeY = highest.imageHeight
def _colorizerFromStyle(self, style): """ Add a specified style to a mapnik raster symbolizer. :param style: a style object. :returns: a mapnik raster colorizer. """ try: scheme = style.get('scheme', 'linear') mapnik_scheme = getattr(mapnik, 'COLORIZER_{}'.format(scheme.upper())) except AttributeError: mapnik_scheme = mapnik.COLORIZER_DISCRETE raise TileSourceException( 'Scheme has to be either "discrete" or "linear".') colorizer = mapnik.RasterColorizer(mapnik_scheme, mapnik.Color(0, 0, 0, 0)) bandInfo = self.getOneBandInformation(style['band']) minimum = style.get('min', 0) maximum = style.get('max', 255) minimum = bandInfo[minimum] if minimum in ('min', 'max') else minimum maximum = bandInfo[maximum] if maximum in ('min', 'max') else maximum if minimum == 'auto': if not (0 <= bandInfo['min'] <= 255 and 1 <= bandInfo['max'] <= 255): minimum = bandInfo['min'] else: minimum = 0 if maximum == 'auto': if not (0 <= bandInfo['min'] <= 255 and 1 <= bandInfo['max'] <= 255): maximum = bandInfo['max'] else: maximum = 255 if style.get('palette') == 'colortable': for value, color in enumerate(bandInfo['colortable']): colorizer.add_stop(value, mapnik.Color(*color)) else: colors = style.get('palette', ['#000000', '#ffffff']) if not isinstance(colors, list): colors = self.getHexColors(colors) else: colors = [ color if isinstance(color, six.binary_type) else color.encode('utf8') for color in colors ] if len(colors) < 2: raise TileSourceException( 'A palette must have at least 2 colors.') values = self.interpolateMinMax(minimum, maximum, len(colors)) for value, color in sorted(zip(values, colors)): try: colorizer.add_stop(value, mapnik.Color(color)) except RuntimeError: raise TileSourceException( 'Mapnik failed to parse color %r.' % color) return colorizer
def __init__(self, path, maxSize=None, **kwargs): """ Initialize the tile class. See the base class for other available parameters. :param path: the associated file path. :param maxSize: either a number or an object with {'width': (width), 'height': height} in pixels. If None, the default max size is used. """ super().__init__(path, **kwargs) self._maxSize = maxSize if isinstance(maxSize, str): try: maxSize = json.loads(maxSize) except Exception: raise TileSourceException( 'maxSize must be None, an integer, a dictionary, or a ' 'JSON string that converts to one of those.') self.maxSize = maxSize largeImagePath = self._getLargeImagePath() # Some formats shouldn't be read this way, even if they could. For # instances, mirax (mrxs) files look like JPEGs, but opening them as # such misses most of the data. if os.path.splitext(largeImagePath)[1] in ('.mrxs', ): raise TileSourceException('File cannot be opened via PIL.') try: self._pilImage = PIL.Image.open(largeImagePath) except OSError: raise TileSourceException('File cannot be opened via PIL.') # If this is encoded as a 32-bit integer or a 32-bit float, convert it # to an 8-bit integer. This expects the source value to either have a # maximum of 1, 2^8-1, 2^16-1, 2^24-1, or 2^32-1, and scales it to # [0, 255] pilImageMode = self._pilImage.mode.split(';')[0] if pilImageMode in ('I', 'F'): imgdata = numpy.asarray(self._pilImage) maxval = 256**math.ceil(math.log(numpy.max(imgdata) + 1, 256)) - 1 self._pilImage = PIL.Image.fromarray( numpy.uint8(numpy.multiply(imgdata, 255.0 / maxval))) self.sizeX = self._pilImage.width self.sizeY = self._pilImage.height # We have just one tile which is the entire image. self.tileWidth = self.sizeX self.tileHeight = self.sizeY self.levels = 1 # Throw an exception if too big if self.tileWidth <= 0 or self.tileHeight <= 0: raise TileSourceException('PIL tile size is invalid.') maxWidth, maxHeight = getMaxSize(maxSize, self.defaultMaxSize()) if self.tileWidth > maxWidth or self.tileHeight > maxHeight: raise TileSourceException('PIL tile size is too large.')
def _parseOMEInfo(self): # noqa if isinstance(self._omeinfo['Image'], dict): self._omeinfo['Image'] = [self._omeinfo['Image']] for img in self._omeinfo['Image']: if isinstance(img['Pixels'].get('TiffData'), dict): img['Pixels']['TiffData'] = [img['Pixels']['TiffData']] if isinstance(img['Pixels'].get('Plane'), dict): img['Pixels']['Plane'] = [img['Pixels']['Plane']] if isinstance(img['Pixels'].get('Channels'), dict): img['Pixels']['Channels'] = [img['Pixels']['Channels']] try: self._omebase = self._omeinfo['Image'][0]['Pixels'] if isinstance(self._omebase.get('Plane'), dict): self._omebase['Plane'] = [self._omebase['Plane']] if ((not len(self._omebase['TiffData']) or len(self._omebase['TiffData']) == 1) and (len(self._omebase.get('Plane', [])) or len(self._omebase.get('Channel', [])))): if not len(self._omebase['TiffData'] ) or self._omebase['TiffData'][0] == {}: self._omebase['TiffData'] = self._omebase.get( 'Plane', self._omebase.get('Channel')) elif (int(self._omebase['TiffData'][0].get( 'PlaneCount', 0)) == len( self._omebase.get('Plane', self._omebase.get('Channel', [])))): planes = copy.deepcopy( self._omebase.get('Plane', self._omebase.get('Channel'))) for idx, plane in enumerate(planes): plane['IFD'] = plane.get( 'IFD', int(self._omebase['TiffData'][0].get('IFD', 0)) + idx) self._omebase['TiffData'] = planes if isinstance(self._omebase['TiffData'], dict): self._omebase['TiffData'] = [self._omebase['TiffData']] if len({ entry.get('UUID', {}).get('FileName', '') for entry in self._omebase['TiffData'] }) > 1: raise TileSourceException('OME Tiff references multiple files') if (len(self._omebase['TiffData']) != int(self._omebase['SizeC']) * int(self._omebase['SizeT']) * int(self._omebase['SizeZ']) or len(self._omebase['TiffData']) != len( self._omebase.get('Plane', self._omebase['TiffData']))): raise TileSourceException( 'OME Tiff contains frames that contain multiple planes') except (KeyError, ValueError, IndexError): print('B') raise TileSourceException( 'OME Tiff does not contain an expected record')
def __init__(self, path, **kwargs): """ Initialize the tile class. See the base class for other available parameters. :param path: a filesystem path for the tile source. """ # Note this is the super of the parent class, not of this class. super(TiffFileTileSource, self).__init__(path, **kwargs) largeImagePath = self._getLargeImagePath() self._largeImagePath = largeImagePath try: base = TiledTiffDirectory(largeImagePath, 0, mustBeTiled=None) except TiffException: raise TileSourceException('Not a recognized OME Tiff') info = getattr(base, '_description_record', None) if not info or not info.get('OME'): raise TileSourceException('Not an OME Tiff') self._omeinfo = info['OME'] self._checkForOMEZLoop(largeImagePath) self._parseOMEInfo() omeimages = [ entry['Pixels'] for entry in self._omeinfo['Image'] if len(entry['Pixels']['TiffData']) == len(self._omebase['TiffData'])] levels = [max(0, int(math.ceil(math.log(max( float(entry['SizeX']) / base.tileWidth, float(entry['SizeY']) / base.tileHeight)) / math.log(2)))) for entry in omeimages] omebylevel = dict(zip(levels, omeimages)) self._omeLevels = [omebylevel.get(key) for key in range(max(omebylevel.keys()) + 1)] if base._tiffInfo.get('istiled'): self._tiffDirectories = [ TiledTiffDirectory(largeImagePath, int(entry['TiffData'][0].get('IFD', 0))) if entry else None for entry in self._omeLevels] else: self._tiffDirectories = [ TiledTiffDirectory(largeImagePath, 0, mustBeTiled=None) if entry else None for entry in self._omeLevels] self.tileWidth = base.tileWidth self.tileHeight = base.tileHeight self.levels = len(self._tiffDirectories) self.sizeX = base.imageWidth self.sizeY = base.imageHeight # We can get the embedded images, but we don't currently use non-tiled # images as associated images. This would require enumerating tiff # directories not mentioned by the ome list. self._associatedImages = {}
def _levelFromIfd(self, ifd, baseifd): """ Get the level based on information in an ifd and on the full-resolution 0-frame ifd. An exception is raised if the ifd does not seem to represent a possible level. :param ifd: an ifd record returned from tifftools. :param baseifd: the ifd record of the full-resolution frame 0. :returns: the level, where self.levels - 1 is full resolution and 0 is the lowest resolution. """ sizeX = ifd['tags'][tifftools.Tag.ImageWidth.value]['data'][0] sizeY = ifd['tags'][tifftools.Tag.ImageLength.value]['data'][0] tileWidth = baseifd['tags'][tifftools.Tag.TileWidth.value]['data'][0] tileHeight = baseifd['tags'][tifftools.Tag.TileLength.value]['data'][0] for tag in { tifftools.Tag.SamplesPerPixel.value, tifftools.Tag.BitsPerSample.value, tifftools.Tag.PlanarConfig.value, tifftools.Tag.Photometric.value, tifftools.Tag.Orientation.value, tifftools.Tag.Compression.value, tifftools.Tag.TileWidth.value, tifftools.Tag.TileLength.value, }: if ((tag in ifd['tags'] and tag not in baseifd['tags']) or (tag not in ifd['tags'] and tag in baseifd['tags']) or (tag in ifd['tags'] and ifd['tags'][tag]['data'] != baseifd['tags'][tag]['data'])): raise TileSourceException('IFD does not match first IFD.') sizes = [(self.sizeX, self.sizeY)] for level in range(self.levels - 1, -1, -1): if (sizeX, sizeY) in sizes: return level altsizes = [] for w, h in sizes: w2f = int(math.floor(w / 2)) h2f = int(math.floor(h / 2)) w2c = int(math.ceil(w / 2)) h2c = int(math.ceil(h / 2)) w2t = int(math.floor( (w / 2 + tileWidth - 1) / tileWidth)) * tileWidth h2t = int(math.floor( (h / 2 + tileHeight - 1) / tileHeight)) * tileHeight for w2, h2 in [(w2f, h2f), (w2f, h2c), (w2c, h2f), (w2c, h2c), (w2t, h2t)]: if (w2, h2) not in altsizes: altsizes.append((w2, h2)) sizes = altsizes raise TileSourceException( 'IFD size is not a power of two smaller than first IFD.')
def __init__(self, path, **kwargs): """ Initialize the tile class. See the base class for other available parameters. :param path: a filesystem path for the tile source. """ super().__init__(path, **kwargs) largeImagePath = self._getLargeImagePath() self._largeImagePath = largeImagePath self._pixelInfo = {} try: self._openjpeg = glymur.Jp2k(largeImagePath) except glymur.jp2box.InvalidJp2kError: raise TileSourceException( 'File cannot be opened via Glymur and OpenJPEG.') glymur.set_option('lib.num_threads', multiprocessing.cpu_count()) self._openjpegHandles = queue.LifoQueue() for _ in range(self._maxOpenHandles - 1): self._openjpegHandles.put(None) self._openjpegHandles.put(self._openjpeg) try: self.sizeY, self.sizeX = self._openjpeg.shape[:2] except IndexError: raise TileSourceException( 'File cannot be opened via Glymur and OpenJPEG.') self.levels = int(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))) # 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 = int( 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 getTile(self, x, y, z, pilImageAllowed=False, mayRedirect=False, **kwargs): if z != 0: raise TileSourceException('z layer does not exist') if x != 0: raise TileSourceException('x is outside layer') if y != 0: raise TileSourceException('y is outside layer') if (mayRedirect and not pilImageAllowed and cherrypy.request and self._pilFormatMatches(self._pilImage, mayRedirect, **kwargs)): url = '%s/api/v1/file/%s/download' % ( cherrypy.request.base, self.item['largeImage']['fileId']) raise cherrypy.HTTPRedirect(url) return self._outputTile(self._pilImage, 'PIL', x, y, z, pilImageAllowed, **kwargs)
def getTile(self, x, y, z, pilImageAllowed=False, mayRedirect=False, **kwargs): if z != 0: raise TileSourceException('z layer does not exist') if x != 0: raise TileSourceException('x is outside layer') if y != 0: raise TileSourceException('y is outside layer') return self._outputTile(self._pilImage, 'PIL', x, y, z, pilImageAllowed, **kwargs)
def getTile(self, x, y, z, *args, **kwargs): widthCount = 2 ** z if not (0 <= x < float(self.sizeX) / self.tileWidth * 2 ** ( z - self.maxLevel)): raise TileSourceException('x is outside layer') if not (0 <= y < float(self.sizeY) / self.tileHeight * 2 ** ( z - self.maxLevel)): raise TileSourceException('y is outside layer') if not (self.minLevel <= z <= self.maxLevel): raise TileSourceException('z layer does not exist') xFraction = float(x) / (widthCount - 1) if z != 0 else 0 yFraction = float(y) / (widthCount - 1) if z != 0 else 0 backgroundColor = colorsys.hsv_to_rgb( h=(0.9 * xFraction), s=(0.3 + (0.7 * yFraction)), v=(0.3 + (0.7 * yFraction)), ) rgbColor = tuple(int(val * 255) for val in backgroundColor) image = Image.new( mode='RGB', size=(self.tileWidth, self.tileHeight), color=(rgbColor if not self.fractal else (255, 255, 255)) ) imageDraw = ImageDraw.Draw(image) if self.fractal: self.fractalTile(image, x, y, widthCount, rgbColor) try: # the font size should fill the whole tile imageDrawFont = ImageFont.truetype( font='/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf', size=int(0.15 * min(self.tileWidth, self.tileHeight)) ) except IOError: imageDrawFont = ImageFont.load_default() imageDraw.multiline_text( xy=(10, 10), text='x=%d\ny=%d\nz=%d' % (x, y, z), fill=(0, 0, 0), font=imageDrawFont ) _counters['tiles'] += 1 return self._outputTile(image, TILE_FORMAT_PIL, x, y, z, **kwargs)
def getTileIOTiffException(self, x, y, z, pilImageAllowed=False, sparseFallback=False, exception=None, **kwargs): if sparseFallback and z and PIL: noedge = kwargs.copy() noedge.pop('edge', None) image = self.getTile(x / 2, y / 2, z - 1, pilImageAllowed=True, sparseFallback=sparseFallback, edge=False, **noedge) if not isinstance(image, PIL.Image.Image): image = PIL.Image.open(BytesIO(image)) image = image.crop( (self.tileWidth / 2 if x % 2 else 0, self.tileHeight / 2 if y % 2 else 0, self.tileWidth if x % 2 else self.tileWidth / 2, self.tileHeight if y % 2 else self.tileHeight / 2)) image = image.resize((self.tileWidth, self.tileHeight)) return self._outputTile(image, 'PIL', x, y, z, pilImageAllowed, **kwargs) raise TileSourceException('Internal I/O failure: %s' % exception.args[0])
def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, **kwargs): self._xyzInRange(x, y, z) svslevel = self._svslevels[z] # When we read a region from the SVS, we have to ask for it in the # SVS level 0 coordinate system. Our x and y is in tile space at the # specifed z level, so the offset in SVS level 0 coordinates has to be # scaled by the tile size and by the z level. scale = 2**(self.levels - 1 - z) offsetx = x * self.tileWidth * scale offsety = y * self.tileHeight * 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. try: tile = self._openslide.read_region( (offsetx, offsety), svslevel['svslevel'], (self.tileWidth * svslevel['scale'], self.tileHeight * svslevel['scale'])) except openslide.lowlevel.OpenSlideError as exc: raise TileSourceException('Failed to get OpenSlide region (%r).' % exc) # Always scale to the svs level 0 tile size. if svslevel['scale'] != 1: tile = tile.resize((self.tileWidth, self.tileHeight), PIL.Image.LANCZOS) return self._outputTile(tile, TILE_FORMAT_PIL, x, y, z, pilImageAllowed, numpyAllowed, **kwargs)
def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, **kwargs): self._xyzInRange(x, y, z) step = int(2**(self.levels - 1 - z)) x0 = x * step * self.tileWidth x1 = min((x + 1) * step * self.tileWidth, self.sizeX) y0 = y * step * self.tileHeight y1 = min((y + 1) * step * self.tileHeight, self.sizeY) frame = kwargs.get('frame') frame = int(frame) if frame else 0 if frame < 0 or frame >= len(self._nd2): raise TileSourceException('Frame does not exist') with self._tileLock: if frame in self._recentFrames: tileframe = self._recentFrames[frame] else: tileframe = self._nd2[frame] self._recentFrames[frame] = tileframe tile = tileframe[y0:y1:step, x0:x1:step].copy() return self._outputTile(tile, TILE_FORMAT_NUMPY, x, y, z, pilImageAllowed, numpyAllowed, **kwargs)
def _getAssociatedImage(self, imageKey): """ Get an associated image in PIL format. :param imageKey: the key of the associated image. :return: the image in PIL format or None. """ info = self._metadata['seriesAssociatedImages'].get(imageKey) if info is None: return series = info['seriesNum'] with self._tileLock: try: javabridge.attach() image = self._bioimage.read( series=series, rescale=False, # return internal data types XYWH=(0, 0, info['sizeX'], info['sizeY'])) except javabridge.JavaException as exc: es = javabridge.to_string(exc.throwable) raise TileSourceException('Failed to get Bioformat series (%s, %r).' % (es, ( series, info['sizeX'], info['sizeY']))) finally: if javabridge.get_env(): javabridge.detach() return large_image.tilesource.base._imageToPIL(image)
def _getLargeImagePath(self): # If self.mayHaveAdjacentFiles is True, we try to use the girder # mount where companion files appear next to each other. try: largeImageFileId = self.item['largeImage']['fileId'] if not hasattr(self, 'mayHaveAdjacentFiles'): # The item has adjacent files if there are any files that # are not the large image file or an original file it # was derived from. This is always the case if there are 3 # or more files. fileIds = [ str(file['_id']) for file in Item().childFiles(self.item, limit=3) ] knownIds = [str(largeImageFileId)] if 'originalId' in self.item['largeImage']: knownIds.append(str(self.item['largeImage']['originalId'])) self.mayHaveAdjacentFiles = (len(fileIds) >= 3 or fileIds[0] not in knownIds or fileIds[-1] not in knownIds) largeImageFile = File().load(largeImageFileId, force=True) if (any(ext in KnownExtensionsWithAdjacentFiles for ext in largeImageFile['exts']) or largeImageFile.get('mimeType') in KnownMimeTypesWithAdjacentFiles): self.mayHaveAdjacentFiles = True largeImagePath = None if self.mayHaveAdjacentFiles and hasattr(File(), 'getGirderMountFilePath'): try: largeImagePath = File().getGirderMountFilePath( largeImageFile) except FilePathException: pass if not largeImagePath: try: largeImagePath = File().getLocalFilePath(largeImageFile) except AttributeError as e: raise TileSourceException( 'No local file path for this file: %s' % e.args[0]) return largeImagePath except (TileSourceAssetstoreException, FilePathException): raise except (KeyError, ValidationException, TileSourceException) as e: raise TileSourceException('No large image file in this item: %s' % e.args[0])
def _initWithProjection(self, unitsPerPixel=None): """ Initialize aspects of the class when a projection is set. """ inProj = self._proj4Proj(InitPrefix + 'epsg:4326') # Since we already converted to bytes decoding is safe here outProj = self._proj4Proj(self.projection) if outProj.crs.is_geographic: raise TileSourceException( 'Projection must not be geographic (it needs to use linear ' 'units, not longitude/latitude).') if unitsPerPixel: self.unitsAcrossLevel0 = float(unitsPerPixel) * self.tileSize else: self.unitsAcrossLevel0 = ProjUnitsAcrossLevel0.get(self.projection) if self.unitsAcrossLevel0 is None: # If unitsPerPixel is not specified, the horizontal distance # between -180,0 and +180,0 is used. Some projections (such as # stereographic) will fail in this case; they must have a # unitsPerPixel specified. equator = pyproj.transform(inProj, outProj, [-180, 180], [0, 0], always_xy=True) self.unitsAcrossLevel0 = abs(equator[0][1] - equator[0][0]) if not self.unitsAcrossLevel0: raise TileSourceException( 'unitsPerPixel must be specified for this projection') if len(ProjUnitsAcrossLevel0) >= ProjUnitsAcrossLevel0_MaxSize: ProjUnitsAcrossLevel0.clear() ProjUnitsAcrossLevel0[self.projection] = self.unitsAcrossLevel0 # This was # self.projectionOrigin = pyproj.transform(inProj, outProj, 0, 0) # but for consistency, it should probably always be (0, 0). Whatever # renders the map would need the same offset as used here. self.projectionOrigin = (0, 0) # Calculate values for this projection self.levels = int( max( int( math.ceil( math.log(self.unitsAcrossLevel0 / self.getPixelSizeInMeters() / self.tileWidth) / math.log(2))) + 1, 1)) # Report sizeX and sizeY as the whole world self.sizeX = 2**(self.levels - 1) * self.tileWidth self.sizeY = 2**(self.levels - 1) * self.tileHeight
def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, sparseFallback=False, **kwargs): frame = int(kwargs.get('frame') or 0) self._xyzInRange( x, y, z, frame, len(self._frames) if hasattr(self, '_frames') else None) if frame > 0: if self._frames[frame]['dirs'][z] is not None: dir = self._getDirFromCache(*self._frames[frame]['dirs'][z]) else: dir = None else: dir = self._tiffDirectories[z] try: allowStyle = True if dir is None: try: tile = self.getTileFromEmptyDirectory(x, y, z, **kwargs) except Exception: if sparseFallback: raise IOTiffException('Missing z level %d' % z) else: raise allowStyle = False format = TILE_FORMAT_PIL else: tile = dir.getTile(x, y) format = 'JPEG' if isinstance(tile, PIL.Image.Image): format = TILE_FORMAT_PIL if isinstance(tile, numpy.ndarray): format = TILE_FORMAT_NUMPY return self._outputTile(tile, format, x, y, z, pilImageAllowed, numpyAllowed, applyStyle=allowStyle, **kwargs) except InvalidOperationTiffException as e: raise TileSourceException(e.args[0]) except IOTiffException as e: return self.getTileIOTiffException(x, y, z, pilImageAllowed=pilImageAllowed, numpyAllowed=numpyAllowed, sparseFallback=sparseFallback, exception=e, **kwargs)
def getTile(self, x, y, z, pilImageAllowed=False, **kwargs): if z < 0 or z >= self.levels: raise TileSourceException('z layer does not exist') step = int(2**(self.levels - 1 - z)) x0 = x * step * self.tileWidth x1 = min((x + 1) * step * self.tileWidth, self.sizeX) y0 = y * step * self.tileHeight y1 = min((y + 1) * step * self.tileHeight, self.sizeY) if x < 0 or x0 >= self.sizeX: 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 = int(2**(self._minlevel - z)) step = int(2**(self.levels - 1 - self._minlevel)) # possible open the file multiple times so multiple threads can access # it concurrently. while True: try: # A timeout prevents uniterupptable waits on some platforms openjpegHandle = self._openjpegHandles.get(timeout=1.0) break except queue.Empty: continue if openjpegHandle is None: openjpegHandle = glymur.Jp2k(self._largeImagePath) try: tile = openjpegHandle[y0:y1:step, x0:x1:step] finally: self._openjpegHandles.put(openjpegHandle) mode = 'L' 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)) tile = wrap return self._outputTile(tile, TILE_FORMAT_PIL, x, y, z, pilImageAllowed, **kwargs)
def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False, sparseFallback=False, **kwargs): if (z < 0 or z >= len(self._omeLevels) or ( self._omeLevels[z] is not None and kwargs.get('frame') in (None, 0, '0', ''))): return super().getTile( x, y, z, pilImageAllowed=pilImageAllowed, numpyAllowed=numpyAllowed, sparseFallback=sparseFallback, **kwargs) frame = int(kwargs.get('frame') or 0) if frame < 0 or frame >= len(self._omebase['TiffData']): raise TileSourceException('Frame does not exist') subdir = None if self._omeLevels[z] is not None: dirnum = int(self._omeLevels[z]['TiffData'][frame].get('IFD', frame)) else: dirnum = int(self._omeLevels[-1]['TiffData'][frame].get('IFD', frame)) subdir = self.levels - 1 - z dir = self._getDirFromCache(dirnum, subdir) if subdir: scale = int(2 ** subdir) if (dir is None or dir.tileWidth != self.tileWidth or dir.tileHeight != self.tileHeight or abs(dir.imageWidth * scale - self.sizeX) > scale or abs(dir.imageHeight * scale - self.sizeY) > scale): return super().getTile( x, y, z, pilImageAllowed=pilImageAllowed, numpyAllowed=numpyAllowed, sparseFallback=sparseFallback, **kwargs) try: tile = dir.getTile(x, y) format = 'JPEG' if isinstance(tile, PIL.Image.Image): format = TILE_FORMAT_PIL if isinstance(tile, numpy.ndarray): format = TILE_FORMAT_NUMPY return self._outputTile(tile, format, x, y, z, pilImageAllowed, numpyAllowed, **kwargs) except InvalidOperationTiffException as e: raise TileSourceException(e.args[0]) except IOTiffException as e: return self.getTileIOTiffException( x, y, z, pilImageAllowed=pilImageAllowed, numpyAllowed=numpyAllowed, sparseFallback=sparseFallback, exception=e, **kwargs)
def getHexColors(palette): """ Returns list of hex colors for a given color palette :returns: List of colors """ try: return attrgetter(palette)(palettable).hex_colors except AttributeError: raise TileSourceException( 'Palette is not a valid palettable path.')
def _loadTileSource(cls, item, **kwargs): if 'largeImage' not in item: raise TileSourceException('No large image file in this item.') if item['largeImage'].get('expected'): raise TileSourceException('The large image file for this item is ' 'still pending creation.') sourceName = item['largeImage']['sourceName'] try: # First try to use the tilesource we recorded as the preferred one. # This is faster than trying to find the best source each time. tileSource = girder_tilesource.AvailableGirderTileSources[ sourceName](item, **kwargs) except TileSourceException: # We could try any source # tileSource = girder_tilesource.getGirderTileSource(item, **kwargs) # but, instead, log that the original source no longer works are # reraise the exception logger.warn('The original tile source for item %s is not working' % item['_id']) raise return tileSource
def interpolateMinMax(start, stop, count): """ Returns interpolated values for a given start, stop and count :returns: List of interpolated values """ try: step = (float(stop) - float(start)) / (float(count) - 1) except ValueError: raise TileSourceException( 'Minimum and maximum values should be numbers, "auto", "min", or "max".') sequence = [float(start + i * step) for i in range(count)] return sequence
def _bandNumber(self, band, exc=True): """ Given a band number or interpretation name, return a validated band number. :param band: either -1, a positive integer, or the name of a band interpretation that is present in the tile source. :param exc: if True, raise an exception if no band matches. :returns: a validated band, either 1 or a positive integer, or None if no matching band and exceptions are not enabled. """ if hasattr(self, '_netcdf') and (':' in str(band) or str(band).isdigit()): key = None if ':' in str(band): key, band = band.split(':', 1) if str(band).isdigit(): band = int(band) else: band = 1 if not key or key == 'default': key = self._netcdf.get('default', None) if key is None: return band if key in self._netcdf['datasets']: return (key, band) bands = self.getBandInformation() if not isinstance(band, int): try: band = next(bandIdx for bandIdx in sorted(bands) if band == bands[bandIdx]['interpretation']) except StopIteration: pass if hasattr(band, 'isdigit') and band.isdigit(): band = int(band) if band != -1 and band not in bands: if exc: raise TileSourceException( 'Band has to be a positive integer, -1, or a band ' 'interpretation found in the source.') return None return int(band)
def __init__(self, path, projection=None, unitsPerPixel=None, **kwargs): """ Initialize the tile class. See the base class for other available parameters. :param path: a filesystem path for the tile source. :param projection: None to use pixel space, otherwise a proj4 projection string or a case-insensitive string of the form 'EPSG:<epsg number>'. If a string and case-insensitively prefixed with 'proj4:', that prefix is removed. For instance, 'proj4:EPSG:3857', 'PROJ4:+init=epsg:3857', and '+init=epsg:3857', and 'EPSG:3857' are all equivilant. :param unitsPerPixel: The size of a pixel at the 0 tile size. Ignored if the projection is None. For projections, None uses the default, which is the distance between (-180,0) and (180,0) in EPSG:4326 converted to the projection divided by the tile size. Proj4 projections that are not latlong (is_geographic is False) must specify unitsPerPixel. """ super(GDALFileTileSource, self).__init__(path, **kwargs) self._logger = config.getConfig('logger') self._bounds = {} self._path = self._getLargeImagePath() try: self.dataset = gdal.Open(self._path, gdalconst.GA_ReadOnly) except RuntimeError: raise TileSourceException('File cannot be opened via GDAL') self._getDatasetLock = threading.RLock() self.tileSize = 256 self.tileWidth = self.tileSize self.tileHeight = self.tileSize self._projection = projection if projection and projection.lower().startswith('epsg:'): projection = InitPrefix + projection.lower() if projection and not isinstance(projection, six.binary_type): projection = projection.encode('utf8') self.projection = projection try: with self._getDatasetLock: self.sourceSizeX = self.sizeX = self.dataset.RasterXSize self.sourceSizeY = self.sizeY = self.dataset.RasterYSize except AttributeError: raise TileSourceException('File cannot be opened via GDAL.') is_netcdf = self._checkNetCDF() try: scale = self.getPixelSizeInMeters() except RuntimeError: raise TileSourceException('File cannot be opened via GDAL.') if not scale and not is_netcdf: raise TileSourceException( 'File does not have a projected scale, so will not be ' 'opened via GDAL.') self.sourceLevels = self.levels = int( max( 0, math.ceil( max(math.log(float(self.sizeX) / self.tileWidth), math.log(float(self.sizeY) / self.tileHeight)) / math.log(2))) + 1) self._unitsPerPixel = unitsPerPixel if self.projection: self._initWithProjection(unitsPerPixel) self._getTileLock = threading.Lock() self._setDefaultStyle()
def _convertProjectionUnits(self, left, top, right, bottom, width, height, units, **kwargs): """ Given bound information and a units string that consists of a proj4 projection (starts with `'proj4:'`, `'epsg:'`, `'+proj='` or is an enumerated value like `'wgs84'`), convert the bounds to either pixel or the class projection coordinates. :param left: the left edge (inclusive) of the region to process. :param top: the top edge (inclusive) of the region to process. :param right: the right edge (exclusive) of the region to process. :param bottom: the bottom edge (exclusive) of the region to process. :param width: the width of the region to process. Ignored if both left and right are specified. :param height: the height of the region to process. Ignores if both top and bottom are specified. :param units: either 'projection', a string starting with 'proj4:', 'epsg:', or '+proj=' or a enumerated value like 'wgs84', or one of the super's values. :param **kwargs: optional parameters. :returns: left, top, right, bottom, units. The new bounds in the either pixel or class projection units. """ if not kwargs.get('unitsWH') or kwargs.get('unitsWH') == units: if left is None and right is not None and width is not None: left = right - width if right is None and left is not None and width is not None: right = left + width if top is None and bottom is not None and height is not None: top = bottom - height if bottom is None and top is not None and height is not None: bottom = top + height if (left is None and right is None) or (top is None and bottom is None): raise TileSourceException( 'Cannot convert from projection unless at least one of ' 'left and right and at least one of top and bottom is ' 'specified.') if not self.projection: pleft, ptop = self.toNativePixelCoordinates( right if left is None else left, bottom if top is None else top, units) pright, pbottom = self.toNativePixelCoordinates( left if right is None else right, top if bottom is None else bottom, units) units = 'base_pixels' else: inProj = self._proj4Proj(units) outProj = self._proj4Proj(self.projection) pleft, ptop = pyproj.transform(inProj, outProj, right if left is None else left, bottom if top is None else top, always_xy=True) pright, pbottom = pyproj.transform( inProj, outProj, left if right is None else right, top if bottom is None else bottom, always_xy=True) units = 'projection' left = pleft if left is not None else None top = ptop if top is not None else None right = pright if right is not None else None bottom = pbottom if bottom is not None else None return left, top, right, bottom, units