mirror of
https://github.com/serengil/deepface.git
synced 2025-06-04 02:20:06 +00:00
450 lines
16 KiB
Python
450 lines
16 KiB
Python
# built-in dependencies
|
|
import os
|
|
import base64
|
|
import unittest
|
|
from unittest.mock import patch, MagicMock
|
|
from packaging import version
|
|
|
|
# 3rd party dependencies
|
|
import gdown
|
|
import numpy as np
|
|
import flask
|
|
from flask import Flask
|
|
import werkzeug
|
|
|
|
# project dependencies
|
|
from deepface.api.src.app import create_app
|
|
from deepface.api.src.modules.core import routes
|
|
from deepface.commons.logger import Logger
|
|
|
|
logger = Logger()
|
|
|
|
IMG1_SOURCE = (
|
|
"https://raw.githubusercontent.com/serengil/deepface/refs/heads/master/tests/dataset/img1.jpg"
|
|
)
|
|
IMG2_SOURCE = (
|
|
"https://raw.githubusercontent.com/serengil/deepface/refs/heads/master/tests/dataset/img2.jpg"
|
|
)
|
|
DUMMY_APP = Flask(__name__)
|
|
|
|
|
|
class TestVerifyEndpoint(unittest.TestCase):
|
|
def setUp(self):
|
|
download_test_images(IMG1_SOURCE)
|
|
download_test_images(IMG2_SOURCE)
|
|
app = create_app()
|
|
app.config["DEBUG"] = True
|
|
app.config["TESTING"] = True
|
|
self.app = app.test_client()
|
|
|
|
def test_tp_verify(self):
|
|
data = {
|
|
"img1": "dataset/img1.jpg",
|
|
"img2": "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": "dataset/img1.jpg",
|
|
"img2": "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 (for image path)")
|
|
|
|
def test_represent_encoded(self):
|
|
image_path = "dataset/img1.jpg"
|
|
with open(image_path, "rb") as image_file:
|
|
encoded_string = "data:image/jpeg;base64," + base64.b64encode(image_file.read()).decode(
|
|
"utf8"
|
|
)
|
|
|
|
data = {"model_name": "Facenet", "detector_backend": "mtcnn", "img": encoded_string}
|
|
|
|
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")) == 128
|
|
assert i.get("face_confidence") is not None
|
|
assert i.get("facial_area") is not None
|
|
|
|
logger.info("✅ representation api test is done (for encoded image)")
|
|
|
|
def test_represent_url(self):
|
|
data = {
|
|
"model_name": "Facenet",
|
|
"detector_backend": "mtcnn",
|
|
"img": "https://github.com/serengil/deepface/blob/master/tests/dataset/couple.jpg?raw=true",
|
|
}
|
|
|
|
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"]) == 2 # 2 faces are in the image link
|
|
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")) == 128
|
|
assert i.get("face_confidence") is not None
|
|
assert i.get("facial_area") is not None
|
|
|
|
logger.info("✅ representation api test is done (for image url)")
|
|
|
|
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_analyze_inputformats(self):
|
|
image_path = "dataset/couple.jpg"
|
|
with open(image_path, "rb") as image_file:
|
|
encoded_image = "data:image/jpeg;base64," + base64.b64encode(image_file.read()).decode(
|
|
"utf8"
|
|
)
|
|
|
|
image_sources = [
|
|
# image path
|
|
image_path,
|
|
# image url
|
|
f"https://github.com/serengil/deepface/blob/master/tests/{image_path}?raw=true",
|
|
# encoded image
|
|
encoded_image,
|
|
]
|
|
|
|
results = []
|
|
for img in image_sources:
|
|
data = {
|
|
"img": img,
|
|
}
|
|
response = self.app.post("/analyze", json=data)
|
|
|
|
assert response.status_code == 200
|
|
result = response.json
|
|
results.append(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
|
|
|
|
assert len(results[0]["results"]) == len(results[1]["results"]) and len(
|
|
results[0]["results"]
|
|
) == len(results[2]["results"])
|
|
|
|
for i in range(len(results[0]["results"])):
|
|
assert (
|
|
results[0]["results"][i]["dominant_emotion"]
|
|
== results[1]["results"][i]["dominant_emotion"]
|
|
and results[0]["results"][i]["dominant_emotion"]
|
|
== results[2]["results"][i]["dominant_emotion"]
|
|
)
|
|
|
|
assert (
|
|
results[0]["results"][i]["dominant_gender"]
|
|
== results[1]["results"][i]["dominant_gender"]
|
|
and results[0]["results"][i]["dominant_gender"]
|
|
== results[2]["results"][i]["dominant_gender"]
|
|
)
|
|
|
|
assert (
|
|
results[0]["results"][i]["dominant_race"]
|
|
== results[1]["results"][i]["dominant_race"]
|
|
and results[0]["results"][i]["dominant_race"]
|
|
== results[2]["results"][i]["dominant_race"]
|
|
)
|
|
|
|
logger.info("✅ different inputs test is done")
|
|
|
|
def test_invalid_verify(self):
|
|
data = {
|
|
"img1": "dataset/invalid_1.jpg",
|
|
"img2": "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
|
|
|
|
def test_analyze_for_multipart_form_data(self):
|
|
if is_form_data_file_testable() is False:
|
|
return
|
|
|
|
with open("/tmp/img1.jpg", "rb") as img_file:
|
|
response = self.app.post(
|
|
"/analyze",
|
|
content_type="multipart/form-data",
|
|
data={
|
|
"img": (img_file, "test_image.jpg"),
|
|
"actions": '["age", "gender"]',
|
|
"detector_backend": "mtcnn",
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
result = response.json
|
|
assert isinstance(result, dict)
|
|
assert result.get("age") is not True
|
|
assert result.get("dominant_gender") is not True
|
|
logger.info("✅ analyze api for multipart form data test is done")
|
|
|
|
def test_verify_for_multipart_form_data(self):
|
|
if is_form_data_file_testable() is False:
|
|
return
|
|
|
|
with open("/tmp/img1.jpg", "rb") as img1_file:
|
|
with open("/tmp/img2.jpg", "rb") as img2_file:
|
|
response = self.app.post(
|
|
"/verify",
|
|
content_type="multipart/form-data",
|
|
data={
|
|
"img1": (img1_file, "first_image.jpg"),
|
|
"img2": (img2_file, "second_image.jpg"),
|
|
"model_name": "Facenet",
|
|
"detector_backend": "mtcnn",
|
|
"distance_metric": "euclidean",
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
result = response.json
|
|
assert isinstance(result, dict)
|
|
assert result.get("verified") is not None
|
|
assert result.get("model") == "Facenet"
|
|
assert result.get("similarity_metric") is not None
|
|
assert result.get("detector_backend") == "mtcnn"
|
|
assert result.get("threshold") is not None
|
|
assert result.get("facial_areas") is not None
|
|
|
|
logger.info("✅ verify api for multipart form data test is done")
|
|
|
|
def test_represent_for_multipart_form_data(self):
|
|
if is_form_data_file_testable() is False:
|
|
return
|
|
|
|
with open("/tmp/img1.jpg", "rb") as img_file:
|
|
response = self.app.post(
|
|
"/represent",
|
|
content_type="multipart/form-data",
|
|
data={
|
|
"img": (img_file, "first_image.jpg"),
|
|
"model_name": "Facenet",
|
|
"detector_backend": "mtcnn",
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
result = response.json
|
|
assert isinstance(result, dict)
|
|
logger.info("✅ represent api for multipart form data test is done")
|
|
|
|
def test_represent_for_multipart_form_data_and_filepath(self):
|
|
if is_form_data_file_testable() is False:
|
|
return
|
|
|
|
response = self.app.post(
|
|
"/represent",
|
|
content_type="multipart/form-data",
|
|
data={
|
|
"img": "/tmp/img1.jpg",
|
|
"model_name": "Facenet",
|
|
"detector_backend": "mtcnn",
|
|
},
|
|
)
|
|
assert response.status_code == 200
|
|
result = response.json
|
|
assert isinstance(result, dict)
|
|
logger.info("✅ represent api for multipart form data and file path test is done")
|
|
|
|
def test_extract_image_from_form_data(self):
|
|
if is_form_data_file_testable() is False:
|
|
return
|
|
|
|
img_key = "img1"
|
|
img_itself = np.zeros((100, 100, 3), dtype=np.uint8)
|
|
# Establish a temporary request context using the Flask app
|
|
with DUMMY_APP.test_request_context("/dummy_endpoint"):
|
|
# Mock the file part
|
|
with patch("deepface.api.src.modules.core.routes.request") as mock_request:
|
|
mock_file = MagicMock()
|
|
mock_file.filename = "image.jpg"
|
|
mock_request.files = {img_key: mock_file}
|
|
|
|
# Mock the image loading function
|
|
with patch(
|
|
"deepface.commons.image_utils.load_image_from_file_storage",
|
|
return_value=img_itself,
|
|
):
|
|
result = routes.extract_image_from_request(img_key)
|
|
|
|
assert isinstance(result, np.ndarray)
|
|
assert np.array_equal(result, img_itself)
|
|
|
|
logger.info("✅ test extract_image_from_request for real image from form data done")
|
|
|
|
def test_extract_image_string_from_json_data(self):
|
|
if is_form_data_file_testable() is False:
|
|
return
|
|
|
|
img_key = "img1"
|
|
img_data = "image_url_or_path_or_base64"
|
|
|
|
with DUMMY_APP.test_request_context("/dummy_endpoint"):
|
|
with patch("deepface.api.src.modules.core.routes.request") as mock_request:
|
|
# Mock JSON data
|
|
mock_request.files = None
|
|
mock_request.is_json = True
|
|
mock_request.get_json = MagicMock(return_value={img_key: img_data})
|
|
|
|
result = routes.extract_image_from_request(img_key)
|
|
|
|
assert isinstance(result, str)
|
|
assert result == img_data
|
|
|
|
logger.info("✅ test extract_image_from_request for image string from json done")
|
|
|
|
def test_extract_image_string_from_form_data(self):
|
|
if is_form_data_file_testable() is False:
|
|
return
|
|
|
|
img_key = "img1"
|
|
img_data = "image_url_or_path_or_base64"
|
|
|
|
with DUMMY_APP.test_request_context("/dummy_endpoint"):
|
|
with patch("deepface.api.src.modules.core.routes.request") as mock_request:
|
|
# Mock form data
|
|
mock_request.files = None
|
|
|
|
mock_request.is_json = False
|
|
mock_request.get_json = MagicMock(return_value=None)
|
|
|
|
mock_request.form = MagicMock()
|
|
mock_request.form.to_dict.return_value = {img_key: img_data}
|
|
|
|
result = routes.extract_image_from_request(img_key)
|
|
|
|
assert isinstance(result, str)
|
|
assert result == img_data
|
|
|
|
logger.info("✅ test extract_image_from_request for image string from form done")
|
|
|
|
|
|
def download_test_images(url: str):
|
|
file_name = url.split("/")[-1]
|
|
target_file = f"/tmp/{file_name}"
|
|
if os.path.exists(target_file) is True:
|
|
return
|
|
|
|
gdown.download(url, target_file, quiet=False)
|
|
|
|
|
|
def is_form_data_file_testable() -> bool:
|
|
"""
|
|
Sending a file from form data fails in unit test with
|
|
415 unsupported media type error for flask 3.X
|
|
but it is working for flask 2.0.2
|
|
Returns:
|
|
is_form_data_file_testable (bool)
|
|
"""
|
|
flask_version = version.parse(flask.__version__)
|
|
werkzeus_version = version.parse(werkzeug.__version__)
|
|
threshold_version = version.parse("2.0.2")
|
|
is_testable = flask_version <= threshold_version and werkzeus_version <= threshold_version
|
|
if is_testable is False:
|
|
logger.warn(
|
|
"sending file in form data is not testable because of flask, werkzeus versions."
|
|
f"Expected <= {threshold_version}, but {flask_version=} and {werkzeus_version}."
|
|
)
|
|
return is_testable
|