diff --git a/Dockerfile b/Dockerfile index 70614723..dca25bc0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -19,11 +19,8 @@ RUN apt-get install ffmpeg libsm6 libxext6 -y # ----------------------------------- # Copy required files from repo into image COPY ./deepface /app/deepface -COPY ./api/src/app.py /app/ -COPY ./api/src/api.py /app/ -COPY ./api/src/routes.py /app/ -COPY ./api/src/service.py /app/ COPY ./requirements.txt /app/ +COPY ./package_info.json /app/package_info.json COPY ./setup.py /app/ COPY ./README.md /app/ @@ -50,5 +47,6 @@ ENV PYTHONUNBUFFERED=1 # ----------------------------------- # run the app (re-configure port if necessary) +WORKDIR /app/deepface/api/src EXPOSE 5000 CMD ["gunicorn", "--workers=1", "--timeout=3600", "--bind=0.0.0.0:5000", "app:create_app()"] diff --git a/README.md b/README.md index c64453d6..673f848d 100644 --- a/README.md +++ b/README.md @@ -280,7 +280,7 @@ user **API** - [`Demo`](https://youtu.be/HeKCQ6U9XmI) -DeepFace serves an API as well. You can clone [`/api`](https://github.com/serengil/deepface/tree/master/api) folder and run the api via gunicorn server. This will get a rest service up. In this way, you can call deepface from an external system such as mobile app or web. +DeepFace serves an API as well. You can clone deepface source code and run the api via gunicorn server with the following command. This will get a rest service up. In this way, you can call deepface from an external system such as mobile app or web. ```shell cd scripts diff --git a/api/src/service.py b/api/src/service.py deleted file mode 100644 index f3a9bbc9..00000000 --- a/api/src/service.py +++ /dev/null @@ -1,42 +0,0 @@ -from deepface import DeepFace - - -def represent(img_path, model_name, detector_backend, enforce_detection, align): - result = {} - embedding_objs = DeepFace.represent( - img_path=img_path, - model_name=model_name, - detector_backend=detector_backend, - enforce_detection=enforce_detection, - align=align, - ) - result["results"] = embedding_objs - return result - - -def verify( - img1_path, img2_path, model_name, detector_backend, distance_metric, enforce_detection, align -): - obj = DeepFace.verify( - img1_path=img1_path, - img2_path=img2_path, - model_name=model_name, - detector_backend=detector_backend, - distance_metric=distance_metric, - align=align, - enforce_detection=enforce_detection, - ) - return obj - - -def analyze(img_path, actions, detector_backend, enforce_detection, align): - result = {} - demographies = DeepFace.analyze( - img_path=img_path, - actions=actions, - detector_backend=detector_backend, - enforce_detection=enforce_detection, - align=align, - ) - result["results"] = demographies - return result diff --git a/deepface/api/__init__.py b/deepface/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api/postman/deepface-api.postman_collection.json b/deepface/api/postman/deepface-api.postman_collection.json similarity index 100% rename from api/postman/deepface-api.postman_collection.json rename to deepface/api/postman/deepface-api.postman_collection.json diff --git a/deepface/api/src/__init__.py b/deepface/api/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api/src/api.py b/deepface/api/src/api.py similarity index 100% rename from api/src/api.py rename to deepface/api/src/api.py diff --git a/api/src/app.py b/deepface/api/src/app.py similarity index 71% rename from api/src/app.py rename to deepface/api/src/app.py index 7ad23ee1..fa0cb878 100644 --- a/api/src/app.py +++ b/deepface/api/src/app.py @@ -1,7 +1,6 @@ # 3rd parth dependencies from flask import Flask -from routes import blueprint - +from deepface.api.src.modules.core.routes import blueprint def create_app(): app = Flask(__name__) diff --git a/deepface/api/src/modules/__init__.py b/deepface/api/src/modules/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/deepface/api/src/modules/core/__init__.py b/deepface/api/src/modules/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api/src/routes.py b/deepface/api/src/modules/core/routes.py similarity index 83% rename from api/src/routes.py rename to deepface/api/src/modules/core/routes.py index 45808320..d36ad246 100644 --- a/api/src/routes.py +++ b/deepface/api/src/modules/core/routes.py @@ -1,5 +1,8 @@ from flask import Blueprint, request -import service +from deepface.api.src.modules.core import service +from deepface.commons.logger import Logger + +logger = Logger(module="api/src/routes.py") blueprint = Blueprint("routes", __name__) @@ -16,7 +19,7 @@ def represent(): if input_args is None: return {"message": "empty input set passed"} - img_path = input_args.get("img") + img_path = input_args.get("img") or input_args.get("img_path") if img_path is None: return {"message": "you must pass img_path input"} @@ -33,6 +36,8 @@ def represent(): align=align, ) + logger.debug(obj) + return obj @@ -43,8 +48,8 @@ def verify(): if input_args is None: return {"message": "empty input set passed"} - img1_path = input_args.get("img1_path") - img2_path = input_args.get("img2_path") + img1_path = input_args.get("img1") or input_args.get("img1_path") + img2_path = input_args.get("img2") or input_args.get("img2_path") if img1_path is None: return {"message": "you must pass img1_path input"} @@ -68,7 +73,7 @@ def verify(): enforce_detection=enforce_detection, ) - verification["verified"] = str(verification["verified"]) + logger.debug(verification) return verification @@ -80,7 +85,7 @@ def analyze(): if input_args is None: return {"message": "empty input set passed"} - img_path = input_args.get("img_path") + img_path = input_args.get("img") or input_args.get("img_path") if img_path is None: return {"message": "you must pass img_path input"} @@ -97,4 +102,6 @@ def analyze(): align=align, ) + logger.debug(demographies) + return demographies diff --git a/deepface/api/src/modules/core/service.py b/deepface/api/src/modules/core/service.py new file mode 100644 index 00000000..6ba3c69e --- /dev/null +++ b/deepface/api/src/modules/core/service.py @@ -0,0 +1,54 @@ +from deepface import DeepFace + +# pylint: disable=broad-except + + +def represent(img_path, model_name, detector_backend, enforce_detection, align): + try: + result = {} + embedding_objs = DeepFace.represent( + img_path=img_path, + model_name=model_name, + detector_backend=detector_backend, + enforce_detection=enforce_detection, + align=align, + ) + result["results"] = embedding_objs + return result + except Exception as err: + return {"error": f"Exception while representing: {str(err)}"}, 400 + + +def verify( + img1_path, img2_path, model_name, detector_backend, distance_metric, enforce_detection, align +): + try: + obj = DeepFace.verify( + img1_path=img1_path, + img2_path=img2_path, + model_name=model_name, + detector_backend=detector_backend, + distance_metric=distance_metric, + align=align, + enforce_detection=enforce_detection, + ) + return obj + except Exception as err: + return {"error": f"Exception while verifying: {str(err)}"}, 400 + + +def analyze(img_path, actions, detector_backend, enforce_detection, align): + try: + result = {} + demographies = DeepFace.analyze( + img_path=img_path, + actions=actions, + detector_backend=detector_backend, + enforce_detection=enforce_detection, + align=align, + silent=True, + ) + result["results"] = demographies + return result + except Exception as err: + return {"error": f"Exception while analyzing: {str(err)}"}, 400 diff --git a/deepface/modules/detection.py b/deepface/modules/detection.py index 406563a7..70cc8b85 100644 --- a/deepface/modules/detection.py +++ b/deepface/modules/detection.py @@ -167,10 +167,10 @@ def extract_faces( { "face": img_pixels[:, :, ::-1] if human_readable is True else img_pixels, "facial_area": { - "x": current_region.x, - "y": current_region.y, - "w": current_region.w, - "h": current_region.h, + "x": int(current_region.x), + "y": int(current_region.y), + "w": int(current_region.w), + "h": int(current_region.h), }, "confidence": confidence, } diff --git a/scripts/service.sh b/scripts/service.sh index 2076648a..8b16c9dc 100755 --- a/scripts/service.sh +++ b/scripts/service.sh @@ -1,3 +1,3 @@ #!/usr/bin/env bash -cd ../api/src +cd ../deepface/api/src gunicorn --workers=1 --timeout=3600 --bind=0.0.0.0:5000 "app:create_app()" \ No newline at end of file diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 00000000..3c318572 --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,123 @@ +import unittest +from deepface.commons.logger import Logger +from deepface.api.src.app import create_app + + +logger = Logger("tests/test_api.py") + + +class TestVerifyEndpoint(unittest.TestCase): + def setUp(self): + app = create_app() + app.config["DEBUG"] = True + app.config["TESTING"] = True + self.app = app.test_client() + + def test_tp_verify(self): + data = { + "img1_path": "dataset/img1.jpg", + "img2_path": "dataset/img2.jpg", + } + response = self.app.post("/verify", json=data) + assert response.status_code == 200 + result = response.json + logger.debug(result) + + assert result.get("verified") is not None + assert result.get("model") is not None + assert result.get("similarity_metric") is not None + assert result.get("detector_backend") is not None + assert result.get("distance") is not None + assert result.get("threshold") is not None + assert result.get("facial_areas") is not None + + assert result.get("verified") is True + + logger.info("✅ true-positive verification api test is done") + + def test_tn_verify(self): + data = { + "img1_path": "dataset/img1.jpg", + "img2_path": "dataset/img2.jpg", + } + response = self.app.post("/verify", json=data) + assert response.status_code == 200 + result = response.json + logger.debug(result) + + assert result.get("verified") is not None + assert result.get("model") is not None + assert result.get("similarity_metric") is not None + assert result.get("detector_backend") is not None + assert result.get("distance") is not None + assert result.get("threshold") is not None + assert result.get("facial_areas") is not None + + assert result.get("verified") is True + + logger.info("✅ true-negative verification api test is done") + + def test_represent(self): + data = { + "img": "dataset/img1.jpg", + } + response = self.app.post("/represent", json=data) + assert response.status_code == 200 + result = response.json + logger.debug(result) + assert result.get("results") is not None + assert isinstance(result["results"], list) is True + assert len(result["results"]) > 0 + for i in result["results"]: + assert i.get("embedding") is not None + assert isinstance(i.get("embedding"), list) is True + assert len(i.get("embedding")) == 4096 + assert i.get("face_confidence") is not None + assert i.get("facial_area") is not None + + logger.info("✅ representation api test is done") + + def test_analyze(self): + data = { + "img": "dataset/img1.jpg", + } + response = self.app.post("/analyze", json=data) + assert response.status_code == 200 + result = response.json + logger.debug(result) + assert result.get("results") is not None + assert isinstance(result["results"], list) is True + assert len(result["results"]) > 0 + for i in result["results"]: + assert i.get("age") is not None + assert isinstance(i.get("age"), (int, float)) + assert i.get("dominant_gender") is not None + assert i.get("dominant_gender") in ["Man", "Woman"] + assert i.get("dominant_emotion") is not None + assert i.get("dominant_race") is not None + + logger.info("✅ analyze api test is done") + + def test_invalid_verify(self): + data = { + "img1_path": "dataset/invalid_1.jpg", + "img2_path": "dataset/invalid_2.jpg", + } + response = self.app.post("/verify", json=data) + assert response.status_code == 400 + logger.info("✅ invalid verification request api test is done") + + def test_invalid_represent(self): + data = { + "img": "dataset/invalid_1.jpg", + } + response = self.app.post("/represent", json=data) + assert response.status_code == 400 + logger.info("✅ invalid represent request api test is done") + + def test_invalid_analyze(self): + data = { + "img": "dataset/invalid.jpg", + } + response = self.app.post("/analyze", json=data) + assert response.status_code == 400