def _xmlToMetadata(self, xml): if not isinstance(xml, dict) or set(xml.keys()) != {'DataObject'}: return xml values = {} try: objlist = xml['DataObject'] if not isinstance(objlist, list): objlist = [objlist] for obj in objlist: attrList = obj['Attribute'] if not isinstance(attrList, list): attrList = [attrList] for attr in attrList: if 'Array' not in attr: values[attr['Name']] = attr.get('text', '') else: if 'DataObject' in attr['Array']: subvalues = self._xmlToMetadata(attr['Array']) for key, subvalue in six.iteritems(subvalues): if key not in {'PIM_DP_IMAGE_DATA', }: values[attr['Name'] + '|' + key] = subvalue except Exception: config.getConfig('logger').exception('Here') return xml return values
def _addAssociatedImage(self, largeImagePath, directoryNum): """ Check if the specified TIFF directory contains a non-tiled image with a sensible image description that can be used as an ID. If so, and if the image isn't too large, add this image as an associated image. :param largeImagePath: path to the TIFF file. :param directoryNum: libtiff directory number of the image. """ try: associated = TiledTiffDirectory(largeImagePath, directoryNum, False) id = associated._tiffInfo.get('imagedescription').strip().split( None, 1)[0].lower() if not isinstance(id, six.text_type): id = id.decode('utf8') # Only use this as an associated image if the parsed id is # a reasonable length, alphanumeric characters, and the # image isn't too large. if (id.isalnum() and len(id) > 3 and len(id) <= 20 and associated._pixelInfo['width'] <= 8192 and associated._pixelInfo['height'] <= 8192): self._associatedImages[id] = associated._tiffFile.read_image() except (TiffException, AttributeError): # If we can't validate or read an associated image or it has no # useful imagedescription, fail quietly without adding an # associated image. pass except Exception: # If we fail for other reasons, don't raise an exception, but log # what happened. config.getConfig('logger').exception( 'Could not use non-tiled TIFF image as an associated image.')
def __init__(self, filePath, directoryNum, mustBeTiled=True): """ Create a new reader for a tiled image file directory in a TIFF file. :param filePath: A path to a TIFF file on disk. :type filePath: str :param directoryNum: The number of the TIFF image file directory to open. :type directoryNum: int :param mustBeTiled: if True, only tiled images validate. If False, only non-tiled images validate. None validates both. :raises: InvalidOperationTiffException or IOTiffException or ValidationTiffException """ # TODO how many to keep in the cache # create local cache to store Jpeg tables and # getTileByteCountsType self.cache = LRUCache(10) self._mustBeTiled = mustBeTiled self._tiffFile = None self._open(filePath, directoryNum) self._loadMetadata() config.getConfig('logger').debug( 'TiffDirectory %d Information %r', directoryNum, self._tiffInfo) try: self._validate() except ValidationTiffException: self._close() raise
def addStyle(self, m, layerSrs, extent=None): """ Attaches raster style option to mapnik raster layer and adds the layer to the mapnik map. :param m: mapnik map. :param layerSrs: the layer projection :param extent: the extent to use for the mapnik layer. """ style = self._styleBands() bands = self.getBandInformation() if not len(style): style.append({'band': -1}) config.getConfig('logger').debug( 'mapnik addTile specified style: %r, used style %r', getattr(self, 'style', None), style) for styleBand in style: if styleBand['band'] != -1: colorizer = self._colorizerFromStyle(styleBand) composite = getattr(mapnik.CompositeOp, styleBand.get( 'composite', 'multiply' if styleBand['band'] == 'alpha' else 'lighten')) nodata = styleBand.get('nodata') if nodata == 'auto': nodata = bands.get('nodata') else: colorizer = None composite = None nodata = None self._addStyleToMap( m, layerSrs, colorizer, styleBand['band'], extent, composite, nodata)
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 testConfigFunctions(): assert isinstance(getConfig(), dict) setConfig('cache_backend', 'python') assert getConfig('cache_backend') == 'python' setConfig('cache_backend', 'memcached') assert getConfig('cache_backend') == 'memcached' setConfig('cache_backend', None) assert getConfig('cache_backend') is None assert getConfig('unknown', 'python') == 'python'
def _addAssociatedImage(self, largeImagePath, directoryNum, mustBeTiled=False, topImage=None): """ Check if the specified TIFF directory contains an image with a sensible image description that can be used as an ID. If so, and if the image isn't too large, add this image as an associated image. :param largeImagePath: path to the TIFF file. :param directoryNum: libtiff directory number of the image. :param mustBeTiles: if true, use tiled images. If false, require untiled images. :param topImage: if specified, add image-embedded metadata to this image. """ try: associated = TiledTiffDirectory(largeImagePath, directoryNum, mustBeTiled) id = '' if associated._tiffInfo.get('imagedescription'): id = associated._tiffInfo.get( 'imagedescription').strip().split(None, 1)[0].lower() elif mustBeTiled: id = 'dir%d' % directoryNum if not len(self._associatedImages): id = 'macro' if not isinstance(id, six.text_type): id = id.decode('utf8') # Only use this as an associated image if the parsed id is # a reasonable length, alphanumeric characters, and the # image isn't too large. if (id.isalnum() and len(id) > 3 and len(id) <= 20 and associated._pixelInfo['width'] <= 8192 and associated._pixelInfo['height'] <= 8192): image = associated._tiffFile.read_image() # Optrascan scanners store xml image descriptions in a "tiled # image". Check if this is the case, and, if so, parse such # data if image.tobytes()[:6] == b'<?xml ': self._parseImageXml( image.tobytes().rsplit(b'>', 1)[0] + b'>', topImage) return self._associatedImages[id] = image except (TiffException, AttributeError): # If we can't validate or read an associated image or it has no # useful imagedescription, fail quietly without adding an # associated image. pass except Exception: # If we fail for other reasons, don't raise an exception, but log # what happened. config.getConfig('logger').exception( 'Could not use non-tiled TIFF image as an associated image.')
def _loadMetadata(self): fields = [key.split('_', 1)[1].lower() for key in dir(libtiff_ctypes.tiff_h) if key.startswith('TIFFTAG_')] info = {} for field in fields: try: value = self._tiffFile.GetField(field) if value is not None: info[field] = value except TypeError as err: config.getConfig('logger').debug( 'Loading field "%s" in directory number %d resulted in TypeError - "%s"', field, self._directoryNum, err) for func in self.CoreFunctions[3:]: if hasattr(self._tiffFile, func): value = getattr(self._tiffFile, func)() if value: info[func.lower()] = value self._tiffInfo = info self._tileWidth = info.get('tilewidth') or info.get('imagewidth') self._tileHeight = info.get('tilelength') or info.get('rowsperstrip') self._imageWidth = info.get('imagewidth') self._imageHeight = info.get('imagelength') if not info.get('tilelength'): self._stripsPerTile = int(max(1, math.ceil(256.0 / self._tileHeight))) self._stripHeight = self._tileHeight self._tileHeight = self._stripHeight * self._stripsPerTile self._stripCount = int(math.ceil(float(self._imageHeight) / self._stripHeight)) if info.get('orientation') in { libtiff_ctypes.ORIENTATION_LEFTTOP, libtiff_ctypes.ORIENTATION_RIGHTTOP, libtiff_ctypes.ORIENTATION_RIGHTBOT, libtiff_ctypes.ORIENTATION_LEFTBOT}: self._imageWidth, self._imageHeight = self._imageHeight, self._imageWidth self._tileWidth, self._tileHeight = self._tileHeight, self._tileWidth self.parse_image_description(info.get('imagedescription', '')) # From TIFF specification, tag 0x128, 2 is inches, 3 is centimeters. units = {2: 25.4, 3: 10} # If the resolution value is less than a threshold (100), don't use it, # as it is probably just an inaccurate default. Values like 72dpi and # 96dpi are common defaults, but so are small metric values, too. if (not self._pixelInfo.get('mm_x') and info.get('xresolution') and units.get(info.get('resolutionunit')) and info.get('xresolution') >= 100): self._pixelInfo['mm_x'] = units[info['resolutionunit']] / info['xresolution'] if (not self._pixelInfo.get('mm_y') and info.get('yresolution') and units.get(info.get('resolutionunit')) and info.get('yresolution') >= 100): self._pixelInfo['mm_y'] = units[info['resolutionunit']] / info['yresolution'] if not self._pixelInfo.get('width') and self._imageWidth: self._pixelInfo['width'] = self._imageWidth if not self._pixelInfo.get('height') and self._imageHeight: self._pixelInfo['height'] = self._imageHeight
def defaultMaxSize(self): """ Get the default max size from the config settings. :returns: the default max size. """ return int(config.getConfig('max_small_image_size', 4096))
def _checkForInefficientDirectories(self, warn=True): """ Raise a warning for inefficient files. :param warn: if True and inefficient, emit a warning. """ missing = [v is None for v in self._tiffDirectories] maxMissing = max(0 if not v else missing.index(False, idx) - idx for idx, v in enumerate(missing)) self._skippedLevels = maxMissing if maxMissing >= self._maxSkippedLevels: if warn: config.getConfig('logger').warning( 'Tiff image is missing many lower resolution levels (%d). ' 'It will be inefficient to read lower resolution tiles.', maxMissing) self._inefficientWarning = True
def __init__(self, filePath, directoryNum, mustBeTiled=True, subDirectoryNum=0, validate=True): """ Create a new reader for a tiled image file directory in a TIFF file. :param filePath: A path to a TIFF file on disk. :type filePath: str :param directoryNum: The number of the TIFF image file directory to open. :type directoryNum: int :param mustBeTiled: if True, only tiled images validate. If False, only non-tiled images validate. None validates both. :type mustBeTiled: bool :param subDirectoryNum: if set, the number of the TIFF subdirectory. :type subDirectoryNum: int :param validate: if False, don't validate that images can be read. :type mustBeTiled: bool :raises: InvalidOperationTiffException or IOTiffException or ValidationTiffException """ # create local cache to store Jpeg tables and getTileByteCountsType self.cache = LRUCache(10) self._mustBeTiled = mustBeTiled self._tiffFile = None self._tileLock = threading.RLock() self._open(filePath, directoryNum, subDirectoryNum) self._loadMetadata() config.getConfig('logger').debug('TiffDirectory %d:%d Information %r', directoryNum, subDirectoryNum or 0, self._tiffInfo) try: if validate: self._validate() except ValidationTiffException: self._close() raise
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 __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(ND2FileTileSource, self).__init__(path, **kwargs) self._largeImagePath = self._getLargeImagePath() self._pixelInfo = {} try: self._nd2 = nd2reader.ND2Reader(self._largeImagePath) except (UnicodeDecodeError, nd2reader.exceptions.InvalidVersionError, nd2reader.exceptions.EmptyFileError): raise TileSourceException('File cannot be opened via nd2reader.') self._logger = config.getConfig('logger') self._tileLock = threading.RLock() self._recentFrames = cachetools.LRUCache(maxsize=6) self.sizeX = self._nd2.metadata['width'] self.sizeY = self._nd2.metadata['height'] self.tileWidth = self.tileHeight = self._tileSize if self.sizeX <= self._singleTileThreshold and self.sizeY <= self._singleTileThreshold: self.tileWidth = self.sizeX self.tileHeight = self.sizeY self.levels = int( max( 1, math.ceil( math.log( float(max(self.sizeX, self.sizeY)) / self.tileWidth) / math.log(2)) + 1)) # There is one file that throws a warning 'Z-levels details missing in # metadata'. In this instance, there should be no z-levels. try: if (self._nd2.sizes.get('z') and self._nd2.sizes.get('z') == self._nd2.sizes.get('v') and not len( self._nd2._parser._raw_metadata._parse_dimension( r""".*?Z\((\d+)\).*?""")) and self._nd2.sizes['v'] * self._nd2.sizes.get('t', 1) == self._nd2.metadata.get('total_images_per_channel')): self._nd2._parser._raw_metadata._metadata_parsed[ 'z_levels'] = [] self._nd2.sizes['z'] = 1 except Exception: pass frames = self._nd2.sizes.get('c', 1) * self._nd2.metadata.get( 'total_images_per_channel', 0) self._framecount = frames if frames else None self._nd2.iter_axes = sorted( [a for a in self._nd2.axes if a not in {'x', 'y', 'v'}], reverse=True) if frames and len(self._nd2) != frames and 'v' in self._nd2.axes: self._nd2.iter_axes = ['v'] + self._nd2.iter_axes if 'c' in self._nd2.iter_axes and len( self._nd2.metadata.get('channels', [])): self._bandnames = { name.lower(): idx for idx, name in enumerate(self._nd2.metadata['channels']) } # self._nd2.metadata # {'channels': ['CY3', 'A594', 'CY5', 'DAPI'], # 'date': datetime.datetime(2019, 7, 21, 15, 13, 45), # 'events': [], # 'experiment': {'description': '', # 'loops': [{'duration': 0, # 'sampling_interval': 0.0, # 'start': 0, # 'stimulation': False}]}, # 'fields_of_view': range(0, 2500), # v # 'frames': [0], # 'height': 1022, # 'num_frames': 1, # 'pixel_microns': 0.219080212825376, # 'total_images_per_channel': 2500, # 'width': 1024, # 'z_coordinates': [1890.8000000000002, # 1891.025, # 1891.1750000000002, # ... # 1905.2250000000001, # 1905.125, # 1905.1000000000001], # 'z_levels': range(0, 2500)} # self._nd2.axes ['x', 'y', 'c', 't', 'z', 'v'] # self._nd2.ndim 6 # self._nd2.pixel_type numpy.float64 # self._nd2.sizes {'x': 1024, 'y': 1022, 'c': 4, 't': 1, 'z': 2500, 'v': 2500} self._getND2Metadata()
from xml.etree import cElementTree from large_image.cache_util import LRUCache, strhash, methodcache from large_image import config try: from libtiff import libtiff_ctypes except ValueError as exc: # If the python libtiff module doesn't contain a pregenerated module for # the appropriate version of libtiff, it tries to generate a module from # the libtiff header file. If it can't find this file (possibly because it # is in a virtual environment), it raises a ValueError instead of an # ImportError. We convert this to an ImportError, so that we will print a # more lucid error message and just fail to load this one tile source # instead of failing to load the whole plugin. config.getConfig('logger').warn( 'Failed to import libtiff; try upgrading the python module (%s)' % exc) raise ImportError(str(exc)) # This suppress warnings about unknown tags libtiff_ctypes.suppress_warnings() def etreeToDict(t): """ Convert an xml etree to a nested dictionary without schema names in the keys. @param t: an etree. @returns: a python dictionary with the results. """ # Remove schema
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() lastException = None # Associated images are smallish TIFF images that have an image # description and are not tiled. They have their own TIFF directory. # Individual TIFF images can also have images embedded into their # directory as tags (this is a vendor-specific method of adding more # images into a file) -- those are stored in the individual # directories' _embeddedImages field. self._associatedImages = {} # Query all know directories in the tif file. Only keep track of # directories that contain tiled images. alldir = [] for directoryNum in itertools.count(): # pragma: no branch try: td = TiledTiffDirectory(largeImagePath, directoryNum) except ValidationTiffException as exc: lastException = exc self._addAssociatedImage(largeImagePath, directoryNum) continue except TiffException as exc: if not lastException: lastException = exc break if not td.tileWidth or not td.tileHeight: continue # Calculate the tile level, where 0 is a single tile, 1 is up to a # set of 2x2 tiles, 2 is 4x4, etc. level = int( math.ceil( math.log( max( float(td.imageWidth) / td.tileWidth, float(td.imageHeight) / td.tileHeight)) / math.log(2))) if level < 0: continue # Store information for sorting with the directory. alldir.append((level > 0, td.tileWidth * td.tileHeight, level, td.imageWidth * td.imageHeight, directoryNum, td)) # 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 __init__(self, path, **kwargs): # noqa """ 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._logger = config.getConfig('logger') largeImagePath = self._getLargeImagePath() ext = os.path.splitext(largeImagePath)[1] if not ext: raise TileSourceException( 'File cannot be opened via bioformats because it has no ' 'extension to specify the file type (%s).' % largeImagePath) if ext.lower() in ('.jpg', '.jpeg', '.jpe', '.png'): raise TileSourceException('File will not be opened by bioformats reader') if not _startJavabridge(self._logger): raise TileSourceException( 'File cannot be opened by bioformats reader because javabridge failed to start') self._tileLock = threading.RLock() try: javabridge.attach() try: self._bioimage = bioformats.ImageReader(largeImagePath) except AttributeError as exc: self._logger.debug('File cannot be opened via Bioformats. (%r)' % exc) raise TileSourceException('File cannot be opened via Bioformats. (%r)' % exc) _openImages.append(self) rdr = self._bioimage.rdr # Bind additional functions not done by bioformats module. # Functions are listed at https://downloads.openmicroscopy.org # //bio-formats/5.1.5/api/loci/formats/IFormatReader.html for (name, params, desc) in [ ('getBitsPerPixel', '()I', 'Get the number of bits per pixel'), ('getEffectiveSizeC', '()I', 'effectiveC * Z * T = imageCount'), ('isNormalized', '()Z', 'Is float data normalized'), ('isMetadataComplete', '()Z', 'True if metadata is completely parsed'), ('getDomains', '()[Ljava/lang/String;', 'Get a list of domains'), ('getZCTCoords', '(I)[I', 'Gets the Z, C and T coordinates ' '(real sizes) corresponding to the given rasterized index value.'), ('getOptimalTileWidth', '()I', 'the optimal sub-image width ' 'for use with openBytes'), ('getOptimalTileHeight', '()I', 'the optimal sub-image height ' 'for use with openBytes'), ('getResolutionCount', '()I', 'The number of resolutions for ' 'the current series'), ('setResolution', '(I)V', 'Set the resolution level'), ('getResolution', '()I', 'The current resolution level'), ('hasFlattenedResolutions', '()Z', 'True if resolutions have been flattened'), ('setFlattenedResolutions', '(Z)V', 'Set if resolution should be flattened'), ]: setattr(rdr, name, types.MethodType( javabridge.jutil.make_method(name, params, desc), rdr)) # rdr.setFlattenedResolutions(False) self._metadata = { 'dimensionOrder': rdr.getDimensionOrder(), 'metadata': javabridge.jdictionary_to_string_dictionary( rdr.getMetadata()), 'seriesMetadata': javabridge.jdictionary_to_string_dictionary( rdr.getSeriesMetadata()), 'seriesCount': rdr.getSeriesCount(), 'imageCount': rdr.getImageCount(), 'rgbChannelCount': rdr.getRGBChannelCount(), 'sizeColorPlanes': rdr.getSizeC(), 'sizeT': rdr.getSizeT(), 'sizeZ': rdr.getSizeZ(), 'sizeX': rdr.getSizeX(), 'sizeY': rdr.getSizeY(), 'pixelType': rdr.getPixelType(), 'isLittleEndian': rdr.isLittleEndian(), 'isRGB': rdr.isRGB(), 'isInterleaved': rdr.isInterleaved(), 'isIndexed': rdr.isIndexed(), 'bitsPerPixel': rdr.getBitsPerPixel(), 'sizeC': rdr.getEffectiveSizeC(), 'normalized': rdr.isNormalized(), 'metadataComplete': rdr.isMetadataComplete(), # 'domains': rdr.getDomains(), 'optimalTileWidth': rdr.getOptimalTileWidth(), 'optimalTileHeight': rdr.getOptimalTileHeight(), 'resolutionCount': rdr.getResolutionCount(), 'flattenedResolutions': rdr.hasFlattenedResolutions(), } self._checkSeries(rdr) bmd = bioformats.metadatatools.MetadataRetrieve(self._bioimage.metadata) try: self._metadata['channelNames'] = [ bmd.getChannelName(0, c) or bmd.getChannelID(0, c) for c in range(self._metadata['sizeColorPlanes'])] except Exception: self._metadata['channelNames'] = [] for key in ['sizeXY', 'sizeC', 'sizeZ', 'sizeT']: if not isinstance(self._metadata[key], int) or self._metadata[key] < 1: self._metadata[key] = 1 self.sizeX = self._metadata['sizeX'] self.sizeY = self._metadata['sizeY'] self._computeTiles() self._computeLevels() self._computeMagnification() except javabridge.JavaException as exc: es = javabridge.to_string(exc.throwable) self._logger.debug('File cannot be opened via Bioformats. (%s)' % es) raise TileSourceException('File cannot be opened via Bioformats. (%s)' % es) except AttributeError: self._logger.exception('The bioformats reader threw an unhandled exception.') raise TileSourceException('The bioformats reader threw an unhandled exception.') finally: if javabridge.get_env(): javabridge.detach() if self.levels < 1: raise TileSourceException( 'OpenSlide image must have at least one level.') if self.sizeX <= 0 or self.sizeY <= 0: raise TileSourceException('Bioformats tile size is invalid.')
def _initWithTiffTools(self): # noqa """ Use tifftools to read all of the tiff directory information. Check if the zeroth directory can be validated as a tiled directory. If so, then check if the remaining directories are either tiled in descending size or have subifds with tiles in descending sizes. All primary tiled directories are the same size and format; all non-tiled directories are treated as associated images. """ dir0 = TiledTiffDirectory(self._largeImagePath, 0) self.tileWidth = dir0.tileWidth self.tileHeight = dir0.tileHeight self.sizeX = dir0.imageWidth self.sizeY = dir0.imageHeight self.levels = max( 1, int( math.ceil( math.log( max(dir0.imageWidth / dir0.tileWidth, dir0.imageHeight / dir0.tileHeight)) / math.log(2))) + 1) info = tifftools.read_tiff(self._largeImagePath) frames = [] associated = [] # for now, a list of directories curframe = -1 for idx, ifd in enumerate(info['ifds']): # if not tiles, add to associated images if tifftools.Tag.tileWidth.value not in ifd['tags']: associated.append(idx) continue level = self._levelFromIfd(ifd, info['ifds'][0]) # if the same resolution as the main image, add a frame if level == self.levels - 1: curframe += 1 frames.append({'dirs': [None] * self.levels}) frames[-1]['dirs'][-1] = (idx, 0) try: frameMetadata = json.loads(ifd['tags'][ tifftools.Tag.ImageDescription.value]['data']) for key in {'channels', 'frame'}: if key in frameMetadata: frames[-1][key] = frameMetadata[key] except Exception: pass # otherwise, add to the first frame missing that level elif level < self.levels - 1 and any( frame for frame in frames if frame['dirs'][level] is None): frames[next( idx for idx, frame in enumerate(frames) if frame['dirs'][level] is None)]['dirs'][level] = (idx, 0) else: raise TileSourceException( 'Tile layers are in a surprising order') # if there are sub ifds, add them if tifftools.Tag.SubIfd.value in ifd['tags']: for subidx, subifds in enumerate( ifd['tags'][tifftools.Tag.SubIfd.value]['ifds']): if len(subifds) != 1: raise TileSourceException( 'When stored in subifds, each subifd should be a single ifd.' ) level = self._levelFromIfd(subifds[0], info['ifds'][0]) if level < self.levels - 1 and frames[-1]['dirs'][ level] is None: frames[-1]['dirs'][level] = (idx, subidx + 1) else: raise TileSourceException( 'Tile layers are in a surprising order') self._associatedImages = {} for dirNum in associated: self._addAssociatedImage(self._largeImagePath, dirNum) self._frames = frames self._tiffDirectories = [ TiledTiffDirectory(self._largeImagePath, frames[0]['dirs'][idx][0], subDirectoryNum=frames[0]['dirs'][idx][1]) if frames[0]['dirs'][idx] is not None else None for idx in range(self.levels - 1) ] self._tiffDirectories.append(dir0) missing = [v is None for v in self._tiffDirectories] maxMissing = max(0 if not v else missing.index(False, idx) - idx for idx, v in enumerate(missing)) if maxMissing >= self._maxSkippedLevels: config.getConfig('logger').warning( 'Tiff image is missing many lower resolution levels (%d). ' 'It will be inefficient to read lower resolution tiles.', maxMissing) return True
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(OpenslideFileTileSource, self).__init__(path, **kwargs) largeImagePath = self._getLargeImagePath() try: self._openslide = openslide.OpenSlide(largeImagePath) except openslide.lowlevel.OpenSlideUnsupportedFormatError: raise TileSourceException('File cannot be opened via OpenSlide.') except openslide.lowlevel.OpenSlideError: raise TileSourceException('File will not be opened via OpenSlide.') svsAvailableLevels = self._getAvailableLevels(largeImagePath) if not len(svsAvailableLevels): raise TileSourceException('OpenSlide image size is invalid.') self.sizeX = svsAvailableLevels[0]['width'] self.sizeY = svsAvailableLevels[0]['height'] if (self.sizeX != self._openslide.dimensions[0] or self.sizeY != self._openslide.dimensions[1]): msg = ('OpenSlide reports a dimension of %d x %d, but base layer ' 'has a dimension of %d x %d -- using base layer ' 'dimensions.' % (self._openslide.dimensions[0], self._openslide.dimensions[1], self.sizeX, self.sizeY)) config.getConfig('logger').info(msg) self._getTileSize() self.levels = int( math.ceil( max(math.log(float(self.sizeX) / self.tileWidth), math.log(float(self.sizeY) / self.tileHeight)) / math.log(2))) + 1 if self.levels < 1: raise TileSourceException( 'OpenSlide image must have at least one level.') self._svslevels = [] # Precompute which SVS level should be used for our tile levels. SVS # level 0 is the maximum resolution. The SVS levels are in descending # resolution and, we assume, are powers of two in scale. For each of # our levels (where 0 is the minimum resolution), find the lowest # resolution SVS level that contains at least as many pixels. If this # is not the same scale as we expect, note the scale factor so we can # load an appropriate area and scale it to the tile size later. maxSize = 16384 # This should probably be based on available memory for level in range(self.levels): levelW = max(1, self.sizeX / 2**(self.levels - 1 - level)) levelH = max(1, self.sizeY / 2**(self.levels - 1 - level)) # bestlevel and scale will be the picked svs level and the scale # between that level and what we really wanted. We expect scale to # always be a positive integer power of two. bestlevel = svsAvailableLevels[0]['level'] scale = 1 for svslevel in range(len(svsAvailableLevels)): if (svsAvailableLevels[svslevel]['width'] < levelW - 1 or svsAvailableLevels[svslevel]['height'] < levelH - 1): break bestlevel = svsAvailableLevels[svslevel]['level'] scale = int( round(svsAvailableLevels[svslevel]['width'] / levelW)) # If there are no tiles at a particular level, we have to read a # larger area of a higher resolution level. If such an area would # be excessively large, we could have memroy issues, so raise an # error. if (self.tileWidth * scale > maxSize or self.tileHeight * scale > maxSize): msg = ('OpenSlide has no small-scale tiles (level %d is at %d ' 'scale)' % (level, scale)) config.getConfig('logger').info(msg) raise TileSourceException(msg) self._svslevels.append({'svslevel': bestlevel, 'scale': scale})