Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactoring bounding_box.py #42

Open
wants to merge 3 commits into
base: develop
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
267 changes: 42 additions & 225 deletions vision/common/bounding_box.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@
are used to convey information between flight and vision processes.
"""

from enum import Enum
from typing import Any, TypeAlias
from typing import TypeAlias

import numpy as np

Expand Down Expand Up @@ -36,6 +35,7 @@ def tlwh_to_vertices(tl_x: int, tl_y: int, width: int, height: int) -> Vertices:
Each coordinate consists of a tuple 2 integers.
Order is (top-left, top-right, bottom-right, bottom-left).
"""

tl_coord: tuple[int, int] = (tl_x, tl_y) # top left
tr_coord: tuple[int, int] = (tl_x + width, tl_y) # top right
br_coord: tuple[int, int] = (tl_x + width, tl_y + height) # bottom right
Expand All @@ -44,157 +44,29 @@ def tlwh_to_vertices(tl_x: int, tl_y: int, width: int, height: int) -> Vertices:
return (tl_coord, tr_coord, br_coord, bl_coord)


class ObjectType(Enum):
"""
Type of object that a BoundingBox represents.
"""

STD_OBJECT: str = "std_object"
EMG_OBJECT: str = "emg_object"
TEXT: str = "text"


class BoundingBox:
"""
A set of 4 coordinates that distinguish a region of an image.
The order of the coordinates is (top-left, top-right, bottom-right, bottom-left).
The top-left coordinate, width, and height are used to create the
BoundingBox.

Parameters
----------
vertices : Vertices
The main structure of the BoundingBox. Denotes the 4 coordinates
representing a box in an image. Vertices is a tuple of 4 coordinates. Each
coordinate consists of a tuple 2 integers.
obj_type : ObjectType
Enumeration that denotes what type of object the BoundingBox represents.
attributes : dict[str, Any] | None
Any additional attributes to convey about the object in the BoundingBox.
top_left : tuple[int, int]
The top-left coordinate of the BoundingBox.
width : int
The width of the BoundingBox.
height : int
The height of the BoundingBox.
"""

def __init__(
self,
vertices: Vertices,
obj_type: ObjectType,
attributes: dict[str, Any] | None = None,
) -> None:
self._vertices: tuple[
tuple[int, int], tuple[int, int], tuple[int, int], tuple[int, int]
] = vertices
self._obj_type: ObjectType = obj_type
self._attributes: dict[str, Any] = attributes if attributes is not None else {}

def __repr__(self) -> str:
"""
Returns a string representation of the BoundingBox
that contains its id, object type, and vertices.

Returns
-------
str
the string representation of the BoundingBox object
"""
return f"BoundingBox[{id(self)}, {self.obj_type}]: {str(self._vertices)}"

@property
def vertices(self) -> Vertices:
"""
Getter for _vertices. Gets the 4 vertices that make up the BoundingBox.

Returns
-------
_vertices : Vertices
The 4 coordinates of the BoundingBox.
"""
return self._vertices

@vertices.setter
def vertices(self, verts: Vertices) -> None:
"""
Setter for _vertices. Sets the 4 vertices that make up the BoundingBox.

Parameters
----------
vert : Vertices
The 4 coordinates to assign to the BoundingBox.
"""
self._vertices = verts

@property
def obj_type(self) -> ObjectType:
"""
Getter for _obj_type. Gets the ObjectType of the BoundingBox.

Returns
-------
_obj_type : ObjectType
The ObjectType of the BoundingBox.
"""
return self._obj_type

@obj_type.setter
def obj_type(self, o_type: ObjectType) -> None:
"""
Setter for _obj_type. Sets the value of the BoundingBox's ObjectType.

Parameters
----------
o_type : ObjectType
The ObjectType to assign to the BoundingBox.
"""
self._obj_type = o_type

@property
def attributes(self) -> dict[str, Any]:
"""
Getter for _attributes. Gets the additional attributes of the BoundingBox.

Returns
-------
_attributes : dict[str, Any]
Any additional attributes of the BoundingBox.
"""
return self._attributes

@attributes.setter
def attributes(self, att: dict[str, Any]) -> None:
"""
Setter for _attributes. Sets the value of the BoundingBox's additional attributes.

Parameters
----------
att : dict[str, Any]
The additional attributes to assign to the BoundingBox.
"""
self._attributes = att

def set_attribute(self, attribute_name: str, attribute: Any) -> None:
"""
Sets an attribute of the BoundingBox.

Parameters
----------
attribute_name : str
the name of the attribute
attribute : Any
the value to set the attribute to, which can be of any type
"""
self.attributes[attribute_name] = attribute
def __init__(self, top_left: tuple[int, int], width: int, height: int) -> None:
self.top_left: tuple[int, int] = top_left
self.width: int = width
self.height: int = height

def get_attribute(self, attribute_name: str) -> Any:
"""
Gets an attribute of the BoundingBox.

Parameters
----------
attribute_name : str
the name of the attribute

Returns
-------
attribute : Any
the value of the attribute, which can be of any type
"""
return self.attributes[attribute_name]
# Calculate the 4 vertices of the bounding box
self.vertices: Vertices = tlwh_to_vertices(top_left[0], top_left[1], width, height)

def get_x_vals(self) -> list[int]:
"""
Expand All @@ -205,7 +77,8 @@ def get_x_vals(self) -> list[int]:
x_vals : list[int]
The 4 x values of the vertices.
"""
x_vals: list[int] = [vert[0] for vert in self._vertices]

x_vals: list[int] = [vert[0] for vert in self.vertices]
return x_vals

