Module datatap.geometry.rectangle
Expand source code
from __future__ import annotations
from shapely.geometry import box, Polygon as ShapelyPolygon
from typing import Sequence, Tuple, Union
from .point import Point, PointJson
from ..utils import basic_repr
RectangleJson = Tuple[PointJson, PointJson]
class Rectangle:
"""
An axis-aligned rectangle in 2D space.
"""
p1: Point
"""
The top-left corner of the rectangle.
"""
p2: Point
"""
The bottom-right corner of the rectangle.
"""
@staticmethod
def from_json(json: RectangleJson) -> Rectangle:
"""
Creates a `Rectangle` from a `RectangleJson`.
"""
return Rectangle(Point.from_json(json[0]), Point.from_json(json[1]))
@staticmethod
def from_point_set(points: Sequence[Point]) -> Rectangle:
"""
Creates the bounding rectangle of a set of points.
Note, it is possible for this to create an invalid rectangle if all points
are colinear and axis-aligned.
"""
return Rectangle(
Point(min(p.x for p in points), min(p.y for p in points)),
Point(max(p.x for p in points), max(p.y for p in points))
)
def __init__(self, p1: Point, p2: Point, normalize: bool = False):
if normalize:
self.p1 = Point(min(p1.x, p2.x), min(p1.y, p2.y))
self.p2 = Point(max(p1.x, p2.x), max(p1.y, p2.y))
else:
self.p1 = p1
self.p2 = p2
def assert_valid(self) -> None:
"""
Ensures that this rectangle is valid on the unit plane.
"""
self.p1.assert_valid()
self.p2.assert_valid()
assert self.p1.x < self.p2.x and self.p1.y < self.p2.y, f"Rectangle has non-positive area; failed on rectangle {repr(self)}"
def to_json(self) -> RectangleJson:
"""
Serializes this object as a `RectangleJson`.
"""
return (self.p1.to_json(), self.p2.to_json())
def to_shapely(self) -> ShapelyPolygon:
"""
Converts this rectangle into a `Shapely.Polygon`.
"""
return box(self.p1.x, self.p1.y, self.p2.x, self.p2.y)
def to_xywh_tuple(self) -> Tuple[float, float, float, float]:
"""
Converts this rectangle into a tuple of `(x_coordinate, y_coordinate, width, height)`.
"""
w = self.p2.x - self.p1.x
h = self.p2.y - self.p1.y
return (self.p1.x, self.p1.y, w, h)
def to_xyxy_tuple(self) -> Tuple[float, float, float, float]:
"""
Converts this rectangle into a tuple of `(x_min, y_min, x_max, y_max)`.
"""
return (self.p1.x, self.p1.y, self.p2.x, self.p2.y)
def area(self) -> float:
"""
Computes the area of this rectangle.
"""
return abs(self.p1.x - self.p2.x) * abs(self.p1.y - self.p2.y)
def iou(self, other: Rectangle) -> float:
"""
Computes the iou (intersection-over-union) of this rectangle with another.
"""
x1 = max(self.p1.x, other.p1.x)
y1 = max(self.p1.y, other.p1.y)
x2 = min(self.p2.x, other.p2.x)
y2 = min(self.p2.y, other.p2.y)
intersection_area = max(x2 - x1, 0) * max(y2 - y1, 0)
union_area = self.area() + other.area() - intersection_area
return intersection_area / union_area
def diagonal(self) -> float:
"""
Computes the diagonal length of this rectangle.
"""
return self.p1.distance(self.p2)
def scale(self, factor: Union[float, int, Tuple[float, float], Point]):
"""
Resizes the rectangle according to `factor`. The scaling factor can
either be a scalar (`int` or `float`), in which case the rectangle will
be scaled by the same factor on both axes, or a point-like
(`Tuple[float, float]` or `Point`), in which case the rectangle will be
scaled independently on each axis.
"""
return Rectangle(self.p1.scale(factor), self.p2.scale(factor))
def center(self) -> Point:
"""
Computes the center of this rectangle.
"""
return Point((self.p1.x + self.p2.x) / 2, (self.p1.y + self.p2.y) / 2)
def scale_from_center(self, factor: Union[float, int, Tuple[float, float], Point]) -> Rectangle:
"""
Resizes the rectangle according to `factor`, though translates it so
that its center does not move. The scaling factor can either be a scalar
(`int` or `float`), in which case the rectangle will be scaled by the
same factor on both axes, or a point-like (`Tuple[float, float]` or
`Point`), in which case the rectangle will be scaled independently on
each axis.
"""
center = self.center()
return Rectangle(
(self.p1 - center).scale(factor) + center,
(self.p2 - center).scale(factor) + center
)
def clip(self) -> Rectangle:
"""
Clips the rectangle the unit-plane.
"""
return Rectangle(self.p1.clip(), self.p2.clip())
def normalize(self) -> Rectangle:
"""
Returns a new rectangle that is guaranteed to have `p1` be the top left
corner and `p2` be the bottom right corner.
"""
return Rectangle(self.p1, self.p2, True)
def __repr__(self) -> str:
return basic_repr("Rectangle", self.p1, self.p2)
def __hash__(self) -> int:
return hash((self.p1, self.p2))
def __eq__(self, other: object) -> bool:
if not isinstance(other, Rectangle):
return NotImplemented
return self.p1 == other.p1 and self.p2 == other.p2
def __mul__(self, o: Union[int, float]) -> Rectangle:
if isinstance(o, (int, float)): # type: ignore - pyright complains about the isinstance check being redundant
return Rectangle(self.p1 * o, self.p2 * o)
return NotImplemented
Classes
class Rectangle (p1: Point, p2: Point, normalize: bool = False)
-
An axis-aligned rectangle in 2D space.
Expand source code
class Rectangle: """ An axis-aligned rectangle in 2D space. """ p1: Point """ The top-left corner of the rectangle. """ p2: Point """ The bottom-right corner of the rectangle. """ @staticmethod def from_json(json: RectangleJson) -> Rectangle: """ Creates a `Rectangle` from a `RectangleJson`. """ return Rectangle(Point.from_json(json[0]), Point.from_json(json[1])) @staticmethod def from_point_set(points: Sequence[Point]) -> Rectangle: """ Creates the bounding rectangle of a set of points. Note, it is possible for this to create an invalid rectangle if all points are colinear and axis-aligned. """ return Rectangle( Point(min(p.x for p in points), min(p.y for p in points)), Point(max(p.x for p in points), max(p.y for p in points)) ) def __init__(self, p1: Point, p2: Point, normalize: bool = False): if normalize: self.p1 = Point(min(p1.x, p2.x), min(p1.y, p2.y)) self.p2 = Point(max(p1.x, p2.x), max(p1.y, p2.y)) else: self.p1 = p1 self.p2 = p2 def assert_valid(self) -> None: """ Ensures that this rectangle is valid on the unit plane. """ self.p1.assert_valid() self.p2.assert_valid() assert self.p1.x < self.p2.x and self.p1.y < self.p2.y, f"Rectangle has non-positive area; failed on rectangle {repr(self)}" def to_json(self) -> RectangleJson: """ Serializes this object as a `RectangleJson`. """ return (self.p1.to_json(), self.p2.to_json()) def to_shapely(self) -> ShapelyPolygon: """ Converts this rectangle into a `Shapely.Polygon`. """ return box(self.p1.x, self.p1.y, self.p2.x, self.p2.y) def to_xywh_tuple(self) -> Tuple[float, float, float, float]: """ Converts this rectangle into a tuple of `(x_coordinate, y_coordinate, width, height)`. """ w = self.p2.x - self.p1.x h = self.p2.y - self.p1.y return (self.p1.x, self.p1.y, w, h) def to_xyxy_tuple(self) -> Tuple[float, float, float, float]: """ Converts this rectangle into a tuple of `(x_min, y_min, x_max, y_max)`. """ return (self.p1.x, self.p1.y, self.p2.x, self.p2.y) def area(self) -> float: """ Computes the area of this rectangle. """ return abs(self.p1.x - self.p2.x) * abs(self.p1.y - self.p2.y) def iou(self, other: Rectangle) -> float: """ Computes the iou (intersection-over-union) of this rectangle with another. """ x1 = max(self.p1.x, other.p1.x) y1 = max(self.p1.y, other.p1.y) x2 = min(self.p2.x, other.p2.x) y2 = min(self.p2.y, other.p2.y) intersection_area = max(x2 - x1, 0) * max(y2 - y1, 0) union_area = self.area() + other.area() - intersection_area return intersection_area / union_area def diagonal(self) -> float: """ Computes the diagonal length of this rectangle. """ return self.p1.distance(self.p2) def scale(self, factor: Union[float, int, Tuple[float, float], Point]): """ Resizes the rectangle according to `factor`. The scaling factor can either be a scalar (`int` or `float`), in which case the rectangle will be scaled by the same factor on both axes, or a point-like (`Tuple[float, float]` or `Point`), in which case the rectangle will be scaled independently on each axis. """ return Rectangle(self.p1.scale(factor), self.p2.scale(factor)) def center(self) -> Point: """ Computes the center of this rectangle. """ return Point((self.p1.x + self.p2.x) / 2, (self.p1.y + self.p2.y) / 2) def scale_from_center(self, factor: Union[float, int, Tuple[float, float], Point]) -> Rectangle: """ Resizes the rectangle according to `factor`, though translates it so that its center does not move. The scaling factor can either be a scalar (`int` or `float`), in which case the rectangle will be scaled by the same factor on both axes, or a point-like (`Tuple[float, float]` or `Point`), in which case the rectangle will be scaled independently on each axis. """ center = self.center() return Rectangle( (self.p1 - center).scale(factor) + center, (self.p2 - center).scale(factor) + center ) def clip(self) -> Rectangle: """ Clips the rectangle the unit-plane. """ return Rectangle(self.p1.clip(), self.p2.clip()) def normalize(self) -> Rectangle: """ Returns a new rectangle that is guaranteed to have `p1` be the top left corner and `p2` be the bottom right corner. """ return Rectangle(self.p1, self.p2, True) def __repr__(self) -> str: return basic_repr("Rectangle", self.p1, self.p2) def __hash__(self) -> int: return hash((self.p1, self.p2)) def __eq__(self, other: object) -> bool: if not isinstance(other, Rectangle): return NotImplemented return self.p1 == other.p1 and self.p2 == other.p2 def __mul__(self, o: Union[int, float]) -> Rectangle: if isinstance(o, (int, float)): # type: ignore - pyright complains about the isinstance check being redundant return Rectangle(self.p1 * o, self.p2 * o) return NotImplemented
Class variables
var p1 : Point
-
The top-left corner of the rectangle.
var p2 : Point
-
The bottom-right corner of the rectangle.
Static methods
def from_json(json: RectangleJson) ‑> Rectangle
-
Creates a
Rectangle
from aRectangleJson
.Expand source code
@staticmethod def from_json(json: RectangleJson) -> Rectangle: """ Creates a `Rectangle` from a `RectangleJson`. """ return Rectangle(Point.from_json(json[0]), Point.from_json(json[1]))
def from_point_set(points: Sequence[Point]) ‑> Rectangle
-
Creates the bounding rectangle of a set of points.
Note, it is possible for this to create an invalid rectangle if all points are colinear and axis-aligned.
Expand source code
@staticmethod def from_point_set(points: Sequence[Point]) -> Rectangle: """ Creates the bounding rectangle of a set of points. Note, it is possible for this to create an invalid rectangle if all points are colinear and axis-aligned. """ return Rectangle( Point(min(p.x for p in points), min(p.y for p in points)), Point(max(p.x for p in points), max(p.y for p in points)) )
Methods
def area(self) ‑> float
-
Computes the area of this rectangle.
Expand source code
def area(self) -> float: """ Computes the area of this rectangle. """ return abs(self.p1.x - self.p2.x) * abs(self.p1.y - self.p2.y)
def assert_valid(self) ‑> None
-
Ensures that this rectangle is valid on the unit plane.
Expand source code
def assert_valid(self) -> None: """ Ensures that this rectangle is valid on the unit plane. """ self.p1.assert_valid() self.p2.assert_valid() assert self.p1.x < self.p2.x and self.p1.y < self.p2.y, f"Rectangle has non-positive area; failed on rectangle {repr(self)}"
def center(self) ‑> Point
-
Computes the center of this rectangle.
Expand source code
def center(self) -> Point: """ Computes the center of this rectangle. """ return Point((self.p1.x + self.p2.x) / 2, (self.p1.y + self.p2.y) / 2)
def clip(self) ‑> Rectangle
-
Clips the rectangle the unit-plane.
Expand source code
def clip(self) -> Rectangle: """ Clips the rectangle the unit-plane. """ return Rectangle(self.p1.clip(), self.p2.clip())
def diagonal(self) ‑> float
-
Computes the diagonal length of this rectangle.
Expand source code
def diagonal(self) -> float: """ Computes the diagonal length of this rectangle. """ return self.p1.distance(self.p2)
def iou(self, other: Rectangle) ‑> float
-
Computes the iou (intersection-over-union) of this rectangle with another.
Expand source code
def iou(self, other: Rectangle) -> float: """ Computes the iou (intersection-over-union) of this rectangle with another. """ x1 = max(self.p1.x, other.p1.x) y1 = max(self.p1.y, other.p1.y) x2 = min(self.p2.x, other.p2.x) y2 = min(self.p2.y, other.p2.y) intersection_area = max(x2 - x1, 0) * max(y2 - y1, 0) union_area = self.area() + other.area() - intersection_area return intersection_area / union_area
def normalize(self) ‑> Rectangle
-
Returns a new rectangle that is guaranteed to have
p1
be the top left corner andp2
be the bottom right corner.Expand source code
def normalize(self) -> Rectangle: """ Returns a new rectangle that is guaranteed to have `p1` be the top left corner and `p2` be the bottom right corner. """ return Rectangle(self.p1, self.p2, True)
def scale(self, factor: Union[float, int, Tuple[float, float], Point])
-
Resizes the rectangle according to
factor
. The scaling factor can either be a scalar (int
orfloat
), in which case the rectangle will be scaled by the same factor on both axes, or a point-like (Tuple[float, float]
orPoint
), in which case the rectangle will be scaled independently on each axis.Expand source code
def scale(self, factor: Union[float, int, Tuple[float, float], Point]): """ Resizes the rectangle according to `factor`. The scaling factor can either be a scalar (`int` or `float`), in which case the rectangle will be scaled by the same factor on both axes, or a point-like (`Tuple[float, float]` or `Point`), in which case the rectangle will be scaled independently on each axis. """ return Rectangle(self.p1.scale(factor), self.p2.scale(factor))
def scale_from_center(self, factor: Union[float, int, Tuple[float, float], Point]) ‑> Rectangle
-
Resizes the rectangle according to
factor
, though translates it so that its center does not move. The scaling factor can either be a scalar (int
orfloat
), in which case the rectangle will be scaled by the same factor on both axes, or a point-like (Tuple[float, float]
orPoint
), in which case the rectangle will be scaled independently on each axis.Expand source code
def scale_from_center(self, factor: Union[float, int, Tuple[float, float], Point]) -> Rectangle: """ Resizes the rectangle according to `factor`, though translates it so that its center does not move. The scaling factor can either be a scalar (`int` or `float`), in which case the rectangle will be scaled by the same factor on both axes, or a point-like (`Tuple[float, float]` or `Point`), in which case the rectangle will be scaled independently on each axis. """ center = self.center() return Rectangle( (self.p1 - center).scale(factor) + center, (self.p2 - center).scale(factor) + center )
def to_json(self) ‑> Tuple[Tuple[float, float], Tuple[float, float]]
-
Serializes this object as a
RectangleJson
.Expand source code
def to_json(self) -> RectangleJson: """ Serializes this object as a `RectangleJson`. """ return (self.p1.to_json(), self.p2.to_json())
def to_shapely(self) ‑> shapely.geometry.polygon.Polygon
-
Converts this rectangle into a
Shapely.Polygon
.Expand source code
def to_shapely(self) -> ShapelyPolygon: """ Converts this rectangle into a `Shapely.Polygon`. """ return box(self.p1.x, self.p1.y, self.p2.x, self.p2.y)
def to_xywh_tuple(self) ‑> Tuple[float, float, float, float]
-
Converts this rectangle into a tuple of
(x_coordinate, y_coordinate, width, height)
.Expand source code
def to_xywh_tuple(self) -> Tuple[float, float, float, float]: """ Converts this rectangle into a tuple of `(x_coordinate, y_coordinate, width, height)`. """ w = self.p2.x - self.p1.x h = self.p2.y - self.p1.y return (self.p1.x, self.p1.y, w, h)
def to_xyxy_tuple(self) ‑> Tuple[float, float, float, float]
-
Converts this rectangle into a tuple of
(x_min, y_min, x_max, y_max)
.Expand source code
def to_xyxy_tuple(self) -> Tuple[float, float, float, float]: """ Converts this rectangle into a tuple of `(x_min, y_min, x_max, y_max)`. """ return (self.p1.x, self.p1.y, self.p2.x, self.p2.y)