Module datatap.geometry

This module provides geometric primitives for storing or manipulating ML annotations.

Generally speaking, a geometric object is considered "valid" in the droplet format when it lies entirely within the unit plane. This is because annotations in the droplet format are scaled to 0-to-1 along both axes so that they are resolution-independent. This can be checked by invoking assert_valid on any of the geometric objects (though this is done automatically when geometric constructs are used to create droplets).

Expand source code
"""
This module provides geometric primitives for storing or manipulating ML annotations.

Generally speaking, a geometric object is considered "valid" in the droplet format when it lies entirely within the unit
plane. This is because annotations in the droplet format are scaled to 0-to-1 along both axes so that they are
resolution-independent. This can be checked by invoking `assert_valid` on any of the geometric objects (though this is
done automatically when geometric constructs are used to create droplets).
"""

from .mask import Mask, MaskJson
from .point import Point, PointJson
from .polygon import Polygon, PolygonJson
from .rectangle import Rectangle, RectangleJson

__all__ = [
        "Mask",
        "MaskJson",
        "Point",
        "PointJson",
        "Polygon",
        "PolygonJson",
        "Rectangle",
        "RectangleJson"
]

Sub-modules

datatap.geometry.mask
datatap.geometry.point
datatap.geometry.polygon
datatap.geometry.rectangle

Classes

class Mask (polygons: Sequence[Polygon])

The shape resulting from XORing a set of polygons in 2D space.

Generally, the expectation is that the polygons have no edge itersections; specifically, that for any pair of polygons in the mask, either they have no intersection or one completely contains the other. However, there is no assertion that this is the case, and generally speaking, the even-odd rule is used to determine if a particular point is contained by the mask.

Expand source code
class Mask:
        """
        The shape resulting from XORing a set of polygons in 2D space.

        Generally, the expectation is that the polygons have no edge itersections; specifically, that for any pair of
        polygons in the mask, either they have no intersection or one completely contains the other.  However, there is no
        assertion that this is the case, and generally speaking, the even-odd rule is used to determine if a particular
        point is contained by the mask.
        """

        polygons: Sequence[Polygon]
        """
        The constituent polygons of this `Mask`.
        """

        @staticmethod
        def from_json(json: MaskJson) -> Mask:
                """
                Creates a `Mask` from a `MaskJson`.
                """
                return Mask([Polygon.from_json(poly) for poly in json])

        def __init__(self, polygons: Sequence[Polygon]):
                self.polygons = polygons

                if len(self.polygons) < 1:
                        raise ValueError(f"A mask must have at least one polygon; failed on mask {repr(self)}")

        def scale(self, factor: Union[float, int, Tuple[float, float], Point]) -> Mask:
                """
                Resizes the mask according to `factor`. The scaling factor can either be
                a scalar (`int` or `float`), in which case the mask will be scaled by
                the same factor on both axes, or a point-like (`Tuple[float, float]`
                or `Point`), in which case the mask will be scaled independently on each
                axis.
                """
                return Mask([p.scale(factor) for p in self.polygons])

        def to_json(self) -> MaskJson:
                """
                Serializes this object as a `MaskJson`.
                """
                return [polygon.to_json() for polygon in self.polygons]

        def assert_valid(self) -> None:
                """
                Asserts that this mask is valid on the unit plane.
                """
                for polygon in self.polygons:
                        polygon.assert_valid()
                # TODO(mdsavage): check for invalid polygon intersections?

        def __repr__(self) -> str:
                return basic_repr("Mask", self.polygons)

        def __eq__(self, other: Mask) -> bool:
                # TODO(mdsavage): currently, this requires the polygons to be in the same order, not just represent the same mask
                if not isinstance(other, Mask): # type: ignore - pyright complains about the isinstance check being redundant
                        return NotImplemented
                return self.polygons == other.polygons

        def __iter__(self) -> Generator[Polygon, None, None]:
                yield from self.polygons

