Source code for EasyVision.base
# -*- coding: utf-8 -*-
"""Base module containing Base and helper classes for all of EasyVision algorithms.
"""
from abc import ABCMeta, abstractmethod, abstractproperty
from datetime import datetime
try:
from functools import lru_cache
except ImportError:
import functools
[docs] class lru_cache(object):
"""Simple lru_cache analog for python 2.7 as python 3.x has it builtin"""
def __init__(self, *args, **kwargs):
self._cache = {}
def __call__(self, func):
@functools.wraps(func)
def wrapper(*args, **kwargs):
arg = "".join(str(i) for i in args)
kwarg = "".join("{}={}".format(k, v) for k, v in kwargs.items())
key = (arg, kwarg)
if key in self._cache:
return self._cache[key]
result = func(*args, **kwargs)
self._cache[key] = result
return result
return wrapper
[docs]class NamedTupleExtendHelper(object):
"""NamedTupleExtendedHelper is a helper Mixin style class that enables to extend namedtuple derived classes with fields
Implements:
__repr__
Builds a string representation of the object
_make
Make a new object from a sequence or iterable
_replace
Return a new object replacing specified fields with new values
example
.. code-block:: python
MyBase = namedtuple('MyBase', 'a b')
class MyNamedTuple(MyBase):
_fields = MyBase._fields + ('c')
__slots__ = ()
def __new__(cls, *args, **kwargs):
return super(MyNamedTuple, self).__new__(*args, **kwargs)
c = property(itemgetter(2), doc="Alias for field number 3")
"""
def __repr__(self):
return "{}({})".format(self.__class__.__name__, ", ".join("%s=%r" % field for field in zip(self._fields, self)))
@classmethod
def _make(cls, iterable, new=None, len=len):
'Make a new object from a sequence or iterable'
result = cls.__new__(cls, *iterable)
if len(result) != len(cls._fields):
raise TypeError('Expected %d arguments, got %d' % (len(cls._fields), len(result)))
return result
def _replace(self, **kwds):
'Return a new object replacing specified fields with new values'
result = self._make(map(kwds.pop, self._fields, self))
if kwds:
raise ValueError('Got unexpected field names: %r' % kwds.keys())
return result
# compatible with Python 2 *and* 3:
ABC = ABCMeta('ABC', (object,), {'__slots__': ()})
[docs]class FPSCounter(object):
"""Simple FPS Counter descriptor.
setting a datetime object or None to this descriptor will update fps.
getting this descriptor will return calculated fps.
deleting this descriptor will reset the counter
"""
def __init__(self, num_frames=30):
self._frames = 0
self._num_frames = num_frames
self.fps = 0
self._last_now = datetime.now()
[docs] def update(self, now):
if now is None:
now = datetime.now()
assert(isinstance(now, datetime))
if self._frames % self._num_frames == 0:
if self._frames != 0:
self.fps = self._num_frames / (now - self._last_now).total_seconds()
self._last_now = now
self._frames += 1
[docs]class EasyVisionBase(ABC):
"""EasyVisionBase is an abstract class for all EasyVision algorithms
Contains simple setup/release, debug/display_results, setup/release and context functionality.
Uses __slots__ to conserve space, so derived classes can choose to continue using __slots__.
Implements these:
__enter__
Implements a context manager
__exit__
Implements a context manager
__next__
Implements an iterator for Python 3.7
__iter__
Implements an iterator
__len__
Allows to receive a preliminary number of frames available
__getattr__
Catches {ClassName}_fps and returns current fps
update_fps
Updates fps counter
name
Returns Derived Class name
Abstract methods:
next
For Python 2.7 compatibility. Implements iterator.
description
Must return a brief description of the algorithm.
setup
Must allocate resources.
release
Must deallocate resources.
Optional methods:
debug_changed
Being called when debug property value is changed
display_results_changed
Being called when display_results property value is changed
"""
__slots__ = ('_debug', '_display_results', '__setup_called', '__fps_counter')
def __init__(self, debug=False, display_results=False, fps_num_frames=30, *args, **kwargs):
"""Initializes the instance. super must be called after all the derived class
initialization is complete.
:param debug: Indicates whether this algorithm should output debug info
:param display_results: Indicates whether this algorithm should output results
"""
self._debug = False
self._display_results = False
self.__setup_called = False
self.debug = debug
self.display_results = display_results
self.__fps_counter = FPSCounter(fps_num_frames)
super(EasyVisionBase, self).__init__()
def __enter__(self):
"""Makes derived class into a context manager. Will call setup()."""
self.setup()
return self
def __exit__(self, type, value, traceback):
"""Makes derived class into a context manager. Will call release()."""
self.release()
def __next__(self):
"""For Python 3.x support"""
return self.next()
def __iter__(self):
"""Makes derived class into iterable."""
return self
def __getattr__(self, name):
if name == "%s_fps" % self.__class__.__name__:
return self.__fps_counter.fps
else:
raise AttributeError("Unsupported dynamic attribute: %s" % name)
[docs] def update_fps(self, now=None):
"""Updates FPS counter
:param now: optional datetime object. If set to None will use datetime.now() instead.
:return: None
"""
self.__fps_counter.update(now)
@abstractmethod
def __len__(self):
"""Abstract method. Used for len(MyAlgorithm). Should return the number of images/frames/computations available.
Will return negative numbers or zero for freerun capturing devices such as video camera.
"""
pass
[docs] @abstractmethod
def next(self):
"""Abstract method. Legacy method for Python 2.7"""
assert(self.__setup_called)
[docs] @abstractmethod
def setup(self):
"""Setup method that should be called before any call to __next__/capture/compute.
This method should be used for resource allocation, algorithm initialization, opening of devices, etc.
All derived classes must implement this method and call it using super after all the initialization is complete.
This is being handled if using the algorithm with "with" statement.
"""
assert(not self.__setup_called)
self.__setup_called = True
self.__fps_counter.reset()
[docs] @abstractmethod
def release(self):
"""Release method that should be called in order to release allocated resources.
This method should be used for resource deallocation and cleaning up.
All derived classes must implement this method and call it using super after all the deallocation is complete.
This is being handled if using the algorithm with "with" statement.
"""
assert(self.__setup_called)
self.__setup_called = False
@property
def name(self):
"""Returns the name of the class."""
return self.__class__.__name__
@abstractproperty
def description(self):
"""Abstract property. Should return a brief description of the algorithm."""
pass
@property
def debug(self):
"""Property specifying whether to perform some outpuf for debugging purposes"""
return self._debug
@property
def display_results(self):
"""Property specifying whether to display intermediate results"""
return self._display_results
@debug.setter
def debug(self, value):
"""This property will call debug_changed if it finds one in the derived class."""
lastdebug, self._debug = self._debug, value
if lastdebug != value and hasattr(self, 'debug_changed'):
self.debug_changed(lastdebug, value)
@display_results.setter
def display_results(self, value):
"""This property will call display_results_changed if it finds one in the derived class."""
lastdisplay, self._display_results = self._display_results, value
if lastdisplay != value and hasattr(self, 'display_results_changed'):
self.display_results_changed(lastdisplay, value)