From 08131731c56e9aae7f0b79bf0465537729df2db9 Mon Sep 17 00:00:00 2001 From: Sefik Ilkin Serengil Date: Sun, 4 Feb 2024 10:36:00 +0000 Subject: [PATCH 01/10] retuning regions as numpy int 32 was causing error in api --- deepface/modules/detection.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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, } From fe977673bee813dd939fd0214f496b95c0ae8b35 Mon Sep 17 00:00:00 2001 From: Sefik Ilkin Serengil Date: Sun, 4 Feb 2024 10:36:45 +0000 Subject: [PATCH 02/10] adding api folder to run linting --- .github/workflows/tests.yml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 4f7e8b95..c84114bd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -66,4 +66,4 @@ jobs: - name: Lint with pylint run: | - pylint --fail-under=10 deepface/ + pylint --fail-under=10 deepface/ api/ diff --git a/Makefile b/Makefile index cb8e9aeb..dbfb1453 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ test: cd tests && python -m pytest . -s --disable-warnings lint: - python -m pylint deepface/ --fail-under=10 + python -m pylint deepface/ api/ --fail-under=10 coverage: pip install pytest-cov && cd tests && python -m pytest --cov=deepface \ No newline at end of file From 53890974ea6d83264a27e62638bdfc6ff1df5929 Mon Sep 17 00:00:00 2001 From: Sefik Ilkin Serengil Date: Sun, 4 Feb 2024 10:38:34 +0000 Subject: [PATCH 03/10] some changes on api services - exception handling added in service - new folder structure for api --- api/src/__init__.py | 0 api/src/app.py | 2 +- api/src/modules/core/__init__.py | 0 api/src/{ => modules/core}/routes.py | 19 ++++++---- api/src/modules/core/service.py | 54 ++++++++++++++++++++++++++++ api/src/service.py | 42 ---------------------- 6 files changed, 68 insertions(+), 49 deletions(-) create mode 100644 api/src/__init__.py create mode 100644 api/src/modules/core/__init__.py rename api/src/{ => modules/core}/routes.py (84%) create mode 100644 api/src/modules/core/service.py delete mode 100644 api/src/service.py diff --git a/api/src/__init__.py b/api/src/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api/src/app.py b/api/src/app.py index 7ad23ee1..f5275db9 100644 --- a/api/src/app.py +++ b/api/src/app.py @@ -1,6 +1,6 @@ # 3rd parth dependencies from flask import Flask -from routes import blueprint +from modules.core.routes import blueprint def create_app(): diff --git a/api/src/modules/core/__init__.py b/api/src/modules/core/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/api/src/routes.py b/api/src/modules/core/routes.py similarity index 84% rename from api/src/routes.py rename to api/src/modules/core/routes.py index 45808320..e0256c1c 100644 --- a/api/src/routes.py +++ b/api/src/modules/core/routes.py @@ -1,5 +1,8 @@ from flask import Blueprint, request -import service +from 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/api/src/modules/core/service.py b/api/src/modules/core/service.py new file mode 100644 index 00000000..6ba3c69e --- /dev/null +++ b/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/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 From 78b8198232bd78f0f81c5f966235203b3496d2d8 Mon Sep 17 00:00:00 2001 From: Sefik Ilkin Serengil Date: Sun, 4 Feb 2024 10:38:47 +0000 Subject: [PATCH 04/10] adding unit tests for api --- tests/test_api.py | 128 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 128 insertions(+) create mode 100644 tests/test_api.py diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 00000000..e5c9e953 --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,128 @@ +import sys +import unittest +from deepface.commons import constant +from deepface.commons.logger import Logger + +# api is not available under deepface import +sys.path.insert(1, f"{constant.ROOT_DIR}/api/src") +# pylint: disable=wrong-import-order, wrong-import-position +from 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 From d080b24cadb5d1c93fab2f895165b82a2b7501cf Mon Sep 17 00:00:00 2001 From: Sefik Ilkin Serengil Date: Sun, 4 Feb 2024 10:46:39 +0000 Subject: [PATCH 05/10] adding api folder into packages --- api/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 api/__init__.py diff --git a/api/__init__.py b/api/__init__.py new file mode 100644 index 00000000..e69de29b From 2e170474f6def42cb4dac6202f4c6f19b5815be6 Mon Sep 17 00:00:00 2001 From: Sefik Ilkin Serengil Date: Sun, 4 Feb 2024 11:03:57 +0000 Subject: [PATCH 06/10] make api compatible for both runtime and unit tests --- api/src/app.py | 8 +++++++- api/src/modules/core/routes.py | 9 ++++++++- tests/test_api.py | 7 +------ 3 files changed, 16 insertions(+), 8 deletions(-) diff --git a/api/src/app.py b/api/src/app.py index f5275db9..84580673 100644 --- a/api/src/app.py +++ b/api/src/app.py @@ -1,6 +1,12 @@ # 3rd parth dependencies from flask import Flask -from modules.core.routes import blueprint + +try: + # unit tests + from api.src.modules.core.routes import blueprint +except: + # runtime + from modules.core.routes import blueprint def create_app(): diff --git a/api/src/modules/core/routes.py b/api/src/modules/core/routes.py index e0256c1c..47bacf42 100644 --- a/api/src/modules/core/routes.py +++ b/api/src/modules/core/routes.py @@ -1,5 +1,12 @@ from flask import Blueprint, request -from modules.core import service + +try: + # unit tests + from api.src.modules.core import service +except: + # runtime + from modules.core import service + from deepface.commons.logger import Logger logger = Logger(module="api/src/routes.py") diff --git a/tests/test_api.py b/tests/test_api.py index e5c9e953..e2dd2e50 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -1,12 +1,7 @@ -import sys import unittest -from deepface.commons import constant from deepface.commons.logger import Logger -# api is not available under deepface import -sys.path.insert(1, f"{constant.ROOT_DIR}/api/src") -# pylint: disable=wrong-import-order, wrong-import-position -from app import create_app +from api.src.app import create_app logger = Logger("tests/test_api.py") From 1ca17d7adad1f852f7ca22e49c90df6ff7d03806 Mon Sep 17 00:00:00 2001 From: Sefik Ilkin Serengil Date: Sun, 4 Feb 2024 11:10:06 +0000 Subject: [PATCH 07/10] exclude api tests --- api/src/app.py | 9 +-------- api/src/modules/core/routes.py | 9 +-------- tests/{test_api.py => testing_api.py} | 7 ++++++- 3 files changed, 8 insertions(+), 17 deletions(-) rename tests/{test_api.py => testing_api.py} (94%) diff --git a/api/src/app.py b/api/src/app.py index 84580673..0103d828 100644 --- a/api/src/app.py +++ b/api/src/app.py @@ -1,13 +1,6 @@ # 3rd parth dependencies from flask import Flask - -try: - # unit tests - from api.src.modules.core.routes import blueprint -except: - # runtime - from modules.core.routes import blueprint - +from modules.core.routes import blueprint def create_app(): app = Flask(__name__) diff --git a/api/src/modules/core/routes.py b/api/src/modules/core/routes.py index 47bacf42..e0256c1c 100644 --- a/api/src/modules/core/routes.py +++ b/api/src/modules/core/routes.py @@ -1,12 +1,5 @@ from flask import Blueprint, request - -try: - # unit tests - from api.src.modules.core import service -except: - # runtime - from modules.core import service - +from modules.core import service from deepface.commons.logger import Logger logger = Logger(module="api/src/routes.py") diff --git a/tests/test_api.py b/tests/testing_api.py similarity index 94% rename from tests/test_api.py rename to tests/testing_api.py index e2dd2e50..e5c9e953 100644 --- a/tests/test_api.py +++ b/tests/testing_api.py @@ -1,7 +1,12 @@ +import sys import unittest +from deepface.commons import constant from deepface.commons.logger import Logger -from api.src.app import create_app +# api is not available under deepface import +sys.path.insert(1, f"{constant.ROOT_DIR}/api/src") +# pylint: disable=wrong-import-order, wrong-import-position +from app import create_app logger = Logger("tests/test_api.py") From 86d424b8acfac339c134f5f7348601ee4482d529 Mon Sep 17 00:00:00 2001 From: Sefik Ilkin Serengil Date: Sun, 4 Feb 2024 12:45:27 +0000 Subject: [PATCH 08/10] put api in deepface folder to make it testable --- Dockerfile | 6 ++---- README.md | 2 +- {api => deepface/api}/__init__.py | 0 .../api}/postman/deepface-api.postman_collection.json | 0 {api => deepface/api}/src/__init__.py | 0 {api => deepface/api}/src/api.py | 0 {api => deepface/api}/src/app.py | 2 +- {api => deepface/api}/src/modules/core/__init__.py | 0 {api => deepface/api}/src/modules/core/routes.py | 2 +- {api => deepface/api}/src/modules/core/service.py | 0 scripts/service.sh | 2 +- tests/{testing_api.py => test_api.py} | 7 +------ 12 files changed, 7 insertions(+), 14 deletions(-) rename {api => deepface/api}/__init__.py (100%) rename {api => deepface/api}/postman/deepface-api.postman_collection.json (100%) rename {api => deepface/api}/src/__init__.py (100%) rename {api => deepface/api}/src/api.py (100%) rename {api => deepface/api}/src/app.py (71%) rename {api => deepface/api}/src/modules/core/__init__.py (100%) rename {api => deepface/api}/src/modules/core/routes.py (98%) rename {api => deepface/api}/src/modules/core/service.py (100%) rename tests/{testing_api.py => test_api.py} (94%) 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/__init__.py b/deepface/api/__init__.py similarity index 100% rename from api/__init__.py rename to deepface/api/__init__.py 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/api/src/__init__.py b/deepface/api/src/__init__.py similarity index 100% rename from api/src/__init__.py rename to deepface/api/src/__init__.py 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 0103d828..fa0cb878 100644 --- a/api/src/app.py +++ b/deepface/api/src/app.py @@ -1,6 +1,6 @@ # 3rd parth dependencies from flask import Flask -from modules.core.routes import blueprint +from deepface.api.src.modules.core.routes import blueprint def create_app(): app = Flask(__name__) diff --git a/api/src/modules/core/__init__.py b/deepface/api/src/modules/core/__init__.py similarity index 100% rename from api/src/modules/core/__init__.py rename to deepface/api/src/modules/core/__init__.py diff --git a/api/src/modules/core/routes.py b/deepface/api/src/modules/core/routes.py similarity index 98% rename from api/src/modules/core/routes.py rename to deepface/api/src/modules/core/routes.py index e0256c1c..d36ad246 100644 --- a/api/src/modules/core/routes.py +++ b/deepface/api/src/modules/core/routes.py @@ -1,5 +1,5 @@ from flask import Blueprint, request -from modules.core import service +from deepface.api.src.modules.core import service from deepface.commons.logger import Logger logger = Logger(module="api/src/routes.py") diff --git a/api/src/modules/core/service.py b/deepface/api/src/modules/core/service.py similarity index 100% rename from api/src/modules/core/service.py rename to deepface/api/src/modules/core/service.py 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/testing_api.py b/tests/test_api.py similarity index 94% rename from tests/testing_api.py rename to tests/test_api.py index e5c9e953..3c318572 100644 --- a/tests/testing_api.py +++ b/tests/test_api.py @@ -1,12 +1,7 @@ -import sys import unittest -from deepface.commons import constant from deepface.commons.logger import Logger +from deepface.api.src.app import create_app -# api is not available under deepface import -sys.path.insert(1, f"{constant.ROOT_DIR}/api/src") -# pylint: disable=wrong-import-order, wrong-import-position -from app import create_app logger = Logger("tests/test_api.py") From a7231ab0cbe327591034f9771e074b92969aa3fc Mon Sep 17 00:00:00 2001 From: Sefik Ilkin Serengil Date: Sun, 4 Feb 2024 12:47:53 +0000 Subject: [PATCH 09/10] adding init py to folders --- deepface/api/src/modules/__init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 deepface/api/src/modules/__init__.py diff --git a/deepface/api/src/modules/__init__.py b/deepface/api/src/modules/__init__.py new file mode 100644 index 00000000..e69de29b From 1931c5ca93e4317678c101be5eb80b2a790869f7 Mon Sep 17 00:00:00 2001 From: Sefik Ilkin Serengil Date: Sun, 4 Feb 2024 12:53:51 +0000 Subject: [PATCH 10/10] api folder moved --- .github/workflows/tests.yml | 2 +- Makefile | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index c84114bd..4f7e8b95 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -66,4 +66,4 @@ jobs: - name: Lint with pylint run: | - pylint --fail-under=10 deepface/ api/ + pylint --fail-under=10 deepface/ diff --git a/Makefile b/Makefile index dbfb1453..cb8e9aeb 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,7 @@ test: cd tests && python -m pytest . -s --disable-warnings lint: - python -m pylint deepface/ api/ --fail-under=10 + python -m pylint deepface/ --fail-under=10 coverage: pip install pytest-cov && cd tests && python -m pytest --cov=deepface \ No newline at end of file