summaryrefslogtreecommitdiff
path: root/megapixels/app/models
diff options
context:
space:
mode:
Diffstat (limited to 'megapixels/app/models')
-rw-r--r--megapixels/app/models/__init__.py0
-rw-r--r--megapixels/app/models/bbox.py236
-rw-r--r--megapixels/app/models/click_factory.py145
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