From 27df43128171542ecb1461ef10562df84c42fd58 Mon Sep 17 00:00:00 2001 From: Andrey Frolov Date: Thu, 17 Dec 2020 12:28:48 +0500 Subject: [PATCH] EFRS-885 Import improvements --- embedding-calculator/gpu.Dockerfile | 2 +- embedding-calculator/src/_endpoints.py | 18 ++-- embedding-calculator/src/constants.py | 2 - .../facescan/plugins/{core.py => base.py} | 18 ++-- .../facescan/plugins/facenet/__init__.py | 2 +- .../facescan/plugins/facenet/facenet.py | 12 +-- .../src/services/facescan/plugins/helpers.py | 72 --------------- .../facescan/plugins/insightface/__init__.py | 22 ++++- .../plugins/insightface/insightface.py | 51 ++++------- .../src/services/facescan/plugins/managers.py | 90 +++++++++++++++++++ .../facescan/plugins/rude_carnie/__init__.py | 2 +- .../plugins/rude_carnie/rude_carnie.py | 27 +++--- .../src/services/facescan/plugins/setup.py | 12 +-- .../services/facescan/scanner/facescanner.py | 19 ++-- 14 files changed, 176 insertions(+), 173 deletions(-) rename embedding-calculator/src/services/facescan/plugins/{core.py => base.py} (93%) delete mode 100644 embedding-calculator/src/services/facescan/plugins/helpers.py create mode 100644 embedding-calculator/src/services/facescan/plugins/managers.py diff --git a/embedding-calculator/gpu.Dockerfile b/embedding-calculator/gpu.Dockerfile index 53fdcb606e..20e349532e 100644 --- a/embedding-calculator/gpu.Dockerfile +++ b/embedding-calculator/gpu.Dockerfile @@ -67,7 +67,7 @@ RUN ln -s $(which $PYTHON) /usr/local/bin/python # Variables for MXNET -ENV MXNET=mxnet_cu101mkl MXNET_CPU_WORKER_NTHREADS=24 +ENV MXNET_CPU_WORKER_NTHREADS=24 ENV MXNET_ENGINE_TYPE=ThreadedEnginePerDevice MXNET_CUDNN_AUTOTUNE_DEFAULT=0 # No access to GPU devices in the build stage, so skip tests diff --git a/embedding-calculator/src/_endpoints.py b/embedding-calculator/src/_endpoints.py index 1f28fa7d08..d7f5f62d92 100644 --- a/embedding-calculator/src/_endpoints.py +++ b/embedding-calculator/src/_endpoints.py @@ -19,18 +19,20 @@ from src.constants import ENV from src.exceptions import NoFaceFoundError -from src.services.facescan.plugins import helpers +from src.services.facescan.plugins import managers from src.services.facescan.scanner.facescanners import scanner from src.services.flask_.constants import ARG from src.services.flask_.needs_attached_file import needs_attached_file from src.services.imgtools.read_img import read_img +from src.services.utils.pyutils import Constants def endpoints(app): @app.route('/status') def status_get(): - availiable_plugins = {p.type: str(p) for p in helpers.get_face_plugins()} - calculator = helpers.get_calculator() + availiable_plugins = {p.slug: str(p) + for p in managers.plugin_manager.plugins} + calculator = managers.plugin_manager.calculator return jsonify(status='OK', build_version=ENV.BUILD_VERSION, calculator_version=str(calculator), availiable_plugins=availiable_plugins) @@ -38,14 +40,16 @@ def status_get(): @app.route('/find_faces', methods=['POST']) @needs_attached_file def find_faces_post(): - detector = helpers.get_detector() - face_plugins = helpers.get_face_plugins(_get_face_plugin_names()) + detector = managers.plugin_manager.detector + face_plugins = managers.plugin_manager.filter_face_plugins( + _get_face_plugin_names() + ) faces = detector( img=read_img(request.files['file']), det_prob_threshold=_get_det_prob_threshold(), face_plugins=face_plugins ) - plugins_versions = {p.type: str(p) for p in [detector] + face_plugins} + plugins_versions = {p.slug: str(p) for p in [detector] + face_plugins} return jsonify(results=faces, plugins_versions=plugins_versions) @app.route('/scan_faces', methods=['POST']) @@ -73,7 +77,7 @@ def _get_face_plugin_names() -> Optional[List[str]]: if ARG.FACE_PLUGINS not in request.values: return return [ - name for name in filter(None, request.values[ARG.FACE_PLUGINS].split(',')) + name for name in Constants.split(request.values[ARG.FACE_PLUGINS]) ] diff --git a/embedding-calculator/src/constants.py b/embedding-calculator/src/constants.py index 6d341d9785..531c9f02e3 100644 --- a/embedding-calculator/src/constants.py +++ b/embedding-calculator/src/constants.py @@ -32,8 +32,6 @@ class ENV(Constants): BUILD_VERSION = get_env('APP_VERSION_STRING', 'dev') GPU_IDX = int(get_env('GPU_IDX', '-1')) - CUDA = get_env('CUDA', '') - INTEL_OPTIMIZATION = get_env('INTEL_OPTIMIZATION', '') LOGGING_LEVEL = logging._nameToLevel[ENV.LOGGING_LEVEL_NAME] diff --git a/embedding-calculator/src/services/facescan/plugins/core.py b/embedding-calculator/src/services/facescan/plugins/base.py similarity index 93% rename from embedding-calculator/src/services/facescan/plugins/core.py rename to embedding-calculator/src/services/facescan/plugins/base.py index 18ed2b888a..63756f8a36 100644 --- a/embedding-calculator/src/services/facescan/plugins/core.py +++ b/embedding-calculator/src/services/facescan/plugins/base.py @@ -50,7 +50,7 @@ def __str__(self): @property def path(self): - return Path(MODELS_ROOT) / self.plugin.backend / self.plugin.type / self.name + return Path(MODELS_ROOT) / self.plugin.backend / self.plugin.slug / self.name def exists(self): return os.path.exists(self.path) @@ -83,9 +83,9 @@ def _extract(self, filename: str): class BasePlugin(ABC): - dependencies: Tuple[str, ...] = () ml_models: Tuple[Tuple[str, str], ...] = () ml_model: Optional[MLModel] = None + plugins_registry = [] def __new__(cls, ml_model_name: str = None): """ @@ -94,13 +94,14 @@ def __new__(cls, ml_model_name: str = None): """ if not hasattr(cls, 'instance'): cls.instance = super(BasePlugin, cls).__new__(cls) + cls.plugins_registry.append(cls.instance) if cls.instance.ml_models: cls.instance.ml_model = MLModel(cls.instance, ml_model_name) return cls.instance @property @abstractmethod - def type(self): + def slug(self): pass @property @@ -123,7 +124,7 @@ def __call__(self, face_img: Array3D) -> plugin_result.PluginResultDTO: class BaseFaceDetector(BasePlugin): - type = 'detector' + slug = 'detector' IMAGE_SIZE: int face_plugins: List[BasePlugin] = [] @@ -141,7 +142,7 @@ def _fetch_faces(self, img: Array3D, det_prob_threshold: float = None): return [ plugin_result.FaceDTO( img=img, face_img=self.crop_face(img, box), box=box, - execution_time={self.type: (time() - start) / len(boxes)} + execution_time={self.slug: (time() - start) / len(boxes)} ) for box in boxes ] @@ -150,11 +151,12 @@ def _apply_face_plugins(self, face: plugin_result.FaceDTO, for plugin in face_plugins: start = time() try: - face._plugins_dto.append(plugin(face._face_img)) + result_dto = plugin(face._face_img) + face._plugins_dto.append(result_dto) except Exception as e: raise exceptions.PluginError(f'{plugin} error - {e}') else: - face.execution_time[plugin.type] = time() - start + face.execution_time[plugin.slug] = time() - start @abstractmethod def find_faces(self, img: Array3D, det_prob_threshold: float = None) -> List[BoundingBoxDTO]: @@ -168,7 +170,7 @@ def crop_face(self, img: Array3D, box: BoundingBoxDTO) -> Array3D: class BaseCalculator(BasePlugin): - type = 'calculator' + slug = 'calculator' DIFFERENCE_THRESHOLD: float diff --git a/embedding-calculator/src/services/facescan/plugins/facenet/__init__.py b/embedding-calculator/src/services/facescan/plugins/facenet/__init__.py index 1a8a92766c..b73206a064 100644 --- a/embedding-calculator/src/services/facescan/plugins/facenet/__init__.py +++ b/embedding-calculator/src/services/facescan/plugins/facenet/__init__.py @@ -12,4 +12,4 @@ # or implied. See the License for the specific language governing # permissions and limitations under the License. -from .facenet import FaceDetector, Calculator +requirements = ('tensorflow~=1.15.4', 'facenet~=1.0.5') diff --git a/embedding-calculator/src/services/facescan/plugins/facenet/facenet.py b/embedding-calculator/src/services/facescan/plugins/facenet/facenet.py index 371aaa91b4..b8d9f66d30 100644 --- a/embedding-calculator/src/services/facescan/plugins/facenet/facenet.py +++ b/embedding-calculator/src/services/facescan/plugins/facenet/facenet.py @@ -27,19 +27,16 @@ from src.services.imgtools.types import Array3D from src.services.utils.pyutils import get_current_dir -from src.services.facescan.plugins import core +from src.services.facescan.plugins import base CURRENT_DIR = get_current_dir(__file__) -DEPENDENCIES = ('tensorflow~=1.15.4', 'facenet~=1.0.5') logger = logging.getLogger(__name__) _EmbeddingCalculator = namedtuple('_EmbeddingCalculator', 'graph sess') _FaceDetectionNets = namedtuple('_FaceDetectionNets', 'pnet rnet onet') -class FaceDetector(core.BaseFaceDetector): - dependencies = DEPENDENCIES - +class FaceDetector(base.BaseFaceDetector): BATCH_SIZE = 25 FACE_MIN_SIZE = 20 SCALE_FACTOR = 0.709 @@ -103,17 +100,14 @@ def find_faces(self, img: Array3D, det_prob_threshold: float = None) -> List[Bou return filtered_bounding_boxes -class Calculator(core.BaseCalculator): - dependencies = DEPENDENCIES +class Calculator(base.BaseCalculator): ml_models = ( # links from https://github.com/davidsandberg/facenet#pre-trained-models # VGGFace2 training set, 0.9965 LFW accuracy ('20180402-114759', 'https://drive.google.com/uc?id=1EXPBSXwTaqrSC0OhUdXNmKSh9qJUQ55-'), - # ('20180402-114759', 'https://file-examples-com.github.io/uploads/2017/02/zip_2MB.zip'), # CASIA-WebFace training set, 0.9905 LFW accuracy ('20180408-102900', 'https://drive.google.com/uc?id=1R77HmFADxe87GmoLwzfgMu_HY0IhcyBz'), ) - BATCH_SIZE = 25 DIFFERENCE_THRESHOLD = 0.2 diff --git a/embedding-calculator/src/services/facescan/plugins/helpers.py b/embedding-calculator/src/services/facescan/plugins/helpers.py deleted file mode 100644 index 58bef6f7bc..0000000000 --- a/embedding-calculator/src/services/facescan/plugins/helpers.py +++ /dev/null @@ -1,72 +0,0 @@ -# Copyright (c) 2020 the original author or authors -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# https://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express -# or implied. See the License for the specific language governing -# permissions and limitations under the License. - -from importlib import import_module -from typing import List, Type, Tuple - -from src import constants -from src.services.facescan.plugins.core import (BasePlugin, BaseFaceDetector, - BaseCalculator) - - -ML_MODEL_SEPARATOR = '@' - - -def import_classes(class_path: str): - module, class_name = class_path.rsplit('.', 1) - return getattr(import_module(module, __package__), class_name) - - -def get_plugin_class_and_model(plugin_name) -> Tuple[Type[BasePlugin], str]: - """ - Init a plugin by it's type. Additionally accepts a model type after '@'. - - >>> get_plugin_class_and_model("facenet.FaceDetector") - (, None) - - >>> get_plugin_class_and_model("facenet.Calculator@20180408-102900") - (, '20180408-102900') - """ - ml_model_name = None - if ML_MODEL_SEPARATOR in plugin_name: - plugin_name, ml_model_name = plugin_name.split(ML_MODEL_SEPARATOR) - - plugin_class = import_classes(f'{__package__}.{plugin_name}') - return plugin_class, ml_model_name - - -def get_plugins(plugins_names: List[str], type_filter: List[str] = None) -> List[BasePlugin]: - plugins = [] - for plugin_name in plugins_names: - plugin_class, model_name = get_plugin_class_and_model(plugin_name) - if type_filter is not None and plugin_class.type not in type_filter: - continue - plugin = plugin_class(model_name) - plugins.append(plugin) - return plugins - - -def get_face_plugins(type_filter: List[str] = None): - return get_plugins( - [constants.ENV.CALCULATION_PLUGIN, *constants.ENV.EXTRA_PLUGINS], - type_filter - ) - - -def get_calculator() -> BaseCalculator: - return get_plugins([constants.ENV.CALCULATION_PLUGIN])[0] - - -def get_detector() -> BaseFaceDetector: - return get_plugins([constants.ENV.FACE_DETECTION_PLUGIN])[0] diff --git a/embedding-calculator/src/services/facescan/plugins/insightface/__init__.py b/embedding-calculator/src/services/facescan/plugins/insightface/__init__.py index c2274397ca..588ffd2ea4 100644 --- a/embedding-calculator/src/services/facescan/plugins/insightface/__init__.py +++ b/embedding-calculator/src/services/facescan/plugins/insightface/__init__.py @@ -12,4 +12,24 @@ # or implied. See the License for the specific language governing # permissions and limitations under the License. -from .insightface import FaceDetector, Calculator, GenderAgeDetector + +from src.constants import ENV +from src.services.utils.pyutils import get_env, get_env_bool + + +def get_requirements(): + cuda_version = get_env('CUDA', '').replace('.', '') + intel_optimization = get_env_bool('INTEL_OPTIMIZATION', False) + + mxnet_lib = 'mxnet-' + if ENV.GPU_IDX > -1 and cuda_version: + mxnet_lib += f"cu{cuda_version}" + if intel_optimization: + mxnet_lib += 'mkl' + mxnet_lib = mxnet_lib.rstrip('-') + return ( + f'{mxnet_lib}<1.7', + 'insightface==0.1.5', + ) + +requirements = get_requirements() diff --git a/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py b/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py index 1c263b924d..defcdf210d 100644 --- a/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py +++ b/embedding-calculator/src/services/facescan/plugins/insightface/insightface.py @@ -19,11 +19,15 @@ import attr import numpy as np from cached_property import cached_property +from insightface.app import FaceAnalysis +from insightface.model_zoo import (model_store, face_detection, + face_recognition, face_genderage) +from insightface.utils import face_align from src.constants import ENV from src.services.dto.bounding_box import BoundingBoxDTO from src.services.facescan.imgscaler.imgscaler import ImgScaler -from src.services.facescan.plugins import core, exceptions +from src.services.facescan.plugins import base, exceptions from src.services.dto import plugin_result from src.services.imgtools.types import Array3D @@ -53,28 +57,22 @@ class InsightFaceMixin: _CTX_ID = ENV.GPU_ID _NMS = 0.4 - @property - def dependencies(self): - mxnet_lib = 'mxnet-' - if ENV.GPU_ID > -1 and ENV.CUDA: - mxnet_lib += f"cu{ENV.CUDA.replace('.', '')}" - if ENV.INTEL_OPTIMIZATION: - mxnet_lib += 'mkl' - mxnet_lib = mxnet_lib.rstrip('-') - return ( - f'{mxnet_lib}<1.7', - 'insightface==0.1.5', - ) - - def get_model_file(self, ml_model: core.MLModel): + def get_model_file(self, ml_model: base.MLModel): if not ml_model.exists(): raise exceptions.ModelImportException( f'Model {ml_model.name} does not exists') - from insightface.model_zoo import model_store return model_store.find_params_file(ml_model.path) -class FaceDetector(InsightFaceMixin, core.BaseFaceDetector): +class DetectionOnlyFaceAnalysis(FaceAnalysis): + rec_model = None + ga_model = None + + def __init__(self, file): + self.det_model = face_detection.FaceDetector(file, 'net3') + + +class FaceDetector(InsightFaceMixin, base.BaseFaceDetector): ml_models = ( ('retinaface_r50_v1', 'http://insightface.ai/files/models/retinaface_r50_v1.zip'), ('retinaface_mnet025_v1', 'http://insightface.ai/files/models/retinaface_mnet025_v1.zip'), @@ -87,16 +85,6 @@ class FaceDetector(InsightFaceMixin, core.BaseFaceDetector): @cached_property def _detection_model(self): - from insightface.app import FaceAnalysis - from insightface.model_zoo.face_detection import FaceDetector - - class DetectionOnlyFaceAnalysis(FaceAnalysis): - rec_model = None - ga_model = None - - def __init__(self, file): - self.det_model = FaceDetector(file, 'net3') - model_file = self.get_model_file(self.ml_model) model = DetectionOnlyFaceAnalysis(model_file) model.prepare(ctx_id=self._CTX_ID, nms=self._NMS) @@ -127,12 +115,11 @@ def find_faces(self, img: Array3D, det_prob_threshold: float = None) -> List[Ins return boxes def crop_face(self, img: Array3D, box: InsightFaceBoundingBox) -> Array3D: - from insightface.utils import face_align return face_align.norm_crop(img, landmark=box.landmark, image_size=self.IMAGE_SIZE) -class Calculator(InsightFaceMixin, core.BaseCalculator): +class Calculator(InsightFaceMixin, base.BaseCalculator): ml_models = ( ('arcface_r100_v1', 'http://insightface.ai/files/models/arcface_r100_v1.zip'), ('arcface_mnet', 'https://drive.google.com/uc?id=1ejWgx_7Nd1PvFXPxCu_1_QNzKfl6v7Hb'), @@ -145,7 +132,6 @@ def calc_embedding(self, face_img: Array3D) -> Array3D: @cached_property def _calculation_model(self): - from insightface.model_zoo import face_recognition model_file = self.get_model_file(self.ml_model) model = face_recognition.FaceRecognition( self.ml_model.name, True, model_file) @@ -159,8 +145,8 @@ class GenderAgeDTO(plugin_result.PluginResultDTO): age: Tuple[int, int] -class GenderAgeDetector(InsightFaceMixin, core.BasePlugin): - type = 'gender_age' +class GenderAgeDetector(InsightFaceMixin, base.BasePlugin): + slug = 'gender_age' ml_models = ( ('genderage_v1', 'http://insightface.ai/files/models/genderage_v1.zip'), ) @@ -173,7 +159,6 @@ def __call__(self, face_img: Array3D): @cached_property def _genderage_model(self): - from insightface.model_zoo import face_genderage model_file = self.get_model_file(self.ml_model) model = face_genderage.FaceGenderage( self.ml_model.name, True, model_file) diff --git a/embedding-calculator/src/services/facescan/plugins/managers.py b/embedding-calculator/src/services/facescan/plugins/managers.py new file mode 100644 index 0000000000..ac996ea976 --- /dev/null +++ b/embedding-calculator/src/services/facescan/plugins/managers.py @@ -0,0 +1,90 @@ +# Copyright (c) 2020 the original author or authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express +# or implied. See the License for the specific language governing +# permissions and limitations under the License. + +from collections import defaultdict +from importlib import import_module +from typing import List, Type, Dict, Tuple +from types import ModuleType +from cached_property import cached_property + +from src import constants +from src.services.facescan.plugins import base + + +ML_MODEL_SEPARATOR = '@' + + +def import_classes(class_path: str): + module, class_name = class_path.rsplit('.', 1) + return getattr(import_module(module, __package__), class_name) + + +class PluginManager: + plugins_modules: Dict[ModuleType, List[str]] + + def __init__(self): + self.plugins_modules = defaultdict(list) + for plugin_name in self.get_plugins_names(): + module = import_module(f'{__package__}.{plugin_name.split(".")[0]}') + self.plugins_modules[module].append(plugin_name) + + @property + def requirements(self): + requirements = set() + for module in self.plugins_modules: + requirements |= set(module.requirements) + return requirements + + def get_plugins_names(self): + return [ + constants.ENV.FACE_DETECTION_PLUGIN, + constants.ENV.CALCULATION_PLUGIN, + *constants.ENV.EXTRA_PLUGINS + ] + + @cached_property + def plugins(self): + plugins = [] + for module, plugins_names in self.plugins_modules.items(): + for pl_name in self.get_plugins_names(): + mlmodel_name = None + if ML_MODEL_SEPARATOR in pl_name: + pl_name, mlmodel_name = pl_name.split(ML_MODEL_SEPARATOR) + pl_path = f'{module.__package__}.{pl_name}' + pl_class = import_classes(pl_path) + plugin = pl_class(ml_model_name=mlmodel_name) + plugins.append(plugin) + return plugins + + @cached_property + def detector(self) -> base.BaseFaceDetector: + return [pl for pl in self.plugins + if isinstance(pl, base.BaseFaceDetector)][0] + + @cached_property + def calculator(self) -> base.BaseCalculator: + return [pl for pl in self.plugins + if isinstance(pl, base.BaseCalculator)][0] + + @cached_property + def face_plugins(self) -> List[base.BasePlugin]: + return [pl for pl in self.plugins + if not isinstance(pl, base.BaseFaceDetector)] + + def filter_face_plugins(self, slugs: List[str]) -> List[base.BasePlugin]: + return [pl for pl in self.face_plugins + if slugs is None or pl.slug in slugs] + + +plugin_manager = PluginManager() diff --git a/embedding-calculator/src/services/facescan/plugins/rude_carnie/__init__.py b/embedding-calculator/src/services/facescan/plugins/rude_carnie/__init__.py index 464ea82bc3..3483f084f5 100644 --- a/embedding-calculator/src/services/facescan/plugins/rude_carnie/__init__.py +++ b/embedding-calculator/src/services/facescan/plugins/rude_carnie/__init__.py @@ -12,4 +12,4 @@ # or implied. See the License for the specific language governing # permissions and limitations under the License. -from .rude_carnie import AgeDetector, GenderDetector +requirements = ('tensorflow~=1.15.4',) diff --git a/embedding-calculator/src/services/facescan/plugins/rude_carnie/rude_carnie.py b/embedding-calculator/src/services/facescan/plugins/rude_carnie/rude_carnie.py index e4e1ec6f7a..5f2f5c1fae 100644 --- a/embedding-calculator/src/services/facescan/plugins/rude_carnie/rude_carnie.py +++ b/embedding-calculator/src/services/facescan/plugins/rude_carnie/rude_carnie.py @@ -16,13 +16,12 @@ from typing import Tuple, Union import numpy as np +import tensorflow as tf from src.services.imgtools.types import Array3D -from src.services.facescan.plugins import core, helpers +from src.services.facescan.plugins import base, managers from src.services.dto import plugin_result - - -DEPENDENCIES = ('tensorflow~=1.15.4',) +from srcext.rude_carnie.model import inception_v3, get_checkpoint def prewhiten(img): @@ -35,11 +34,9 @@ def prewhiten(img): @lru_cache(maxsize=2) -def _get_rude_carnie_model(type: str, labels: Tuple, model_dir: str): - import tensorflow as tf - from srcext.rude_carnie.model import inception_v3, get_checkpoint +def _get_rude_carnie_model(labels: Tuple, model_dir: str): - IMAGE_SIZE = helpers.get_detector().IMAGE_SIZE + IMAGE_SIZE = managers.plugin_manager.detector.IMAGE_SIZE g = tf.Graph() with g.as_default(): @@ -63,29 +60,27 @@ def get_value(img: Array3D) -> Tuple[Union[str, Tuple], float]: return get_value -class AgeDetector(core.BasePlugin): - dependencies = DEPENDENCIES - type = 'age' +class AgeDetector(base.BasePlugin): + slug = 'age' LABELS = ((0, 2), (4, 6), (8, 12), (15, 20), (25, 32), (38, 43), (48, 53), (60, 100)) ml_models = ( ('22801', 'https://drive.google.com/uc?id=1JSggfO1FPu8eM1BeQ6yMG5nKrGbJDyqG'), ) def __call__(self, face_img: Array3D): - model = _get_rude_carnie_model(self.name, self.LABELS, self.ml_model.path) + model = _get_rude_carnie_model(self.LABELS, self.ml_model.path) value, probability = model(face_img) return plugin_result.AgeDTO(age=value, age_probability=probability) -class GenderDetector(core.BasePlugin): - dependencies = DEPENDENCIES - type = 'gender' +class GenderDetector(base.BasePlugin): + slug = 'gender' LABELS = ('male', 'female') ml_models = ( ('21936', 'https://drive.google.com/uc?id=1Gem2hM6bg746pqgTHQCyNv-Egf3CFgYf'), ) def __call__(self, face_img: Array3D): - model = _get_rude_carnie_model(self.name, self.LABELS, self.ml_model.path) + model = _get_rude_carnie_model(self.LABELS, self.ml_model.path) value, probability = model(face_img) return plugin_result.GenderDTO(gender=value, gender_probability=probability) diff --git a/embedding-calculator/src/services/facescan/plugins/setup.py b/embedding-calculator/src/services/facescan/plugins/setup.py index 3733c47385..39591fa559 100644 --- a/embedding-calculator/src/services/facescan/plugins/setup.py +++ b/embedding-calculator/src/services/facescan/plugins/setup.py @@ -1,7 +1,7 @@ import subprocess import sys -from src.services.facescan.plugins import helpers +from src.services.facescan.plugins.managers import plugin_manager def install_requirements(requirements: set): @@ -14,15 +14,9 @@ def install_requirements(requirements: set): if __name__ == '__main__': - plugins = helpers.get_face_plugins() - plugins.append(helpers.get_detector()) + install_requirements(plugin_manager.requirements) - dependencies = set() - for plugin in plugins: - dependencies |= set(plugin.dependencies) - install_requirements(dependencies) - - for plugin in plugins: + for plugin in plugin_manager.plugins: if plugin.ml_model: print(f'Checking models for {plugin}...') plugin.ml_model.download_if_not_exists() diff --git a/embedding-calculator/src/services/facescan/scanner/facescanner.py b/embedding-calculator/src/services/facescan/scanner/facescanner.py index 3cd107d726..2e24ec90f4 100644 --- a/embedding-calculator/src/services/facescan/scanner/facescanner.py +++ b/embedding-calculator/src/services/facescan/scanner/facescanner.py @@ -20,8 +20,7 @@ from src.services.dto.bounding_box import BoundingBoxDTO from src.services.dto.scanned_face import ScannedFace from src.services.imgtools.types import Array3D -from src.services.facescan.plugins import (helpers as plugins_helpers, - core as plugins_core) +from src.services.facescan.plugins.managers import plugin_manager class FaceScanner(ABC): @@ -57,24 +56,18 @@ class ScannerWithPluggins(FaceScanner): Class for backward compatibility. The scanner only performs face detection and embedding calculation. """ - ID = "ScannerWithPluggins" - detector: plugins_core.BaseFaceDetector - calculator: plugins_core.BaseCalculator - - def __init__(self): - super().__init__() - self.detector = plugins_helpers.get_detector() - self.calculator = plugins_helpers.get_calculator() + ID = "ScannerWithPlugins" def scan(self, img: Array3D, det_prob_threshold: float = None): - return self.detector(img, det_prob_threshold, [self.calculator]) + return plugin_manager.detector(img, det_prob_threshold, + [plugin_manager.calculator]) def find_faces(self, img: Array3D, det_prob_threshold: float = None) -> List[BoundingBoxDTO]: - return self.detector.find_faces(img, det_prob_threshold) + return plugin_manager.detector.find_faces(img, det_prob_threshold) @property def difference_threshold(self): - return self.calculator.DIFFERENCE_THRESHOLD + return plugin_manager.calculator.DIFFERENCE_THRESHOLD class MockScanner(FaceScanner):