Exemplo n.º 1
0
 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
Exemplo n.º 2
0
    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.')
Exemplo n.º 3
0
    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
Exemplo n.º 4
0
    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)
Exemplo n.º 5
0
    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
Exemplo n.º 6
0
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'
Exemplo n.º 7
0
    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.')
Exemplo n.º 8
0
    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
Exemplo n.º 9
0
    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))
Exemplo n.º 10
0
    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
Exemplo n.º 11
0
    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
Exemplo n.º 12
0
    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()
Exemplo n.º 13
0
    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()
Exemplo n.º 14
0
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
Exemplo n.º 15
0
    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
Exemplo n.º 16
0
    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.')
Exemplo n.º 17
0
 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
Exemplo n.º 18
0
    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})