def get_y_vals(self) -> list[int]:
Expand All @@ -217,7 +90,8 @@ def get_y_vals(self) -> list[int]:
y_vals : list[int]
The 4 y values of the vertices.
"""
y_vals: list[int] = [vert[1] for vert in self._vertices]

y_vals: list[int] = [vert[1] for vert in self.vertices]
return y_vals

def get_x_extremes(self) -> tuple[int, int]:
Expand All @@ -229,6 +103,7 @@ def get_x_extremes(self) -> tuple[int, int]:
min_x, max_x : tuple[int, int]
The minimum and maximum x values.
"""

x_vals: list[int] = self.get_x_vals()
min_x: int = np.amin(x_vals)
max_x: int = np.amax(x_vals)
Expand All @@ -244,6 +119,7 @@ def get_y_extremes(self) -> tuple[int, int]:
min_y, max_y : tuple[int, int]
The minimum and maximum y values.
"""

y_vals: list[int] = self.get_y_vals()
min_y: int = np.amin(y_vals)
max_y: int = np.amax(y_vals)
Expand All @@ -259,6 +135,7 @@ def get_x_avg(self) -> int:
average : int
the average of the 4 coordinates' x-values
"""

return int(np.mean(self.get_x_vals()))

def get_y_avg(self) -> int:
Expand All @@ -270,6 +147,7 @@ def get_y_avg(self) -> int:
average : int
the average of the 4 coordinates' y-values
"""

return int(np.mean(self.get_y_vals()))

def get_center_coord(self) -> tuple[int, int]:
Expand All @@ -281,30 +159,8 @@ def get_center_coord(self) -> tuple[int, int]:
center_pt : tuple[int, int]
the coordinate point at the center of the bounding box
"""
return (self.get_x_avg(), self.get_y_avg())

def get_rotation_angle(self) -> float:
"""
Calculates the angle of rotation of the BoundingBox
based on the top left and right coordinates.

Returns
-------
angle : float
The angle of rotation of the BoundingBox in degrees.
"""
tl_x: int = self.vertices[0][0]
tr_x: int = self.vertices[1][0]
tl_y: int = self.vertices[0][1]
tr_y: int = self.vertices[1][1]

angle: float = 0
if tr_x - tl_x == 0: # prevent division by 0
angle = 90.0 if (tr_y - tl_y > 0) else -90.0
else:
angle = np.rad2deg(np.arctan((tr_y - tl_y) / (tr_x - tl_x)))

return angle
return (self.get_x_avg(), self.get_y_avg())

def get_width(self) -> int:
"""
Expand All @@ -315,6 +171,9 @@ def get_width(self) -> int:
width: int
the width of the BoundingBox based on max and min x values.
"""

# Get the min and max x values, then calculate the width by subtracting
# the min from the max
min_x: int
max_x: int
min_x, max_x = self.get_x_extremes()
Expand All @@ -331,6 +190,9 @@ def get_height(self) -> int:
height: int
the height of the BoundingBox based on max and min x values.
"""

# Get the min and max y values, then calculate the height by
# subtracting the min from the max
min_y: int
max_y: int
min_y, max_y = self.get_y_extremes()
Expand All @@ -347,68 +209,24 @@ def get_width_height(self) -> tuple[int, int]:
(width, height) : tuple[int, int]
the width and height of the bounding box
"""
return self.get_width(), self.get_height()

def get_tlwh(self) -> tuple[int, int, int, int]:
"""
Gets the BoundingBox formatted with top left coordinate, width, and height.

Returns
-------
tlwh_coord : tuple[int, int, int, int]
the bounding box in top left, width, height format

tl_x : int
the top-left x coordinate of the bounding box
tl_y : int
the top-left y coordinate of the bounding box
width : int
the width of the bounding box
height : int
the height of the bounding box
"""
tl_x: int = self.vertices[0][0]
tl_y: int = self.vertices[0][1]
width: int = self.get_width()
height: int = self.get_height()

return tl_x, tl_y, width, height
return self.get_width(), self.get_height()


# Driver for testing functionality of BoundingBox object
if __name__ == "__main__":
coordinates: Vertices = (
(0, 0),
(10, 0),
(10, 10),
(0, 10),
)
object_type: ObjectType = ObjectType.STD_OBJECT
object_attributes: dict[str, Any] = {"shape": "triangle", "latitude": 89.9}

# constructor
object_bounds = BoundingBox(
vertices=coordinates, obj_type=object_type, attributes=object_attributes
)

# repr
print(object_bounds)

# vertices
print("Vertices:", object_bounds.vertices)

# object type
print("Object Type:", object_bounds.obj_type)
test_top_left: tuple[int, int] = (0, 0)
test_width: int = 39
test_height: int = 50

# various ways to interact with attributes
print("Attributes:", object_bounds.attributes)
print("Shape Attribute:", object_bounds.attributes["shape"])

object_bounds.set_attribute("longitude", 120.3)
print("Longitude Attribute:", object_bounds.get_attribute("longitude"))
# constructor
object_bounds = BoundingBox(top_left=test_top_left, width=test_width, height=test_height)

object_bounds.attributes["altitude"] = 50
print("Altitude Attribute:", object_bounds.attributes["altitude"])
# width, height
print("Width:", object_bounds.get_width())
print("Height:", object_bounds.get_height())
print("Width and Height:", object_bounds.get_width_height())

# values, extremes, average
print()
Expand All @@ -419,7 +237,6 @@ def get_tlwh(self) -> tuple[int, int, int, int]:
print("X average:", object_bounds.get_x_avg())
print("Y average:", object_bounds.get_y_avg())

# center and rotation
# center
print()
print("Center coordinate:", object_bounds.get_center_coord())
print("Rotation angle:", object_bounds.get_rotation_angle())