def parse_region(
    in_image: Image, top: Size, left: Size, width: Size, height: Size, tier_idx: int = 0,
    tier_type: TierIndexType = TierIndexType.LEVEL, silent_oob: bool = False
) -> Region:
    """
    Parse a region

    Parameters
    ----------
    in_image
        Image in which region is extracted
    top
    left
    width
    height
    tier_idx
        Tier index to use as reference
    tier_type
        Type of tier index
    silent_oob
        Whether out of bounds region should raise an error or not.

    Returns
    -------
    region
        The parsed region

    Raises
    ------
    BadRequestException
        If a region coordinate is out of bound and silent_oob is False.
    """
    if tier_type == TierIndexType.ZOOM:
        check_zoom_validity(in_image.pyramid, tier_idx)
        ref_tier = in_image.pyramid.get_tier_at_zoom(tier_idx)
    else:
        check_level_validity(in_image.pyramid, tier_idx)
        ref_tier = in_image.pyramid.get_tier_at_level(tier_idx)

    if type(top) == float:
        top *= ref_tier.height
    if type(left) == float:
        left *= ref_tier.width
    if type(width) == float:
        width *= ref_tier.width
    if type(height) == float:
        height *= ref_tier.height

    downsample = (ref_tier.width_factor, ref_tier.height_factor)
    region = Region(top, left, width, height, downsample)

    if not silent_oob:
        clipped = copy(region).clip(ref_tier.width, ref_tier.height)
        if clipped != region:
            raise BadRequestException(
                detail=f"Some coordinates of region {region} are out of bounds."
            )

    return region
def test_pyramid_tier_indexes():
    tier = PyramidTier(1000, 2000, 256, Pyramid())
    assert tier.max_tx == 4
    assert tier.max_ty == 8
    assert tier.max_ti == 32

    assert tier.txty2ti(0, 0) == 0
    assert tier.txty2ti(3, 7) == 31

    assert tier.ti2txty(0) == (0, 0)
    assert tier.ti2txty(31) == (3, 7)

    assert tier.get_txty_tile(0, 0) == Region(0, 0, 256, 256)
    assert tier.get_txty_tile(3, 7) == Region(1792, 768, 1000 - 768, 2000 - 1792)

    assert tier.get_ti_tile(0) == Region(0, 0, 256, 256)
    assert tier.get_ti_tile(31) == Region(1792, 768, 1000 - 768, 2000 - 1792)
Beispiel #3
0
def get_annotation_region(in_image: Image,
                          annots: ParsedAnnotations,
                          context_factor: float = 1.0,
                          try_square: bool = False) -> Region:
    """
    Get the region describing the rectangular envelope of all
    annotations multiplied by an optional context factor.

    Parameters
    ----------
    in_image
        Image in which region is extracted.
    annots
        List of parsed annotations
    context_factor
        Context factor
    try_square
        Try to adapt region's width or height to have a square region.
    Returns
    -------
    Region
    """

    # All computation are done in non normalized float.
    minx, miny, maxx, maxy = annots.bounds
    left = minx
    top = miny
    width = maxx - minx
    height = maxy - miny
    if context_factor and context_factor != 1.0:
        left -= width * (context_factor - 1) / 2.0
        top -= height * (context_factor - 1) / 2.0
        width *= context_factor
        height *= context_factor

    if try_square:
        if width < height:
            delta = height - width
            left -= delta / 2
            width += delta
        elif height < width:
            delta = width - height
            top -= delta / 2
            height += delta

    width = min(width, in_image.width)
    if left < 0:
        left = 0
    else:
        left = min(left, in_image.width - width)

    height = min(height, in_image.height)
    if top < 0:
        top = 0
    else:
        top = min(top, in_image.height - height)

    return Region(top, left, width, height)
Beispiel #4
0
 def read_window(
     self, region: Region, out_width: int, out_height: int,
     c: Optional[Union[int, List[int]]] = None, **other
 ) -> VIPSImage:
     image = cached_vips_file(self.format)
     region = region.scale_to_tier(self.format.pyramid.base)
     im = image.crop(region.left, region.top, region.width, region.height)
     if im.hasalpha():
         im = im.flatten()
     return self._extract_channels(im, c)
