Last active
August 22, 2024 13:51
-
-
Save p-i-/f236f662adf4b6a22aba5ac7df365af8 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
from collections.abc import Iterable | |
from math import ceil, floor | |
# from numbers import Number | |
from Coord import Coord | |
VERBOSE = False | |
def pr(*args, **kwargs): | |
if VERBOSE: | |
print(*args, **kwargs) | |
class Rect: | |
def __init__(self, *args, **kwargs): | |
self._t = self._b = self._l = self._r = 0 | |
if args: | |
if len(args) == 1: | |
a = args[0] | |
if isinstance(a, Rect): | |
self.tl_br = a.tl_br | |
else: | |
# This will handle a numeric type or an iterable | |
# (like Coord or [3,2] or np.array([3,2])) | |
self.tl = self.br = Coord(args[0]) | |
elif len(args) == 2: | |
# We need to be careful whether we're dealing with (t, l) or (tl, br) | |
a, b = args | |
if isinstance(a, Iterable) or isinstance(b, Iterable): | |
self.tl, self.br = a, b | |
else: | |
raise Exception("What are you trying to do?") | |
elif len(args) == 4: | |
self.tlbr = args | |
else: | |
assert False | |
for k, v in kwargs.items(): | |
setattr(self, k, v) | |
def __getattr__(self, k): | |
# https://stackoverflow.com/questions/78899267/ | |
if k.startswith('_'): | |
return | |
is_property = isinstance(getattr(self.__class__, k, None), property) | |
if is_property: | |
return object.__getattribute__(self, k) | |
if '_' in k: | |
return [getattr(self, u) for u in k.split('_')] | |
if len(k) > 1: | |
is_lower = k.islower() | |
k = k.lower() | |
L = [getattr(self, u) for u in k] | |
return Coord(L) if len(L) == 2 and is_lower else L | |
assert False | |
def __setattr__(self, k, v): | |
if k.startswith('_'): | |
object.__setattr__(self, k, v) | |
return | |
is_property = isinstance(getattr(self.__class__, k, None), property) | |
if is_property: | |
object.__setattr__(self, k, v) | |
return | |
# We need to check whether the attribute actually exists for the object, e.g. 'center' DOES exist as a property | |
# This should only execute if there's NO corresponding attribute | |
if len(k) == 1: | |
object.__setattr__(self, k, v) | |
else: | |
if '_' in k: | |
k = k.split('_') | |
if not isinstance(v, Iterable): | |
v = [v] * len(k) | |
for k_, v_ in zip(k, v): | |
pr('setting:', k_, v_) | |
setattr(self, k_, v_) | |
def _make_property(attr): | |
return property( | |
lambda self: getattr(self, f'_{attr}'), | |
lambda self, v: setattr(self, f'_{attr}', v) | |
) | |
# getter, setter) | |
t = _make_property('t') | |
b = _make_property('b') | |
l = _make_property('l') | |
r = _make_property('r') | |
@property | |
def h(self): | |
return self.b - self.t + 1 | |
@h.setter | |
def h(self, v): | |
self.b = self.t + v - 1 | |
@property | |
def w(self): | |
return self.r - self.l + 1 | |
@w.setter | |
def w(self, v): | |
self.r = self.l + v - 1 | |
# Note `R.center.y += 1` will fail. That's as it should be. | |
@property | |
def center(self): | |
return (self.tl + self.br) / 2 | |
@center.setter | |
def center(self, v): | |
self += -self.center + Coord(v) | |
# Allow: newR = R(t=0, b=1) | |
def __call__(self, **D): | |
R = Rect(self.t, self.b, self.l, self.r) | |
R.set(**D) | |
return R | |
# Allow R.set(t=0) | |
def set(self, **D): | |
for k, v in D.items(): | |
setattr(self, k, v) | |
return self | |
@property | |
def copy(self): | |
t, l, b, r = self.tlbr | |
return Rect(t, l, b, r) | |
# Allow: newR = R.new(t=0, b=1) | |
def spawn(self, **D): | |
return self.copy.set(**D) | |
@property | |
def int(self): | |
return Rect([int(u) for u in self.tlbr]) | |
def __repr__(self): | |
return f'{self.tl:~>2} to {self.br:~>2}' | |
def _slice(self, fix_right_margin=False): | |
return tuple( | |
slice( | |
ceil(p), | |
None if fix_right_margin and q == 0 else floor(q) + 1 | |
) | |
for (p, q) in self.tb_lr | |
) | |
@property | |
def slice(self): | |
return self._slice() | |
@property | |
def mslice(self): | |
return self._slice(fix_right_margin=True) | |
def __contains__(self, p): | |
return self.tl <= Coord(p) <= self.br | |
def __iadd__(self, u): | |
u = Coord(u) | |
self.tl += u | |
self.br += u | |
return self | |
def __add__(self, u): | |
u = Coord(u) | |
return Rect(tl=self.tl + u, br=self.br + u) | |
def __sub__(self, u): | |
u = Coord(u) | |
return Rect(tl=self.tl - Coord(u), br=self.br - u) | |
def __and__(self, u): | |
if not isinstance(u, Rect): | |
u = Rect(u) | |
t, l, b, r = self.tlbr | |
T, L, B, R = u.tlbr | |
R = Rect(t=max(t, T), l=max(l, L), b=min(b, B), r=min(r, R)) | |
return None if R.r < R.l or R.b < R.t else R | |
def __or__(self, u): | |
if not isinstance(u, Rect): | |
u = Rect(u) | |
t, l, b, r = self.tlbr | |
T, L, B, R = u.tlbr | |
return Rect(t=min(t, T), l=min(l, L), b=max(b, B), r=max(r, R)) | |
def __eq__(self, u): | |
if not isinstance(u, Rect): | |
u = Rect(u) | |
return all(p == q for p, q in zip(self, u)) | |
# def __mul__(self, k): | |
# return Rect(k * self._) | |
def extend(self, in_place=False, *args, **kwargs): | |
M = Rect(*args, **kwargs) | |
if in_place: | |
self.tl -= M.tl | |
self.br += M.br | |
return self | |
return Rect(tl=self.tl - M.tl, br=self.br - M.br) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment