Coretex
bbox.py
1 # Copyright (C) 2023 Coretex LLC
2 
3 # This file is part of Coretex.ai
4 
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU Affero General Public License as
7 # published by the Free Software Foundation, either version 3 of the
8 # License, or (at your option) any later version.
9 
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU Affero General Public License for more details.
14 
15 # You should have received a copy of the GNU Affero General Public License
16 # along with this program. If not, see <https://www.gnu.org/licenses/>.
17 
18 from typing import List, Dict, Optional, Tuple, Union
19 from typing_extensions import Self
20 
21 from ....codable import Codable, KeyDescriptor
22 
23 
24 class BBox(Codable):
25 
26  """
27  Bounding Box as a python class with utility methods
28 
29  Properties
30  ----------
31  minX : int
32  top left x coordinate
33  minY : int
34  top left y coordinate
35  width : int
36  width of the bounding box
37  height : int
38  height of the bounding box
39  """
40 
41  def __init__(self, minX: int = 0, minY: int = 0, width: int = 0, height: int = 0) -> None:
42  self.minXminX = minX
43  self.minYminY = minY
44 
45  self.widthwidth = width
46  self.heightheight = height
47 
48  @property
49  def maxX(self) -> int:
50  """
51  Returns
52  -------
53  int -> bottom right x coordinate
54  """
55 
56  return self.minXminX + self.widthwidth
57 
58  @property
59  def maxY(self) -> int:
60  """
61  Returns
62  -------
63  int -> bottom right y coordinate
64  """
65 
66  return self.minYminY + self.heightheight
67 
68  @property
69  def polygon(self) -> List[int]:
70  """
71  Returns
72  -------
73  List[int] -> Bounding box represented as a polygon (x, y) values
74  """
75 
76  return [
77  self.minXminX, self.minYminY, # top left
78  self.maxXmaxX, self.minYminY, # top right
79  self.maxXmaxX, self.maxYmaxY, # bottom right
80  self.minXminX, self.maxYmaxY, # bottom left
81  self.minXminX, self.minYminY # top left
82  ]
83 
84  @property
85  def area(self) -> int:
86  return self.widthwidth * self.heightheight
87 
88  @classmethod
89  def _keyDescriptors(cls) -> Dict[str, KeyDescriptor]:
90  descriptors = super()._keyDescriptors()
91 
92  descriptors["minX"] = KeyDescriptor("top_left_x")
93  descriptors["minY"] = KeyDescriptor("top_left_y")
94 
95  return descriptors
96 
97  @classmethod
98  def create(cls, minX: int, minY: int, maxX: int, maxY: int) -> Self:
99  """
100  Utility constructor which has maxX and maxY as parameters instead
101  of width and height
102 
103  Parameters
104  ----------
105  minX : int
106  top left x coordinate
107  minY : int
108  top left y coordinate
109  maxX : int
110  bottom right x coordinate
111  maxY : int
112  bottom right y coordinate
113 
114  Returns
115  -------
116  Self -> bounding box
117  """
118 
119  return cls(minX, minY, maxX - minX, maxY - minY)
120 
121  @classmethod
122  def fromPoly(cls, polygon: List[int]) -> Self:
123  """
124  Creates bounding box from a polygon, by finding
125  the minimum x and y coordinates and calculating
126  width and height of the polygon
127 
128  Parameters
129  ----------
130  polygon : List[int]
131  list of x, y points - length must be even
132 
133  Returns
134  -------
135  Self -> bounding box
136 
137  Example
138  -------
139  >>> from coretex import Bbox
140  \b
141  >>> polygon = [0, 0, 0, 3, 4, 3, 4, 0]
142  >>> bbox = Bbox.fromPoly(polygon)
143  >>> print(f"minX: {bbox.minX}, minY: {bbox.minY}, width: {bbox.width}, height: {bbox.height}")
144  "minX: 0, minY: 0, width: 4, height: 3"
145  """
146 
147  x: List[int] = []
148  y: List[int] = []
149 
150  for index, value in enumerate(polygon):
151  if index % 2 == 0:
152  x.append(value)
153  else:
154  y.append(value)
155 
156  return cls.createcreate(min(x), min(y), max(x), max(y))
157 
158  def iou(self, other: 'BBox') -> float:
159  """
160  Calculate Intersection over Union (IoU) between two bounding boxes
161 
162  Parameters
163  ----------
164  other : BBox
165  bounding box for which the IoU will be calculated
166 
167  Returns
168  -------
169  float -> IoU score
170  """
171 
172  x1 = max(self.minXminX, other.minX)
173  y1 = max(self.minYminY, other.minY)
174  x2 = min(self.maxXmaxX, other.maxX)
175  y2 = min(self.maxYmaxY, other.maxY)
176 
177  intersectionArea = max(0, x2 - x1) * max(0, y2 - y1)
178 
179  unionArea = self.areaarea + other.area - intersectionArea
180  return intersectionArea / unionArea if unionArea > 0 else 0.0
181 
182  def inflate(self, percentage: int, imageSize: Optional[Tuple[Union[int, float], Union[int, float]]] = None) -> None:
183  """
184  Increases the size of the bounding box by a percentage
185 
186  Parameters
187  ----------
188  percentage : int
189  the percentage by which the bounding box will be inflated
190  imageSize : Optional[Tuple[int, int]]
191  bounding box will not be able to go beyond these dimensions (width, height)
192  """
193 
194  if imageSize is None:
195  imageSize = (float("inf"), float("inf"))
196 
197  imageWidth, imageHeight = imageSize
198 
199  inflateFactor = percentage / 100.0
200  inflateWidth = self.widthwidth * inflateFactor / 2
201  inflateHeight = self.heightheight * inflateFactor / 2
202 
203  self.minXminX = int(max(0, self.minXminX - inflateWidth))
204  self.minYminY = int(max(0, self.minYminY - inflateHeight))
205  self.widthwidth = int(min(imageWidth, self.widthwidth + inflateWidth * 2))
206  self.heightheight = int(min(imageHeight, self.heightheight + inflateHeight * 2))
Self fromPoly(cls, List[int] polygon)
Definition: bbox.py:122
None inflate(self, int percentage, Optional[Tuple[Union[int, float], Union[int, float]]] imageSize=None)
Definition: bbox.py:182
Self create(cls, int minX, int minY, int maxX, int maxY)
Definition: bbox.py:98
float iou(self, 'BBox' other)
Definition: bbox.py:158