def test_parse_region():
    img = FakeImagePyramid(1000, 2000, 3)

    region = {'top': 100, 'left': 50, 'width': 128, 'height': 128}
    assert parse_region(img,
                        **region,
                        tier_idx=0,
                        tier_type=TierIndexType.LEVEL) == Region(
                            100, 50, 128, 128)
    assert parse_region(img,
                        **region,
                        tier_idx=1,
                        tier_type=TierIndexType.LEVEL) == Region(100,
                                                                 50,
                                                                 128,
                                                                 128,
                                                                 downsample=2)

    region = {'top': 0.1, 'left': 0.15, 'width': 0.02, 'height': 0.2}
    assert parse_region(img,
                        **region,
                        tier_idx=0,
                        tier_type=TierIndexType.LEVEL) == Region(
                            200, 150, 20, 400)
    assert parse_region(img,
                        **region,
                        tier_idx=1,
                        tier_type=TierIndexType.LEVEL) == Region(100,
                                                                 75,
                                                                 10,
                                                                 200,
                                                                 downsample=2)

    with pytest.raises(BadRequestException):
        region = {'top': 100, 'left': 900, 'width': 1280, 'height': 1280}
        parse_region(img,
                     **region,
                     tier_idx=0,
                     tier_type=TierIndexType.LEVEL,
                     silent_oob=False)
Beispiel #6
0
    def read_thumb(self,
                   out_width,
                   out_height,
                   precomputed=None,
                   c=None,
                   z=None,
                   t=None):
        image = cached_pillow_file(self.format, self.FORMAT_SLUG)

        # We do not use Pillow resize() method as resize will be better handled
        # by vips in response generation.
        return self.read_window(Region(0, 0, image.width, image.height),
                                out_width, out_height, c, z, t)
Beispiel #7
0
def test_annotation_region():
    class FakeImage:
        def __init__(self, w, h):
            self.width = w
            self.height = h

    al = ParsedAnnotations()
    al.append(ParsedAnnotation(box(10, 20, 30, 40)))
    assert get_annotation_region(FakeImage(100, 100),
                                 al) == Region(20, 10, 20, 20)
    assert get_annotation_region(FakeImage(100, 100), al,
                                 context_factor=1.5) == Region(15, 5, 30, 30)

    al = ParsedAnnotations()
    al.append(ParsedAnnotation(box(10, 20, 30, 30)))
    assert get_annotation_region(FakeImage(100, 100), al,
                                 try_square=True) == Region(15, 10, 20, 20)

    al = ParsedAnnotations()
    al.append(ParsedAnnotation(box(20, 10, 30, 30)))
    assert get_annotation_region(FakeImage(100, 100), al,
                                 try_square=True) == Region(10, 15, 20, 20)
Beispiel #8
0
    def check_integrity(
        self, lazy_mode: bool = False, check_metadata: bool = True,
        check_tile: bool = False, check_thumb: bool = False,
        check_window: bool = False, check_associated: bool = False
    ) -> List[Tuple[str, Exception]]:
        """
        Check integrity of the image: ensure that asked checks do not raise
        errors. In lazy mode, stop at first error.

        Returns
        -------
        errors
            A list of problematic attributes with the associated exception.
            Some attributes are inter-dependent, so the same exception can
            appear for several attributes.
        """
        errors = []

        if check_metadata:
            attributes = (
                'width', 'height', 'depth', 'duration', 'n_channels',
                'pixel_type', 'physical_size_x', 'physical_size_y',
                'physical_size_z', 'frame_rate', 'description',
                'acquisition_datetime', 'channels', 'objective', 'microscope',
                'associated_thumb', 'associated_label', 'associated_macro',
                'raw_metadata', 'annotations', 'pyramid'
            )
            for attr in attributes:
                try:
                    getattr(self, attr)
                except Exception as e:
                    errors.append((attr, e))
                    if lazy_mode:
                        return errors

        if check_tile:
            try:
                tier_idx = self.pyramid.max_zoom // 2
                tier = self.pyramid.tiers[tier_idx]
                tx = tier.max_tx // 2
                ty = tier.max_ty // 2
                self.tile(Tile(tier, tx, ty))
            except Exception as e:
                errors.append(('tile', e))
                if lazy_mode:
                    return errors

        if check_thumb:
            try:
                self.thumbnail(128, 128)
            except Exception as e:
                errors.append(('thumbnail', e))
                if lazy_mode:
                    return errors

        if check_window:
            try:
                w = round(0.1 * self.width)
                h = round(0.1 * self.height)
                self.window(
                    Region(self.height - h, self.width - w, w, h), 128, 128
                )
            except Exception as e:
                errors.append(('window', e))
                if lazy_mode:
                    return errors

        if check_associated:
            try:
                self.thumbnail(128, 128, precomputed=True)
            except Exception as e:
                errors.append(('precomputed_thumbnail', e))
                if lazy_mode:
                    return errors

            try:
                self.label(128, 128)
            except Exception as e:
                errors.append(('label', e))
                if lazy_mode:
                    return errors

            try:
                self.macro(128, 128)
            except Exception as e:
                errors.append(('macro', e))
                if lazy_mode:
                    return errors

        return errors
Beispiel #9
0
 def region(self) -> Region:
     left, top, right, bottom = self.bounds
     return Region(top, left, right - left, bottom - top)