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)
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)
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)
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)
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)
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
def region(self) -> Region: left, top, right, bottom = self.bounds return Region(top, left, right - left, bottom - top)