bbox.py 2.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. from __future__ import annotations
  2. import typing as T
  3. class BoundingBox(T.NamedTuple):
  4. x: int = 0
  5. y: int = 0
  6. w: int = 0
  7. h: int = 0
  8. @property
  9. def x0(self) -> int:
  10. return self.x
  11. @property
  12. def x1(self) -> int:
  13. return self.x + self.w
  14. @property
  15. def y0(self) -> int:
  16. return self.y
  17. @property
  18. def y1(self) -> int:
  19. return self.y + self.h
  20. @property
  21. def center(self) -> T.Tuple[int, int]:
  22. return self.x + self.w // 2, self.y + self.h // 2
  23. def squared(self, *, smaller_size: bool = False) -> BoundingBox:
  24. cx, cy = self.center
  25. func = min if smaller_size else max
  26. size = func(self.w, self.h)
  27. x0 = max(cx - size // 2, 0)
  28. y0 = max(cy - size // 2, 0)
  29. return BoundingBox(x0, y0, size, size)
  30. @classmethod
  31. def new(cls, *, resize=None, **kwargs) -> BoundingBox:
  32. # parameters that should be passed once
  33. once = [
  34. ("x", "x0"),
  35. ("y", "y0"),
  36. ("w", "x1"),
  37. ("h", "y1"),
  38. ]
  39. for p1, p2 in once:
  40. assert (p1 in kwargs and p2 not in kwargs) \
  41. or (p2 in kwargs and p1 not in kwargs), \
  42. f"please pass only one of these arguments: {p1}, {p2}"
  43. x = kwargs.get("x", kwargs["x0"])
  44. y = kwargs.get("y", kwargs["y0"])
  45. if "x1" in kwargs:
  46. w = kwargs["x1"] - x
  47. if "y1" in kwargs:
  48. h = kwargs["y1"] - y
  49. coords = x, y, w, h
  50. coords = cls.resize(*coords, resize=resize)
  51. coords = map(int, coords)
  52. return cls(*coords)
  53. @classmethod
  54. def resize(cls, *coords, resize=None):
  55. if resize is None or not isinstance(resize, (tuple, int, float)):
  56. return coords
  57. # check if the coordinates are relative
  58. _check = lambda value: 0 <= value < 1
  59. if not all(map(_check, coords)):
  60. return coords
  61. if isinstance(resize, tuple):
  62. W, H = resize
  63. else: # int
  64. W = H = resize
  65. x, y, w, h = coords
  66. return x*W, y*H, w*W, h*H
  67. def get(self, *attrs) -> T.Tuple:
  68. return tuple(getattr(self, attr) for attr in attrs)
  69. def __repr__(self) -> str:
  70. return f"<BoundingBox [0x{id(self):x}]: (({self.x0}, {self.y0}), ({self.x1}, {self.y1})) [{self.w}x{self.h} px]>"
  71. if __name__ == '__main__':
  72. print(BoundingBox.new(x0=0., x1=0.999, y0=0., y1=0.999))
  73. print(BoundingBox.new(x0=1, x1=200, y0=10, y1=90))
  74. print(BoundingBox.new(x0=.1, x1=.5, y0=0, y1=.3, resize=1000))
  75. print(BoundingBox.new(x0=.25, x1=.5, y0=0, y1=.1, resize=(160, 90)))
  76. print(BoundingBox.new(x0=.25, x1=.5, y0=0, y1=.1, resize=(1600, 900)))