# 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 a `RectangleJson`.

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 and `p2` 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` 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.

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` 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.

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)``````