import abc
import logging
import typing
from typing import Optional, Set, List, Union
from arthropod_describer.common.common import Info
from arthropod_describer.common.label_image import RegionProperty, LabelImg
from arthropod_describer.common.photo import Photo
from arthropod_describer.common.regions_cache import RegionsCache
from arthropod_describer.common.state import State
from arthropod_describer.common.tool import Tool
from arthropod_describer.common.units import Unit, BaseUnit, SIPrefix
from arthropod_describer.common.user_params import UserParam
from arthropod_describer.common.utils import get_dict_from_doc_str
logging.basicConfig(filename='arthropod_logger.log', level=logging.INFO, format="%(asctime)s %(filename)-30s %(levelname)-8s %(message)s")
logger = logging.getLogger("plugin.py")
[docs]class RegionComputation:
def __init__(self, info: Optional[Info] = None):
self.info = Info.load_from_doc_str(self.__doc__) if info is None else info
self._region_restricted = "REGION_RESTRICTED" in self.__doc__
self._user_params = UserParam.load_params_from_doc_str(self.__doc__)
@abc.abstractmethod
def __call__(self, photo: Photo, labels: Optional[Set[int]] = None, storage=None) -> List[LabelImg]:
pass
@property
def user_params(self) -> List[UserParam]:
return list(self._user_params.values())
@property
def region_restricted(self) -> bool:
return self._region_restricted
[docs]class PropertyComputation:
def __init__(self, info: Optional[Info] = None):
doc_dict = get_dict_from_doc_str(self.__doc__)
self.info = Info.load_from_dict(doc_dict) if info is None else info
self._user_params = UserParam.load_params_from_doc_str(self.__doc__)
self._region_restricted = self.__doc__ is not None and "REGION_RESTRICTED" in self.__doc__
self._group = doc_dict['GROUP'] if 'GROUP' in doc_dict else 'General'
self._px_unit: Unit = Unit(BaseUnit.px, prefix=SIPrefix.none, dim=1)
self._no_unit: Unit = Unit(BaseUnit.none, prefix=SIPrefix.none, dim=0)
@abc.abstractmethod
def __call__(self, photo: Photo, region_labels: typing.List[int], regions_cache: RegionsCache) -> typing.List[RegionProperty]:
pass
@property
def user_params(self) -> List[UserParam]:
return list(self._user_params.values())
@property
def region_restricted(self) -> bool:
return self._region_restricted
@property
@abc.abstractmethod
def computes(self) -> typing.Dict[str, Info]:
pass
[docs] @abc.abstractmethod
def example(self, prop_name: str) -> RegionProperty:
pass
[docs] def target_worksheet(self, prop_name: str) -> str:
return 'common'
@property
def group(self) -> str:
return self._group
[docs]class GeneralAction:
def __init__(self, info: Optional[Info] = None):
doc_dict = get_dict_from_doc_str(self.__doc__)
self.info = Info.load_from_dict(doc_dict) if info is None else info
self._user_params = UserParam.load_params_from_doc_str(self.__doc__)
self._group = doc_dict['GROUP'] if 'GROUP' in doc_dict else 'General'
@abc.abstractmethod
def __call__(self, state: State) -> None:
pass
@property
def user_params(self) -> List[UserParam]:
return list(self._user_params.values())
@property
def group(self) -> str:
return self._group
[docs]class Plugin:
def __init__(self, info: Optional[Info] = None):
self._plugin_id = -1
self.info = Info.load_from_doc_str(self.__doc__) if info is None else info
self._region_computations: List[RegionComputation] = []
self._property_computations: List[PropertyComputation] = []
self._general_actions: List[GeneralAction] = []
@property
def plugin_id(self) -> int:
return self._plugin_id
@property
def region_computations(self) -> Optional[List[RegionComputation]]:
return self._region_computations
@property
def property_computations(self) -> Optional[List[PropertyComputation]]:
return self._property_computations
@property
def general_actions(self) -> Optional[List[GeneralAction]]:
return self._general_actions
@property
def tools(self) -> Optional[List[Tool]]:
return None
def _load_info_from_doc(self) -> Info:
doc_str = self.__doc__
lines = [line for line in doc_str.splitlines() if len(line) > 0]
name = lines[0].split(':')[1].strip()
desc = lines[1].split(':')[1].strip()
return Info(name, desc)
[docs] def register_computation(self, cls):
try:
obj: Union[RegionComputation, PropertyComputation, GeneralAction] = cls()
obj.info.key = cls.__module__
if issubclass(cls, RegionComputation):
self._region_computations.append(obj)
elif issubclass(cls, PropertyComputation):
self._property_computations.append(obj)
else:
self._general_actions.append(obj)
except AttributeError:
logger.error(f'Could not register computation {cls}.')
[docs]def global_computation_key(global_property_key: str) -> str:
return '.'.join(global_property_key.split('.')[:-1])
[docs]def local_property_key(global_property_key: str) -> str:
return global_property_key.split('.')[-1]