diff options
Diffstat (limited to 'megapixels/app/models')
| -rw-r--r-- | megapixels/app/models/__init__.py | 0 | ||||
| -rw-r--r-- | megapixels/app/models/bbox.py | 236 | ||||
| -rw-r--r-- | megapixels/app/models/click_factory.py | 145 |
3 files changed, 381 insertions, 0 deletions
diff --git a/megapixels/app/models/__init__.py b/megapixels/app/models/__init__.py new file mode 100644 index 00000000..e69de29b --- /dev/null +++ b/megapixels/app/models/__init__.py diff --git a/megapixels/app/models/bbox.py b/megapixels/app/models/bbox.py new file mode 100644 index 00000000..41b67416 --- /dev/null +++ b/megapixels/app/models/bbox.py @@ -0,0 +1,236 @@ +from dlib import rectangle as dlib_rectangle +import numpy as np + +class BBoxPoint: + + def __init__(self, x, y): + self._x = x + self._y = y + + @property + def x(self): + return self._x + + @property + def y(self): + return self._y + + def offset(self, x, y): + return (self._x + x, self._y + y) + + def tuple(self): + return (self._x, self._y) + + +class BBox: + + def __init__(self, x1, y1, x2, y2): + """Represents a bounding box and provides methods for accessing and modifying + :param x1: normalized left coord + :param y1: normalized top coord + :param x2: normalized right coord + :param y2: normalized bottom coord + """ + self._x1 = x1 + self._y1 = y1 + self._x2 = x2 + self._y2 = y2 + self._width = x2 - x1 + self._height = y2 - y1 + self._cx = x1 + (self._width // 2) + self._cy = y1 + (self._height // 2) + self._tl = (x1, y1) + self._br = (x2, y2) + self._rect = (self._x1, self._y1, self._x2, self._y2) + + + @property + def pt_tl(self): + return self._tl + + @property + def pt_br(self): + return self._br + + @property + def x(self): + return self._x1 + + @property + def y(self): + return self._y1 + + @property + def x1(self): + return self._x1 + + @property + def y1(self): + return self._y1 + + + @property + def x2(self): + return self._x2 + + @property + def y2(self): + return self._y2 + + @property + def height(self): + return self._height + + @property + def width(self): + return self._width + + @property + def h(self): + return self._height + + @property + def w(self): + return self._width + + @property + def cx(self): + return self._cx + + @property + def cy(self): + return self._cy + + # # ----------------------------------------------------------------- + # # Utils + + # def constrain(self, dim): + + + # ----------------------------------------------------------------- + # Modify + + def expand_dim(self, amt, dim): + """Expands BBox within dim + :param box: (tuple) left, top, right, bottom + :param dim: (tuple) width, height + :returns (BBox) in pixel dimensions + """ + # expand + rect_exp = list( (np.array(self._rect) + np.array([-amt, -amt, amt, amt])).astype('int')) + # outliers + oob = list(range(4)) + oob[0] = min(rect_exp[0], 0) + oob[1] = min(rect_exp[1], 0) + oob[2] = dim[0] - max(rect_exp[2], 2) + oob[3] = dim[1] - max(rect_exp[3], 3) + oob = np.array(oob) + oob[oob > 0] = 0 + # amount + oob = np.absolute(oob) + # threshold + rect_exp[0] = max(rect_exp[0], 0) + rect_exp[1] = max(rect_exp[1], 0) + rect_exp[2] = min(rect_exp[2], dim[0]) + rect_exp[3] = min(rect_exp[3], dim[1]) + # redistribute oob amounts + oob = np.array([-oob[2], -oob[3], oob[0], oob[1]]) + rect_exp = np.add(np.array(rect_exp), oob) + return BBox(*rect_exp) + + + # ----------------------------------------------------------------- + # Convert to + + def to_dim(self, dim): + """scale is (w, h) is tuple of dimensions""" + w, h = dim + rect = list((np.array(self._rect) * np.array([w, h, w, h])).astype('int')) + return BBox(*rect) + + def normalize(self, rect, dim): + w, h = dim + x1, y1, x2, y2 = rect + return (x1 / w, y1 / h, x2 / w, y2 / h) + + # ----------------------------------------------------------------- + # Format as + + def as_xyxy(self): + """Converts BBox back to x1, y1, x2, y2 rect""" + return (self._x1, self._y1, self._x2, self._y2) + + def as_xywh(self): + """Converts BBox back to haar type""" + return (self._x1, self._y1, self._width, self._height) + + def as_trbl(self): + """Converts BBox to CSS (top, right, bottom, left)""" + return (self._y1, self._x2, self._y2, self._x1) + + def as_dlib(self): + """Converts BBox to dlib rect type""" + return dlib.rectangle(self._x1, self._y1, self._x2, self._y2) + + def as_yolo(self): + """Converts BBox to normalized center x, center y, w, h""" + return (self._cx, self._cy, self._width, self._height) + + + # ----------------------------------------------------------------- + # Create from + + @classmethod + def from_xyxy_dim(cls, x1, y1, x2, y2, dim): + """Converts x1, y1, w, h to BBox and normalizes + :returns BBox + """ + rect = cls.normalize(cls, (x1, y1, x2, y2), dim) + return cls(*rect) + + @classmethod + def from_xywh_dim(cls, x, y, w, h, dim): + """Converts x1, y1, w, h to BBox and normalizes + :param rect: (list) x1, y1, w, h + :param dim: (list) w, h + :returns BBox + """ + rect = cls.normalize(cls, (x, y, x + w, y + h), dim) + return cls(*rect) + + @classmethod + def from_xywh(cls, x, y, w, h): + """Converts x1, y1, w, h to BBox + :param rect: (list) x1, y1, w, h + :param dim: (list) w, h + :returns BBox + """ + return cls(x, y, x+w, y+h) + + @classmethod + def from_css(cls, rect, dim): + """Converts rect from CSS (top, right, bottom, left) to BBox + :param rect: (list) x1, y1, x2, y2 + :param dim: (list) w, h + :returns BBox + """ + rect = (rect[3], rect[0], rect[1], rect[2]) + rect = cls.normalize(cls, rect, dim) + return cls(*rect) + + @classmethod + def from_dlib_dim(cls, rect, dim): + """Converts dlib.rectangle to BBox + :param rect: (list) x1, y1, x2, y2 + :param dim: (list) w, h + :returns dlib.rectangle + """ + rect = (rect.left(), rect.top(), rect.right(), rect.bottom()) + rect = cls.normalize(cls, rect, dim) + return cls(*rect) + + + def str(self): + """Return BBox as a string "x1, y1, x2, y2" """ + return self.as_box() + diff --git a/megapixels/app/models/click_factory.py b/megapixels/app/models/click_factory.py new file mode 100644 index 00000000..61a3b5e5 --- /dev/null +++ b/megapixels/app/models/click_factory.py @@ -0,0 +1,145 @@ +""" +Click processor factory +- Inspired by and used code from @wiretapped's HTSLAM codebase +- In particular the very useful +""" + +import os +import sys +from os.path import join +from pathlib import Path +import os +from os.path import join +import sys +from functools import update_wrapper, wraps +import itertools +from pathlib import Path +from glob import glob +import importlib +import logging + +import click +from app.settings import app_cfg as cfg + + +# -------------------------------------------------------- +# Click Group Class +# -------------------------------------------------------- + +# set global variable during parent class create +dir_plugins = None # set in create + +class ClickComplex: + """Wrapper generator for custom Click CLI's based on LR's coroutine""" + + def __init__(self): + pass + + + class CustomGroup(click.Group): + #global dir_plugins # from CliGenerator init + + # lists commands in plugin directory + def list_commands(self, ctx): + global dir_plugins # from CliGenerator init + rv = list(self.commands.keys()) + fp_cmds = [Path(x) for x in Path(dir_plugins).iterdir() \ + if str(x).endswith('.py') \ + and '__init__' not in str(x)] + for fp_cmd in fp_cmds: + try: + assert fp_cmd.name not in rv, "[-] Error: {} can't exist in cli.py and {}".format(fp_cmd.name) + except Exception as ex: + logging.getLogger('app').error('{}'.format(ex)) + rv.append(fp_cmd.stem) + rv.sort() + return rv + + # Complex version: gets commands in directory and in this file + # Based on code from @wiretapped + HTSLAM + def get_command(self, ctx, cmd_name): + global dir_plugins + if cmd_name in self.commands: + return self.commands[cmd_name] + ns = {} + fpp_cmd = Path(dir_plugins, cmd_name + '.py') + fp_cmd = fpp_cmd.as_posix() + if not fpp_cmd.exists(): + sys.exit('[-] {} file does not exist'.format(fpp_cmd)) + code = compile(fpp_cmd.read_bytes(), fp_cmd, 'exec') + try: + eval(code, ns, ns) + except Exception as ex: + logging.getLogger('vframe').error('exception: {}'.format(ex)) + @click.command() + def _fail(): + raise Exception('while loading {}'.format(fpp_cmd.name)) + _fail.short_help = repr(ex) + _fail.help = repr(ex) + return _fail + if 'cli' not in ns: + sys.exit('[-] Error: {} does not contain a cli function'.format(fp_cmd)) + return ns['cli'] + + @classmethod + def create(self, dir_plugins_local): + global dir_plugins + dir_plugins = dir_plugins_local + return self.CustomGroup + + + +class ClickSimple: + """Wrapper generator for custom Click CLI's""" + + def __init__(self): + pass + + + class CustomGroup(click.Group): + #global dir_plugins # from CliGenerator init + + # lists commands in plugin directory + def list_commands(self, ctx): + global dir_plugins # from CliGenerator init + rv = list(self.commands.keys()) + fp_cmds = [Path(x) for x in Path(dir_plugins).iterdir() \ + if str(x).endswith('.py') \ + and '__init__' not in str(x)] + for fp_cmd in fp_cmds: + assert fp_cmd.name not in rv, "[-] Error: {} can't exist in cli.py and {}".format(fp_cmd.name) + rv.append(fp_cmd.stem) + rv.sort() + return rv + + # Complex version: gets commands in directory and in this file + # from HTSLAM + def get_command(self, ctx, cmd_name): + global dir_plugins # from CliGenerator init + if cmd_name in self.commands: + return self.commands[cmd_name] + ns = {} + fpp_cmd = Path(dir_plugins, cmd_name + '.py') + fp_cmd = fpp_cmd.as_posix() + if not fpp_cmd.exists(): + sys.exit('[-] {} file does not exist'.format(fpp_cmd)) + code = compile(fpp_cmd.read_bytes(), fp_cmd, 'exec') + try: + eval(code, ns, ns) + except Exception as ex: + logging.getLogger('vframe').error('exception: {}'.format(ex)) + @click.command() + def _fail(): + raise Exception('while loading {}'.format(fpp_cmd.name)) + _fail.short_help = repr(ex) + _fail.help = repr(ex) + return _fail + if 'cli' not in ns: + sys.exit('[-] Error: {} does not contain a cli function'.format(fp_cmd)) + return ns['cli'] + + @classmethod + def create(self, dir_plugins_local): + global dir_plugins + dir_plugins = dir_plugins_local + return self.CustomGroup |