Class variables

var polygons : Sequence[Polygon]

The constituent polygons of this Mask.

Static methods

def from_json(json: MaskJson) ‑> Mask

Creates a Mask from a MaskJson.

Expand source code
@staticmethod
def from_json(json: MaskJson) -> Mask:
        """
        Creates a `Mask` from a `MaskJson`.
        """
        return Mask([Polygon.from_json(poly) for poly in json])

Methods

def assert_valid(self) ‑> None

Asserts that this mask is valid on the unit plane.

Expand source code
def assert_valid(self) -> None:
        """
        Asserts that this mask is valid on the unit plane.
        """
        for polygon in self.polygons:
                polygon.assert_valid()
        # TODO(mdsavage): check for invalid polygon intersections?
def scale(self, factor: Union[float, int, Tuple[float, float], Point]) ‑> Mask

Resizes the mask according to factor. The scaling factor can either be a scalar (int or float), in which case the mask will be scaled by the same factor on both axes, or a point-like (Tuple[float, float] or Point), in which case the mask will be scaled independently on each axis.

Expand source code
def scale(self, factor: Union[float, int, Tuple[float, float], Point]) -> Mask:
        """
        Resizes the mask according to `factor`. The scaling factor can either be
        a scalar (`int` or `float`), in which case the mask will be scaled by
        the same factor on both axes, or a point-like (`Tuple[float, float]`
        or `Point`), in which case the mask will be scaled independently on each
        axis.
        """
        return Mask([p.scale(factor) for p in self.polygons])
def to_json(self) ‑> Sequence[Sequence[Tuple[float, float]]]

Serializes this object as a MaskJson.

Expand source code
def to_json(self) -> MaskJson:
        """
        Serializes this object as a `MaskJson`.
        """
        return [polygon.to_json() for polygon in self.polygons]
class Point (x: float, y: float, clip: bool = False)

A point in 2D space. Also often used to represent a 2D vector.

Expand source code
class Point:
        """
        A point in 2D space.  Also often used to represent a 2D vector.
        """

        x: float
        """
        The x-coordinate of the point.
        """

        y: float
        """
        The y-coordinate of the point.
        """

        @staticmethod
        def from_json(json: PointJson) -> Point:
                """
                Creates a `Point` from a `PointJson`.
                """
                return Point(json[0], json[1])

        def __init__(self, x: float, y: float, clip: bool = False):
                self.x = min(max(x, 0), 1) if clip else x
                self.y = min(max(y, 0), 1) if clip else y

        def to_json(self) -> PointJson:
                """
                Serializes this object as a `PointJson`.
                """
                return (self.x, self.y)

        def distance(self, other: Point) -> float:
                """
                Computes the scalar distance to another point.
                """
                return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5

        def assert_valid(self) -> None:
                """
                Asserts that this polygon is valid on the unit plane.
                """
                assert 0 <= self.x <= 1 and 0 <= self.y <= 1, f"Point coordinates must be between 0 and 1; failed on point {repr(self)}"

        def clip(self) -> Point:
                """
                Clips both coordinates of this point to the range [0, 1].
                """
                return Point(self.x, self.y, clip = True)

        def scale(self, factor: Union[float, int, Tuple[float, float], Point]) -> Point:
                """
                Resizes the point according to `factor`. The scaling factor can either
                be a scalar (`int` or `float`), in which case the point will be scaled
                by the same factor on both axes, or a point-like (`Tuple[float, float]`
                or `Point`), in which case the point will be scaled independently on
                each axis.
                """
                if isinstance(factor, (float, int)):
                        return self * factor
                if isinstance(factor, tuple):
                        return Point(self.x * factor[0], self.y * factor[1])
                return Point(self.x * factor.x, self.x * factor.y)

        def __add__(self, o: Point) -> Point:
                if isinstance(o, Point): # type: ignore - pyright complains about the isinstance check being redundant
                        return Point(self.x + o.x, self.y + o.y)
                return NotImplemented

        def __sub__(self, o: Point) -> Point:
                if isinstance(o, Point): # type: ignore - pyright complains about the isinstance check being redundant
                        return Point(self.x - o.x, self.y - o.y)
                return NotImplemented

        def __mul__(self, o: Union[int, float]) -> Point:
                if isinstance(o, (int, float)): # type: ignore - pyright complains about the isinstance check being redundant
                        return Point(self.x * o, self.y * o)
                return NotImplemented

        def __truediv__(self, o: Union[int, float]) -> Point:
                if isinstance(o, (int, float)): # type: ignore - pyright complains about the isinstance check being redundant
                        return Point(self.x / o, self.y / o)
                return NotImplemented

        def __repr__(self) -> str:
                return basic_repr("Point", self.x, self.y)

        def __hash__(self) -> int:
                return hash((self.x, self.y))

        def __eq__(self, other: Point) -> bool:
                if isinstance(other, Point): # type: ignore - pyright complains about the isinstance check being redundant
                        return self.x == other.x and self.y == other.y
                return NotImplemented

Class variables

var x : float

The x-coordinate of the point.

var y : float

The y-coordinate of the point.

Static methods

def from_json(json: PointJson) ‑> Point

Creates a Point from a PointJson.

Expand source code
@staticmethod
def from_json(json: PointJson) -> Point:
        """
        Creates a `Point` from a `PointJson`.
        """
        return Point(json[0], json[1])

Methods

def assert_valid(self) ‑> None

Asserts that this polygon is valid on the unit plane.

Expand source code
def assert_valid(self) -> None:
        """
        Asserts that this polygon is valid on the unit plane.
        """
        assert 0 <= self.x <= 1 and 0 <= self.y <= 1, f"Point coordinates must be between 0 and 1; failed on point {repr(self)}"
def clip(self) ‑> Point

Clips both coordinates of this point to the range [0, 1].

Expand source code
def clip(self) -> Point:
        """
        Clips both coordinates of this point to the range [0, 1].
        """
        return Point(self.x, self.y, clip = True)
def distance(self, other: Point) ‑> float

Computes the scalar distance to another point.

Expand source code
def distance(self, other: Point) -> float:
        """
        Computes the scalar distance to another point.
        """
        return ((self.x - other.x) ** 2 + (self.y - other.y) ** 2) ** 0.5
def scale(self, factor: Union[float, int, Tuple[float, float], Point]) ‑> Point

Resizes the point according to factor. The scaling factor can either be a scalar (int or float), in which case the point will be scaled by the same factor on both axes, or a point-like (Tuple[float, float] or Point), in which case the point will be scaled independently on each axis.

Expand source code
def scale(self, factor: Union[float, int, Tuple[float, float], Point]) -> Point:
        """
        Resizes the point according to `factor`. The scaling factor can either
        be a scalar (`int` or `float`), in which case the point will be scaled
        by the same factor on both axes, or a point-like (`Tuple[float, float]`
        or `Point`), in which case the point will be scaled independently on
        each axis.
        """
        if isinstance(factor, (float, int)):
                return self * factor
        if isinstance(factor, tuple):
                return Point(self.x * factor[0], self.y * factor[1])
        return Point(self.x * factor.x, self.x * factor.y)
def to_json(self) ‑> Tuple[float, float]

Serializes this object as a PointJson.

Expand source code
def to_json(self) -> PointJson:
        """
        Serializes this object as a `PointJson`.
        """
        return (self.x, self.y)
class Polygon (points: Sequence[Point])

A polygon in 2D space.

