Пример #1
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().__init__(path, **kwargs)

        self._largeImagePath = self._getLargeImagePath()
        if not os.path.isfile(self._largeImagePath):
            try:
                possibleYaml = self._largeImagePath.split('multi://', 1)[-1]
                self._info = yaml.safe_load(possibleYaml)
                self._validator.validate(self._info)
                self._basePath = Path('.')
            except Exception:
                raise TileSourceFileNotFoundError(self._largeImagePath) from None
        else:
            try:
                with builtins.open(self._largeImagePath) as fptr:
                    start = fptr.read(1024).strip()
                    if start[:1] not in ('{', '#', '-') and (start[:1] < 'a' or start[:1] > 'z'):
                        raise TileSourceError('File cannot be opened via multi-source reader.')
                    fptr.seek(0)
                    self._info = yaml.safe_load(fptr)
            except (json.JSONDecodeError, yaml.YAMLError, UnicodeDecodeError):
                raise TileSourceError('File cannot be opened via multi-source reader.')
            self._validator.validate(self._info)
            self._basePath = Path(self._largeImagePath).parent
        self._basePath /= Path(self._info.get('basePath', '.'))
        self._collectFrames()
Пример #2
0
    def _loadTileSource(cls, item, **kwargs):
        if 'largeImage' not in item:
            raise TileSourceError('No large image file in this item.')
        if item['largeImage'].get('expected'):
            raise TileSourceError('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 TileSourceError as exc:
            # 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.warning('The original tile source for item %s is not working' % item['_id'])
            try:
                file = File().load(item['largeImage']['fileId'], force=True)
                localPath = File().getLocalFilePath(file)
                open(localPath, 'rb').read(1)
            except IOError:
                logger.warning(
                    'Is the original data reachable and readable (it fails via %r)?', localPath)
                raise IOError(localPath) from None
            except Exception:
                pass
            raise exc
        return tileSource
Пример #3
0
 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 TileSourceError(
                     'No local file path for this file: %s' % e.args[0])
         return largeImagePath
     except (TileSourceAssetstoreError, FilePathException):
         raise
     except (KeyError, ValidationException, TileSourceError) as e:
         raise TileSourceError('No large image file in this item: %s' %
                               e.args[0])
Пример #4
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().__init__(path, **kwargs)

        self._largeImagePath = str(self._getLargeImagePath())
        self._pixelInfo = {}
        try:
            self._openjpeg = glymur.Jp2k(self._largeImagePath)
            if not self._openjpeg.shape:
                if not os.path.isfile(self._largeImagePath):
                    raise FileNotFoundError()
                raise TileSourceError(
                    'File cannot be opened via Glymur and OpenJPEG.')
        except (glymur.jp2box.InvalidJp2kError, struct.error):
            raise TileSourceError(
                'File cannot be opened via Glymur and OpenJPEG.')
        except FileNotFoundError:
            if not os.path.isfile(self._largeImagePath):
                raise TileSourceFileNotFoundError(
                    self._largeImagePath) from None
            raise
        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 TileSourceError(
                '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()
Пример #5
0
    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 TileSourceError(
                    '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.
        self._ignoreSourceNames('pil', largeImagePath)

        try:
            self._pilImage = PIL.Image.open(largeImagePath)
        except OSError:
            if not os.path.isfile(largeImagePath):
                raise TileSourceFileNotFoundError(largeImagePath) from None
            raise TileSourceError('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 TileSourceError('PIL tile size is invalid.')
        maxWidth, maxHeight = getMaxSize(maxSize, self.defaultMaxSize())
        if self.tileWidth > maxWidth or self.tileHeight > maxHeight:
            raise TileSourceError('PIL tile size is too large.')
Пример #6
0
 def getTileIOTiffError(self, x, y, z, pilImageAllowed=False,
                        numpyAllowed=False, sparseFallback=False,
                        exception=None, **kwargs):
     if sparseFallback:
         if z:
             noedge = kwargs.copy()
             noedge.pop('edge', None)
             noedge['inSparseFallback'] = True
             image = self.getTile(
                 x // 2, y // 2, z - 1, pilImageAllowed=True, numpyAllowed=False,
                 sparseFallback=sparseFallback, edge=False,
                 **noedge)
             if not isinstance(image, PIL.Image.Image):
                 image = PIL.Image.open(io.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))
         else:
             image = PIL.Image.new('RGBA', (self.tileWidth, self.tileHeight))
         return self._outputTile(image, TILE_FORMAT_PIL, x, y, z, pilImageAllowed,
                                 numpyAllowed, applyStyle=False, **kwargs)
     raise TileSourceError('Internal I/O failure: %s' % exception.args[0])
Пример #7
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
     # specified 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 TileSourceError('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),
                            getattr(PIL.Image, 'Resampling',
                                    PIL.Image).LANCZOS)
     return self._outputTile(tile, TILE_FORMAT_PIL, x, y, z,
                             pilImageAllowed, numpyAllowed, **kwargs)
Пример #8
0
    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 TileSourceError(
                    '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)
Пример #9
0
def _lazyImport():
    """
    Import the tifffile module.  This is done when needed rather than in the
    module initialization because it is slow.
    """
    global tifffile

    if tifffile is None:
        try:
            import tifffile
        except ImportError:
            raise TileSourceError('tifffile module not found.')
        if not hasattr(tifffile.TiffTag, 'dtype_name') or not hasattr(
                tifffile.TiffPage, 'aszarr'):
            tifffile = None
            raise TileSourceError('tifffile module is too old.')
Пример #10
0
    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 TileSourceError('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 TileSourceError('IFD size is not a power of two smaller than first IFD.')
Пример #11
0
    def getTile(self, x, y, z, *args, **kwargs):
        frame = self._getFrame(**kwargs)
        self._xyzInRange(
            x, y, z, frame,
            len(self._frames) if hasattr(self, '_frames') else None)

        if not (self.minLevel <= z <= self.maxLevel):
            raise TileSourceError('z layer does not exist')

        xFraction = (x + 0.5) * self.tileWidth * 2**(self.levels - 1 -
                                                     z) / self.sizeX
        yFraction = (y + 0.5) * self.tileHeight * 2**(self.levels - 1 -
                                                      z) / self.sizeY
        fFraction = yFraction
        if hasattr(self, '_frames'):
            fFraction = float(frame) / (len(self._frames) - 1)

        backgroundColor = colorsys.hsv_to_rgb(
            h=xFraction,
            s=(0.3 + (0.7 * fFraction)),
            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, 2**z, rgbColor)

        fontsize = 0.15
        text = 'x=%d\ny=%d\nz=%d' % (x, y, z)
        if hasattr(self, '_frames'):
            if self._framesParts == 1:
                text += '\nf=%d' % frame
            else:
                for k1, k2 in [('C', 'IndexC'), ('Z', 'IndexZ'),
                               ('T', 'IndexT'), ('XY', 'IndexXY')]:
                    if k2 in self._frames[frame]:
                        text += '\n%s=%d' % (k1, self._frames[frame][k2])
                fontsize = min(fontsize, 0.8 / len(text.split('\n')))
        try:
            # the font size should fill the whole tile
            imageDrawFont = ImageFont.truetype(
                font='/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf',
                size=int(fontsize * min(self.tileWidth, self.tileHeight)))
        except OSError:
            imageDrawFont = ImageFont.load_default()
        imageDraw.multiline_text(xy=(10, 10),
                                 text=text,
                                 fill=(0, 0, 0),
                                 font=imageDrawFont)
        _counters['tiles'] += 1
        if self.monochrome:
            image = image.convert('L')
        return self._outputTile(image, TILE_FORMAT_PIL, x, y, z, **kwargs)
Пример #12
0
 def mm_y(self, value):
     self._checkEditable()
     value = float(value) if value is not None else None
     if value is not None and value <= 0:
         raise TileSourceError('mm_y must be positive or None')
     if value != getattr(self, '_minHeight', None):
         self._mm_y = value
         self._invalidateImage()
Пример #13
0
 def minWidth(self, value):
     self._checkEditable()
     value = int(value) if value is not None else None
     if value is not None and value <= 0:
         raise TileSourceError('minWidth must be positive or None')
     if value != getattr(self, '_minWidth', None):
         self._minWidth = value
         self._invalidateImage()
Пример #14
0
    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, f'COLORIZER_{scheme.upper()}')
        except AttributeError:
            mapnik_scheme = mapnik.COLORIZER_DISCRETE
            raise TileSourceError(
                '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 = self.getHexColors(
                style.get('palette', ['#000000', '#ffffff']))
            if len(colors) < 2:
                raise TileSourceError('A palette must have at least 2 colors.')
            values = self.interpolateMinMax(minimum, maximum, len(colors))
            for value, color in sorted(zip(values, colors)):
                colorizer.add_stop(value, mapnik.Color(color))

        return colorizer
Пример #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().__init__(path, **kwargs)

        self._largeImagePath = str(self._getLargeImagePath())

        _lazyImport()
        try:
            self._tf = tifffile.TiffFile(self._largeImagePath)
        except Exception:
            if not os.path.isfile(self._largeImagePath):
                raise TileSourceFileNotFoundError(
                    self._largeImagePath) from None
            raise TileSourceError('File cannot be opened via tifffile.')
        maxseries, maxsamples = self._biggestSeries()
        self.tileWidth = self.tileHeight = self._tileSize
        s = self._tf.series[maxseries]
        self._baseSeries = s
        page = s.pages[0]
        if ('TileWidth' in page.tags and self._minTileSize <=
                page.tags['TileWidth'].value <= self._maxTileSize):
            self.tileWidth = page.tags['TileWidth'].value
        if ('TileLength' in page.tags and self._minTileSize <=
                page.tags['TileLength'].value <= self._maxTileSize):
            self.tileHeight = page.tags['TileLength'].value
        self.sizeX = s.shape[s.axes.index('X')]
        self.sizeY = s.shape[s.axes.index('Y')]
        try:
            unit = {2: 25.4, 3: 10}[page.tags['ResolutionUnit'].value.real]

            self._mm_x = (unit * page.tags['XResolution'].value[1] /
                          page.tags['XResolution'].value[0])
            self._mm_y = (unit * page.tags['YResolution'].value[1] /
                          page.tags['YResolution'].value[0])
        except Exception:
            self._mm_x = self._mm_y = None
        self._findMatchingSeries()
        self.levels = int(
            max(
                1,
                math.ceil(
                    math.log(
                        float(max(self.sizeX, self.sizeY)) / self.tileWidth) /
                    math.log(2)) + 1))
        self._findAssociatedImages()
        for key in dir(self._tf):
            if (key.startswith('is_') and hasattr(self, '_handle_' + key[3:])
                    and getattr(self._tf, key)):
                getattr(self, '_handle_' + key[3:])()
Пример #16
0
def _lazyImport():
    """
    Import the nd2 module.  This is done when needed rather than in the module
    initialization because it is slow.
    """
    global nd2

    if nd2 is None:
        try:
            import nd2
        except ImportError:
            raise TileSourceError('nd2 module not found.')
Пример #17
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().__init__(path, **kwargs)

        if str(path).startswith(NEW_IMAGE_PATH_FLAG):
            return self._initNew(**kwargs)
        self._largeImagePath = str(self._getLargeImagePath())
        self._editable = False

        self._ignoreSourceNames('vips', self._largeImagePath)
        try:
            self._image = pyvips.Image.new_from_file(self._largeImagePath)
        except pyvips.error.Error:
            if not os.path.isfile(self._largeImagePath):
                raise TileSourceFileNotFoundError(
                    self._largeImagePath) from None
            raise TileSourceError('File cannot be opened via pyvips')
        self.sizeX = self._image.width
        self.sizeY = self._image.height
        self.tileWidth = self.tileHeight = self._tileSize
        pages = 1
        if 'n-pages' in self._image.get_fields():
            pages = self._image.get('n-pages')
        self._frames = [0]
        for page in range(1, pages):
            subInputPath = self._largeImagePath + '[page=%d]' % page
            subImage = pyvips.Image.new_from_file(subInputPath)
            if subImage.width == self.sizeX and subImage.height == self.sizeY:
                self._frames.append(page)
                continue
            if subImage.width * subImage.height < self.sizeX * self.sizeY:
                continue
            self._frames = [page]
            self.sizeX = subImage.width
            self.sizeY = subImage.height
            self._image = subImage
        self.levels = int(
            max(
                1,
                math.ceil(
                    math.log(
                        float(max(self.sizeX, self.sizeY)) / self.tileWidth) /
                    math.log(2)) + 1))
        if len(self._frames) > 1:
            self._recentFrames = cachetools.LRUCache(maxsize=6)
            self._frameLock = threading.RLock()
Пример #18
0
    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 TileSourceError(
                'Minimum and maximum values should be numbers, "auto", "min", or "max".'
            )
        return [float(start + i * step) for i in range(count)]
Пример #19
0
 def crop(self, value):
     self._checkEditable()
     if value is None:
         self._crop = None
         return
     x, y, w, h = value
     x = int(x)
     y = int(y)
     w = int(w)
     h = int(h)
     if x < 0 or y < 0 or w <= 0 or h <= 0:
         raise TileSourceError(
             'Crop must have non-negative x, y and positive w, h')
     self._crop = (x, y, w, h)
Пример #20
0
    def _sourceBoundingBox(self, source, width, height):
        """
        Given a source with a possible transform and an image width and height,
        compute the bounding box for the source.  If a crop is used, it is
        included in the results.  If a non-identify transform is used, both it
        and its inverse are included in the results.

        :param source: a dictionary that may have a position record.
        :param width: the width of the source to transform.
        :param height: the height of the source to transform.
        :returns: a dictionary with left, top, right, bottom of the bounding
            box in the final coordinate space.
        """
        pos = source.get('position')
        bbox = {'left': 0, 'top': 0, 'right': width, 'bottom': height}
        if not pos:
            return bbox
        x0, y0, x1, y1 = 0, 0, width, height
        if 'crop' in pos:
            x0 = min(max(pos['crop'].get('left', x0), 0), width)
            y0 = min(max(pos['crop'].get('top', y0), 0), height)
            x1 = min(max(pos['crop'].get('right', x1), x0), width)
            y1 = min(max(pos['crop'].get('bottom', y1), y0), height)
            bbox['crop'] = {'left': x0, 'top': y0, 'right': x1, 'bottom': y1}
        corners = numpy.array([[x0, y0, 1], [x1, y0, 1], [x0, y1, 1], [x1, y1, 1]])
        m = numpy.identity(3)
        m[0][0] = pos.get('s11', 1) * pos.get('scale', 1)
        m[0][1] = pos.get('s12', 0) * pos.get('scale', 1)
        m[0][2] = pos.get('x', 0)
        m[1][0] = pos.get('s21', 0) * pos.get('scale', 1)
        m[1][1] = pos.get('s22', 1) * pos.get('scale', 1)
        m[1][2] = pos.get('y', 0)
        if not numpy.array_equal(m, numpy.identity(3)):
            bbox['transform'] = m
            try:
                bbox['inverse'] = numpy.linalg.inv(m)
            except numpy.linalg.LinAlgError:
                raise TileSourceError('The position for a source is not invertable (%r)', pos)
        transcorners = numpy.dot(m, corners.T)
        bbox['left'] = min(transcorners[0])
        bbox['top'] = min(transcorners[1])
        bbox['right'] = max(transcorners[0])
        bbox['bottom'] = max(transcorners[1])
        return bbox
Пример #21
0
 def getTile(self, x, y, z, pilImageAllowed=False, numpyAllowed=False,
             sparseFallback=False, **kwargs):
     frame = self._getFrame(**kwargs)
     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:
                 if not kwargs.get('inSparseFallback'):
                     tile = self.getTileFromEmptyDirectory(x, y, z, **kwargs)
                 else:
                     raise IOTiffException('Missing z level %d' % z)
             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 TileSourceError(e.args[0])
     except IOTiffException as e:
         return self.getTileIOTiffError(
             x, y, z, pilImageAllowed=pilImageAllowed,
             numpyAllowed=numpyAllowed, sparseFallback=sparseFallback,
             exception=e, **kwargs)
Пример #22
0
    def _biggestSeries(self):
        """
        Find the series with the most pixels.  Use all series that have the
        same dimensionality and resolution.  They can differ in X, Y size.

        :returns: index of the largest series, number of pixels in a frame in
            that series.
        """
        maxseries = None
        maxsamples = 0
        try:
            for idx, s in enumerate(self._tf.series):
                samples = numpy.prod(s.shape)
                if samples > maxsamples and 'X' in s.axes and 'Y' in s.axes:
                    maxseries = idx
                    maxsamples = samples
        except Exception as exc:
            self.logger.debug('Cannot use tifffile: %r', exc)
            maxseries = None
        if maxseries is None:
            raise TileSourceError('File cannot be opened via tifffile source.')
        return maxseries, maxsamples
Пример #23
0
    def _addSourceToTile(self, tile, sourceEntry, corners, scale):
        """
        Add a source to the current tile.

        :param tile: a numpy array with the tile, or None if there is no data
            yet.
        :param sourceEntry: the current record from the sourceList.  This
            contains the sourcenum, kwargs to apply when opening the source,
            and the frame within the source to fetch.
        :param corners: the four corners of the tile in the main image space
            coordinates.
        :param scale: power of 2 scale of the output; this is tne number of
            pixels that are conceptually aggregated from the source for one
            output pixel.
        :returns: a numpy array of the tile.
        """
        source = self._sources[sourceEntry['sourcenum']]
        ts = self._openSource(source, sourceEntry['kwargs'])
        # If tile is outside of bounding box, skip it
        bbox = source['bbox']
        if (corners[2][0] <= bbox['left'] or corners[0][0] >= bbox['right'] or
                corners[2][1] <= bbox['top'] or corners[0][1] >= bbox['bottom']):
            return tile
        transform = bbox.get('transform')
        srccorners = (
            list(numpy.dot(bbox['inverse'], numpy.array(corners).T).T)
            if transform is not None else corners)
        x = y = 0
        # If there is no transform or the diagonals are positive and there is
        #   no sheer, use getRegion with an appropriate size (be wary of edges)
        if (transform is None or
                transform[0][0] > 0 and transform[0][1] == 0 and
                transform[1][0] == 0 and transform[1][1] > 0):
            scaleX = transform[0][0] if transform is not None else 1
            scaleY = transform[1][1] if transform is not None else 1
            region = {
                'left': srccorners[0][0], 'top': srccorners[0][1],
                'right': srccorners[2][0], 'bottom': srccorners[2][1]
            }
            output = {
                'maxWidth': (corners[2][0] - corners[0][0]) // scale,
                'maxHeight': (corners[2][1] - corners[0][1]) // scale,
            }
            if region['left'] < 0:
                x -= region['left'] * scaleX // scale
                output['maxWidth'] += int(region['left'] * scaleX // scale)
                region['left'] = 0
            if region['top'] < 0:
                y -= region['top'] * scaleY // scale
                output['maxHeight'] += int(region['top'] * scaleY // scale)
                region['top'] = 0
            if region['right'] > source['metadata']['sizeX']:
                output['maxWidth'] -= int(
                    (region['right'] - source['metadata']['sizeX']) * scaleX // scale)
                region['right'] = source['metadata']['sizeX']
            if region['bottom'] > source['metadata']['sizeY']:
                output['maxHeight'] -= int(
                    (region['bottom'] - source['metadata']['sizeY']) * scaleY // scale)
                region['bottom'] = source['metadata']['sizeY']
            for key in region:
                region[key] = int(round(region[key]))
            self.logger.debug('getRegion: ts: %r, region: %r, output: %r', ts, region, output)
            sourceTile, _ = ts.getRegion(
                region=region, output=output, frame=sourceEntry.get('frame', 0),
                format=TILE_FORMAT_NUMPY)
        # Otherwise, get an area twice as big as needed and use
        #  scipy.ndimage.affine_transform to transform it
        else:
            # TODO
            raise TileSourceError('Not implemented')
        # Crop
        # TODO
        tile = self._mergeTiles(tile, sourceTile, x, y)
        return tile
Пример #24
0
 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 = self._getFrame(**kwargs)
     if frame < 0 or frame >= len(self._omebase['TiffData']):
         raise TileSourceError('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
                             and dir.tileWidth != dir.imageWidth)
                 or (dir.tileHeight != self.tileHeight
                     and dir.tileHeight != dir.imageHeight)
                 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 TileSourceError(e.args[0])
     except IOTiffException as e:
         return self.getTileIOTiffError(x,
                                        y,
                                        z,
                                        pilImageAllowed=pilImageAllowed,
                                        numpyAllowed=numpyAllowed,
                                        sparseFallback=sparseFallback,
                                        exception=e,
                                        **kwargs)
Пример #25
0
 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] == {}
                     or int(self._omebase['TiffData'][0].get(
                         'PlaneCount', 0)) == 1):
                 planes = copy.deepcopy(
                     self._omebase.get('Plane',
                                       self._omebase.get('Channel')))
                 if isinstance(planes, dict):
                     planes = [planes]
                     self._omebase['SizeC'] = 1
                 for idx, plane in enumerate(planes):
                     plane['IndexC'] = idx
                 self._omebase['TiffData'] = planes
             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 TileSourceError('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 TileSourceError(
                 'OME Tiff contains frames that contain multiple planes')
     except (KeyError, ValueError, IndexError, TypeError):
         raise TileSourceError(
             'OME Tiff does not contain an expected record')
Пример #26
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.
        """
        # Note this is the super of the parent class, not of this class.
        super(TiffFileTileSource, self).__init__(path, **kwargs)

        self._largeImagePath = str(self._getLargeImagePath())

        try:
            base = TiledTiffDirectory(self._largeImagePath,
                                      0,
                                      mustBeTiled=None)
        except TiffException:
            if not os.path.isfile(self._largeImagePath):
                raise TileSourceFileNotFoundError(
                    self._largeImagePath) from None
            raise TileSourceError('Not a recognized OME Tiff')
        info = getattr(base, '_description_record', None)
        if not info or not info.get('OME'):
            raise TileSourceError('Not an OME Tiff')
        self._omeinfo = info['OME']
        self._checkForOMEZLoop(self._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(self._largeImagePath,
                                   int(entry['TiffData'][0].get('IFD', 0)))
                if entry else None for entry in self._omeLevels
            ]
        else:
            self._tiffDirectories = [
                TiledTiffDirectory(self._largeImagePath, 0, mustBeTiled=None)
                if entry else None for entry in self._omeLevels
            ]
            self._checkForInefficientDirectories(warn=False)
            _maxChunk = min(base.imageWidth, base.tileWidth * self._skippedLevels ** 2) * \
                min(base.imageHeight, base.tileHeight * self._skippedLevels ** 2)
            if _maxChunk > self._maxUntiledChunk:
                raise TileSourceError(
                    'Untiled image is too large to access with the OME Tiff source'
                )
        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 = {}
        self._checkForInefficientDirectories()
Пример #27
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.
        """
        super().__init__(path, **kwargs)

        largeImagePath = str(self._getLargeImagePath())
        self._ignoreSourceNames('bioformats', largeImagePath, r'\.png$')

        if not _startJavabridge(self.logger):
            raise TileSourceError(
                '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, OSError) as exc:
                if not os.path.isfile(largeImagePath):
                    raise TileSourceFileNotFoundError(largeImagePath) from None
                self.logger.debug(
                    'File cannot be opened via Bioformats. (%r)' % exc)
                raise TileSourceError(
                    '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 TileSourceError(
                'File cannot be opened via Bioformats. (%s)' % es)
        except (AttributeError, UnicodeDecodeError):
            self.logger.exception(
                'The bioformats reader threw an unhandled exception.')
            raise TileSourceError(
                'The bioformats reader threw an unhandled exception.')
        finally:
            if javabridge.get_env():
                javabridge.detach()

        if self.levels < 1:
            raise TileSourceError(
                'OpenSlide image must have at least one level.')

        if self.sizeX <= 0 or self.sizeY <= 0:
            raise TileSourceError('Bioformats tile size is invalid.')
Пример #28
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().__init__(path, **kwargs)

        self._largeImagePath = str(self._getLargeImagePath())

        self._pixelInfo = {}

        _lazyImport()
        try:
            self._nd2 = nd2.ND2File(self._largeImagePath, validate_frames=True)
        except Exception:
            if not os.path.isfile(self._largeImagePath):
                raise TileSourceFileNotFoundError(
                    self._largeImagePath) from None
            raise TileSourceError('File cannot be opened via nd2reader.')
        # We use dask to allow lazy reading of large images
        self._nd2array = self._nd2.to_dask(copy=False)
        arrayOrder = list(self._nd2.sizes)
        # Reorder this so that it is XY (P), T, Z, C, Y, X, S (or at least end
        # in Y, X[, S]).
        newOrder = [k for k in arrayOrder if k not in {'C', 'X', 'Y', 'S'}
                    ] + (['C'] if 'C' in arrayOrder else []) + ['Y', 'X'] + (
                        ['S'] if 'S' in arrayOrder else [])
        if newOrder != arrayOrder:
            self._nd2array = numpy.moveaxis(
                self._nd2array, list(range(len(arrayOrder))),
                [newOrder.index(k) for k in arrayOrder])
        self._nd2order = newOrder
        self._nd2origindex = {}
        basis = 1
        for k in arrayOrder:
            if k not in {'C', 'X', 'Y', 'S'}:
                self._nd2origindex[k] = basis
                basis *= self._nd2.sizes[k]
        self.sizeX = self._nd2.sizes['X']
        self.sizeY = self._nd2.sizes['Y']
        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))
        self._framecount = (self._nd2.metadata.contents.channelCount *
                            self._nd2.metadata.contents.frameCount)
        self._bandnames = {
            chan.channel.name.lower(): idx
            for idx, chan in enumerate(self._nd2.metadata.channels)
        }
        self._channels = [
            chan.channel.name for chan in self._nd2.metadata.channels
        ]
        self._tileLock = threading.RLock()
Пример #29
0
    def getTile(self,
                x,
                y,
                z,
                pilImageAllowed=False,
                numpyAllowed=False,
                **kwargs):
        self._xyzInRange(x, y, z)
        ft = fc = fz = 0
        fseries = self._metadata['frameSeries'][0]
        if kwargs.get('frame') is not None:
            frame = self._getFrame(**kwargs)
            fc = frame % self._metadata['sizeC']
            fz = (frame // self._metadata['sizeC']) % self._metadata['sizeZ']
            ft = (frame // self._metadata['sizeC'] //
                  self._metadata['sizeZ']) % self._metadata['sizeT']
            fxy = (frame // self._metadata['sizeC'] //
                   self._metadata['sizeZ'] // self._metadata['sizeT'])
            if frame < 0 or fxy > self._metadata['sizeXY']:
                raise TileSourceError('Frame does not exist')
            fseries = self._metadata['frameSeries'][fxy]
        seriesLevel = self.levels - 1 - z
        scale = 1
        while seriesLevel >= len(fseries['series']):
            seriesLevel -= 1
            scale *= 2
        offsetx = x * self.tileWidth * scale
        offsety = y * self.tileHeight * scale
        width = min(self.tileWidth * scale,
                    self.sizeX // 2**seriesLevel - offsetx)
        height = min(self.tileHeight * scale,
                     self.sizeY // 2**seriesLevel - offsety)
        sizeXAtScale = fseries['sizeX'] // (2**seriesLevel)
        sizeYAtScale = fseries['sizeY'] // (2**seriesLevel)
        finalWidth = width // scale
        finalHeight = height // scale
        width = min(width, sizeXAtScale - offsetx)
        height = min(height, sizeYAtScale - offsety)

        with self._tileLock:
            try:
                javabridge.attach()
                if width > 0 and height > 0:
                    tile = self._bioimage.read(
                        c=fc,
                        z=fz,
                        t=ft,
                        series=fseries['series'][seriesLevel],
                        rescale=False,  # return internal data types
                        XYWH=(offsetx, offsety, width, height))
                else:
                    # We need the same dtype, so read 1x1 at 0x0
                    tile = self._bioimage.read(
                        c=fc,
                        z=fz,
                        t=ft,
                        series=fseries['series'][seriesLevel],
                        rescale=False,  # return internal data types
                        XYWH=(0, 0, 1, 1))
                    tile = numpy.zeros(tuple([0, 0] + list(tile.shape[2:])),
                                       dtype=tile.dtype)
                format = TILE_FORMAT_NUMPY
            except javabridge.JavaException as exc:
                es = javabridge.to_string(exc.throwable)
                raise TileSourceError(
                    'Failed to get Bioformat region (%s, %r).' %
                    (es, (fc, fz, ft, fseries, self.sizeX, self.sizeY, offsetx,
                          offsety, width, height)))
            finally:
                if javabridge.get_env():
                    javabridge.detach()
        if scale > 1:
            tile = tile[::scale, ::scale]
        if tile.shape[:2] != (finalHeight, finalWidth):
            fillValue = 0
            if tile.dtype == numpy.uint16:
                fillValue = 65535
            elif tile.dtype == numpy.uint8:
                fillValue = 255
            elif tile.dtype.kind == 'f':
                fillValue = 1
            retile = numpy.full(tuple([finalHeight, finalWidth] +
                                      list(tile.shape[2:])),
                                fillValue,
                                dtype=tile.dtype)
            retile[0:min(tile.shape[0], finalHeight),
                   0:min(tile.shape[1], finalWidth)] = tile[
                       0:min(tile.shape[0], finalHeight),
                       0:min(tile.shape[1], finalWidth)]
            tile = retile
        return self._outputTile(tile, format, x, y, z, pilImageAllowed,
                                numpyAllowed, **kwargs)
Пример #30
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().__init__(path, **kwargs)

        self._largeImagePath = self._getLargeImagePath()
        # Read the root dzi file and check that the expected image files exist
        try:
            with builtins.open(self._largeImagePath) as fptr:
                if fptr.read(1024).strip()[:5] != '<?xml':
                    raise TileSourceError(
                        'File cannot be opened via deepzoom reader.')
                fptr.seek(0)
            xml = ElementTree.parse(self._largeImagePath).getroot()
            self._info = etreeToDict(xml)['Image']
        except (ElementTree.ParseError, KeyError, UnicodeDecodeError):
            raise TileSourceError('File cannot be opened via Deepzoom reader.')
        except FileNotFoundError:
            if not os.path.isfile(self._largeImagePath):
                raise TileSourceFileNotFoundError(
                    self._largeImagePath) from None
            raise
        # We should now have a dictionary like
        # {'Format': 'png',   # or 'jpeg'
        #  'Overlap': '1',
        #  'Size': {'Height': '41784', 'Width': '44998'},
        #  'TileSize': '254'}
        # and a file structure like
        # <rootname>_files/<level>/<x>_<y>.<format>
        # images will be TileSize+Overlap square; final images will be
        # truncated.  Base level is either 0 or probably 8 (level 0 is a 1x1
        # pixel tile)
        self.sizeX = int(self._info['Size']['Width'])
        self.sizeY = int(self._info['Size']['Height'])
        self.tileWidth = self.tileHeight = int(self._info['TileSize'])
        maxXY = max(self.sizeX, self.sizeY)
        self.levels = int(
            math.ceil(math.log(maxXY / self.tileWidth) / math.log(2))) + 1
        tiledirName = os.path.splitext(os.path.basename(
            self._largeImagePath))[0] + '_files'
        rootdir = os.path.dirname(self._largeImagePath)
        self._tiledir = os.path.join(rootdir, tiledirName)
        if not os.path.isdir(self._tiledir):
            rootdir = os.path.dirname(rootdir)
            self._tiledir = os.path.join(rootdir, tiledirName)
        zeroname = '0_0.%s' % self._info['Format']
        self._nested = os.path.isdir(os.path.join(self._tiledir, '0',
                                                  zeroname))
        zeroimg = PIL.Image.open(
            os.path.join(self._tiledir, '0', zeroname) if not self._nested else
            os.path.join(self._tiledir, '0', zeroname, zeroname))
        if zeroimg.size == (1, 1):
            self._baselevel = int(
                math.ceil(math.log(maxXY) / math.log(2)) -
                math.ceil(math.log(maxXY / self.tileWidth) / math.log(2)))
        else:
            self._baselevel = 0