Module delta.imagery.rectangle
Simple rectangle class, useful for dealing with ROIs and tiles.
Classes
class Rectangle (min_x, min_y, max_x=0, max_y=0, width=0, height=0)-
Simple rectangle class for ROIs. Max values are NON-INCLUSIVE. When using it, stay consistent with float or integer values.
Parameters
min_x:intmin_y:intmax_x:intmax_y:int- Rectangle bounds.
width:intheight:int- Specify width / height to use these instead of max_x/max_y.
Expand source code
class Rectangle: """ Simple rectangle class for ROIs. Max values are NON-INCLUSIVE. When using it, stay consistent with float or integer values. """ def __init__(self, min_x, min_y, max_x=0, max_y=0, width=0, height=0): """ Parameters ---------- min_x: int min_y: int max_x: int max_y: int Rectangle bounds. width: int height: int Specify width / height to use these instead of max_x/max_y. """ self.min_x = min_x self.min_y = min_y if width > 0: self.max_x = min_x + width else: self.max_x = max_x if height > 0: self.max_y = min_y + height else: self.max_y = max_y def __str__(self): if isinstance(self.min_x, int): return ('min_x: %d, max_x: %d, min_y: %d, max_y: %d' % (self.min_x, self.max_x, self.min_y, self.max_y)) return ('min_x: %f, max_x: %f, min_y: %f, max_y: %f' % (self.min_x, self.max_x, self.min_y, self.max_y)) def __repr__(self): return self.__str__() # def indexGenerator(self): # '''Generator function used to iterate over all integer indices. # Only use this with integer boundaries!''' # for row in range(self.min_y, self.max_y): # for col in range(self.min_x, self.max_x): # yield(TileIndex(row,col)) def bounds(self): """ Returns ------- (int, int, int, int): (min_x, max_x, min_y, max_y) """ return (self.min_x, self.max_x, self.min_y, self.max_y) def width(self): return self.max_x - self.min_x def height(self): return self.max_y - self.min_y def has_area(self): """ Returns ------- bool: true if the rectangle contains any area. """ return (self.width() > 0) and (self.height() > 0) def perimeter(self): return 2*self.width() + 2*self.height() def area(self): if not self.has_area(): return 0 return self.height() * self.width() def get_min_coord(self): return (self.min_x, self.min_y) def get_max_coord(self): return (self.max_x, self.max_y) def shift(self, dx, dy): '''Shifts the entire box''' self.min_x += dx self.max_x += dx self.min_y += dy self.max_y += dy def scale_by_constant(self, xScale, yScale): '''Scale the units by a constant''' if yScale is None: yScale = xScale self.min_x *= xScale self.max_x *= xScale self.min_y *= yScale self.max_y *= yScale def expand(self, left, down, right=None, up=None): '''Expand the box by an amount in each direction''' self.min_x -= left self.min_y -= down if right is None: # If right and up are not passed in, use left and down for both sides. right = left if up is None: up = down self.max_x += right self.max_y += up def expand_to_contain_pt(self, x, y): '''Expands the rectangle to contain the given point''' if isinstance(self.min_x, float): delta = 0.001 else: # These are needed because the upper bound is non-exclusive. delta = 1 if x < self.min_x: self.min_x = x if y < self.min_y: self.min_y = y if x > self.max_x: self.max_x = x + delta if y > self.max_y: self.max_y = y + delta def expand_to_contain_rect(self, other_rect): '''Expands the rectangle to contain the given rectangle''' if other_rect.min_x < self.min_x: self.min_x = other_rect.min_x if other_rect.min_y < self.min_y: self.min_y = other_rect.min_y if other_rect.max_x > self.max_x: self.max_x = other_rect.max_x if other_rect.max_y > self.max_y: self.max_y = other_rect.max_y def get_intersection(self, other_rect): '''Returns the overlapping region of two rectangles''' overlap = Rectangle(max(self.min_x, other_rect.min_x), max(self.min_y, other_rect.min_y), min(self.max_x, other_rect.max_x), min(self.max_y, other_rect.max_y)) return overlap def contains_pt(self, x, y): '''Returns true if this rect contains the given point''' if self.min_x > x: return False if self.min_y > y: return False if self.max_x < x: return False if self.max_y < y: return False return True def contains_rect(self, other_rect): '''Returns true if this rect contains all of the other rect''' if self.min_x > other_rect.min_x: return False if self.min_y > other_rect.min_y: return False if self.max_x < other_rect.max_x: return False if self.max_y < other_rect.max_y: return False return True def overlaps(self, other_rect): '''Returns true if there is any overlap between this and another rectangle''' overlap_area = self.get_intersection(other_rect) return overlap_area.has_area() def make_tile_rois(self, tile_shape, overlap_shape=(0, 0), include_partials=True, min_shape=(0, 0), #pylint: disable=R0912,R0914 partials_overlap=False, by_block=False, containing_rect=None): """ Return a list of tiles encompassing the entire area of this Rectangle. Parameters ---------- tile_shape: (int, int) Shape of each tile (width, height) overlap_shape: (int, int) Amount to overlap tiles in x and y direction include_partials: bool If true, include partial tiles at the edge of the image. min_shape: (int, int) If true and `partials` is true, keep partial tiles of this minimum size. partials_overlap: bool If `partials` is false, and this is true, expand partial tiles to the desired size. Tiles may overlap in some areas. by_block: bool If true, changes the returned generator to group tiles by block. This is intended to optimize disk reads by reading the entire block at once. containing_rect: Rectangle Tiles are restricted to fit inside this rectangle instead of "self". Returns ------- List[Rectangle]: Generator yielding ROIs. If `by_block` is true, returns a generator of (Rectangle, List[Rectangle]) instead, where the first rectangle is a larger block containing multiple tiles in a list. List[Rectangle]: Same as the first output, but the ROIs do not include any overlap regions or any area outside the rectangle. """ tile_width, tile_height = tile_shape min_width, min_height = min_shape if not containing_rect: containing_rect = self half_overlap = (overlap_shape[0] // 2, overlap_shape[1] // 2) tile_spacing_x = tile_width - overlap_shape[0] tile_spacing_y = tile_height - overlap_shape[1] num_tiles_round_up = (int(math.ceil((self.width() - overlap_shape[0]) / tile_spacing_x)), int(math.ceil((self.height()- overlap_shape[1]) / tile_spacing_y))) if include_partials or partials_overlap: num_tiles = num_tiles_round_up else: containing_rect_tiles = (int(math.floor((containing_rect.width() - overlap_shape[0]) / tile_spacing_x)), int(math.floor((containing_rect.height()- overlap_shape[1]) / tile_spacing_y))) num_tiles = (min(num_tiles_round_up[0], containing_rect_tiles[0]), min(num_tiles_round_up[1], containing_rect_tiles[1])) output_tiles = [] unique_tiles = [] for r in range(0, num_tiles[1]): row_tiles = [] unique_row_tiles = [] for c in range(0, num_tiles[0]): tile = Rectangle(self.min_x + c*tile_spacing_x, self.min_y + r*tile_spacing_y, width=tile_width, height=tile_height) # The unique tile has overlap regions removed # and is always constrained by the rectangle size unique_tile = copy.copy(tile) if c > 0: unique_tile.min_x += half_overlap[0] if r > 0: unique_tile.min_y += half_overlap[1] if c < num_tiles[0]-1: unique_tile.max_x -= half_overlap[0] if r < num_tiles[1]-1: unique_tile.max_y -= half_overlap[1] unique_tile = unique_tile.get_intersection(self) if include_partials: # Crop the tile to the valid area and use it tile = tile.get_intersection(self) if tile.width() < min_width or tile.height() < min_height: continue else: # Only use it if the uncropped tile fits entirely in this Rectangle if not containing_rect.contains_rect(tile): if not partials_overlap: continue # Try shifting tile "back" in x/y so that we can fit the entire proposed tile new_max_x = tile.max_x new_max_y = tile.max_y if tile.max_x > containing_rect.max_x: new_max_x = min(containing_rect.max_x, tile.max_x) if tile.max_y > containing_rect.max_y: new_max_y = min(containing_rect.max_y, tile.max_y) tile = Rectangle(new_max_x - tile_width, new_max_y - tile_height, width=tile_width, height=tile_height) if not containing_rect.contains_rect(tile): continue if by_block: row_tiles.append(tile) unique_row_tiles.append(unique_tile) else: output_tiles.append(tile) unique_tiles.append(unique_tile) if by_block and row_tiles: row_rect = Rectangle(row_tiles[0].min_x, row_tiles[0].min_y, row_tiles[-1].max_x, row_tiles[-1].max_y) for t in row_tiles: t.shift(-row_rect.min_x, -row_rect.min_y) for t in unique_row_tiles: t.shift(-row_rect.min_x, -row_rect.min_y) output_tiles.append((row_rect, row_tiles)) unique_tiles.append((row_rect, unique_row_tiles)) return output_tiles, unique_tiles def make_tile_rois_yx(self, tile_shape, overlap_shape=(0, 0), include_partials=True, min_shape=(0, 0), partials_overlap=False, by_block=False, containing_rect=None): '''As make_tile_rois but using a (y,x) input format instead of (x,y)''' return self.make_tile_rois((tile_shape[1], tile_shape[0]), (overlap_shape[1], overlap_shape[0]), include_partials, (min_shape[1], min_shape[0]), partials_overlap, by_block, containing_rect)Methods
def area(self)def bounds(self)-
Returns
(int, int, int, int): (min_x, max_x, min_y, max_y)
def contains_pt(self, x, y)-
Returns true if this rect contains the given point
def contains_rect(self, other_rect)-
Returns true if this rect contains all of the other rect
def expand(self, left, down, right=None, up=None)-
Expand the box by an amount in each direction
def expand_to_contain_pt(self, x, y)-
Expands the rectangle to contain the given point
def expand_to_contain_rect(self, other_rect)-
Expands the rectangle to contain the given rectangle
def get_intersection(self, other_rect)-
Returns the overlapping region of two rectangles
def get_max_coord(self)def get_min_coord(self)def has_area(self)-
Returns
bool:- true if the rectangle contains any area.
def height(self)def make_tile_rois(self, tile_shape, overlap_shape=(0, 0), include_partials=True, min_shape=(0, 0), partials_overlap=False, by_block=False, containing_rect=None)-
Return a list of tiles encompassing the entire area of this Rectangle.
Parameters
tile_shape:(int, int)- Shape of each tile (width, height)
overlap_shape:(int, int)- Amount to overlap tiles in x and y direction
include_partials:bool- If true, include partial tiles at the edge of the image.
min_shape:(int, int)- If true and
partialsis true, keep partial tiles of this minimum size. partials_overlap:bool- If
partialsis false, and this is true, expand partial tiles to the desired size. Tiles may overlap in some areas. by_block:bool- If true, changes the returned generator to group tiles by block. This is intended to optimize disk reads by reading the entire block at once.
containing_rect:Rectangle- Tiles are restricted to fit inside this rectangle instead of "self".
Returns
List[Rectangle]:- Generator yielding ROIs. If
by_blockis true, returns a generator of (Rectangle, List[Rectangle]) instead, where the first rectangle is a larger block containing multiple tiles in a list. List[Rectangle]:- Same as the first output, but the ROIs do not include any overlap regions or any area outside the rectangle.
def make_tile_rois_yx(self, tile_shape, overlap_shape=(0, 0), include_partials=True, min_shape=(0, 0), partials_overlap=False, by_block=False, containing_rect=None)-
As make_tile_rois but using a (y,x) input format instead of (x,y)
def overlaps(self, other_rect)-
Returns true if there is any overlap between this and another rectangle
def perimeter(self)def scale_by_constant(self, xScale, yScale)-
Scale the units by a constant
def shift(self, dx, dy)-
Shifts the entire box
def width(self)