Expand source code
class Polygon:
        """
        A polygon in 2D space.
        """

        points: Sequence[Point]
        """
        The vertices of this polygon.
        """

        @staticmethod
        def from_json(json: PolygonJson) -> Polygon:
                """
                Creates a `Polygon` from a `PolygonJson`.
                """
                return Polygon([Point.from_json(pt) for pt in json])

        def __init__(self, points: Sequence[Point]):
                self.points = points

                if len(self.points) < 3:
                        raise ValueError(f"A polygon must have at least three points; failed on polygon {repr(self)}")

        def scale(self, factor: Union[float, int, Tuple[float, float], Point]) -> Polygon:
                """
                Resizes the polygon according to `factor`. The scaling factor can either
                be a scalar (`int` or `float`), in which case the polygon will be scaled
                by the same factor on both axes, or a point-like (`Tuple[float, float]`
                or `Point`), in which case the polygon will be scaled independently on
                each axis.
                """
                return Polygon([p.scale(factor) for p in self.points])

        def to_json(self) -> PolygonJson:
                """
                Serializes this object as a `PolygonJson`.
                """
                return [point.to_json() for point in self.points]

        def assert_valid(self) -> None:
                """
                Ensures that this polygon is valid on the unit plane.
                """
                for point in self.points:
                        point.assert_valid()
                # TODO(mdsavage): check for self-intersection?

        def __repr__(self) -> str:
                return basic_repr("Polygon", self.points)

        def __eq__(self, other: Any) -> bool:
                # TODO(mdsavage): currently, this requires the points to be in the same order, not just represent the same polygon
                if not isinstance(other, Polygon):
                        return NotImplemented
                return self.points == other.points

        def __mul__(self, o: Union[int, float]) -> Polygon:
                if not isinstance(o, (int, float)): # type: ignore - pyright complains about the isinstance check being redundant
                        return NotImplemented
                return Polygon([p * o for p in self.points])

        def __iter__(self) -> Generator[Point, None, None]:
                yield from self.points

Class variables

var points : Sequence[Point]

The vertices of this polygon.

Static methods

def from_json(json: PolygonJson) ‑> Polygon

Creates a Polygon from a PolygonJson.

Expand source code
@staticmethod
def from_json(json: PolygonJson) -> Polygon:
        """
        Creates a `Polygon` from a `PolygonJson`.
        """
        return Polygon([Point.from_json(pt) for pt in json])

Methods

def assert_valid(self) ‑> None

Ensures that this polygon is valid on the unit plane.

Expand source code
def assert_valid(self) -> None:
        """
        Ensures that this polygon is valid on the unit plane.
        """
        for point in self.points:
                point.assert_valid()
        # TODO(mdsavage): check for self-intersection?
def scale(self, factor: Union[float, int, Tuple[float, float], Point]) ‑> Polygon

Resizes the polygon according to factor. The scaling factor can either be a scalar (int or float), in which case the polygon will be scaled by the same factor on both axes, or a point-like (Tuple[float, float] or Point), in which case the polygon will be scaled independently on each axis.

Expand source code
def scale(self, factor: Union[float, int, Tuple[float, float], Point]) -> Polygon:
        """
        Resizes the polygon according to `factor`. The scaling factor can either
        be a scalar (`int` or `float`), in which case the polygon will be scaled
        by the same factor on both axes, or a point-like (`Tuple[float, float]`
        or `Point`), in which case the polygon will be scaled independently on
        each axis.
        """
        return Polygon([p.scale(factor) for p in self.points])
def to_json(self) ‑> Sequence[Tuple[float, float]]

Serializes this object as a PolygonJson.

Expand source code
def to_json(self) -> PolygonJson:
        """
        Serializes this object as a `PolygonJson`.
        """
        return [point.to_json() for point in self.points]
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 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.p1 + self.p2) / 2
                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: Rectangle) -> bool:
                if not isinstance(other, Rectangle): # type: ignore - pyright complains about the isinstance check being redundant
                        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 p1Point

The top-left corner of the rectangle.

var p2Point

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 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.p1 + self.p2) / 2
        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)