From 8ca2b9c246c981f34faf28fc574b63df49686973 Mon Sep 17 00:00:00 2001 From: ahodkov Date: Thu, 30 Mar 2023 16:17:23 +0200 Subject: [PATCH 1/5] Migration from 0.6 to 1.2.0 --- README.md | 192 ++++++++++++- compreface/client/__init__.py | 3 + compreface/client/add_example_of_subject.py | 44 ++- compreface/client/delete_example_by_id.py | 24 +- compreface/client/detect_face_from_image.py | 17 +- .../client/recognize_face_from_embeddings.py | 69 +++++ .../client/recognize_face_from_image.py | 15 +- compreface/client/subject_client.py | 15 +- .../verification_face_from_embeddings.py | 62 +++++ .../client/verification_face_from_image.py | 28 +- .../client/verify_face_from_embeddings.py | 61 +++++ compreface/client/verify_face_from_image.py | 32 ++- compreface/collections/face_collections.py | 253 +++++++++++------- compreface/common/multipart_constructor.py | 23 +- compreface/common/typed_dict.py | 43 ++- compreface/config/api_list.py | 15 +- compreface/core/model.py | 27 +- compreface/service/detection_service.py | 41 ++- compreface/service/recognition_service.py | 83 ++++-- compreface/service/verification_service.py | 91 +++++-- compreface/use_cases/__init__.py | 4 + .../use_cases/add_example_of_subject.py | 8 +- compreface/use_cases/add_subject.py | 7 +- .../delete_all_examples_of_subject_by_name.py | 5 +- compreface/use_cases/delete_all_subjects.py | 7 +- compreface/use_cases/delete_example_by_id.py | 5 +- compreface/use_cases/delete_example_by_ids.py | 34 +++ .../use_cases/delete_subject_by_name.py | 7 +- .../use_cases/detect_face_from_image.py | 8 +- compreface/use_cases/get_subjects.py | 7 +- .../use_cases/list_of_all_saved_subjects.py | 10 +- .../recognize_face_from_embedding.py | 39 +++ .../use_cases/recognize_face_from_image.py | 8 +- compreface/use_cases/update_subject.py | 7 +- .../verification_face_from_embedding.py | 37 +++ .../use_cases/verification_face_from_image.py | 10 +- .../use_cases/verify_face_from_embedding.py | 38 +++ ...m_images.py => verify_face_from_images.py} | 11 +- examples/add_example_of_a_subject.py | 17 +- examples/add_subject.py | 8 +- examples/delete_all_examples_of_subject.py | 11 +- examples/delete_all_subjects.py | 6 +- examples/delete_example_by_id.py | 17 +- examples/delete_subject_by_name.py | 8 +- examples/detect_face_from_image.py | 33 +-- examples/get_list_of_all_subjects.py | 9 +- examples/recognize_face_from_image.py | 22 +- examples/update_existing_subject.py | 10 +- examples/verification_face_from_image.py | 28 +- examples/verify_face_from_image.py | 27 +- setup.py | 50 ++-- tests/client/__init__.py | 1 - tests/client/const_config.py | 10 +- .../test_add_example_of_subject_client.py | 157 +++++++---- .../test_delete_example_by_id_client.py | 97 ++++++- tests/client/test_detect_face_from_image.py | 127 +++++++-- .../test_recognize_face_from_embeddings.py | 115 ++++++++ .../client/test_recognize_face_from_image.py | 82 ++++-- tests/client/test_subject_crud_client.py | 74 ++--- .../test_verification_face_from_embeddings.py | 96 +++++++ .../test_verification_face_from_image.py | 81 ++++-- .../test_verify_face_from_embeddings.py | 94 +++++++ tests/client/test_verify_face_from_image.py | 107 ++++++-- tests/collections/__init__.py | 0 tests/collections/test_face_collections.py | 173 ++++++++++++ .../test_face_collections_subject.py | 114 ++++++++ tests/core/__init__.py | 15 ++ tests/core/test_model.py | 123 +++++++++ tests/service/__init__.py | 15 ++ tests/service/test_detection_service.py | 55 ++++ tests/service/test_recognition_service.py | 119 ++++++++ tests/service/test_verification_service.py | 88 ++++++ .../compreface_webcam_detection_demo.py | 98 ++++--- .../compreface_webcam_recognition_demo.py | 142 +++++++--- 74 files changed, 2917 insertions(+), 702 deletions(-) create mode 100644 compreface/client/recognize_face_from_embeddings.py create mode 100644 compreface/client/verification_face_from_embeddings.py create mode 100644 compreface/client/verify_face_from_embeddings.py create mode 100644 compreface/use_cases/delete_example_by_ids.py create mode 100644 compreface/use_cases/recognize_face_from_embedding.py create mode 100644 compreface/use_cases/verification_face_from_embedding.py create mode 100644 compreface/use_cases/verify_face_from_embedding.py rename compreface/use_cases/{verifiy_face_from_images.py => verify_face_from_images.py} (79%) create mode 100644 tests/client/test_recognize_face_from_embeddings.py create mode 100644 tests/client/test_verification_face_from_embeddings.py create mode 100644 tests/client/test_verify_face_from_embeddings.py create mode 100644 tests/collections/__init__.py create mode 100644 tests/collections/test_face_collections.py create mode 100644 tests/collections/test_face_collections_subject.py create mode 100644 tests/core/__init__.py create mode 100644 tests/core/test_model.py create mode 100644 tests/service/__init__.py create mode 100644 tests/service/test_detection_service.py create mode 100644 tests/service/test_recognition_service.py create mode 100644 tests/service/test_verification_service.py diff --git a/README.md b/README.md index 9bb4685..f8da4af 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,10 @@ CompreFace Python SDK makes face recognition into your application even easier. # Table of content +- [CompreFace Python SDK](#compreface-python-sdk) +- [Table of content](#table-of-content) - [Requirements](#requirements) + - [CompreFace compatibility matrix](#compreface-compatibility-matrix) - [Installation](#installation) - [Usage](#usage) - [Initialization](#initialization) @@ -11,30 +14,34 @@ CompreFace Python SDK makes face recognition into your application even easier. - [Recognition](#recognition) - [Webcam demo](#webcam-demo) - [Reference](#reference) - - [CompreFace Global Object](#compreFace-global-object) + - [CompreFace Global Object](#compreface-global-object) - [Methods](#methods) - [Options structure](#options-structure) - [Face Recognition Service](#face-recognition-service) - [Recognize Faces from a Given Image](#recognize-faces-from-a-given-image) + - [Recognize Faces from a Given Image, Embedding](#recognize-faces-from-a-given-image-embedding) - [Get Face Collection](#get-face-collection) - [Add an Example of a Subject](#add-an-example-of-a-subject) - [List of All Saved Examples of the Subject](#list-of-all-saved-examples-of-the-subject) - [Delete All Examples of the Subject by Name](#delete-all-examples-of-the-subject-by-name) - [Delete an Example of the Subject by ID](#delete-an-example-of-the-subject-by-id) + - [Delete Multiple Examples](#delete-multiple-examples) - [Verify Faces from a Given Image](#verify-faces-from-a-given-image) + - [Verify Faces from a Given Image, Embedding](#verify-faces-from-a-given-image-embedding) - [Get Subjects](#get-subjects) - [Add a Subject](#add-a-subject) - [List Subjects](#list-subjects) - [Rename a Subject](#rename-a-subject) - [Delete a Subject](#delete-a-subject) - [Delete All Subjects](#delete-all-subjects) - - [Face Detection Service](#face-detection-service) - - [Detect](#detect) - - [Face Verification Service](#face-verification-service) - - [Verify](#verify) + - [Face Detection Service](#face-detection-service) + - [Detect](#detect) + - [Face Verification Service](#face-verification-service) + - [Verify Image](#verify-image) + - [Verify Embedding](#verify-embedding) - [Contributing](#contributing) - - [Report Bugs](#report-bugs) - - [Submit Feedback](#submit-feedback) + - [Report Bugs](#report-bugs) + - [Submit Feedback](#submit-feedback) - [License info](#license-info) # Requirements @@ -236,6 +243,10 @@ If the option’s value is set in the global object and passed as a function arg ```python +class PredictionCountOptionsDict(TypedDict): + prediction_count: int + + class DetProbOptionsDict(TypedDict): det_prob_threshold: float @@ -246,6 +257,12 @@ class ExpandedOptionsDict(DetProbOptionsDict): face_plugins: str +class SavedObjectOptions(TypedDict): + page: int + size: int + subject: str + + class AllOptionsDict(ExpandedOptionsDict): prediction_count: int @@ -286,7 +303,7 @@ Recognizes all faces from the image. The first argument is the image location, it can be an url, local path or bytes. ```python -recognition.recognize(image_path, options) +recognition.recognize_image(image_path, options) ``` | Argument | Type | Required | Notes | @@ -359,6 +376,47 @@ Response: | execution_time | object | execution time of all plugins | | plugins_versions | object | contains information about plugin versions | +### Recognize Faces from a Given Image, Embedding + +Determine similarities between input embeddings and embeddings within the Face Collection. + +```python +recognition.recognize_embedding(self, embeddings: list, options: PredictionCountOptionsDict = {}) +``` + +| Argument | Type | Required | Notes | +| ------------------ | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| embeddings | list | required | An input embeddings. The length depends on the model (e.g. 512 or 128) | +| options | object | optional | `PredictionCountOptionsDict` object can be used in this method. See more [here](#options-structure). | + +Response: + +```json +{ + "result": [ + { + "embedding": [0.0627421774604647, "...", -0.0236684433507126], + "similarities": [ + { + "subject": "John", + "similarity": 0.55988 + }, + "..." + ] + }, + "..." + ] +} +``` + +| Element | Type | Description | +|--------------|--------|--------------------------------------------------------------------------------------------| +| result | array | an array that contains all the results | +| embedding | array | an input embedding | +| similarities | array | an array that contains results of similarity between the embedding and the input embedding | +| subject | string | a subject in which the similar embedding was found | +| similarity | float | a similarity between the embedding and the input embedding | + ### Get Face Collection ```python @@ -411,8 +469,11 @@ Response: To retrieve a list of subjects saved in a Face Collection: ```python -face_collection.list() +face_collection.list(options) ``` +| Argument | Type | Required | Notes | +| ------------------ | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| options | object | optional | `SavedObjectOptions` object can be used in this method. See more [here](#options-structure). | Response: @@ -424,7 +485,11 @@ Response: "subject": }, ... - ] + ], + "page_number": int, + "page_size": int, + "total_pages": int, + "total_elements": int } ``` @@ -432,6 +497,10 @@ Response: | -------- | ------ | ----------------------------------------------------------------- | | image_id | UUID | UUID of the face | | subject | string | of the person, whose picture was saved for this api key | +| page_number | integer | page number | +| page_size | integer | **requested** page size | +| total_pages | integer | total pages | +| total_elements | integer | total faces | #### Delete All Examples of the Subject by Name @@ -486,12 +555,40 @@ Response: | image_id | UUID | UUID of the removed face | | subject | string | of the person, whose picture was saved for this api key | + +#### Delete Multiple Examples + +To delete several subject examples + +```python +face_collection.delete_multiple(image_ids) +``` +| Argument | Type | Required | Notes | +| --------- | ------ | -------- | ------------------------------------------------------------ +| image_ids | list | required | list of str or UUID of the removing face | + +Response: + +``` +{ + "image_id": , + "subject": +} +``` + +| Element | Type | Description | +| -------- | ------ | ----------------------------------------------------------------- | +| image_id | UUID | UUID of the removed face | +| subject | string | of the person, whose picture was saved for this api key | + +If some image ids are not exists, they will be ignored + #### Verify Faces from a Given Image *[Example](examples/verification_face_from_image.py)* ```python -face_collection.verify(image_path, image_id, options) +face_collection.verify_image(image_path, image_id, options) ``` Compares similarities of given image with image from your face collection. @@ -566,6 +663,41 @@ Response: | execution_time | object | execution time of all plugins | | plugins_versions | object | contains information about plugin versions | + +#### Verify Faces from a Given Image, Embedding + +```python +face_collection.verify_embeddings(embedding, image_id) +``` + +Compare input embeddings to the embedding stored in Face Collection. + + +| Argument | Type | Required | Notes | +| ------------------ | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| embeddings | array | required | An input embeddings. The length depends on the model (e.g. 512 or 128) | +| image_id | str or UUID | required | An id of the source embedding within the Face Collection | + +Response: + +```json +{ + "result": [ + { + "embedding": [0.0627421774604647, "...", -0.0236684433507126], + "similarity": 0.55988 + }, + "..." + ] +} +``` + +| Element | Type | Description | +|-------------|--------|------------------------------------------------------------------------------| +| result | array | an array that contains all the results | +| embedding | array | a source embedding which we are comparing to embedding from Face Collection | +| similarity | float | a similarity between the source embedding and embedding from Face Collection | + ### Get Subjects ```python @@ -614,6 +746,7 @@ Returns all subject related to Face Collection. subjects.list() ``` + Response: ```json @@ -792,10 +925,10 @@ A source image should contain only one face which will be compared to all faces **Methods:** -### Verify +### Verify Image ```python -verify.verify(source_image_path, target_image_path, options) +verify.verify_image(source_image_path, target_image_path, options) ``` Compares two images provided in arguments. Source image should contain only one face, it will be compared to all faces in the target image. @@ -903,6 +1036,39 @@ Response: | execution_time | object | execution time of all plugins | | plugins_versions | object | contains information about plugin versions | +### Verify Embedding + +```python +verify.verify_embedding(source_embeddings, targets_embeddings) +``` + +Determine similarities between an input source embedding and input target embeddings. + +| Argument | Type | Required | Notes | +| ------------------ | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| source_embeddings | list | required | An input embeddings. The length depends on the model (e.g. 512 or 128) | +| targets_embeddings | list | required | An array of the target embeddings. The length depends on the model (e.g. 512 or 128) | + +Response: + +```json +{ + "result": [ + { + "embedding": [0.0627421774604647, "...", -0.0236684433507126], + "similarity": 0.55988 + }, + "..." + ] +} +``` + +| Element | Type | Description | +|-------------|--------|--------------------------------------------------------------------| +| result | array | an array that contains all the results | +| embedding | array | a target embedding which we are comparing to source embedding | +| similarity | float | a similarity between the source embedding and the target embedding | + # Contributing Contributions are what make the open source community such an amazing place to be learn, inspire, and create. Any contributions you make are greatly appreciated. diff --git a/compreface/client/__init__.py b/compreface/client/__init__.py index a99b78b..09a01ee 100644 --- a/compreface/client/__init__.py +++ b/compreface/client/__init__.py @@ -21,3 +21,6 @@ from .detect_face_from_image import DetectFaceFromImageClient from .verify_face_from_image import VerifyFaceFromImageClient from .subject_client import SubjectClient +from .recognize_face_from_embeddings import RecognizeFaceFromEmbeddingClient +from .verification_face_from_embeddings import VerificationFaceFromEmbeddingClient +from .verify_face_from_embeddings import VerifyFaceFromEmbeddingClient diff --git a/compreface/client/add_example_of_subject.py b/compreface/client/add_example_of_subject.py index ef4ce9e..77f47e1 100644 --- a/compreface/client/add_example_of_subject.py +++ b/compreface/client/add_example_of_subject.py @@ -17,18 +17,21 @@ import requests from compreface.common.multipart_constructor import multipart_constructor -from compreface.common.typed_dict import DetProbOptionsDict, check_fields_by_name +from compreface.common.typed_dict import ( + DetProbOptionsDict, + SavedObjectOptions, + check_fields_by_name, +) from compreface.config.api_list import RECOGNIZE_CRUD_API from ..common import ClientRequest class AddExampleOfSubjectClient(ClientRequest): - def __init__(self, api_key: str, domain: str, port: str): super().__init__() self.client_url: str = RECOGNIZE_CRUD_API self.api_key: str = api_key - self.url: str = domain + ':' + port + self.client_url + self.url: str = domain + ":" + port + self.client_url """ GET request for get all subjects. @@ -36,9 +39,14 @@ def __init__(self, api_key: str, domain: str, port: str): :return: json with subjects from server. """ - def get(self) -> dict: - url: str = self.url - result = requests.get(url, headers={'x-api-key': self.api_key}) + def get(self, options: SavedObjectOptions = {}) -> dict: + url: str = self.url + "?" + for key in options.keys(): + # Checks fields with necessary rules. + # key - key field by options. + check_fields_by_name(key, options[key]) + url += "&" + key + "=" + str(options[key]) + result = requests.get(url, headers={"x-api-key": self.api_key}) return result.json() """ @@ -51,17 +59,25 @@ def get(self) -> dict: :return: json with this subject from server. """ - def post(self, image: str = '' or bytes, subject: str = '', options: DetProbOptionsDict = {}) -> dict: - url: str = self.url + '?subject=' + subject + def post( + self, + image: str = "" or bytes, + subject: str = "", + options: DetProbOptionsDict = {}, + ) -> dict: + url: str = self.url + "?subject=" + subject # Validation loop and adding fields to the url. for key in options.keys(): # Checks fields with necessary rules. # key - key field by options. check_fields_by_name(key, options[key]) - url += '&' + key + "=" + str(options[key]) + url += "&" + key + "=" + str(options[key]) m = multipart_constructor(image) - result = requests.post(url, data=m, headers={'Content-Type': m.content_type, - 'x-api-key': self.api_key}) + result = requests.post( + url, + data=m, + headers={"Content-Type": m.content_type, "x-api-key": self.api_key}, + ) return result.json() def put(self): @@ -75,7 +91,7 @@ def put(self): :return: json from server. """ - def delete(self, subject: str = ''): - url: str = self.url + '?subject=' + subject - result = requests.delete(url, headers={'x-api-key': self.api_key}) + def delete(self, subject: str = ""): + url: str = self.url + "?subject=" + subject + result = requests.delete(url, headers={"x-api-key": self.api_key}) return result.json() diff --git a/compreface/client/delete_example_by_id.py b/compreface/client/delete_example_by_id.py index 7e84f9d..c6a44c1 100644 --- a/compreface/client/delete_example_by_id.py +++ b/compreface/client/delete_example_by_id.py @@ -23,20 +23,30 @@ class DeleteExampleByIdClient(ClientRequest): """ - Delete example by id from image_id. + Delete example by id from image_id. """ def __init__(self, api_key: str, domain: str, port: str): super().__init__() self.client_url: str = RECOGNIZE_CRUD_API self.api_key: str = api_key - self.url: str = domain + ':' + port + self.client_url + self.url: str = domain + ":" + port + self.client_url def get(self): pass - def post(self): - pass + """ + POST request to delete several subject examples. + + :param image_id: UUID of the removing face. + + :return: json from server. + """ + + def post(self, image_ids: list = []): + url: str = self.url + "/delete" + result = requests.post(url, json=image_ids, headers={"x-api-key": self.api_key}) + return result.json() def put(self): pass @@ -49,7 +59,7 @@ def put(self): :return: json from server. """ - def delete(self, image_id: str = ''): - url: str = self.url + '/' + image_id - result = requests.delete(url, headers={'x-api-key': self.api_key}) + def delete(self, image_id: str = ""): + url: str = self.url + "/" + image_id + result = requests.delete(url, headers={"x-api-key": self.api_key}) return result.json() diff --git a/compreface/client/detect_face_from_image.py b/compreface/client/detect_face_from_image.py index d6da005..9dca107 100644 --- a/compreface/client/detect_face_from_image.py +++ b/compreface/client/detect_face_from_image.py @@ -23,14 +23,14 @@ class DetectFaceFromImageClient(ClientRequest): """ - Detection faces in image. It uses image path for encode and send to CompreFace server. + Detection faces in image. It uses image path for encode and send to CompreFace server. """ def __init__(self, api_key: str, domain: str, port: str): super().__init__() self.client_url: str = DETECTION_API self.api_key: str = api_key - self.url: str = domain + ':' + port + self.client_url + self.url: str = domain + ":" + port + self.client_url def get(self): pass @@ -44,22 +44,25 @@ def get(self): :return: json from server. """ - def post(self, image: str = '' or bytes, options: ExpandedOptionsDict = {}): - url: str = self.url + '?' + def post(self, image: str = "" or bytes, options: ExpandedOptionsDict = {}): + url: str = self.url + "?" # Validation loop and adding fields to the url. for key in options.keys(): # Checks fields with necessary rules. # key - key field by options. check_fields_by_name(key, options[key]) - url += '&' + key + "=" + str(options[key]) + url += "&" + key + "=" + str(options[key]) # Encoding image from path and encode in multipart for sending to the server. m = multipart_constructor(image) # Sending encode image for detection faces. - result = requests.post(url, data=m, headers={'Content-Type': m.content_type, - 'x-api-key': self.api_key}) + result = requests.post( + url, + data=m, + headers={"Content-Type": m.content_type, "x-api-key": self.api_key}, + ) return result.json() def put(self): diff --git a/compreface/client/recognize_face_from_embeddings.py b/compreface/client/recognize_face_from_embeddings.py new file mode 100644 index 0000000..a9693ac --- /dev/null +++ b/compreface/client/recognize_face_from_embeddings.py @@ -0,0 +1,69 @@ +""" + Copyright(c) 2021 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. + """ +import requests + +from compreface.common.typed_dict import ( + PredictionCountOptionsDict, + check_fields_by_name, +) +from compreface.config.api_list import RECOGNIZE_EMBEDDINGS_API +from ..common import ClientRequest + + +class RecognizeFaceFromEmbeddingClient(ClientRequest): + """ + The service is used to determine similarities between input embeddings and embeddings within the Face Collection. + """ + + def __init__(self, api_key: str, domain: str, port: str): + super().__init__() + self.client_url: str = RECOGNIZE_EMBEDDINGS_API + self.api_key: str = api_key + self.url: str = domain + ":" + port + self.client_url + + def get(self): + pass + + """ + POST request for recognize faces in embeddings. + + :param embeddings: An input embeddings. The length depends on the model. + :param options: dictionary with options for server. + + :return: json from server. + """ + + def post(self, embeddings: list = [], options: PredictionCountOptionsDict = {}): + url: str = self.url + "/recognize?" + + # Validation loop and adding fields to the url. + for key in options.keys(): + # Checks fields with necessary rules. + # key - key field by options. + check_fields_by_name(key, options[key]) + url += "&" + key + "=" + str(options[key]) + + # Sending an input source embedding for recognize faces. + result = requests.post( + url, json={"embeddings": embeddings}, headers={"x-api-key": self.api_key} + ) + return result.json() + + def put(self): + pass + + def delete(self): + pass diff --git a/compreface/client/recognize_face_from_image.py b/compreface/client/recognize_face_from_image.py index 09f8d31..ac13c5c 100644 --- a/compreface/client/recognize_face_from_image.py +++ b/compreface/client/recognize_face_from_image.py @@ -23,14 +23,14 @@ class RecognizeFaceFromImageClient(ClientRequest): """ - Recognize faces in image. It uses image path for encode and send to CompreFace server. + Recognize faces in image. It uses image path for encode and send to CompreFace server. """ def __init__(self, api_key: str, domain: str, port: str): super().__init__() self.client_url: str = RECOGNIZE_API self.api_key: str = api_key - self.url: str = domain + ':' + port + self.client_url + self.url: str = domain + ":" + port + self.client_url def get(self): pass @@ -44,7 +44,7 @@ def get(self): :return: json from server. """ - def post(self, image: str = '' or bytes, options: AllOptionsDict = {}): + def post(self, image: str = "" or bytes, options: AllOptionsDict = {}): url: str = self.url + "?" # Validation loop and adding fields to the url. @@ -52,14 +52,17 @@ def post(self, image: str = '' or bytes, options: AllOptionsDict = {}): # Checks fields with necessary rules. # key - key field by options. check_fields_by_name(key, options[key]) - url += '&' + key + "=" + str(options[key]) + url += "&" + key + "=" + str(options[key]) # Encoding image from path and encode in multipart for sending to the server. m = multipart_constructor(image) # Sending encode image for recognize faces. - result = requests.post(url, data=m, headers={'Content-Type': m.content_type, - 'x-api-key': self.api_key}) + result = requests.post( + url, + data=m, + headers={"Content-Type": m.content_type, "x-api-key": self.api_key}, + ) return result.json() def put(self): diff --git a/compreface/client/subject_client.py b/compreface/client/subject_client.py index 172367b..079f7ec 100644 --- a/compreface/client/subject_client.py +++ b/compreface/client/subject_client.py @@ -22,13 +22,12 @@ class SubjectClient(ClientRequest): - def __init__(self, api_key: str, domain: str, port: str): super().__init__() self.client_url: str = SUBJECTS_CRUD_API self.api_key: str = api_key - self.url: str = domain + ':' + port + self.client_url - self.headers = {'Content-Type': 'application/json', 'x-api-key': api_key} + self.url: str = domain + ":" + port + self.client_url + self.headers = {"Content-Type": "application/json", "x-api-key": api_key} """ GET request for get all subjects. @@ -49,7 +48,7 @@ def get(self) -> dict: :return: json with this subject from server. """ - def post(self, subject: dict = '') -> dict: + def post(self, subject: dict = "") -> dict: url: str = self.url result = requests.post(url, data=json.dumps(subject), headers=self.headers) return result.json() @@ -62,8 +61,8 @@ def post(self, subject: dict = '') -> dict: :return: json from server. """ - def put(self, request: dict = '') -> dict: - url: str = self.url + '/' + request.get('api_endpoint') + def put(self, request: dict = "") -> dict: + url: str = self.url + "/" + request.get("api_endpoint") result = requests.put(url, data=json.dumps(request), headers=self.headers) return result.json() @@ -75,7 +74,7 @@ def put(self, request: dict = '') -> dict: :return: json from server. """ - def delete(self, subject: str = '') -> dict: - url: str = self.url + '/' + subject if subject else self.url + def delete(self, subject: str = "") -> dict: + url: str = self.url + "/" + subject if subject else self.url result = requests.delete(url, headers=self.headers) return result.json() diff --git a/compreface/client/verification_face_from_embeddings.py b/compreface/client/verification_face_from_embeddings.py new file mode 100644 index 0000000..4d4f403 --- /dev/null +++ b/compreface/client/verification_face_from_embeddings.py @@ -0,0 +1,62 @@ +""" + Copyright(c) 2021 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. + """ +import requests + +from compreface.config.api_list import RECOGNIZE_EMBEDDINGS_API +from compreface.exceptions.field_exception import IncorrectFieldException +from ..common import ClientRequest + + +class VerificationFaceFromEmbeddingClient(ClientRequest): + """ + Face Verification Service, Embedding. + """ + + def __init__(self, api_key: str, domain: str, port: str): + super().__init__() + self.client_url: str = RECOGNIZE_EMBEDDINGS_API + self.api_key: str = api_key + self.url: str = domain + ":" + port + self.client_url + + def get(self): + pass + + """ + POST the endpoint is used to compare input embeddings to the embedding stored in Face Collection. + + :param image_id: an id of the source embedding within the Face Collection. + :param embeddings: an input embeddings. The length depends on the model. + + :return: json from server. + """ + + def post(self, embeddings: list = [], image_id: str = "") -> dict: + if image_id is None or image_id == "": + raise IncorrectFieldException("image_id should be not empty.") + + url: str = self.url + "/faces/{}/verify".format(image_id) + + # Sending input source embedding and input target embeddings. + result = requests.post( + url, json={"embeddings": embeddings}, headers={"x-api-key": self.api_key} + ) + return result.json() + + def put(self): + pass + + def delete(self): + pass diff --git a/compreface/client/verification_face_from_image.py b/compreface/client/verification_face_from_image.py index 022dd5b..c72889b 100644 --- a/compreface/client/verification_face_from_image.py +++ b/compreface/client/verification_face_from_image.py @@ -23,15 +23,15 @@ class VerificationFaceFromImageClient(ClientRequest): """ - Compare face in image. It uses image path for encode and send to CompreFace - server with validation by image id. + Compare face in image. It uses image path for encode and send to CompreFace + server with validation by image id. """ def __init__(self, api_key: str, domain: str, port: str): super().__init__() self.client_url: str = RECOGNIZE_CRUD_API self.api_key: str = api_key - self.url: str = domain + ':' + port + self.client_url + self.url: str = domain + ":" + port + self.client_url def get(self): pass @@ -46,26 +46,30 @@ def get(self): :return: json from server. """ - def post(self, - image: str = '' or bytes, - image_id: str = '', - options: ExpandedOptionsDict = {}) -> dict: - - url: str = self.url + '/' + image_id + '/verify?' + def post( + self, + image: str = "" or bytes, + image_id: str = "", + options: ExpandedOptionsDict = {}, + ) -> dict: + url: str = self.url + "/" + image_id + "/verify?" # Validation loop and adding fields to the url. for key in options.keys(): # Checks fields with necessary rules. # key - key field by options. check_fields_by_name(key, options[key]) - url += '&' + key + "=" + str(options[key]) + url += "&" + key + "=" + str(options[key]) # Encoding image from path and encode in multipart for sending to the server. m = multipart_constructor(image) # Sending encode image for verify face. - result = requests.post(url, data=m, headers={'Content-Type': m.content_type, - 'x-api-key': self.api_key}) + result = requests.post( + url, + data=m, + headers={"Content-Type": m.content_type, "x-api-key": self.api_key}, + ) return result.json() def put(self): diff --git a/compreface/client/verify_face_from_embeddings.py b/compreface/client/verify_face_from_embeddings.py new file mode 100644 index 0000000..edb36ee --- /dev/null +++ b/compreface/client/verify_face_from_embeddings.py @@ -0,0 +1,61 @@ +""" + Copyright(c) 2021 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. + """ +import requests + +from compreface.config.api_list import VERIFICATION_EMBEDDINGS_API +from compreface.common.client import ClientRequest + + +class VerifyFaceFromEmbeddingClient(ClientRequest): + """ + Verify Faces from a Given Image, Embedding. + """ + + def __init__(self, api_key: str, domain: str, port: str): + super().__init__() + self.client_url: str = VERIFICATION_EMBEDDINGS_API + self.api_key: str = api_key + self.url: str = domain + ":" + port + self.client_url + + def get(self): + pass + + """ + POST request for compare face between an input source embedding and input target embeddings. + + + :param source_embeddings: An input embeddings. The length depends on the model. + :param targets_embeddings: An array of the target embeddings. The length depends on the model. + + :return: json from server. + """ + + def post(self, source_embeddings: list, targets_embeddings: list) -> dict: + url: str = self.url + "/verify" + + # Sending an input source embedding. + result = requests.post( + url, + json={"targets": source_embeddings, "source": targets_embeddings}, + headers={"x-api-key": self.api_key}, + ) + return result.json() + + def put(self): + pass + + def delete(self): + pass diff --git a/compreface/client/verify_face_from_image.py b/compreface/client/verify_face_from_image.py index 4b7b196..8cd4a4c 100644 --- a/compreface/client/verify_face_from_image.py +++ b/compreface/client/verify_face_from_image.py @@ -14,7 +14,9 @@ permissions and limitations under the License. """ -from compreface.common.multipart_constructor import multipart_constructor_with_two_images +from compreface.common.multipart_constructor import ( + multipart_constructor_with_two_images, +) import requests from compreface.config.api_list import VERIFICATION_API from compreface.common.typed_dict import ExpandedOptionsDict, check_fields_by_name @@ -23,15 +25,15 @@ class VerifyFaceFromImageClient(ClientRequest): """ - Verify face in image. It uses source and target images for encode and send to CompreFace - server with validation by image id. + Verify face in image. It uses source and target images for encode and send to CompreFace + server with validation by image id. """ def __init__(self, api_key: str, domain: str, port: str): super().__init__() self.client_url: str = VERIFICATION_API self.api_key: str = api_key - self.url: str = domain + ':' + port + self.client_url + self.url: str = domain + ":" + port + self.client_url def get(self): pass @@ -47,25 +49,29 @@ def get(self): :return: json from server. """ - def post(self, - source_image: str = '' or bytes, - target_image: str = '' or bytes, - options: ExpandedOptionsDict = {}) -> dict: - - url: str = self.url + '/verify?' + def post( + self, + source_image: str = "" or bytes, + target_image: str = "" or bytes, + options: ExpandedOptionsDict = {}, + ) -> dict: + url: str = self.url + "/verify?" # Validation loop and adding fields to the url. for key in options.keys(): # Checks fields with necessary rules. # key - key field by options. check_fields_by_name(key, options[key]) - url += '&' + key + "=" + str(options[key]) + url += "&" + key + "=" + str(options[key]) # Encoding image from path and encode in multipart for sending to the server. m = multipart_constructor_with_two_images(source_image, target_image) # Sending encode image for verify face. - result = requests.post(url, data=m, headers={'Content-Type': m.content_type, - 'x-api-key': self.api_key}) + result = requests.post( + url, + data=m, + headers={"Content-Type": m.content_type, "x-api-key": self.api_key}, + ) return result.json() def put(self): diff --git a/compreface/collections/face_collections.py b/compreface/collections/face_collections.py index 3acdc62..243a48f 100644 --- a/compreface/collections/face_collections.py +++ b/compreface/collections/face_collections.py @@ -14,7 +14,13 @@ permissions and limitations under the License. """ -from compreface.common.typed_dict import AllOptionsDict, ExpandedOptionsDict, DetProbOptionsDict, pass_dict +from compreface.common.typed_dict import ( + AllOptionsDict, + ExpandedOptionsDict, + DetProbOptionsDict, + SavedObjectOptions, + pass_dict, +) from ..use_cases import ( AddExampleOfSubject, AddSubject, @@ -22,182 +28,247 @@ DeleteSubjectByName, DeleteAllSubjects, DeleteExampleById, + DeleteExampleByIds, GetSubjects, UpdateSubject, VerificationFaceFromImage, - ListOfAllSavedSubjects + VerificationFaceFromEmbedding, + ListOfAllSavedSubjects, ) class FaceCollection: - def __init__(self, api_key: str, domain: str, port: str, options: AllOptionsDict = {}): + def __init__( + self, api_key: str, domain: str, port: str, options: AllOptionsDict = {} + ): """Init service with define API Key""" self.available_services = [] self.api_key = api_key self.options = options self.add_example: AddExampleOfSubject = AddExampleOfSubject( - domain=domain, - port=port, - api_key=api_key + domain=domain, port=port, api_key=api_key ) - self.list_of_all_saved_subjects: ListOfAllSavedSubjects = ListOfAllSavedSubjects( - domain=domain, - port=port, - api_key=api_key + self.list_of_all_saved_subjects: ListOfAllSavedSubjects = ( + ListOfAllSavedSubjects(domain=domain, port=port, api_key=api_key) ) self.delete_all_examples_of_subject_by_name: DeleteAllExamplesOfSubjectByName = DeleteAllExamplesOfSubjectByName( - domain=domain, - port=port, - api_key=api_key + domain=domain, port=port, api_key=api_key ) self.delete_all_examples_by_id: DeleteExampleById = DeleteExampleById( - domain=domain, - port=port, - api_key=api_key + domain=domain, port=port, api_key=api_key + ) + self.delete_all_examples_by_ids: DeleteExampleByIds = DeleteExampleByIds( + domain=domain, port=port, api_key=api_key ) - self.verify_face_from_image: VerificationFaceFromImage = VerificationFaceFromImage( - domain=domain, - port=port, - api_key=api_key + self.verify_face_from_image: VerificationFaceFromImage = ( + VerificationFaceFromImage(domain=domain, port=port, api_key=api_key) + ) + self.verify_face_from_embeddings: VerificationFaceFromEmbedding = ( + VerificationFaceFromEmbedding(domain=domain, port=port, api_key=api_key) ) - def list(self) -> dict: + def list(self, options: SavedObjectOptions = {}) -> dict: """ - Get list of collections + Retrieve a list of subjects saved in a Face Collection + + :param options: Optional[dict] + + Options contains args: + + page int: page number of examples to return. Can be used for pagination. Default value is 0. + size int: faces on page (page size). Can be used for pagination. Default value is 20. + subject str: what subject examples endpoint should return. If empty, return examples for all subjects. + :return: """ - return self.list_of_all_saved_subjects.execute() + return self.list_of_all_saved_subjects.execute( + pass_dict(options, SavedObjectOptions) if options == {} else options + ) - def add(self, image_path: str, subject: str, options: DetProbOptionsDict = {}) -> dict: + def add( + self, image_path: str, subject: str, options: DetProbOptionsDict = {} + ) -> dict: """ - Add example to collection - :param image_path: - :param subject: + This creates an example of the subject by saving images. + You can add as many images as you want to train the system. Image should contain only one face. + + :param image_path: str, path to the image, + allowed image formats: jpeg, jpg, ico, png, bmp, gif, tif, tiff, webp. Max size is 5Mb + + :param subject: str, it is the name you assign to the image you save + :param options: dict[Optional] + + Options contains args: + det_prob_threshold float: minimum required confidence that a recognized face is actually a face. + Value is between 0.0 and 1.0. + :return: """ request = AddExampleOfSubject.Request( - api_key=self.api_key, - image_path=image_path, - subject=subject + api_key=self.api_key, image_path=image_path, subject=subject + ) + return self.add_example.execute( + request, + pass_dict(options, DetProbOptionsDict) if options == {} else options, ) - return self.add_example.execute(request, pass_dict(options, DetProbOptionsDict) if options == {} else options) def delete(self, image_id: str) -> dict: """ - Delete example by Id - :param image_id: - :return: + Delete an Example of the Subject by ID + + :param image_id: str or UUID of the removing face + :return: dict """ - request = DeleteExampleById.Request( - api_key=self.api_key, - image_id=image_id - ) + request = DeleteExampleById.Request(api_key=self.api_key, image_id=image_id) return self.delete_all_examples_by_id.execute(request) + def delete_multiple(self, image_ids: list) -> dict: + """ + Delete several subject examples. + + :param image_ids: list of str or UUID of the removing face. + :return dict: + """ + request = DeleteExampleByIds.Request(api_key=self.api_key, image_ids=image_ids) + return self.delete_all_examples_by_ids.execute(request) + def delete_all(self, subject: str) -> dict: """ - Delete all examples of subject - :param subject: - :return: + Delete All Examples of the Subject by Name + + :param subject: str, it is the name subject. + If this parameter is absent, all faces in Face Collection will be removed. + + :return: dict """ request = DeleteAllExamplesOfSubjectByName.Request( - api_key=self.api_key, - subject=subject + api_key=self.api_key, subject=subject ) return self.delete_all_examples_of_subject_by_name.execute(request) - def verify(self, image_path: str, image_id: str, options: ExpandedOptionsDict = {}) -> dict: + def verify_image( + self, image_path: str, image_id: str, options: ExpandedOptionsDict = {} + ) -> dict: """ - Compare image - :param image_path: - :param image_id: - :return: + Compare faces from the uploaded images with the face in saved image ID. + + :param image_path: str, path to the image, + allowed image formats: jpeg, jpg, ico, png, bmp, gif, tif, tiff, webp. Max size is 5Mb + + :param image_id: str or UUID of the verifying face + :param options: dict, Optional. + + Options contains args: + limit int: maximum number of faces on the target image to be recognized. + It recognizes the biggest faces first. Value of 0 represents no limit. + Default value: 0 + + det_prob_threshold float: minimum required confidence that a recognized face is actually a face. + Value is between 0.0 and 1.0. + + face_plugins str: comma-separated slugs of face plugins. + If empty, no additional information is returned. + + status bool: if true includes system information like execution_time and plugin_version fields. + Default value is false + + + :return: dict """ request = VerificationFaceFromImage.Request( - api_key=self.api_key, - image_path=image_path, - image_id=image_id + api_key=self.api_key, image_path=image_path, image_id=image_id + ) + return self.verify_face_from_image.execute( + request, + pass_dict(options, ExpandedOptionsDict) if options == {} else options, + ) + + def verify_embeddings(self, embeddings: list, image_id: str) -> dict: + """ + Compare input embeddings to the embedding stored in Face Collection. + + :param embeddings: list, an input embeddings. The length depends on the model (e.g. 512 or 128) + :param image_id: str or UUID, an id of the source embedding within the Face Collection + + :return: dict + """ + request = VerificationFaceFromEmbedding.Request( + api_key=self.api_key, embeddings=embeddings, image_id=image_id ) - return self.verify_face_from_image.execute(request, pass_dict(options, ExpandedOptionsDict) if options == {} else options) + + return self.verify_face_from_embeddings.execute(request) class Subjects: - def __init__(self, api_key: str, domain: str, port: str, options: AllOptionsDict = {}): + def __init__( + self, api_key: str, domain: str, port: str, options: AllOptionsDict = {} + ): """Init service with define API Key""" self.available_services = [] self.api_key = api_key self.options = options self.add_subject: AddSubject = AddSubject( - domain=domain, - port=port, - api_key=api_key + domain=domain, port=port, api_key=api_key ) self.update_subject: UpdateSubject = UpdateSubject( - domain=domain, - port=port, - api_key=api_key + domain=domain, port=port, api_key=api_key ) self.delete_subject: DeleteSubjectByName = DeleteSubjectByName( - domain=domain, - port=port, - api_key=api_key + domain=domain, port=port, api_key=api_key ) self.delete_all_subjects: DeleteAllSubjects = DeleteAllSubjects( - domain=domain, - port=port, - api_key=api_key + domain=domain, port=port, api_key=api_key ) self.list_of_all_saved_subjects: GetSubjects = GetSubjects( - domain=domain, - port=port, - api_key=api_key + domain=domain, port=port, api_key=api_key ) def list(self) -> dict: """ - Get list of subjects + This returns all subject related to Face Collection. + :return: """ return self.list_of_all_saved_subjects.execute() def add(self, subject: str) -> dict: """ - Add subject - :param subject: - :return: + Create a new subject in Face Collection. Creating a subject is an optional step, + you can upload an example without an existing subject, and a subject will be created automatically. + + :param subject: str, it is the name of the subject. It can be a person name, but it can be any string + :return: dict """ - request = AddSubject.Request( - subject=subject - ) + request = AddSubject.Request(subject=subject) return self.add_subject.execute(request) def update(self, subject: str, new_name: str) -> dict: """ - Update subject by name - :param subject: - :param new_name: - :return: + Rename existing subject. If a new subject name already exists, + subjects are merged - all faces from the old subject name are reassigned to + the subject with the new name, old subject removed. + + :param subject: str, existing subject + :param new_name: str, it is the name of the subject. It can be a person name, but it can be any string + :return: dict """ - request = UpdateSubject.Request( - subject=new_name, - api_endpoint=subject - ) + request = UpdateSubject.Request(subject=new_name, api_endpoint=subject) return self.update_subject.execute(request) def delete(self, subject: str) -> dict: """ - Delete subject by name - :param subject: - :return: + Delete existing subject and all saved faces. + + :param subject: str,it is the name of the subject. It can be a person name, but it can be any string + :return: dict """ - request = DeleteSubjectByName.Request( - subject=subject - ) + request = DeleteSubjectByName.Request(subject=subject) return self.delete_subject.execute(request) def delete_all(self) -> dict: """ - Delete all subjects - :return: + Delete all existing subjects and all saved faces. + + :return: dict """ return self.delete_all_subjects.execute() diff --git a/compreface/common/multipart_constructor.py b/compreface/common/multipart_constructor.py index ba3fbf0..a322f64 100644 --- a/compreface/common/multipart_constructor.py +++ b/compreface/common/multipart_constructor.py @@ -4,30 +4,31 @@ from requests_toolbelt.multipart.encoder import MultipartEncoder -def get_file(image: str = '' or bytes): +def get_file(image: str = "" or bytes): if not os.path.isfile(image): if type(image) != bytes: response = requests.get(image) file = response.content else: file = image - file = ('image.jpg', file) + file = ("image.jpg", file) else: name_img: str = os.path.basename(image) - file = (name_img, open(image, 'rb')) + file = (name_img, open(image, "rb")) return file -def multipart_constructor(image: str = '' or bytes): - +def multipart_constructor(image: str = "" or bytes): # Encoding image from path and encode in multipart for sending to the server. - return MultipartEncoder( - fields={'file': get_file(image)} - ) + return MultipartEncoder(fields={"file": get_file(image)}) -def multipart_constructor_with_two_images(source_image: str = '' or bytes, target_image: str = '' or bytes): +def multipart_constructor_with_two_images( + source_image: str = "" or bytes, target_image: str = "" or bytes +): return MultipartEncoder( - fields={'source_image': get_file( - source_image), 'target_image': get_file(target_image)} + fields={ + "source_image": get_file(source_image), + "target_image": get_file(target_image), + } ) diff --git a/compreface/common/typed_dict.py b/compreface/common/typed_dict.py index 161562c..4720f49 100644 --- a/compreface/common/typed_dict.py +++ b/compreface/common/typed_dict.py @@ -18,6 +18,10 @@ from typing import Any, TypedDict +class PredictionCountOptionsDict(TypedDict): + prediction_count: int + + class DetProbOptionsDict(TypedDict): det_prob_threshold: float @@ -28,6 +32,12 @@ class ExpandedOptionsDict(DetProbOptionsDict): face_plugins: str +class SavedObjectOptions(TypedDict): + page: int + size: int + subject: str + + class AllOptionsDict(ExpandedOptionsDict): prediction_count: int @@ -42,24 +52,39 @@ class AllOptionsDict(ExpandedOptionsDict): def check_fields_by_name(name: str, value: Any): - if name == 'limit' or name == "prediction_count": + if name == "limit" or name == "prediction_count": if value < 0: raise IncorrectFieldException( - '{} must be greater or equal zero.'.format(name)) - if name == 'det_prob_threshold': + "{} must be greater or equal zero.".format(name) + ) + if name == "det_prob_threshold": if value < 0.0 or value > 1.0: raise IncorrectFieldException( - 'det_prob_threshold must be between 0.0 and 1.0. Received value {}'.format(value)) + "det_prob_threshold must be between 0.0 and 1.0. Received value {}".format( + value + ) + ) if name == "face_plugins": values = value.strip() - for row in values.split(','): - if row == ',': + for row in values.split(","): + if row == ",": pass - if row.find('age') == -1 and row.find('calculator') == -1 and row.find('gender') == -1 \ - and row.find('landmarks') == -1 and row.find('mask') == -1: + if ( + row.find("age") == -1 + and row.find("calculator") == -1 + and row.find("gender") == -1 + and row.find("landmarks") == -1 + and row.find("mask") == -1 + ): raise IncorrectFieldException( "face_plugins must be only contains calculator,age,gender,landmarks,mask. " - "Incorrect value {}".format(row)) + "Incorrect value {}".format(row) + ) + if name == "page" or name == "size": + if value < 0: + raise IncorrectFieldException( + "{} must be greater or equal zero.".format(name) + ) def pass_dict(options: AllOptionsDict, type: DetProbOptionsDict or ExpandedOptionsDict): diff --git a/compreface/config/api_list.py b/compreface/config/api_list.py index 52d63e0..3758970 100644 --- a/compreface/config/api_list.py +++ b/compreface/config/api_list.py @@ -15,12 +15,15 @@ """ -RECOGNITION_ROOT_API: str = '/api/v1/recognition' +RECOGNITION_ROOT_API: str = "/api/v1/recognition" -RECOGNIZE_API: str = RECOGNITION_ROOT_API + '/recognize' -RECOGNIZE_CRUD_API: str = RECOGNITION_ROOT_API + '/faces' -SUBJECTS_CRUD_API: str = RECOGNITION_ROOT_API + '/subjects' +RECOGNIZE_API: str = RECOGNITION_ROOT_API + "/recognize" +RECOGNIZE_CRUD_API: str = RECOGNITION_ROOT_API + "/faces" +SUBJECTS_CRUD_API: str = RECOGNITION_ROOT_API + "/subjects" +RECOGNIZE_EMBEDDINGS_API: str = RECOGNITION_ROOT_API + "/embeddings" -DETECTION_API: str = '/api/v1/detection/detect' +DETECTION_API: str = "/api/v1/detection/detect" -VERIFICATION_API: str = '/api/v1/verification' +VERIFICATION_API: str = "/api/v1/verification" + +VERIFICATION_EMBEDDINGS_API: str = VERIFICATION_API + "/embeddings" diff --git a/compreface/core/model.py b/compreface/core/model.py index 73be277..b6abae3 100644 --- a/compreface/core/model.py +++ b/compreface/core/model.py @@ -16,11 +16,7 @@ from compreface.common.typed_dict import AllOptionsDict from typing import Optional -from ..service import ( - RecognitionService, - VerificationService, - DetectionService -) +from ..service import RecognitionService, VerificationService, DetectionService class CompreFace(object): @@ -66,10 +62,9 @@ def init_face_recognition(self, api_key: str) -> RecognitionService: :param api_key: :return: """ - self.recognition = RecognitionService(api_key=api_key, - domain=self.domain, - port=self.port, - options=self.options) + self.recognition = RecognitionService( + api_key=api_key, domain=self.domain, port=self.port, options=self.options + ) return self.recognition def init_face_verification(self, api_key: str) -> VerificationService: @@ -78,10 +73,9 @@ def init_face_verification(self, api_key: str) -> VerificationService: :param api_key: :return: """ - self.verification = VerificationService(api_key=api_key, - domain=self.domain, - port=self.port, - options=self.options) + self.verification = VerificationService( + api_key=api_key, domain=self.domain, port=self.port, options=self.options + ) return self.verification def init_face_detection(self, api_key: str) -> DetectionService: @@ -90,8 +84,7 @@ def init_face_detection(self, api_key: str) -> DetectionService: :param api_key: :return: """ - self.detection = DetectionService(api_key=api_key, - domain=self.domain, - port=self.port, - options=self.options) + self.detection = DetectionService( + api_key=api_key, domain=self.domain, port=self.port, options=self.options + ) return self.detection diff --git a/compreface/service/detection_service.py b/compreface/service/detection_service.py index 9ff6e33..586389c 100644 --- a/compreface/service/detection_service.py +++ b/compreface/service/detection_service.py @@ -24,14 +24,14 @@ class DetectionService(Service): """Detection service""" - def __init__(self, api_key: str, domain: str, port: str, options: AllOptionsDict = {}): + def __init__( + self, api_key: str, domain: str, port: str, options: AllOptionsDict = {} + ): """Init service with define API Key""" super().__init__(api_key, options) self.available_services = [] self.detect_face_from_image: DetectFaceFromImage = DetectFaceFromImage( - domain=domain, - port=port, - api_key=api_key + domain=domain, port=port, api_key=api_key ) def get_available_functions(self) -> List[str]: @@ -43,14 +43,33 @@ def get_available_functions(self) -> List[str]: def detect(self, image_path: str, options: ExpandedOptionsDict = {}) -> dict: """ - Detect face in image - :param image_path: - :param options: + Detect faces from the uploaded image + + :param image_path: image where to detect faces. + Allowed image formats: jpeg, jpg, ico, png, bmp, gif, tif, tiff, webp. Max size is 5Mb + + :param options: dict, Optional. + + Options contains args: + limit int: maximum number of faces on the target image to be recognized. + It recognizes the biggest faces first. Value of 0 represents no limit. + Default value: 0 + + det_prob_threshold float: minimum required confidence that a recognized face is actually a face. + Value is between 0.0 and 1.0. + + face_plugins str: comma-separated slugs of face plugins. + If empty, no additional information is returned. + + status bool: if true includes system information like execution_time and plugin_version fields. + Default value is false + :return: """ request = DetectFaceFromImage.Request( - api_key=self.api_key, - image_path=image_path + api_key=self.api_key, image_path=image_path + ) + return self.detect_face_from_image.execute( + request, + pass_dict(self.options, ExpandedOptionsDict) if options == {} else options, ) - return self.detect_face_from_image.execute(request, pass_dict( - self.options, ExpandedOptionsDict) if options == {} else options) diff --git a/compreface/service/recognition_service.py b/compreface/service/recognition_service.py index 18eff3e..32723ee 100644 --- a/compreface/service/recognition_service.py +++ b/compreface/service/recognition_service.py @@ -14,37 +14,34 @@ permissions and limitations under the License. """ -from compreface.common.typed_dict import AllOptionsDict +from compreface.common.typed_dict import AllOptionsDict, PredictionCountOptionsDict from typing import List from ..common import Service from ..collections import FaceCollection, Subjects -from ..use_cases import RecognizeFaceFromImage +from ..use_cases import RecognizeFaceFromImage, RecognizeFaceFromEmbedding class RecognitionService(Service): """Recognition service""" - def __init__(self, api_key: str, domain: str, port: str, options: AllOptionsDict = {}): + def __init__( + self, api_key: str, domain: str, port: str, options: AllOptionsDict = {} + ): """Init service with define API Key""" super().__init__(api_key, options) self.available_services = [] - self.recognize_face_from_images: RecognizeFaceFromImage = RecognizeFaceFromImage( - domain=domain, - port=port, - api_key=api_key + self.recognize_face_from_images: RecognizeFaceFromImage = ( + RecognizeFaceFromImage(domain=domain, port=port, api_key=api_key) + ) + self.recognize_face_from_embedding: RecognizeFaceFromEmbedding = ( + RecognizeFaceFromEmbedding(domain=domain, port=port, api_key=api_key) ) self.face_collection: FaceCollection = FaceCollection( - domain=domain, - port=port, - api_key=api_key, - options=options + domain=domain, port=port, api_key=api_key, options=options ) self.subjects: Subjects = Subjects( - domain=domain, - port=port, - api_key=api_key, - options=options + domain=domain, port=port, api_key=api_key, options=options ) def get_available_functions(self) -> List[str]: @@ -54,18 +51,60 @@ def get_available_functions(self) -> List[str]: """ return self.available_services - def recognize(self, image_path: str, options: AllOptionsDict = {}) -> dict: + def recognize_image(self, image_path: str, options: AllOptionsDict = {}) -> dict: """ - Recognize image - :param image_path: - :param options: + Recognize faces from the uploaded image + + :param image_path: allowed image formats: + jpeg, jpg, ico, png, bmp, gif, tif, tiff, webp. Max size is 5Mb + + :param options: dict, Optional. + + Options contains args: + limit int: maximum number of faces on the target image to be recognized. + It recognizes the biggest faces first. Value of 0 represents no limit. + Default value: 0 + + det_prob_threshold float: minimum required confidence that a recognized face is actually a face. + Value is between 0.0 and 1.0. + + face_plugins str: comma-separated slugs of face plugins. + If empty, no additional information is returned. + + status bool: if true includes system information like execution_time and plugin_version fields. + Default value is false + :return: """ request = RecognizeFaceFromImage.Request( - api_key=self.api_key, - image_path=image_path + api_key=self.api_key, image_path=image_path + ) + return self.recognize_face_from_images.execute( + request, self.options if options == {} else options + ) + + def recognize_embedding( + self, embeddings: list, options: PredictionCountOptionsDict = {} + ): + """ + Recognize faces from embeddings + + :param embeddings: an input embeddings. The length depends on the model (e.g. 512 or 128) + + :param options: dict, Optional. + + Options contains args: + prediction_count: the maximum number of subject predictions per embedding. + It returns the most similar subjects. Default value: 1 + + :return: + """ + request = RecognizeFaceFromEmbedding.Request( + api_key=self.api_key, embeddings=embeddings + ) + return self.recognize_face_from_embedding.execute( + request, self.options if options == {} else options ) - return self.recognize_face_from_images.execute(request, self.options if options == {} else options) def get_face_collection(self) -> FaceCollection: """ diff --git a/compreface/service/verification_service.py b/compreface/service/verification_service.py index 68de5d4..2e3b385 100644 --- a/compreface/service/verification_service.py +++ b/compreface/service/verification_service.py @@ -14,9 +14,9 @@ permissions and limitations under the License. """ -from compreface.use_cases.verifiy_face_from_images import VerifyFaceFromImage +from compreface.use_cases.verify_face_from_images import VerifyFaceFromImage +from compreface.use_cases.verify_face_from_embedding import VerifyFaceFromEmbedding from compreface.common.typed_dict import AllOptionsDict, ExpandedOptionsDict, pass_dict -from compreface.client.verify_face_from_image import VerifyFaceFromImageClient from typing import List from ..common import Service @@ -25,14 +25,17 @@ class VerificationService(Service): """Verification service""" - def __init__(self, api_key: str, domain: str, port: str, options: AllOptionsDict = {}): + def __init__( + self, api_key: str, domain: str, port: str, options: AllOptionsDict = {} + ): """Init service with define API Key""" super().__init__(api_key, options) self.available_services = [] - self.verify_face_from_image: VerifyFaceFromImageClient = VerifyFaceFromImageClient( - domain=domain, - port=port, - api_key=api_key + self.verify_face_from_image: VerifyFaceFromImage = VerifyFaceFromImage( + domain=domain, port=port, api_key=api_key + ) + self.verify_face_from_embedding: VerifyFaceFromEmbedding = ( + VerifyFaceFromEmbedding(domain=domain, api_key=api_key, port=port) ) def get_available_functions(self) -> List[str]: @@ -42,20 +45,66 @@ def get_available_functions(self) -> List[str]: """ return self.available_services - def verify(self, source_image_path: str, target_image_path: str, options: ExpandedOptionsDict = {}) -> dict: + def verify_image( + self, + source_image_path: str, + target_image_path: str, + options: ExpandedOptionsDict = {}, + ) -> dict: """ - Verify face in images - :param source_image_path: - :param target_image_path: - :param options: - :return: + Verify face to compare faces from given two images + + :param source_image_path: file to be verified. + Allowed image formats: jpeg, jpg, ico, png, bmp, gif, tif, tiff, webp. Max size is 5Mb + + :param target_image_path: reference file to check the source file. + Allowed image formats: jpeg, jpg, ico, png, bmp, gif, tif, tiff, webp. Max size is 5Mb + + :param options: dict, Optional. + + Options contains args: + limit int: maximum number of faces on the target image to be recognized. + It recognizes the biggest faces first. Value of 0 represents no limit. + Default value: 0 + + det_prob_threshold float: minimum required confidence that a recognized face is actually a face. + Value is between 0.0 and 1.0. + + face_plugins str: comma-separated slugs of face plugins. + If empty, no additional information is returned. + + status bool: if true includes system information like execution_time and plugin_version fields. + Default value is false + + :return: dict """ - request = VerifyFaceFromImage.Request(api_key=self.api_key, - source_image_path=source_image_path, - target_image_path=target_image_path) - return self.verify_face_from_image.post( - source_image=request.source_image_path, - target_image=request.target_image_path, - options=pass_dict( - self.options, ExpandedOptionsDict) if options == {} else options + request = VerifyFaceFromImage.Request( + api_key=self.api_key, + source_image_path=source_image_path, + target_image_path=target_image_path, + ) + return self.verify_face_from_image.execute( + request=request, + options=pass_dict(self.options, ExpandedOptionsDict) + if options == {} + else options, ) + + def verify_embedding( + self, source_embeddings: list, targets_embeddings: list + ) -> dict: + """ + Verify face from given embeddings + + :param source_embeddings: An input embeddings. The length depends on the model. + :param targets_embeddings: An array of the target embeddings. The length depends on the model. + + :return: dict + """ + request = VerifyFaceFromEmbedding.Request( + api_key=self.api_key, + source_embeddings=source_embeddings, + targets_embeddings=targets_embeddings, + ) + + return self.verify_face_from_embedding.execute(request=request) diff --git a/compreface/use_cases/__init__.py b/compreface/use_cases/__init__.py index 540286f..b899af3 100644 --- a/compreface/use_cases/__init__.py +++ b/compreface/use_cases/__init__.py @@ -17,6 +17,7 @@ from .add_example_of_subject import AddExampleOfSubject from .delete_all_examples_of_subject_by_name import DeleteAllExamplesOfSubjectByName from .delete_example_by_id import DeleteExampleById +from .delete_example_by_ids import DeleteExampleByIds from .list_of_all_saved_subjects import ListOfAllSavedSubjects from .recognize_face_from_image import RecognizeFaceFromImage from .verification_face_from_image import VerificationFaceFromImage @@ -26,3 +27,6 @@ from .update_subject import UpdateSubject from .delete_subject_by_name import DeleteSubjectByName from .delete_all_subjects import DeleteAllSubjects +from .verify_face_from_embedding import VerifyFaceFromEmbedding +from .recognize_face_from_embedding import RecognizeFaceFromEmbedding +from .verification_face_from_embedding import VerificationFaceFromEmbedding diff --git a/compreface/use_cases/add_example_of_subject.py b/compreface/use_cases/add_example_of_subject.py index 0a0bfec..e16abde 100644 --- a/compreface/use_cases/add_example_of_subject.py +++ b/compreface/use_cases/add_example_of_subject.py @@ -20,7 +20,6 @@ class AddExampleOfSubject: - @dataclass class Request: api_key: str @@ -29,12 +28,11 @@ class Request: def __init__(self, domain: str, port: str, api_key: str): self.add_example_of_subject = AddExampleOfSubjectClient( - api_key=api_key, - domain=domain, - port=port + api_key=api_key, domain=domain, port=port ) def execute(self, request: Request, options: DetProbOptionsDict = {}) -> dict: result: dict = self.add_example_of_subject.post( - request.image_path, request.subject, options) + request.image_path, request.subject, options + ) return result diff --git a/compreface/use_cases/add_subject.py b/compreface/use_cases/add_subject.py index e2f84c4..a16cc0c 100644 --- a/compreface/use_cases/add_subject.py +++ b/compreface/use_cases/add_subject.py @@ -20,17 +20,12 @@ class AddSubject: - @dataclass class Request: subject: str def __init__(self, domain: str, port: str, api_key: str): - self.subject_client = SubjectClient( - api_key=api_key, - domain=domain, - port=port - ) + self.subject_client = SubjectClient(api_key=api_key, domain=domain, port=port) def execute(self, request: Request) -> dict: result: dict = self.subject_client.post(asdict(request)) diff --git a/compreface/use_cases/delete_all_examples_of_subject_by_name.py b/compreface/use_cases/delete_all_examples_of_subject_by_name.py index 1703844..c1b27d8 100644 --- a/compreface/use_cases/delete_all_examples_of_subject_by_name.py +++ b/compreface/use_cases/delete_all_examples_of_subject_by_name.py @@ -19,7 +19,6 @@ class DeleteAllExamplesOfSubjectByName: - @dataclass class Request: api_key: str @@ -27,9 +26,7 @@ class Request: def __init__(self, domain: str, port: str, api_key: str): self.add_example_of_subject = AddExampleOfSubjectClient( - api_key=api_key, - domain=domain, - port=port + api_key=api_key, domain=domain, port=port ) def execute(self, request: Request) -> dict: diff --git a/compreface/use_cases/delete_all_subjects.py b/compreface/use_cases/delete_all_subjects.py index a07c5f9..01e3ea6 100644 --- a/compreface/use_cases/delete_all_subjects.py +++ b/compreface/use_cases/delete_all_subjects.py @@ -20,17 +20,12 @@ class DeleteAllSubjects: - @dataclass class Request: pass def __init__(self, domain: str, port: str, api_key: str): - self.add_subject = SubjectClient( - api_key=api_key, - domain=domain, - port=port - ) + self.add_subject = SubjectClient(api_key=api_key, domain=domain, port=port) def execute(self) -> dict: result: dict = self.add_subject.delete() diff --git a/compreface/use_cases/delete_example_by_id.py b/compreface/use_cases/delete_example_by_id.py index 05eed2e..877df3a 100644 --- a/compreface/use_cases/delete_example_by_id.py +++ b/compreface/use_cases/delete_example_by_id.py @@ -19,7 +19,6 @@ class DeleteExampleById: - @dataclass class Request: api_key: str @@ -27,9 +26,7 @@ class Request: def __init__(self, domain: str, port: str, api_key: str): self.delete_example_by_id = DeleteExampleByIdClient( - api_key=api_key, - domain=domain, - port=port + api_key=api_key, domain=domain, port=port ) def execute(self, request: Request): diff --git a/compreface/use_cases/delete_example_by_ids.py b/compreface/use_cases/delete_example_by_ids.py new file mode 100644 index 0000000..9546e5f --- /dev/null +++ b/compreface/use_cases/delete_example_by_ids.py @@ -0,0 +1,34 @@ +""" + Copyright(c) 2021 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 dataclasses import dataclass +from ..client import DeleteExampleByIdClient + + +class DeleteExampleByIds: + @dataclass + class Request: + api_key: str + image_ids: list + + def __init__(self, domain: str, port: str, api_key: str): + self.delete_example_by_id = DeleteExampleByIdClient( + api_key=api_key, domain=domain, port=port + ) + + def execute(self, request: Request): + result: dict = self.delete_example_by_id.post(request.image_ids) + return result diff --git a/compreface/use_cases/delete_subject_by_name.py b/compreface/use_cases/delete_subject_by_name.py index 3f1a856..a5ece6f 100644 --- a/compreface/use_cases/delete_subject_by_name.py +++ b/compreface/use_cases/delete_subject_by_name.py @@ -20,17 +20,12 @@ class DeleteSubjectByName: - @dataclass class Request: subject: str def __init__(self, domain: str, port: str, api_key: str): - self.subject_client = SubjectClient( - api_key=api_key, - domain=domain, - port=port - ) + self.subject_client = SubjectClient(api_key=api_key, domain=domain, port=port) def execute(self, request: Request) -> dict: result: dict = self.subject_client.delete(request.subject) diff --git a/compreface/use_cases/detect_face_from_image.py b/compreface/use_cases/detect_face_from_image.py index 11bfe74..e19338a 100644 --- a/compreface/use_cases/detect_face_from_image.py +++ b/compreface/use_cases/detect_face_from_image.py @@ -20,7 +20,6 @@ class DetectFaceFromImage: - @dataclass class Request: api_key: str @@ -28,12 +27,9 @@ class Request: def __init__(self, domain: str, port: str, api_key: str): self.detect_face_from_image = DetectFaceFromImageClient( - api_key=api_key, - domain=domain, - port=port + api_key=api_key, domain=domain, port=port ) def execute(self, request: Request, options: ExpandedOptionsDict = {}) -> dict: - result: dict = self.detect_face_from_image.post( - request.image_path, options) + result: dict = self.detect_face_from_image.post(request.image_path, options) return result diff --git a/compreface/use_cases/get_subjects.py b/compreface/use_cases/get_subjects.py index 7631bbf..bb529a9 100644 --- a/compreface/use_cases/get_subjects.py +++ b/compreface/use_cases/get_subjects.py @@ -20,17 +20,12 @@ class GetSubjects: - @dataclass class Request: pass def __init__(self, domain: str, port: str, api_key: str): - self.subject_client = SubjectClient( - api_key=api_key, - domain=domain, - port=port - ) + self.subject_client = SubjectClient(api_key=api_key, domain=domain, port=port) def execute(self) -> dict: result: dict = self.subject_client.get() diff --git a/compreface/use_cases/list_of_all_saved_subjects.py b/compreface/use_cases/list_of_all_saved_subjects.py index 22e265d..654c564 100644 --- a/compreface/use_cases/list_of_all_saved_subjects.py +++ b/compreface/use_cases/list_of_all_saved_subjects.py @@ -16,21 +16,19 @@ from dataclasses import dataclass from ..client import AddExampleOfSubjectClient +from ..common.typed_dict import SavedObjectOptions class ListOfAllSavedSubjects: - @dataclass class Request: pass def __init__(self, domain: str, port: str, api_key: str): self.add_example_of_subject = AddExampleOfSubjectClient( - api_key=api_key, - domain=domain, - port=port + api_key=api_key, domain=domain, port=port ) - def execute(self) -> dict: - result: dict = self.add_example_of_subject.get() + def execute(self, options: SavedObjectOptions = {}) -> dict: + result: dict = self.add_example_of_subject.get(options) return result diff --git a/compreface/use_cases/recognize_face_from_embedding.py b/compreface/use_cases/recognize_face_from_embedding.py new file mode 100644 index 0000000..5274eba --- /dev/null +++ b/compreface/use_cases/recognize_face_from_embedding.py @@ -0,0 +1,39 @@ +""" + Copyright(c) 2021 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 compreface.common.typed_dict import PredictionCountOptionsDict +from dataclasses import dataclass +from ..client import RecognizeFaceFromEmbeddingClient + + +class RecognizeFaceFromEmbedding: + @dataclass + class Request: + api_key: str + embeddings: list + + def __init__(self, domain: str, port: str, api_key: str): + self.recognize_face_from_embeddings = RecognizeFaceFromEmbeddingClient( + api_key=api_key, domain=domain, port=port + ) + + def execute( + self, request: Request, options: PredictionCountOptionsDict = {} + ) -> dict: + result: dict = self.recognize_face_from_embeddings.post( + request.embeddings, options + ) + return result diff --git a/compreface/use_cases/recognize_face_from_image.py b/compreface/use_cases/recognize_face_from_image.py index 8cc916c..31e94d2 100644 --- a/compreface/use_cases/recognize_face_from_image.py +++ b/compreface/use_cases/recognize_face_from_image.py @@ -20,7 +20,6 @@ class RecognizeFaceFromImage: - @dataclass class Request: api_key: str @@ -28,12 +27,9 @@ class Request: def __init__(self, domain: str, port: str, api_key: str): self.recognize_face_from_image = RecognizeFaceFromImageClient( - api_key=api_key, - domain=domain, - port=port + api_key=api_key, domain=domain, port=port ) def execute(self, request: Request, options: AllOptionsDict = {}) -> dict: - result: dict = self.recognize_face_from_image.post( - request.image_path, options) + result: dict = self.recognize_face_from_image.post(request.image_path, options) return result diff --git a/compreface/use_cases/update_subject.py b/compreface/use_cases/update_subject.py index b3e4c67..bc1c423 100644 --- a/compreface/use_cases/update_subject.py +++ b/compreface/use_cases/update_subject.py @@ -20,18 +20,13 @@ class UpdateSubject: - @dataclass class Request: subject: str api_endpoint: str def __init__(self, domain: str, port: str, api_key: str): - self.subject_client = SubjectClient( - api_key=api_key, - domain=domain, - port=port - ) + self.subject_client = SubjectClient(api_key=api_key, domain=domain, port=port) def execute(self, request: Request) -> dict: result: dict = self.subject_client.put(asdict(request)) diff --git a/compreface/use_cases/verification_face_from_embedding.py b/compreface/use_cases/verification_face_from_embedding.py new file mode 100644 index 0000000..b258776 --- /dev/null +++ b/compreface/use_cases/verification_face_from_embedding.py @@ -0,0 +1,37 @@ +""" + Copyright(c) 2021 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 dataclasses import dataclass +from ..client import VerificationFaceFromEmbeddingClient + + +class VerificationFaceFromEmbedding: + @dataclass + class Request: + api_key: str + embeddings: list + image_id: str + + def __init__(self, domain: str, port: str, api_key: str): + self.verify_face_from_embedding = VerificationFaceFromEmbeddingClient( + api_key=api_key, domain=domain, port=port + ) + + def execute(self, request: Request): + result: dict = self.verify_face_from_embedding.post( + request.embeddings, request.image_id + ) + return result diff --git a/compreface/use_cases/verification_face_from_image.py b/compreface/use_cases/verification_face_from_image.py index 0d29c2e..201cb41 100644 --- a/compreface/use_cases/verification_face_from_image.py +++ b/compreface/use_cases/verification_face_from_image.py @@ -20,7 +20,6 @@ class VerificationFaceFromImage: - @dataclass class Request: api_key: str @@ -29,12 +28,11 @@ class Request: def __init__(self, domain: str, port: str, api_key: str): self.verify_face_from_image = VerificationFaceFromImageClient( - api_key=api_key, - domain=domain, - port=port + api_key=api_key, domain=domain, port=port ) def execute(self, request: Request, options: ExpandedOptionsDict = {}): - result: dict = self.verify_face_from_image.post(request.image_path, - request.image_id, options) + result: dict = self.verify_face_from_image.post( + request.image_path, request.image_id, options + ) return result diff --git a/compreface/use_cases/verify_face_from_embedding.py b/compreface/use_cases/verify_face_from_embedding.py new file mode 100644 index 0000000..d48da3c --- /dev/null +++ b/compreface/use_cases/verify_face_from_embedding.py @@ -0,0 +1,38 @@ +""" + Copyright(c) 2021 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 compreface.client.verify_face_from_embeddings import VerifyFaceFromEmbeddingClient +from dataclasses import dataclass + + +class VerifyFaceFromEmbedding: + @dataclass + class Request: + api_key: str + targets_embeddings: list + source_embeddings: list + + def __init__(self, domain: str, port: str, api_key: str): + self.verify_face_from_embedding = VerifyFaceFromEmbeddingClient( + api_key=api_key, domain=domain, port=port + ) + + def execute(self, request: Request): + result: dict = self.verify_face_from_embedding.post( + request.source_embeddings, + request.targets_embeddings, + ) + return result diff --git a/compreface/use_cases/verifiy_face_from_images.py b/compreface/use_cases/verify_face_from_images.py similarity index 79% rename from compreface/use_cases/verifiy_face_from_images.py rename to compreface/use_cases/verify_face_from_images.py index 8184ad4..e233862 100644 --- a/compreface/use_cases/verifiy_face_from_images.py +++ b/compreface/use_cases/verify_face_from_images.py @@ -20,7 +20,6 @@ class VerifyFaceFromImage: - @dataclass class Request: api_key: str @@ -29,13 +28,11 @@ class Request: def __init__(self, domain: str, port: str, api_key: str): self.verify_face_from_image = VerifyFaceFromImageClient( - api_key=api_key, - domain=domain, - port=port + api_key=api_key, domain=domain, port=port ) def execute(self, request: Request, options: ExpandedOptionsDict = {}): - result: dict = self.verify_face_from_image.post(request.source_image_path, - request.target_image_path, - options) + result: dict = self.verify_face_from_image.post( + request.source_image_path, request.target_image_path, options + ) return result diff --git a/examples/add_example_of_a_subject.py b/examples/add_example_of_a_subject.py index ecb97c0..fd758f4 100644 --- a/examples/add_example_of_a_subject.py +++ b/examples/add_example_of_a_subject.py @@ -19,21 +19,18 @@ from compreface.service import RecognitionService from compreface.collections import FaceCollection -DOMAIN: str = 'http://localhost' -PORT: str = '8000' -RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' +DOMAIN: str = "http://localhost" +PORT: str = "8000" +RECOGNITION_API_KEY: str = "00000000-0000-0000-0000-000000000002" -compre_face: CompreFace = CompreFace(DOMAIN, PORT, { - "det_prob_threshold": 0.8 -}) +compre_face: CompreFace = CompreFace(DOMAIN, PORT, {"det_prob_threshold": 0.8}) -recognition: RecognitionService = compre_face.init_face_recognition( - RECOGNITION_API_KEY) +recognition: RecognitionService = compre_face.init_face_recognition(RECOGNITION_API_KEY) face_collection: FaceCollection = recognition.get_face_collection() # Image from local path. -image: str = 'common/jonathan-petit-unsplash.jpg' -subject: str = 'Jonathan Petit' +image: str = "common/jonathan-petit-unsplash.jpg" +subject: str = "Jonathan Petit" print(face_collection.add(image, subject)) diff --git a/examples/add_subject.py b/examples/add_subject.py index a99f6c0..97020fa 100644 --- a/examples/add_subject.py +++ b/examples/add_subject.py @@ -18,9 +18,9 @@ from compreface.service import RecognitionService from compreface.collections import Subjects -DOMAIN: str = 'http://localhost' -PORT: str = '8000' -RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' +DOMAIN: str = "http://localhost" +PORT: str = "8000" +RECOGNITION_API_KEY: str = "00000000-0000-0000-0000-000000000002" compre_face: CompreFace = CompreFace(DOMAIN, PORT) @@ -28,6 +28,6 @@ subjects: Subjects = recognition.get_subjects() -subject: str = 'Test Subject' +subject: str = "Test Subject" print(subjects.add(subject)) diff --git a/examples/delete_all_examples_of_subject.py b/examples/delete_all_examples_of_subject.py index 57d21a2..5b370a0 100644 --- a/examples/delete_all_examples_of_subject.py +++ b/examples/delete_all_examples_of_subject.py @@ -18,16 +18,15 @@ from compreface.service import RecognitionService from compreface.collections import FaceCollection -DOMAIN: str = 'http://localhost' -PORT: str = '8000' -RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' +DOMAIN: str = "http://localhost" +PORT: str = "8000" +RECOGNITION_API_KEY: str = "00000000-0000-0000-0000-000000000002" compre_face: CompreFace = CompreFace(DOMAIN, PORT) -recognition: RecognitionService = compre_face.init_face_recognition( - RECOGNITION_API_KEY) -subject: str = 'Jonathan Petit' +recognition: RecognitionService = compre_face.init_face_recognition(RECOGNITION_API_KEY) +subject: str = "Jonathan Petit" face_collection: FaceCollection = recognition.get_face_collection() diff --git a/examples/delete_all_subjects.py b/examples/delete_all_subjects.py index ac11b67..5336e6b 100644 --- a/examples/delete_all_subjects.py +++ b/examples/delete_all_subjects.py @@ -18,9 +18,9 @@ from compreface.service import RecognitionService from compreface.collections import Subjects -DOMAIN: str = 'http://localhost' -PORT: str = '8000' -RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' +DOMAIN: str = "http://localhost" +PORT: str = "8000" +RECOGNITION_API_KEY: str = "00000000-0000-0000-0000-000000000002" compre_face: CompreFace = CompreFace(DOMAIN, PORT) diff --git a/examples/delete_example_by_id.py b/examples/delete_example_by_id.py index d7dfc2d..47c6d22 100644 --- a/examples/delete_example_by_id.py +++ b/examples/delete_example_by_id.py @@ -18,22 +18,21 @@ from compreface.service import RecognitionService from compreface.collections import FaceCollection -DOMAIN: str = 'http://localhost' -PORT: str = '8000' -RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' +DOMAIN: str = "http://localhost" +PORT: str = "8000" +RECOGNITION_API_KEY: str = "00000000-0000-0000-0000-000000000002" compre_face: CompreFace = CompreFace(DOMAIN, PORT) -recognition: RecognitionService = compre_face.init_face_recognition( - RECOGNITION_API_KEY) +recognition: RecognitionService = compre_face.init_face_recognition(RECOGNITION_API_KEY) face_collection: FaceCollection = recognition.get_face_collection() -faces: list = face_collection.list().get('faces') +faces: list = face_collection.list().get("faces") -if(len(faces) != 0): +if len(faces) != 0: last_face: dict = faces[len(faces) - 1] - print(face_collection.delete(last_face.get('image_id'))) + print(face_collection.delete(last_face.get("image_id"))) else: - print('No subject found') + print("No subject found") diff --git a/examples/delete_subject_by_name.py b/examples/delete_subject_by_name.py index 3bd85e9..c41963c 100644 --- a/examples/delete_subject_by_name.py +++ b/examples/delete_subject_by_name.py @@ -18,9 +18,9 @@ from compreface.service import RecognitionService from compreface.collections import Subjects -DOMAIN: str = 'http://localhost' -PORT: str = '8000' -RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' +DOMAIN: str = "http://localhost" +PORT: str = "8000" +RECOGNITION_API_KEY: str = "00000000-0000-0000-0000-000000000002" compre_face: CompreFace = CompreFace(DOMAIN, PORT) @@ -28,6 +28,6 @@ subjects: Subjects = recognition.get_subjects() -subject: str = 'Test Subject' +subject: str = "Test Subject" print(subjects.delete(subject)) diff --git a/examples/detect_face_from_image.py b/examples/detect_face_from_image.py index 45bcc23..907fbae 100644 --- a/examples/detect_face_from_image.py +++ b/examples/detect_face_from_image.py @@ -18,20 +18,23 @@ from compreface.service import DetectionService -DOMAIN: str = 'http://localhost' -PORT: str = '8000' -DETECTION_API_KEY: str = 'f4bdcf1f-1ef9-442f-863d-7bcd170723db' - -compre_face: CompreFace = CompreFace(DOMAIN, PORT, { - "limit": 0, - "det_prob_threshold": 0.8, - "face_plugins": "age,gender", - "status": "true" -}) - -detection: DetectionService = compre_face.init_face_detection( - DETECTION_API_KEY) - -image_path: str = 'common/jonathan-petit-unsplash.jpg' +DOMAIN: str = "http://localhost" +PORT: str = "8000" +DETECTION_API_KEY: str = "f4bdcf1f-1ef9-442f-863d-7bcd170723db" + +compre_face: CompreFace = CompreFace( + DOMAIN, + PORT, + { + "limit": 0, + "det_prob_threshold": 0.8, + "face_plugins": "age,gender", + "status": "true", + }, +) + +detection: DetectionService = compre_face.init_face_detection(DETECTION_API_KEY) + +image_path: str = "common/jonathan-petit-unsplash.jpg" print(detection.detect(image_path)) diff --git a/examples/get_list_of_all_subjects.py b/examples/get_list_of_all_subjects.py index 644d5ea..786d962 100644 --- a/examples/get_list_of_all_subjects.py +++ b/examples/get_list_of_all_subjects.py @@ -18,15 +18,14 @@ from compreface.service import RecognitionService from compreface.collections import Subjects -DOMAIN: str = 'http://localhost' -PORT: str = '8000' -RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' +DOMAIN: str = "http://localhost" +PORT: str = "8000" +RECOGNITION_API_KEY: str = "00000000-0000-0000-0000-000000000002" compre_face: CompreFace = CompreFace(DOMAIN, PORT) -recognition: RecognitionService = compre_face.init_face_recognition( - RECOGNITION_API_KEY) +recognition: RecognitionService = compre_face.init_face_recognition(RECOGNITION_API_KEY) subjects: Subjects = recognition.get_subjects() diff --git a/examples/recognize_face_from_image.py b/examples/recognize_face_from_image.py index 53f7b49..f6b38f2 100644 --- a/examples/recognize_face_from_image.py +++ b/examples/recognize_face_from_image.py @@ -17,21 +17,19 @@ from compreface import CompreFace from compreface.service import RecognitionService -DOMAIN: str = 'http://localhost' -PORT: str = '8000' -RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' +DOMAIN: str = "http://localhost" +PORT: str = "8000" +RECOGNITION_API_KEY: str = "00000000-0000-0000-0000-000000000002" -compre_face: CompreFace = CompreFace(DOMAIN, PORT, { - "limit": 0, - "det_prob_threshold": 0.8, - "prediction_count": 1, - "status": "true" -}) +compre_face: CompreFace = CompreFace( + DOMAIN, + PORT, + {"limit": 0, "det_prob_threshold": 0.8, "prediction_count": 1, "status": "true"}, +) -recognition: RecognitionService = compre_face.init_face_recognition( - RECOGNITION_API_KEY) +recognition: RecognitionService = compre_face.init_face_recognition(RECOGNITION_API_KEY) -image_path: str = 'common/jonathan-petit-unsplash.jpg' +image_path: str = "common/jonathan-petit-unsplash.jpg" print(recognition.recognize(image_path)) diff --git a/examples/update_existing_subject.py b/examples/update_existing_subject.py index 392fd56..5920040 100644 --- a/examples/update_existing_subject.py +++ b/examples/update_existing_subject.py @@ -18,9 +18,9 @@ from compreface.service import RecognitionService from compreface.collections import Subjects -DOMAIN: str = 'http://localhost' -PORT: str = '8000' -RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' +DOMAIN: str = "http://localhost" +PORT: str = "8000" +RECOGNITION_API_KEY: str = "00000000-0000-0000-0000-000000000002" compre_face: CompreFace = CompreFace(DOMAIN, PORT) @@ -28,7 +28,7 @@ subjects: Subjects = recognition.get_subjects() -subject: str = 'Test Subject' -new_name: str = 'Updated Subject' +subject: str = "Test Subject" +new_name: str = "Updated Subject" print(subjects.update(subject, new_name)) diff --git a/examples/verification_face_from_image.py b/examples/verification_face_from_image.py index aafb048..52c68b2 100644 --- a/examples/verification_face_from_image.py +++ b/examples/verification_face_from_image.py @@ -18,28 +18,28 @@ from compreface import CompreFace from compreface.service import RecognitionService -DOMAIN: str = 'http://localhost' -PORT: str = '8000' -RECOGNITION_API_KEY: str = '00000000-0000-0000-0000-000000000002' +DOMAIN: str = "http://localhost" +PORT: str = "8000" +RECOGNITION_API_KEY: str = "00000000-0000-0000-0000-000000000002" -compre_face: CompreFace = CompreFace(DOMAIN, PORT, { - "limit": 0, - "det_prob_threshold": 0.8, - "status": "true" -}) +compre_face: CompreFace = CompreFace( + DOMAIN, PORT, {"limit": 0, "det_prob_threshold": 0.8, "status": "true"} +) -recognition: RecognitionService = compre_face.init_face_recognition( - RECOGNITION_API_KEY) +recognition: RecognitionService = compre_face.init_face_recognition(RECOGNITION_API_KEY) -image_path: str = 'common/jonathan-petit-unsplash.jpg' +image_path: str = "common/jonathan-petit-unsplash.jpg" face_collection: FaceCollection = recognition.get_face_collection() print(face_collection.list()) -face: dict = next(item for item in face_collection.list().get('faces') if item['subject'] == - 'Jonathan Petit') +face: dict = next( + item + for item in face_collection.list().get("faces") + if item["subject"] == "Jonathan Petit" +) -image_id = face.get('image_id') +image_id = face.get("image_id") print(face_collection.verify(image_path, image_id)) diff --git a/examples/verify_face_from_image.py b/examples/verify_face_from_image.py index 265c0be..19d8f2f 100644 --- a/examples/verify_face_from_image.py +++ b/examples/verify_face_from_image.py @@ -17,21 +17,24 @@ from compreface import CompreFace from compreface.service import VerificationService -DOMAIN: str = 'http://localhost' -PORT: str = '8000' -VERIFICATION_API_KEY: str = '5c765423-4192-4fe8-9c60-092f495a332a' +DOMAIN: str = "http://localhost" +PORT: str = "8000" +VERIFICATION_API_KEY: str = "5c765423-4192-4fe8-9c60-092f495a332a" -compre_face: CompreFace = CompreFace(DOMAIN, PORT, { - "limit": 0, - "det_prob_threshold": 0.8, - "face_plugins": "age,gender", - "status": "true" -}) +compre_face: CompreFace = CompreFace( + DOMAIN, + PORT, + { + "limit": 0, + "det_prob_threshold": 0.8, + "face_plugins": "age,gender", + "status": "true", + }, +) -verify: VerificationService = compre_face.init_face_verification( - VERIFICATION_API_KEY) +verify: VerificationService = compre_face.init_face_verification(VERIFICATION_API_KEY) -image_path: str = 'common/jonathan-petit-unsplash.jpg' +image_path: str = "common/jonathan-petit-unsplash.jpg" print(verify.verify(image_path, image_path)) diff --git a/setup.py b/setup.py index 9f47001..afa3e61 100644 --- a/setup.py +++ b/setup.py @@ -21,17 +21,17 @@ long_description = fh.read() setup( - name='compreface-sdk', + name="compreface-sdk", packages=find_packages(exclude=("tests",)), - version='0.6.0', - license='Apache License 2.0', - description='CompreFace Python SDK makes face recognition into your application even easier.', - long_description=long_description, - long_description_content_type="text/markdown", - author='Artsiom Liubymov aliubymov@exadel.com, Artsiom Khadzkou akhadzkou@exadel.com, Aliaksei Tauhen atauhen@exadel.com', - author_email='aliubymov@exadel.com, akhadzkou@exadel.com, atauhen@exadel.com', - url='https://exadel.com/solutions/compreface/', - download_url='https://github.com/exadel-inc/compreface-python-sdk/archive/refs/tags/0.6.0.tar.gz', + version="0.6.0", + license="Apache License 2.0", + description="CompreFace Python SDK makes face recognition into your application even easier.", + long_description=long_description, + long_description_content_type="text/markdown", + author="Artsiom Liubymov aliubymov@exadel.com, Artsiom Khadzkou akhadzkou@exadel.com, Aliaksei Tauhen atauhen@exadel.com", + author_email="aliubymov@exadel.com, akhadzkou@exadel.com, atauhen@exadel.com", + url="https://exadel.com/solutions/compreface/", + download_url="https://github.com/exadel-inc/compreface-python-sdk/archive/refs/tags/0.6.0.tar.gz", keywords=[ "CompreFace", "Face Recognition", @@ -39,23 +39,21 @@ "Face Verification", "Face Identification", "Computer Vision", - "SDK" - ], - install_requires=[ - 'requests-toolbelt==0.9.1' + "SDK", ], + install_requires=["requests-toolbelt==0.9.1"], classifiers=[ - 'Development Status :: 5 - Production/Stable', - 'Intended Audience :: Developers', - 'Intended Audience :: Science/Research', - 'Intended Audience :: Information Technology', - 'Topic :: Software Development', - 'Topic :: Software Development :: Build Tools', - 'Topic :: Software Development :: Libraries', - 'Topic :: Scientific/Engineering :: Image Recognition', - 'Topic :: Scientific/Engineering :: Artificial Intelligence', - 'License :: OSI Approved :: Apache Software License', - 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.7' + "Development Status :: 5 - Production/Stable", + "Intended Audience :: Developers", + "Intended Audience :: Science/Research", + "Intended Audience :: Information Technology", + "Topic :: Software Development", + "Topic :: Software Development :: Build Tools", + "Topic :: Software Development :: Libraries", + "Topic :: Scientific/Engineering :: Image Recognition", + "Topic :: Scientific/Engineering :: Artificial Intelligence", + "License :: OSI Approved :: Apache Software License", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.7", ], ) diff --git a/tests/client/__init__.py b/tests/client/__init__.py index 160057e..942f6b1 100644 --- a/tests/client/__init__.py +++ b/tests/client/__init__.py @@ -1,4 +1,3 @@ - """ Copyright(c) 2021 the original author or authors diff --git a/tests/client/const_config.py b/tests/client/const_config.py index 774cb79..8a37168 100644 --- a/tests/client/const_config.py +++ b/tests/client/const_config.py @@ -17,10 +17,10 @@ from compreface.config.api_list import DETECTION_API -DOMAIN: str = 'http://localhost' -PORT: str = '8000' -RECOGNIZE_API_KEY: str = '9916f5d1-216f-4049-9e06-51c140bfa898' +DOMAIN: str = "http://localhost" +PORT: str = "8000" +RECOGNIZE_API_KEY: str = "9916f5d1-216f-4049-9e06-51c140bfa898" FILE_PATH: str = "tests/common/jonathan-petit-unsplash.jpg" IMAGE_ID: str = "image-id" -DETECTION_API_KEY: str = 'a482a613-3118-4554-a295-153bd6e8ac65' -VERIFICATION_API_KEY: str = '3c6171a4-e115-41f0-afda-4032bda4bfe9' +DETECTION_API_KEY: str = "a482a613-3118-4554-a295-153bd6e8ac65" +VERIFICATION_API_KEY: str = "3c6171a4-e115-41f0-afda-4032bda4bfe9" diff --git a/tests/client/test_add_example_of_subject_client.py b/tests/client/test_add_example_of_subject_client.py index be3991b..6894faf 100644 --- a/tests/client/test_add_example_of_subject_client.py +++ b/tests/client/test_add_example_of_subject_client.py @@ -1,4 +1,3 @@ - """ Copyright(c) 2021 the original author or authors @@ -15,6 +14,7 @@ permissions and limitations under the License. """ +from compreface.common.typed_dict import DetProbOptionsDict, SavedObjectOptions import pytest import os import httpretty @@ -23,6 +23,7 @@ from compreface.client import AddExampleOfSubjectClient from compreface.config.api_list import RECOGNIZE_CRUD_API from tests.client.const_config import DOMAIN, PORT, RECOGNIZE_API_KEY, FILE_PATH + """ Server configuration """ @@ -34,13 +35,15 @@ def test_get(): httpretty.register_uri( httpretty.GET, url, - headers={'x-api-key': RECOGNIZE_API_KEY}, - body='{"faces": [{"image_id": "image_id_subject", "subject": "Subject"}]}' + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"faces": [{"image_id": "image_id_subject", "subject": "Subject"}]}', ) test_subject: AddExampleOfSubjectClient = AddExampleOfSubjectClient( - RECOGNIZE_API_KEY, DOMAIN, PORT) + RECOGNIZE_API_KEY, DOMAIN, PORT + ) response: dict = requests.get( - url=url, headers={'x-api-key': RECOGNIZE_API_KEY}).json() + url=url, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() test_response: dict = test_subject.get() assert response == test_response @@ -49,42 +52,72 @@ def test_get(): def test_delete(): httpretty.register_uri( httpretty.DELETE, - url + '?subject=Subject', - headers={'x-api-key': RECOGNIZE_API_KEY}, - body='{"faces": [{"image_id": "image_id_subject", "subject": "Subject"}]}' + url + "?subject=Subject", + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"faces": [{"image_id": "image_id_subject", "subject": "Subject"}]}', ) response: dict = requests.delete( - url=url, headers={'x-api-key': RECOGNIZE_API_KEY}).json() + url=url, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() test_subject: AddExampleOfSubjectClient = AddExampleOfSubjectClient( - RECOGNIZE_API_KEY, DOMAIN, PORT) + RECOGNIZE_API_KEY, DOMAIN, PORT + ) test_response: dict = test_subject.delete("Subject") assert response == test_response @httpretty.activate(verbose=True, allow_net_connect=False) def test_post(): - httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': RECOGNIZE_API_KEY, - 'Content-Type': 'multipart/form-data'}, - body='{"image_id": "image_id_subject", "subject": "Subject"}' + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"image_id": "image_id_subject", "subject": "Subject"}', ) name_img: str = os.path.basename(FILE_PATH) m: MultipartEncoder = MultipartEncoder( - fields={'file': (name_img, open(FILE_PATH, 'rb'))} + fields={"file": (name_img, open(FILE_PATH, "rb"))} ) response: dict = requests.post( - url=url, data=m, headers={'x-api-key': RECOGNIZE_API_KEY, - 'Content-Type': 'multipart/form-data'}).json() + url=url, + data=m, + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + ).json() test_subject: AddExampleOfSubjectClient = AddExampleOfSubjectClient( - RECOGNIZE_API_KEY, DOMAIN, PORT) - test_response: dict = test_subject.post( - FILE_PATH, "Subject") + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + test_response: dict = test_subject.post(FILE_PATH, "Subject") + assert response == test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_post_with_options(): + options_url: str = url + "?subject=Subject&det_prob_threshold=1" + httpretty.register_uri( + httpretty.POST, + options_url, + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"image_id": "image_id_subject", "subject": "Subject"}', + ) + + name_img: str = os.path.basename(FILE_PATH) + m: MultipartEncoder = MultipartEncoder( + fields={"file": (name_img, open(FILE_PATH, "rb"))} + ) + response: dict = requests.post( + url=options_url, + data=m, + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + ).json() + + test_subject: AddExampleOfSubjectClient = AddExampleOfSubjectClient( + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + options: DetProbOptionsDict = {"det_prob_threshold": 1} + test_response: dict = test_subject.post(FILE_PATH, "Subject", options) assert response == test_response @@ -93,30 +126,30 @@ def test_post_incorrect_response(): httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': RECOGNIZE_API_KEY, - 'Content-Type': 'multipart/form-data'}, - body='{"image_id": "image_id_subject", "subject": "Subject"}' + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"image_id": "image_id_subject", "subject": "Subject"}', ) name_img: str = os.path.basename(FILE_PATH) m: MultipartEncoder = MultipartEncoder( - fields={'file': (name_img, open(FILE_PATH, 'rb'))} + fields={"file": (name_img, open(FILE_PATH, "rb"))} ) response: dict = requests.post( - url=url, data=m, headers={'x-api-key': RECOGNIZE_API_KEY, - 'Content-Type': 'multipart/form-data'}).json() + url=url, + data=m, + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + ).json() httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': RECOGNIZE_API_KEY, - 'Content-Type': 'multipart/form-data'}, - body='{"image_id": "image_id_subjectssss", "subject": "Subjectss"}' + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"image_id": "image_id_subjectssss", "subject": "Subjectss"}', ) test_subject: AddExampleOfSubjectClient = AddExampleOfSubjectClient( - RECOGNIZE_API_KEY, DOMAIN, PORT) - test_response: dict = test_subject.post( - FILE_PATH, "Subject") + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + test_response: dict = test_subject.post(FILE_PATH, "Subject") assert response != test_response @@ -125,41 +158,73 @@ def test_get_with_empty_list(): httpretty.register_uri( httpretty.GET, url, - headers={'x-api-key': RECOGNIZE_API_KEY}, - body='{"faces": [{"image_id": "image_id_subject", "subject": "Subject"}]}' + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"faces": [{"image_id": "image_id_subject", "subject": "Subject"}]}', ) test_subject: AddExampleOfSubjectClient = AddExampleOfSubjectClient( - RECOGNIZE_API_KEY, DOMAIN, PORT) + RECOGNIZE_API_KEY, DOMAIN, PORT + ) response: dict = requests.get( - url=url, headers={'x-api-key': RECOGNIZE_API_KEY}).json() + url=url, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() httpretty.register_uri( httpretty.GET, url, - headers={'x-api-key': RECOGNIZE_API_KEY}, - body='{"faces": []}' + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"faces": []}', ) test_response: dict = test_subject.get() assert response != test_response +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_get_with_options(): + options_url = url + "?&page=1&size=5&subject=Subject" + httpretty.register_uri( + httpretty.GET, + options_url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"faces": [{"image_id": "image_id_subject", "subject": "Subject"}]}', + ) + test_subject: AddExampleOfSubjectClient = AddExampleOfSubjectClient( + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + response: dict = requests.get( + url=options_url, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() + options: SavedObjectOptions = {"page": 1, "size": 5, "subject": "Subject"} + test_response: dict = test_subject.get(options) + assert response == test_response + + @httpretty.activate(verbose=True, allow_net_connect=False) def test_delete_incorrect_response(): httpretty.register_uri( httpretty.DELETE, - url + '?subject=Subject', - headers={'x-api-key': RECOGNIZE_API_KEY}, - body='{"faces": [{"image_id": "image_id_subject", "subject": "Subject"}]}' + url + "?subject=Subject", + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"faces": [{"image_id": "image_id_subject", "subject": "Subject"}]}', ) response: dict = requests.delete( - url=url, headers={'x-api-key': RECOGNIZE_API_KEY}).json() + url=url, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() httpretty.register_uri( httpretty.DELETE, - url + '?subject=Subject', - headers={'x-api-key': RECOGNIZE_API_KEY}, - body='{"faces": []}' + url + "?subject=Subject", + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"faces": []}', ) test_subject: AddExampleOfSubjectClient = AddExampleOfSubjectClient( - RECOGNIZE_API_KEY, DOMAIN, PORT) + RECOGNIZE_API_KEY, DOMAIN, PORT + ) test_response: dict = test_subject.delete("Subject") assert response != test_response + + +def test_not_implemented_methods(): + test_subject: AddExampleOfSubjectClient = AddExampleOfSubjectClient( + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + + assert test_subject.put() is None diff --git a/tests/client/test_delete_example_by_id_client.py b/tests/client/test_delete_example_by_id_client.py index 30a1410..c1acd43 100644 --- a/tests/client/test_delete_example_by_id_client.py +++ b/tests/client/test_delete_example_by_id_client.py @@ -21,7 +21,7 @@ from compreface.config.api_list import RECOGNIZE_CRUD_API from tests.client.const_config import DOMAIN, PORT, RECOGNIZE_API_KEY, IMAGE_ID -url: str = DOMAIN + ":" + PORT + RECOGNIZE_CRUD_API + '/' + IMAGE_ID +url: str = DOMAIN + ":" + PORT + RECOGNIZE_CRUD_API + "/" + IMAGE_ID @httpretty.activate(verbose=True, allow_net_connect=False) @@ -29,14 +29,16 @@ def test_delete(): httpretty.register_uri( httpretty.DELETE, url, - headers={'x-api-key': RECOGNIZE_API_KEY}, - body='{"image_id": "image_id", "subject": "Donatello"}' + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"image_id": "image_id", "subject": "Donatello"}', ) response: dict = requests.delete( - url=url, headers={'x-api-key': RECOGNIZE_API_KEY}).json() + url=url, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() test_subject: DeleteExampleByIdClient = DeleteExampleByIdClient( - RECOGNIZE_API_KEY, DOMAIN, PORT) + RECOGNIZE_API_KEY, DOMAIN, PORT + ) test_response: dict = test_subject.delete(IMAGE_ID) assert response == test_response @@ -46,19 +48,90 @@ def test_delete_other_response(): httpretty.register_uri( httpretty.DELETE, url, - headers={'x-api-key': RECOGNIZE_API_KEY}, - body='{"image_id": "image_id", "subject": "Donatello"}' + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"image_id": "image_id", "subject": "Donatello"}', ) response: dict = requests.delete( - url=url, headers={'x-api-key': RECOGNIZE_API_KEY}).json() + url=url, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() test_subject: DeleteExampleByIdClient = DeleteExampleByIdClient( - RECOGNIZE_API_KEY, DOMAIN, PORT) + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + httpretty.register_uri( + httpretty.DELETE, url, headers={"x-api-key": RECOGNIZE_API_KEY}, body="{}" + ) + test_response: dict = test_subject.delete(IMAGE_ID) + assert response != test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_delete_empty(): + empty_url: str = DOMAIN + ":" + PORT + RECOGNIZE_CRUD_API + "/" httpretty.register_uri( httpretty.DELETE, - url, - headers={'x-api-key': RECOGNIZE_API_KEY}, - body='{}' + empty_url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"image_id": "image_id", "subject": "Donatello"}', + ) + response: dict = requests.delete( + url=empty_url, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() + + test_subject: DeleteExampleByIdClient = DeleteExampleByIdClient( + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + httpretty.register_uri( + httpretty.DELETE, url, headers={"x-api-key": RECOGNIZE_API_KEY}, body="{}" ) test_response: dict = test_subject.delete(IMAGE_ID) assert response != test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_multiple_delete(): + url_delete: str = DOMAIN + ":" + PORT + RECOGNIZE_CRUD_API + "/delete" + image_ids: list = ["1", "2", "3"] + httpretty.register_uri( + httpretty.POST, + url_delete, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"image_id": "image_id", "subject": "Donatello"}', + ) + response: dict = requests.post( + url=url_delete, json=image_ids, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() + + test_subject: DeleteExampleByIdClient = DeleteExampleByIdClient( + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + test_response: dict = test_subject.post(image_ids) + assert response == test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_multiple_delete_empty(): + url_delete: str = DOMAIN + ":" + PORT + RECOGNIZE_CRUD_API + "/delete" + httpretty.register_uri( + httpretty.POST, + url_delete, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"image_id": "image_id", "subject": "Donatello"}', + ) + response: dict = requests.post( + url=url_delete, json=[], headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() + + test_subject: DeleteExampleByIdClient = DeleteExampleByIdClient( + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + test_response: dict = test_subject.post() + assert response == test_response + + +def test_not_implemented_methods(): + test_subject: DeleteExampleByIdClient = DeleteExampleByIdClient( + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + assert test_subject.get() is None + assert test_subject.put() is None diff --git a/tests/client/test_detect_face_from_image.py b/tests/client/test_detect_face_from_image.py index 82b1db9..ba61134 100644 --- a/tests/client/test_detect_face_from_image.py +++ b/tests/client/test_detect_face_from_image.py @@ -15,12 +15,21 @@ """ import os +import pytest import httpretty import requests + +from compreface.exceptions.field_exception import IncorrectFieldException +from compreface.common.typed_dict import ExpandedOptionsDict from compreface.config.api_list import DETECTION_API from requests_toolbelt.multipart.encoder import MultipartEncoder from compreface.client.detect_face_from_image import DetectFaceFromImageClient -from tests.client.const_config import DOMAIN, PORT, DETECTION_API_KEY, FILE_PATH, IMAGE_ID +from tests.client.const_config import ( + DOMAIN, + PORT, + DETECTION_API_KEY, + FILE_PATH, +) url: str = DOMAIN + ":" + PORT + DETECTION_API @@ -30,47 +39,133 @@ def test_post(): httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': DETECTION_API_KEY, - 'Content-Type': 'multipart/form-data'}, - body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}' + headers={"x-api-key": DETECTION_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', ) name_img: str = os.path.basename(FILE_PATH) m: MultipartEncoder = MultipartEncoder( - fields={'file': (name_img, open(FILE_PATH, 'rb'))} + fields={"file": (name_img, open(FILE_PATH, "rb"))} ) response: dict = requests.post( - url=url, data=m, headers={'x-api-key': DETECTION_API_KEY}).json() + url=url, data=m, headers={"x-api-key": DETECTION_API_KEY} + ).json() test_subject: DetectFaceFromImageClient = DetectFaceFromImageClient( - DETECTION_API_KEY, DOMAIN, PORT) + DETECTION_API_KEY, DOMAIN, PORT + ) test_response: dict = test_subject.post(FILE_PATH) assert response == test_response +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_post_with_options(): + options_url: str = ( + url + + "?&det_prob_threshold=1&limit=1&status=true&face_plugins=calculator,gender" + ) + httpretty.register_uri( + httpretty.POST, + options_url, + headers={"x-api-key": DETECTION_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + name_img: str = os.path.basename(FILE_PATH) + m: MultipartEncoder = MultipartEncoder( + fields={"file": (name_img, open(FILE_PATH, "rb"))} + ) + response: dict = requests.post( + url=options_url, data=m, headers={"x-api-key": DETECTION_API_KEY} + ).json() + test_subject: DetectFaceFromImageClient = DetectFaceFromImageClient( + DETECTION_API_KEY, DOMAIN, PORT + ) + options: ExpandedOptionsDict = { + "det_prob_threshold": 1, + "limit": 1, + "status": True, + "face_plugins": "calculator,gender", + } + test_response: dict = test_subject.post(FILE_PATH, options) + assert response == test_response + + +def test_post_with_options_failed_limit(): + options: ExpandedOptionsDict = { + "det_prob_threshold": 1, + "limit": -1, + "status": True, + "face_plugins": "calculator", + } + test_subject: DetectFaceFromImageClient = DetectFaceFromImageClient( + DETECTION_API_KEY, DOMAIN, PORT + ) + with pytest.raises(IncorrectFieldException): + test_subject.post(FILE_PATH, options) + + +def test_post_with_options_failed_det_prob_threshold(): + options: ExpandedOptionsDict = { + "det_prob_threshold": -1, + "limit": 1, + "status": True, + "face_plugins": "calculator", + } + test_subject: DetectFaceFromImageClient = DetectFaceFromImageClient( + DETECTION_API_KEY, DOMAIN, PORT + ) + with pytest.raises(IncorrectFieldException): + test_subject.post(FILE_PATH, options) + + +def test_post_with_options_failed_face_plugins(): + options: ExpandedOptionsDict = { + "det_prob_threshold": 1, + "limit": 1, + "status": True, + "face_plugins": "calculator", + "size": -1, + } + test_subject: DetectFaceFromImageClient = DetectFaceFromImageClient( + DETECTION_API_KEY, DOMAIN, PORT + ) + with pytest.raises(IncorrectFieldException): + test_subject.post(FILE_PATH, options) + + @httpretty.activate(verbose=True, allow_net_connect=False) def test_post_other_response(): httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': DETECTION_API_KEY, - 'Content-Type': 'multipart/form-data'}, - body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}' + headers={"x-api-key": DETECTION_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', ) name_img: str = os.path.basename(FILE_PATH) m: MultipartEncoder = MultipartEncoder( - fields={'file': (name_img, open(FILE_PATH, 'rb'))} + fields={"file": (name_img, open(FILE_PATH, "rb"))} ) response: dict = requests.post( - url=url, data=m, headers={'x-api-key': DETECTION_API_KEY}).json() + url=url, data=m, headers={"x-api-key": DETECTION_API_KEY} + ).json() test_subject: DetectFaceFromImageClient = DetectFaceFromImageClient( - DETECTION_API_KEY, DOMAIN, PORT) + DETECTION_API_KEY, DOMAIN, PORT + ) httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': DETECTION_API_KEY, - 'Content-Type': 'multipart/form-data'}, - body='{"result" : [{"age" : [ 21, 32 ], "gender" : "female"}]}' + headers={"x-api-key": DETECTION_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"result" : [{"age" : [ 21, 32 ], "gender" : "female"}]}', ) test_response: dict = test_subject.post(FILE_PATH) assert response != test_response + + +def test_not_implemented_methods(): + test_subject: DetectFaceFromImageClient = DetectFaceFromImageClient( + DETECTION_API_KEY, DOMAIN, PORT + ) + assert test_subject.get() is None + assert test_subject.put() is None + assert test_subject.delete() is None diff --git a/tests/client/test_recognize_face_from_embeddings.py b/tests/client/test_recognize_face_from_embeddings.py new file mode 100644 index 0000000..c289dfb --- /dev/null +++ b/tests/client/test_recognize_face_from_embeddings.py @@ -0,0 +1,115 @@ +""" + Copyright(c) 2021 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 compreface.common.typed_dict import PredictionCountOptionsDict +import pytest +import httpretty +import requests +from compreface.client.recognize_face_from_embeddings import ( + RecognizeFaceFromEmbeddingClient, +) +from compreface.config.api_list import RECOGNIZE_EMBEDDINGS_API +from tests.client.const_config import DOMAIN, PORT, RECOGNIZE_API_KEY + +""" + Server configuration +""" +url: str = DOMAIN + ":" + PORT + RECOGNIZE_EMBEDDINGS_API + "/recognize" + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_recognize(): + embeddings: list = [2, 5, 6] + httpretty.register_uri( + httpretty.POST, + url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + response: dict = requests.post( + url=url, + json={"embeddings": embeddings}, + headers={"x-api-key": RECOGNIZE_API_KEY}, + ).json() + + test_subject: RecognizeFaceFromEmbeddingClient = RecognizeFaceFromEmbeddingClient( + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + test_response: dict = test_subject.post(embeddings) + assert response == test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_recognize_with_options(): + embeddings: list = [2, 5, 6] + options_url: str = url + "?&prediction_count=1" + httpretty.register_uri( + httpretty.POST, + options_url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + response: dict = requests.post( + url=options_url, + json={"embeddings": embeddings}, + headers={"x-api-key": RECOGNIZE_API_KEY}, + ).json() + + options: PredictionCountOptionsDict = {"prediction_count": 1} + + test_subject: RecognizeFaceFromEmbeddingClient = RecognizeFaceFromEmbeddingClient( + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + test_response: dict = test_subject.post(embeddings, options) + assert response == test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_recognize_other_response(): + embeddings: list = [2, 5, 6] + httpretty.register_uri( + httpretty.POST, + url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + response: dict = requests.post( + url=url, + json={"embeddings": embeddings}, + headers={"x-api-key": RECOGNIZE_API_KEY}, + ).json() + httpretty.register_uri( + httpretty.POST, + url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"result" : [{"age" : [ 26, 31 ], "gender" : "female"}]}', + ) + test_subject: RecognizeFaceFromEmbeddingClient = RecognizeFaceFromEmbeddingClient( + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + test_response: dict = test_subject.post(embeddings) + assert response != test_response + + +def test_not_implemented_methods(): + test_subject: RecognizeFaceFromEmbeddingClient = RecognizeFaceFromEmbeddingClient( + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + assert test_subject.get() is None + assert test_subject.put() is None + assert test_subject.delete() is None diff --git a/tests/client/test_recognize_face_from_image.py b/tests/client/test_recognize_face_from_image.py index 2b07d6e..fce8d08 100644 --- a/tests/client/test_recognize_face_from_image.py +++ b/tests/client/test_recognize_face_from_image.py @@ -15,6 +15,7 @@ """ import os +from compreface.common.typed_dict import AllOptionsDict import pytest import httpretty import requests @@ -22,6 +23,7 @@ from compreface.client.recognize_face_from_image import RecognizeFaceFromImageClient from compreface.config.api_list import RECOGNIZE_API from tests.client.const_config import DOMAIN, PORT, RECOGNIZE_API_KEY, FILE_PATH + """ Server configuration """ @@ -33,19 +35,22 @@ def test_recognize(): httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': RECOGNIZE_API_KEY, - 'Content-Type': 'multipart/form-data'}, - body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}' + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', ) name_img: str = os.path.basename(FILE_PATH) m: MultipartEncoder = MultipartEncoder( - fields={'file': (name_img, open(FILE_PATH, 'rb'))} + fields={"file": (name_img, open(FILE_PATH, "rb"))} ) response: dict = requests.post( - url=url, data=m, headers={'x-api-key': RECOGNIZE_API_KEY, 'Content-Type': 'multipart/form-data'}).json() + url=url, + data=m, + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + ).json() test_subject: RecognizeFaceFromImageClient = RecognizeFaceFromImageClient( - RECOGNIZE_API_KEY, DOMAIN, PORT) + RECOGNIZE_API_KEY, DOMAIN, PORT + ) test_response: dict = test_subject.post(FILE_PATH) assert response == test_response @@ -55,24 +60,71 @@ def test_recognize_other_response(): httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': RECOGNIZE_API_KEY, - 'Content-Type': 'multipart/form-data'}, - body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}' + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', ) name_img: str = os.path.basename(FILE_PATH) m: MultipartEncoder = MultipartEncoder( - fields={'file': (name_img, open(FILE_PATH, 'rb'))} + fields={"file": (name_img, open(FILE_PATH, "rb"))} ) response: dict = requests.post( - url=url, data=m, headers={'x-api-key': RECOGNIZE_API_KEY, 'Content-Type': 'multipart/form-data'}).json() + url=url, + data=m, + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + ).json() httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': RECOGNIZE_API_KEY, - 'Content-Type': 'multipart/form-data'}, - body='{"result" : [{"age" : [ 26, 31 ], "gender" : "female"}]}' + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"result" : [{"age" : [ 26, 31 ], "gender" : "female"}]}', ) test_subject: RecognizeFaceFromImageClient = RecognizeFaceFromImageClient( - RECOGNIZE_API_KEY, DOMAIN, PORT) + RECOGNIZE_API_KEY, DOMAIN, PORT + ) test_response: dict = test_subject.post(FILE_PATH) assert response != test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_recognize_with_options(): + options_url: str = ( + url + + "?&prediction_count=1&limit=1&status=true&face_plugins=calculator&det_prob_threshold=1" + ) + httpretty.register_uri( + httpretty.POST, + options_url, + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + name_img: str = os.path.basename(FILE_PATH) + m: MultipartEncoder = MultipartEncoder( + fields={"file": (name_img, open(FILE_PATH, "rb"))} + ) + response: dict = requests.post( + url=options_url, + data=m, + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + ).json() + + options: AllOptionsDict = { + "prediction_count": 1, + "limit": 1, + "status": True, + "face_plugins": "calculator", + "det_prob_threshold": 1, + } + test_subject: RecognizeFaceFromImageClient = RecognizeFaceFromImageClient( + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + test_response: dict = test_subject.post(FILE_PATH, options) + assert response == test_response + + +def test_not_implemented_methods(): + test_subject: RecognizeFaceFromImageClient = RecognizeFaceFromImageClient( + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + assert test_subject.get() is None + assert test_subject.put() is None + assert test_subject.delete() is None diff --git a/tests/client/test_subject_crud_client.py b/tests/client/test_subject_crud_client.py index 416a02b..831cf47 100644 --- a/tests/client/test_subject_crud_client.py +++ b/tests/client/test_subject_crud_client.py @@ -1,4 +1,3 @@ - """ Copyright(c) 2021 the original author or authors @@ -20,6 +19,7 @@ from compreface.client import SubjectClient from compreface.config.api_list import SUBJECTS_CRUD_API from tests.client.const_config import DOMAIN, PORT, DETECTION_API_KEY + """ Server configuration """ @@ -31,11 +31,13 @@ def test_get(): httpretty.register_uri( httpretty.GET, url, - headers={'x-api-key': DETECTION_API_KEY}, - body='{"subjects": ["Subject", "Subject2"]}' + headers={"x-api-key": DETECTION_API_KEY}, + body='{"subjects": ["Subject", "Subject2"]}', ) test_subject: SubjectClient = SubjectClient(DETECTION_API_KEY, DOMAIN, PORT) - response: dict = requests.get(url=url, headers={'x-api-key': DETECTION_API_KEY}).json() + response: dict = requests.get( + url=url, headers={"x-api-key": DETECTION_API_KEY} + ).json() test_response: dict = test_subject.get() assert response == test_response @@ -45,19 +47,20 @@ def test_post(): httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': DETECTION_API_KEY, - 'Content-Type': 'application/json'}, - body='{"subject": "Subject"}' + headers={"x-api-key": DETECTION_API_KEY, "Content-Type": "application/json"}, + body='{"subject": "Subject"}', ) - data = {'subject': 'Subject'} + data: dict = {"subject": "Subject"} response: dict = requests.post( - url=url, data=json.dumps(data), headers={'x-api-key': DETECTION_API_KEY, - 'Content-Type': 'application/json'}).json() + url=url, + data=json.dumps(data), + headers={"x-api-key": DETECTION_API_KEY, "Content-Type": "application/json"}, + ).json() test_subject: SubjectClient = SubjectClient(DETECTION_API_KEY, DOMAIN, PORT) - test_response: dict = test_subject.post({'subject': 'Subject'}) + test_response: dict = test_subject.post({"subject": "Subject"}) assert response == test_response @@ -66,44 +69,44 @@ def test_post_incorrect_response(): httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': DETECTION_API_KEY, - 'Content-Type': 'application/json'}, - body='{"subject": "Subjectss"}' + headers={"x-api-key": DETECTION_API_KEY, "Content-Type": "application/json"}, + body='{"subject": "Subjectss"}', ) - data = {'subject': 'Subjectss'} + data: dict = {"subject": "Subjectss"} response: dict = requests.post( - url=url, data=json.dumps(data), headers={'x-api-key': DETECTION_API_KEY, - 'Content-Type': 'application/json'}).json() + url=url, + data=json.dumps(data), + headers={"x-api-key": DETECTION_API_KEY, "Content-Type": "application/json"}, + ).json() httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': DETECTION_API_KEY, - 'Content-Type': 'application/json'}, - body='{"subject": "Subject"}' + headers={"x-api-key": DETECTION_API_KEY, "Content-Type": "application/json"}, + body='{"subject": "Subject"}', ) test_subject: SubjectClient = SubjectClient(DETECTION_API_KEY, DOMAIN, PORT) - test_response: dict = test_subject.post({'subject': 'Subject'}) + test_response: dict = test_subject.post({"subject": "Subject"}) assert response != test_response @httpretty.activate(verbose=True, allow_net_connect=False) def test_delete(): - test_url = url + '/Subject' + test_url: str = url + "/Subject" httpretty.register_uri( httpretty.DELETE, test_url, - headers={'x-api-key': DETECTION_API_KEY, - 'Content-Type': 'application/json'}, - body='{"subject": "Subject"}' + headers={"x-api-key": DETECTION_API_KEY, "Content-Type": "application/json"}, + body='{"subject": "Subject"}', ) - response: dict = requests.delete(url=test_url, - headers={'x-api-key': DETECTION_API_KEY, - 'Content-Type': 'application/json'}).json() + response: dict = requests.delete( + url=test_url, + headers={"x-api-key": DETECTION_API_KEY, "Content-Type": "application/json"}, + ).json() test_subject: SubjectClient = SubjectClient(DETECTION_API_KEY, DOMAIN, PORT) test_response: dict = test_subject.delete("Subject") @@ -112,19 +115,22 @@ def test_delete(): @httpretty.activate(verbose=True, allow_net_connect=False) def test_put(): - test_url = url + '/Subject' + test_url: str = url + "/Subject" httpretty.register_uri( httpretty.PUT, test_url, - headers={'x-api-key': DETECTION_API_KEY, - 'Content-Type': 'application/json'}, - body='{"subject": "NewSubject"}' + headers={"x-api-key": DETECTION_API_KEY, "Content-Type": "application/json"}, + body='{"subject": "NewSubject"}', ) data = {"subject": "NewSubject"} - response: dict = requests.put(url=test_url, data=json.dumps(data), headers={'x-api-key': DETECTION_API_KEY}).json() + response: dict = requests.put( + url=test_url, data=json.dumps(data), headers={"x-api-key": DETECTION_API_KEY} + ).json() test_subject: SubjectClient = SubjectClient(DETECTION_API_KEY, DOMAIN, PORT) - test_response: dict = test_subject.put({"subject": "NewSubject", "api_endpoint": "Subject"}) + test_response: dict = test_subject.put( + {"subject": "NewSubject", "api_endpoint": "Subject"} + ) assert response == test_response diff --git a/tests/client/test_verification_face_from_embeddings.py b/tests/client/test_verification_face_from_embeddings.py new file mode 100644 index 0000000..6690210 --- /dev/null +++ b/tests/client/test_verification_face_from_embeddings.py @@ -0,0 +1,96 @@ +""" + Copyright(c) 2021 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 compreface.exceptions.field_exception import IncorrectFieldException +import pytest +import httpretty +import requests +from compreface.config.api_list import RECOGNIZE_EMBEDDINGS_API +from compreface.client import VerificationFaceFromEmbeddingClient +from tests.client.const_config import ( + DOMAIN, + PORT, + RECOGNIZE_API_KEY, + IMAGE_ID, +) + + +url: str = ( + DOMAIN + ":" + PORT + RECOGNIZE_EMBEDDINGS_API + "/faces/{}/verify".format(IMAGE_ID) +) + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_post(): + embedding: list = [1, 6, 7, 2] + + httpretty.register_uri( + httpretty.POST, + url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + response: dict = requests.post( + url=url, + json={"embeddings": embedding}, + headers={"x-api-key": RECOGNIZE_API_KEY}, + ).json() + + test_subject: VerificationFaceFromEmbeddingClient = ( + VerificationFaceFromEmbeddingClient(RECOGNIZE_API_KEY, DOMAIN, PORT) + ) + test_response: dict = test_subject.post(embedding, IMAGE_ID) + assert response == test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_post_empty_embeddings(): + httpretty.register_uri( + httpretty.POST, + url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + response: dict = requests.post( + url=url, + json={"embeddings": []}, + headers={"x-api-key": RECOGNIZE_API_KEY}, + ).json() + + test_subject: VerificationFaceFromEmbeddingClient = ( + VerificationFaceFromEmbeddingClient(RECOGNIZE_API_KEY, DOMAIN, PORT) + ) + test_response: dict = test_subject.post(image_id=IMAGE_ID) + assert response == test_response + + +def test_post_failed(): + with pytest.raises(IncorrectFieldException): + test_subject: VerificationFaceFromEmbeddingClient = ( + VerificationFaceFromEmbeddingClient(RECOGNIZE_API_KEY, DOMAIN, PORT) + ) + test_subject.post() + + +def test_not_implemented_methods(): + test_subject: VerificationFaceFromEmbeddingClient = ( + VerificationFaceFromEmbeddingClient(RECOGNIZE_API_KEY, DOMAIN, PORT) + ) + assert test_subject.get() is None + assert test_subject.put() is None + assert test_subject.delete() is None diff --git a/tests/client/test_verification_face_from_image.py b/tests/client/test_verification_face_from_image.py index e06cbea..5ef70c3 100644 --- a/tests/client/test_verification_face_from_image.py +++ b/tests/client/test_verification_face_from_image.py @@ -15,14 +15,21 @@ """ import os +from compreface.common.typed_dict import ExpandedOptionsDict import httpretty import requests from requests_toolbelt.multipart.encoder import MultipartEncoder from compreface.config.api_list import RECOGNIZE_CRUD_API from compreface.client import VerificationFaceFromImageClient -from tests.client.const_config import DOMAIN, PORT, RECOGNIZE_API_KEY, FILE_PATH, IMAGE_ID +from tests.client.const_config import ( + DOMAIN, + PORT, + RECOGNIZE_API_KEY, + FILE_PATH, + IMAGE_ID, +) -url: str = DOMAIN + ":" + PORT + RECOGNIZE_CRUD_API + '/' + IMAGE_ID + '/verify' +url: str = DOMAIN + ":" + PORT + RECOGNIZE_CRUD_API + "/" + IMAGE_ID + "/verify" @httpretty.activate(verbose=True, allow_net_connect=False) @@ -30,20 +37,21 @@ def test_post(): httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': RECOGNIZE_API_KEY, - 'Content-Type': 'multipart/form-data'}, - body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}' + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', ) name_img: str = os.path.basename(FILE_PATH) m: MultipartEncoder = MultipartEncoder( - fields={'file': (name_img, open(FILE_PATH, 'rb'))} + fields={"file": (name_img, open(FILE_PATH, "rb"))} ) response: dict = requests.post( - url=url, data=m, headers={'x-api-key': RECOGNIZE_API_KEY}).json() + url=url, data=m, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() test_subject: VerificationFaceFromImageClient = VerificationFaceFromImageClient( - RECOGNIZE_API_KEY, DOMAIN, PORT) + RECOGNIZE_API_KEY, DOMAIN, PORT + ) test_response: dict = test_subject.post(FILE_PATH, IMAGE_ID) assert response == test_response @@ -53,26 +61,65 @@ def test_post_other_response(): httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': RECOGNIZE_API_KEY, - 'Content-Type': 'multipart/form-data'}, - body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}' + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', ) name_img: str = os.path.basename(FILE_PATH) m: MultipartEncoder = MultipartEncoder( - fields={'file': (name_img, open(FILE_PATH, 'rb'))} + fields={"file": (name_img, open(FILE_PATH, "rb"))} ) response: dict = requests.post( - url=url, data=m, headers={'x-api-key': RECOGNIZE_API_KEY}).json() + url=url, data=m, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': RECOGNIZE_API_KEY, - 'Content-Type': 'multipart/form-data'}, - body='{"result" : [{"age" : [ 26, 31 ], "gender" : "female"}]}' + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"result" : [{"age" : [ 26, 31 ], "gender" : "female"}]}', ) test_subject: VerificationFaceFromImageClient = VerificationFaceFromImageClient( - RECOGNIZE_API_KEY, DOMAIN, PORT) + RECOGNIZE_API_KEY, DOMAIN, PORT + ) test_response: dict = test_subject.post(FILE_PATH, IMAGE_ID) assert response != test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_post_with_options(): + options_url: str = url + "?&limit=1&status=True&face_plugins=calculator" + httpretty.register_uri( + httpretty.POST, + options_url, + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + name_img: str = os.path.basename(FILE_PATH) + m: MultipartEncoder = MultipartEncoder( + fields={"file": (name_img, open(FILE_PATH, "rb"))} + ) + response: dict = requests.post( + url=options_url, data=m, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() + + options: ExpandedOptionsDict = { + "limit": 1, + "status": True, + "face_plugins": "calculator", + } + test_subject: VerificationFaceFromImageClient = VerificationFaceFromImageClient( + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + test_response: dict = test_subject.post(FILE_PATH, IMAGE_ID, options) + assert response == test_response + + +def test_not_implemented_methods(): + test_subject: VerificationFaceFromImageClient = VerificationFaceFromImageClient( + RECOGNIZE_API_KEY, DOMAIN, PORT + ) + assert test_subject.get() is None + assert test_subject.put() is None + assert test_subject.delete() is None diff --git a/tests/client/test_verify_face_from_embeddings.py b/tests/client/test_verify_face_from_embeddings.py new file mode 100644 index 0000000..d1877e1 --- /dev/null +++ b/tests/client/test_verify_face_from_embeddings.py @@ -0,0 +1,94 @@ +""" + Copyright(c) 2021 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. + """ + +import pytest +import httpretty +import requests +from compreface.client import VerifyFaceFromEmbeddingClient +from compreface.config.api_list import VERIFICATION_EMBEDDINGS_API +from tests.client.const_config import DOMAIN, PORT, VERIFICATION_API_KEY + +""" + Server configuration +""" +url: str = DOMAIN + ":" + PORT + VERIFICATION_EMBEDDINGS_API + "/verify" + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_post(): + source_embeddings: list = [1, 3, 4] + targets_embeddings: list = [2, 5, 6] + httpretty.register_uri( + httpretty.POST, + url, + headers={ + "x-api-key": VERIFICATION_API_KEY, + }, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + response: dict = requests.post( + url=url, + json={"targets": source_embeddings, "source": targets_embeddings}, + headers={"x-api-key": VERIFICATION_API_KEY}, + ).json() + test_subject: VerifyFaceFromEmbeddingClient = VerifyFaceFromEmbeddingClient( + VERIFICATION_API_KEY, DOMAIN, PORT + ) + test_response: dict = test_subject.post(source_embeddings, targets_embeddings) + assert response == test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_post_other_response(): + source_embeddings: list = [1, 3, 4] + targets_embeddings: list = [2, 5, 6] + httpretty.register_uri( + httpretty.POST, + url, + headers={ + "x-api-key": VERIFICATION_API_KEY, + }, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + response: dict = requests.post( + url=url, + json={"targets": source_embeddings, "source": targets_embeddings}, + headers={"x-api-key": VERIFICATION_API_KEY}, + ).json() + test_subject: VerifyFaceFromEmbeddingClient = VerifyFaceFromEmbeddingClient( + VERIFICATION_API_KEY, DOMAIN, PORT + ) + httpretty.register_uri( + httpretty.POST, + url, + headers={ + "x-api-key": VERIFICATION_API_KEY, + }, + body='{"result" : [{"age" : [ 21, 32 ], "gender" : "female"}]}', + ) + test_response: dict = test_subject.post(source_embeddings, targets_embeddings) + assert response != test_response + + +def test_not_implemented_methods(): + test_subject: VerifyFaceFromEmbeddingClient = VerifyFaceFromEmbeddingClient( + VERIFICATION_API_KEY, DOMAIN, PORT + ) + assert test_subject.get() is None + assert test_subject.put() is None + assert test_subject.delete() is None diff --git a/tests/client/test_verify_face_from_image.py b/tests/client/test_verify_face_from_image.py index 865b00b..1cfe32a 100644 --- a/tests/client/test_verify_face_from_image.py +++ b/tests/client/test_verify_face_from_image.py @@ -14,6 +14,7 @@ permissions and limitations under the License. """ +from compreface.common.typed_dict import ExpandedOptionsDict import pytest import os import httpretty @@ -22,10 +23,11 @@ from compreface.client import VerifyFaceFromImageClient from compreface.config.api_list import VERIFICATION_API from tests.client.const_config import DOMAIN, PORT, VERIFICATION_API_KEY, FILE_PATH + """ Server configuration """ -url: str = DOMAIN + ":" + PORT + VERIFICATION_API + '/verify' +url: str = DOMAIN + ":" + PORT + VERIFICATION_API + "/verify" @httpretty.activate(verbose=True, allow_net_connect=False) @@ -33,21 +35,28 @@ def test_post(): httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': VERIFICATION_API_KEY, - 'Content-Type': 'multipart/form-data'}, - body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}' + headers={ + "x-api-key": VERIFICATION_API_KEY, + "Content-Type": "multipart/form-data", + }, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', ) name_img: str = os.path.basename(FILE_PATH) m: MultipartEncoder = MultipartEncoder( - fields={'source_image': (name_img, open( - FILE_PATH, 'rb')), 'target_image': (name_img, open( - FILE_PATH, 'rb'))} + fields={ + "source_image": (name_img, open(FILE_PATH, "rb")), + "target_image": (name_img, open(FILE_PATH, "rb")), + } ) response: dict = requests.post( - url=url, data=m, headers={'x-api-key': VERIFICATION_API_KEY, 'Content-Type': m.content_type}).json() + url=url, + data=m, + headers={"x-api-key": VERIFICATION_API_KEY, "Content-Type": m.content_type}, + ).json() test_subject: VerifyFaceFromImageClient = VerifyFaceFromImageClient( - VERIFICATION_API_KEY, DOMAIN, PORT) + VERIFICATION_API_KEY, DOMAIN, PORT + ) test_response: dict = test_subject.post(FILE_PATH, FILE_PATH) assert response == test_response @@ -57,27 +66,85 @@ def test_post_other_response(): httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': VERIFICATION_API_KEY, - 'Content-Type': 'multipart/form-data'}, - body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}' + headers={ + "x-api-key": VERIFICATION_API_KEY, + "Content-Type": "multipart/form-data", + }, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', ) name_img: str = os.path.basename(FILE_PATH) m: MultipartEncoder = MultipartEncoder( - fields={'source_image': (name_img, open( - FILE_PATH, 'rb')), 'target_image': (name_img, open( - FILE_PATH, 'rb'))} + fields={ + "source_image": (name_img, open(FILE_PATH, "rb")), + "target_image": (name_img, open(FILE_PATH, "rb")), + } ) response: dict = requests.post( - url=url, data=m, headers={'x-api-key': VERIFICATION_API_KEY, 'Content-Type': m.content_type}).json() + url=url, + data=m, + headers={"x-api-key": VERIFICATION_API_KEY, "Content-Type": m.content_type}, + ).json() test_subject: VerifyFaceFromImageClient = VerifyFaceFromImageClient( - VERIFICATION_API_KEY, DOMAIN, PORT) + VERIFICATION_API_KEY, DOMAIN, PORT + ) httpretty.register_uri( httpretty.POST, url, - headers={'x-api-key': VERIFICATION_API_KEY, - 'Content-Type': 'multipart/form-data'}, - body='{"result" : [{"age" : [ 21, 32 ], "gender" : "female"}]}' + headers={ + "x-api-key": VERIFICATION_API_KEY, + "Content-Type": "multipart/form-data", + }, + body='{"result" : [{"age" : [ 21, 32 ], "gender" : "female"}]}', ) test_response: dict = test_subject.post(FILE_PATH, FILE_PATH) assert response != test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_post_with_options(): + options_url: str = ( + url + "?&det_prob_threshold=1&limit=1&status=true&face_plugins=calculator" + ) + httpretty.register_uri( + httpretty.POST, + options_url, + headers={ + "x-api-key": VERIFICATION_API_KEY, + "Content-Type": "multipart/form-data", + }, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + name_img: str = os.path.basename(FILE_PATH) + m: MultipartEncoder = MultipartEncoder( + fields={ + "source_image": (name_img, open(FILE_PATH, "rb")), + "target_image": (name_img, open(FILE_PATH, "rb")), + } + ) + response: dict = requests.post( + url=options_url, + data=m, + headers={"x-api-key": VERIFICATION_API_KEY, "Content-Type": m.content_type}, + ).json() + test_subject: VerifyFaceFromImageClient = VerifyFaceFromImageClient( + VERIFICATION_API_KEY, DOMAIN, PORT + ) + options: ExpandedOptionsDict = { + "det_prob_threshold": 1, + "limit": 1, + "status": True, + "face_plugins": "calculator", + } + test_response: dict = test_subject.post(FILE_PATH, FILE_PATH, options) + assert response == test_response + + +def test_not_implemented_methods(): + test_subject: VerifyFaceFromImageClient = VerifyFaceFromImageClient( + VERIFICATION_API_KEY, DOMAIN, PORT + ) + assert test_subject.get() is None + assert test_subject.put() is None + assert test_subject.delete() is None diff --git a/tests/collections/__init__.py b/tests/collections/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/collections/test_face_collections.py b/tests/collections/test_face_collections.py new file mode 100644 index 0000000..bf6954a --- /dev/null +++ b/tests/collections/test_face_collections.py @@ -0,0 +1,173 @@ +""" + Copyright(c) 2021 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. + """ +import os +import pytest +import httpretty +import requests +from requests_toolbelt.multipart.encoder import MultipartEncoder + +from compreface.collections.face_collections import FaceCollection +from compreface.config.api_list import RECOGNIZE_CRUD_API, RECOGNIZE_EMBEDDINGS_API +from tests.client.const_config import ( + DOMAIN, + FILE_PATH, + IMAGE_ID, + PORT, + RECOGNIZE_API_KEY, +) + +url: str = DOMAIN + ":" + PORT + RECOGNIZE_CRUD_API + +face_collection: FaceCollection = FaceCollection( + api_key=RECOGNIZE_API_KEY, domain=DOMAIN, port=PORT +) + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_list_face_collection(): + httpretty.register_uri( + httpretty.GET, + url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='["Subject1", "Subject2"]', + ) + response: dict = requests.get( + url=url, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() + subject_response: dict = face_collection.list() + assert subject_response == response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_add_face_collection(): + httpretty.register_uri( + httpretty.POST, + url, + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"image_id": "image_id_subject", "subject": "Subject"}', + ) + name_img: str = os.path.basename(FILE_PATH) + m: MultipartEncoder = MultipartEncoder( + fields={"file": (name_img, open(FILE_PATH, "rb"))} + ) + response: dict = requests.post( + url=url, + data=m, + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + ).json() + subject_response: dict = face_collection.add(FILE_PATH, "Subject") + assert subject_response == response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_delete_face_collection(): + delete_url: str = url + "/" + IMAGE_ID + httpretty.register_uri( + httpretty.DELETE, + delete_url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"image_id": "image_id", "subject": "Donatello"}', + ) + response: dict = requests.delete( + url=delete_url, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() + + test_response: dict = face_collection.delete(IMAGE_ID) + assert response == test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_delete_all_face_collection(): + delete_url: str = url + "?subject=Subject1" + httpretty.register_uri( + httpretty.DELETE, + delete_url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"image_id": "image_id", "subject": "Donatello"}', + ) + response: dict = requests.delete( + url=delete_url, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() + + test_response: dict = face_collection.delete_all("Subject1") + assert response == test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_delete_multiple_face_collection(): + delete_url: str = url + "/delete" + image_ids: list = ["1", "2", "3"] + httpretty.register_uri( + httpretty.POST, + delete_url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"image_id": "image_id", "subject": "Donatello"}', + ) + response: dict = requests.post( + url=delete_url, json=image_ids, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() + + test_response: dict = face_collection.delete_multiple(image_ids) + assert response == test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_verify_image_face_collection(): + verify_url: str = url + "/" + IMAGE_ID + "/verify" + httpretty.register_uri( + httpretty.POST, + verify_url, + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + name_img: str = os.path.basename(FILE_PATH) + m: MultipartEncoder = MultipartEncoder( + fields={"file": (name_img, open(FILE_PATH, "rb"))} + ) + response: dict = requests.post( + url=verify_url, data=m, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() + test_response: dict = face_collection.verify_image(FILE_PATH, IMAGE_ID) + assert response == test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_verify_embeddings_face_collection(): + embedding: list = [1, 6, 7, 2] + verify_url: str = ( + DOMAIN + + ":" + + PORT + + RECOGNIZE_EMBEDDINGS_API + + "/faces/" + + IMAGE_ID + + "/verify" + ) + httpretty.register_uri( + httpretty.POST, + verify_url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + response: dict = requests.post( + url=verify_url, + json={"embeddings": embedding}, + headers={"x-api-key": RECOGNIZE_API_KEY}, + ).json() + test_response: dict = face_collection.verify_embeddings(embedding, IMAGE_ID) + assert response == test_response diff --git a/tests/collections/test_face_collections_subject.py b/tests/collections/test_face_collections_subject.py new file mode 100644 index 0000000..10f4f5e --- /dev/null +++ b/tests/collections/test_face_collections_subject.py @@ -0,0 +1,114 @@ +""" + Copyright(c) 2021 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. + """ +import json +import pytest +import httpretty +import requests + + +from compreface.config.api_list import SUBJECTS_CRUD_API +from tests.client.const_config import DOMAIN, PORT, RECOGNIZE_API_KEY + +from compreface.collections.face_collections import Subjects + +url: str = DOMAIN + ":" + PORT + SUBJECTS_CRUD_API + +subject = Subjects(api_key=RECOGNIZE_API_KEY, domain=DOMAIN, port=PORT) + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_subject_list(): + httpretty.register_uri( + httpretty.GET, + url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='["Subject1", "Subject2"]', + ) + response: dict = requests.get( + url=url, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() + subject_response: dict = subject.list() + assert subject_response == response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_subject_add(): + subject_added: str = "Subject1" + httpretty.register_uri( + httpretty.POST, + url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='["Subject1", "Subject2"]', + ) + response: dict = requests.post( + url=url, + data=json.dumps(subject_added), + headers={"x-api-key": RECOGNIZE_API_KEY}, + ).json() + subject_response: dict = subject.add(subject_added) + assert subject_response == response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_subject_update(): + subject_added: str = "Subject1" + subject_new_name: str = "Subject2" + update_url: str = url + "/" + subject_added + httpretty.register_uri( + httpretty.PUT, + update_url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='["Subject2"]', + ) + response: dict = requests.put( + url=update_url, + data=json.dumps(subject_new_name), + headers={"x-api-key": RECOGNIZE_API_KEY}, + ).json() + subject_response: dict = subject.update(subject_added, subject_new_name) + assert subject_response == response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_subject_delete(): + subject_added: str = "Subject1" + delete_url: str = url + "/" + subject_added + httpretty.register_uri( + httpretty.DELETE, + delete_url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='["Subject1"]', + ) + response: dict = requests.delete( + url=delete_url, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() + subject_response: dict = subject.delete(subject_added) + assert subject_response == response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_subject_delete_all(): + httpretty.register_uri( + httpretty.DELETE, + url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='["Subject1"]', + ) + response: dict = requests.delete( + url=url, headers={"x-api-key": RECOGNIZE_API_KEY} + ).json() + subject_response: dict = subject.delete_all() + assert subject_response == response diff --git a/tests/core/__init__.py b/tests/core/__init__.py new file mode 100644 index 0000000..e82733f --- /dev/null +++ b/tests/core/__init__.py @@ -0,0 +1,15 @@ +""" + Copyright(c) 2021 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. + """ diff --git a/tests/core/test_model.py b/tests/core/test_model.py new file mode 100644 index 0000000..08281c8 --- /dev/null +++ b/tests/core/test_model.py @@ -0,0 +1,123 @@ +""" + Copyright(c) 2021 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. + """ +import pytest +import requests +import httpretty +import os +from requests_toolbelt.multipart.encoder import MultipartEncoder +from compreface.config.api_list import ( + DETECTION_API, + RECOGNIZE_EMBEDDINGS_API, + VERIFICATION_EMBEDDINGS_API, +) + +from compreface.core.model import CompreFace +from tests.client.const_config import ( + DETECTION_API_KEY, + RECOGNIZE_API_KEY, + DOMAIN, + FILE_PATH, + PORT, + VERIFICATION_API_KEY, +) + +compreface = CompreFace(port="", domain="") +compreface.options = {} +compreface.port = PORT +compreface.domain = DOMAIN + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_init_face_recognition_compreface(): + recognition_service = compreface.init_face_recognition(RECOGNIZE_API_KEY) + embeddings: list = [2, 5, 6] + embeddings_url: str = ( + compreface.domain + + ":" + + compreface.port + + RECOGNIZE_EMBEDDINGS_API + + "/recognize" + ) + httpretty.register_uri( + httpretty.POST, + embeddings_url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + response: dict = requests.post( + url=embeddings_url, + json={"embeddings": embeddings}, + headers={"x-api-key": RECOGNIZE_API_KEY}, + ).json() + + assert response == recognition_service.recognize_embedding( + embeddings, compreface.options + ) + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_init_face_verification_compreface(): + verification_service = compreface.init_face_verification(VERIFICATION_API_KEY) + embeddings_url: str = ( + compreface.domain + + ":" + + compreface.port + + VERIFICATION_EMBEDDINGS_API + + "/verify" + ) + source_embeddings: list = [1, 3, 4] + targets_embeddings: list = [2, 5, 6] + httpretty.register_uri( + httpretty.POST, + embeddings_url, + headers={ + "x-api-key": VERIFICATION_API_KEY, + }, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + response: dict = requests.post( + url=embeddings_url, + json={"targets": source_embeddings, "source": targets_embeddings}, + headers={"x-api-key": VERIFICATION_API_KEY}, + ).json() + subject_response: dict = verification_service.verify_embedding( + source_embeddings, targets_embeddings + ) + assert response == subject_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_init_face_detection_compreface(): + detection_service = compreface.init_face_detection(VERIFICATION_API_KEY) + detection_url = compreface.domain + ":" + compreface.port + DETECTION_API + httpretty.register_uri( + httpretty.POST, + detection_url, + headers={"x-api-key": DETECTION_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + name_img: str = os.path.basename(FILE_PATH) + m: MultipartEncoder = MultipartEncoder( + fields={"file": (name_img, open(FILE_PATH, "rb"))} + ) + response: dict = requests.post( + url=detection_url, data=m, headers={"x-api-key": DETECTION_API_KEY} + ).json() + subject_response: dict = detection_service.detect(image_path=FILE_PATH) + assert response == subject_response diff --git a/tests/service/__init__.py b/tests/service/__init__.py new file mode 100644 index 0000000..e82733f --- /dev/null +++ b/tests/service/__init__.py @@ -0,0 +1,15 @@ +""" + Copyright(c) 2021 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. + """ diff --git a/tests/service/test_detection_service.py b/tests/service/test_detection_service.py new file mode 100644 index 0000000..e1cc5b6 --- /dev/null +++ b/tests/service/test_detection_service.py @@ -0,0 +1,55 @@ +""" + Copyright(c) 2021 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. + """ + +import pytest +import httpretty +import requests +import os +from requests_toolbelt.multipart.encoder import MultipartEncoder +from compreface.config.api_list import DETECTION_API + +from compreface.service.detection_service import DetectionService +from tests.client.const_config import DETECTION_API_KEY, DOMAIN, FILE_PATH, PORT + +url: str = DOMAIN + ":" + PORT + DETECTION_API + +detection_service = DetectionService( + api_key=DETECTION_API_KEY, domain=DOMAIN, port=PORT +) + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_detect_image(): + httpretty.register_uri( + httpretty.POST, + url, + headers={"x-api-key": DETECTION_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + name_img: str = os.path.basename(FILE_PATH) + m: MultipartEncoder = MultipartEncoder( + fields={"file": (name_img, open(FILE_PATH, "rb"))} + ) + response: dict = requests.post( + url=url, data=m, headers={"x-api-key": DETECTION_API_KEY} + ).json() + subject_response: dict = detection_service.detect(image_path=FILE_PATH) + assert response == subject_response + + +def test_get_available_functions_recognition_service(): + assert detection_service.get_available_functions() == [] diff --git a/tests/service/test_recognition_service.py b/tests/service/test_recognition_service.py new file mode 100644 index 0000000..9dc0583 --- /dev/null +++ b/tests/service/test_recognition_service.py @@ -0,0 +1,119 @@ +""" + Copyright(c) 2021 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. + """ + +import pytest +import httpretty +import requests +import os +from requests_toolbelt.multipart.encoder import MultipartEncoder +from compreface.collections.face_collections import FaceCollection, Subjects + +from compreface.config.api_list import ( + RECOGNIZE_API, + RECOGNIZE_EMBEDDINGS_API, + RECOGNIZE_CRUD_API, + SUBJECTS_CRUD_API, +) +from tests.client.const_config import DOMAIN, PORT, RECOGNIZE_API_KEY, FILE_PATH +from compreface.service.recognition_service import RecognitionService + +url: str = DOMAIN + ":" + PORT + RECOGNIZE_API + +recognition_service = RecognitionService( + api_key=RECOGNIZE_API_KEY, domain=DOMAIN, port=PORT +) + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_get_face_collection_recognition_service(): + face_collection_url: str = DOMAIN + ":" + PORT + RECOGNIZE_CRUD_API + httpretty.register_uri( + httpretty.GET, + face_collection_url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='["Subject1", "Subject2"]', + ) + face_collection_expected = FaceCollection( + api_key=RECOGNIZE_API_KEY, domain=DOMAIN, port=PORT + ) + face_collection_actual = recognition_service.get_face_collection() + + subject_response: dict = face_collection_expected.list() + response: dict = face_collection_actual.list() + + assert subject_response == response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_get_subjects_recognition_service(): + httpretty.register_uri( + httpretty.GET, + DOMAIN + ":" + PORT + SUBJECTS_CRUD_API, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='["Subject1", "Subject2"]', + ) + subject = Subjects(api_key=RECOGNIZE_API_KEY, domain=DOMAIN, port=PORT) + subject_expected = recognition_service.get_subjects() + + response: dict = subject.list() + subject_response: dict = subject_expected.list() + + assert subject_response == response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_recognize_image_recognition_servce(): + httpretty.register_uri( + httpretty.POST, + url, + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + name_img: str = os.path.basename(FILE_PATH) + m: MultipartEncoder = MultipartEncoder( + fields={"file": (name_img, open(FILE_PATH, "rb"))} + ) + response: dict = requests.post( + url=url, + data=m, + headers={"x-api-key": RECOGNIZE_API_KEY, "Content-Type": "multipart/form-data"}, + ).json() + subject_response: dict = recognition_service.recognize_image(FILE_PATH) + assert response == subject_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_recognize_embeddings_recognition_service(): + embeddings: list = [2, 5, 6] + embeddings_url: str = DOMAIN + ":" + PORT + RECOGNIZE_EMBEDDINGS_API + "/recognize" + httpretty.register_uri( + httpretty.POST, + embeddings_url, + headers={"x-api-key": RECOGNIZE_API_KEY}, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + response: dict = requests.post( + url=embeddings_url, + json={"embeddings": embeddings}, + headers={"x-api-key": RECOGNIZE_API_KEY}, + ).json() + subject_response: dict = recognition_service.recognize_embedding(embeddings) + assert response == subject_response + + +def test_get_available_functions_recognition_service(): + assert recognition_service.get_available_functions() == [] diff --git a/tests/service/test_verification_service.py b/tests/service/test_verification_service.py new file mode 100644 index 0000000..dbe928e --- /dev/null +++ b/tests/service/test_verification_service.py @@ -0,0 +1,88 @@ +""" + Copyright(c) 2021 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. + """ + +import pytest +import httpretty +import requests +import os +from requests_toolbelt.multipart.encoder import MultipartEncoder + +from compreface.config.api_list import VERIFICATION_API, VERIFICATION_EMBEDDINGS_API +from tests.client.const_config import DOMAIN, PORT, VERIFICATION_API_KEY, FILE_PATH +from compreface.service.verification_service import VerificationService + +url: str = DOMAIN + ":" + PORT + VERIFICATION_API + +verification_service = VerificationService( + api_key=VERIFICATION_API_KEY, domain=DOMAIN, port=PORT +) + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_recognize_image_recognition_servce(): + httpretty.register_uri( + httpretty.POST, + url + "/verify", + headers={ + "x-api-key": VERIFICATION_API_KEY, + "Content-Type": "multipart/form-data", + }, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + name_img: str = os.path.basename(FILE_PATH) + m: MultipartEncoder = MultipartEncoder( + fields={ + "source_image": (name_img, open(FILE_PATH, "rb")), + "target_image": (name_img, open(FILE_PATH, "rb")), + } + ) + response: dict = requests.post( + url=url + "/verify", + data=m, + headers={"x-api-key": VERIFICATION_API_KEY, "Content-Type": m.content_type}, + ).json() + subject_response: dict = verification_service.verify_image(FILE_PATH, FILE_PATH) + assert response == subject_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_verify_embedding_recognition_service(): + embeddings_url: str = DOMAIN + ":" + PORT + VERIFICATION_EMBEDDINGS_API + "/verify" + source_embeddings: list = [1, 3, 4] + targets_embeddings: list = [2, 5, 6] + httpretty.register_uri( + httpretty.POST, + embeddings_url, + headers={ + "x-api-key": VERIFICATION_API_KEY, + }, + body='{"result" : [{"age" : [ 25, 32 ], "gender" : "male"}]}', + ) + + response: dict = requests.post( + url=embeddings_url, + json={"targets": source_embeddings, "source": targets_embeddings}, + headers={"x-api-key": VERIFICATION_API_KEY}, + ).json() + subject_response: dict = verification_service.verify_embedding( + source_embeddings, targets_embeddings + ) + assert response == subject_response + + +def test_get_available_functions_recognition_service(): + assert verification_service.get_available_functions() == [] diff --git a/webcam_demo/compreface_webcam_detection_demo.py b/webcam_demo/compreface_webcam_detection_demo.py index 6af0601..78477df 100644 --- a/webcam_demo/compreface_webcam_detection_demo.py +++ b/webcam_demo/compreface_webcam_detection_demo.py @@ -26,14 +26,22 @@ def parseArguments(): parser = argparse.ArgumentParser() - parser.add_argument("--api-key", help="CompreFace detection service API key", type=str, required=True) - parser.add_argument("--host", help="CompreFace host", type=str, default='http://localhost') - parser.add_argument("--port", help="CompreFace port", type=str, default='8000') + parser.add_argument( + "--api-key", + help="CompreFace detection service API key", + type=str, + required=True, + ) + parser.add_argument( + "--host", help="CompreFace host", type=str, default="http://localhost" + ) + parser.add_argument("--port", help="CompreFace port", type=str, default="8000") args = parser.parse_args() return args + class ThreadedCamera: def __init__(self, api_key, host, port): self.active = True @@ -41,17 +49,21 @@ def __init__(self, api_key, host, port): self.capture = cv2.VideoCapture(0) self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 2) - compre_face: CompreFace = CompreFace(host, port, { - "limit": 0, - "det_prob_threshold": 0.8, - "prediction_count": 1, - "face_plugins": "age,gender,mask", - "status": False - }) + compre_face: CompreFace = CompreFace( + host, + port, + { + "limit": 0, + "det_prob_threshold": 0.8, + "prediction_count": 1, + "face_plugins": "age,gender,mask", + "status": False, + }, + ) self.detection: DetectionService = compre_face.init_face_detection(api_key) - self.FPS = 1/30 + self.FPS = 1 / 30 # Start frame retrieval thread self.thread = Thread(target=self.show_frame, args=()) @@ -67,49 +79,75 @@ def show_frame(self): if self.results: results = self.results for result in results: - box = result.get('box') - age = result.get('age') - gender = result.get('gender') - mask = result.get('mask') + box = result.get("box") + age = result.get("age") + gender = result.get("gender") + mask = result.get("mask") if box: - cv2.rectangle(img=self.frame, pt1=(box['x_min'], box['y_min']), - pt2=(box['x_max'], box['y_max']), color=(0, 255, 0), thickness=1) + cv2.rectangle( + img=self.frame, + pt1=(box["x_min"], box["y_min"]), + pt2=(box["x_max"], box["y_max"]), + color=(0, 255, 0), + thickness=1, + ) if age: age = f"Age: {age['low']} - {age['high']}" - cv2.putText(self.frame, age, (box['x_max'], box['y_min'] + 15), - cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) + cv2.putText( + self.frame, + age, + (box["x_max"], box["y_min"] + 15), + cv2.FONT_HERSHEY_SIMPLEX, + 0.6, + (0, 255, 0), + 1, + ) if gender: gender = f"Gender: {gender['value']}" - cv2.putText(self.frame, gender, (box['x_max'], box['y_min'] + 35), - cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) + cv2.putText( + self.frame, + gender, + (box["x_max"], box["y_min"] + 35), + cv2.FONT_HERSHEY_SIMPLEX, + 0.6, + (0, 255, 0), + 1, + ) if mask: mask = f"Mask: {mask['value']}" - cv2.putText(self.frame, mask, (box['x_max'], box['y_min'] + 55), - cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) - - cv2.imshow('CompreFace demo', self.frame) + cv2.putText( + self.frame, + mask, + (box["x_max"], box["y_min"] + 55), + cv2.FONT_HERSHEY_SIMPLEX, + 0.6, + (0, 255, 0), + 1, + ) + + cv2.imshow("CompreFace demo", self.frame) time.sleep(self.FPS) if cv2.waitKey(1) & 0xFF == 27: self.capture.release() cv2.destroyAllWindows() - self.active=False + self.active = False def is_active(self): return self.active def update(self): - if not hasattr(self, 'frame'): + if not hasattr(self, "frame"): return _, im_buf_arr = cv2.imencode(".jpg", self.frame) byte_im = im_buf_arr.tobytes() data = self.detection.detect(byte_im) - self.results = data.get('result') + self.results = data.get("result") -if __name__ == '__main__': +if __name__ == "__main__": args = parseArguments() threaded_camera = ThreadedCamera(args.api_key, args.host, args.port) while threaded_camera.is_active(): - threaded_camera.update() \ No newline at end of file + threaded_camera.update() diff --git a/webcam_demo/compreface_webcam_recognition_demo.py b/webcam_demo/compreface_webcam_recognition_demo.py index bc04502..d2b2149 100644 --- a/webcam_demo/compreface_webcam_recognition_demo.py +++ b/webcam_demo/compreface_webcam_recognition_demo.py @@ -22,17 +22,26 @@ from compreface import CompreFace from compreface.service import RecognitionService + def parseArguments(): parser = argparse.ArgumentParser() - parser.add_argument("--api-key", help="CompreFace recognition service API key", type=str, default='00000000-0000-0000-0000-000000000002') - parser.add_argument("--host", help="CompreFace host", type=str, default='http://localhost') - parser.add_argument("--port", help="CompreFace port", type=str, default='8000') + parser.add_argument( + "--api-key", + help="CompreFace recognition service API key", + type=str, + default="00000000-0000-0000-0000-000000000002", + ) + parser.add_argument( + "--host", help="CompreFace host", type=str, default="http://localhost" + ) + parser.add_argument("--port", help="CompreFace port", type=str, default="8000") args = parser.parse_args() return args + class ThreadedCamera: def __init__(self, api_key, host, port): self.active = True @@ -40,17 +49,23 @@ def __init__(self, api_key, host, port): self.capture = cv2.VideoCapture(0) self.capture.set(cv2.CAP_PROP_BUFFERSIZE, 2) - compre_face: CompreFace = CompreFace(host, port, { - "limit": 0, - "det_prob_threshold": 0.8, - "prediction_count": 1, - "face_plugins": "age,gender", - "status": False - }) + compre_face: CompreFace = CompreFace( + host, + port, + { + "limit": 0, + "det_prob_threshold": 0.8, + "prediction_count": 1, + "face_plugins": "age,gender", + "status": False, + }, + ) - self.recognition: RecognitionService = compre_face.init_face_recognition(api_key) + self.recognition: RecognitionService = compre_face.init_face_recognition( + api_key + ) - self.FPS = 1/30 + self.FPS = 1 / 30 # Start frame retrieval thread self.thread = Thread(target=self.show_frame, args=()) @@ -66,63 +81,112 @@ def show_frame(self): if self.results: results = self.results for result in results: - box = result.get('box') - age = result.get('age') - gender = result.get('gender') - mask = result.get('mask') - subjects = result.get('subjects') + box = result.get("box") + age = result.get("age") + gender = result.get("gender") + mask = result.get("mask") + subjects = result.get("subjects") if box: - cv2.rectangle(img=self.frame, pt1=(box['x_min'], box['y_min']), - pt2=(box['x_max'], box['y_max']), color=(0, 255, 0), thickness=1) + cv2.rectangle( + img=self.frame, + pt1=(box["x_min"], box["y_min"]), + pt2=(box["x_max"], box["y_max"]), + color=(0, 255, 0), + thickness=1, + ) if age: age = f"Age: {age['low']} - {age['high']}" - cv2.putText(self.frame, age, (box['x_max'], box['y_min'] + 15), - cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) + cv2.putText( + self.frame, + age, + (box["x_max"], box["y_min"] + 15), + cv2.FONT_HERSHEY_SIMPLEX, + 0.6, + (0, 255, 0), + 1, + ) if gender: gender = f"Gender: {gender['value']}" - cv2.putText(self.frame, gender, (box['x_max'], box['y_min'] + 35), - cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) + cv2.putText( + self.frame, + gender, + (box["x_max"], box["y_min"] + 35), + cv2.FONT_HERSHEY_SIMPLEX, + 0.6, + (0, 255, 0), + 1, + ) if mask: mask = f"Mask: {mask['value']}" - cv2.putText(self.frame, mask, (box['x_max'], box['y_min'] + 55), - cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) + cv2.putText( + self.frame, + mask, + (box["x_max"], box["y_min"] + 55), + cv2.FONT_HERSHEY_SIMPLEX, + 0.6, + (0, 255, 0), + 1, + ) if subjects: - subjects = sorted(subjects, key=lambda k: k['similarity'], reverse=True) + subjects = sorted( + subjects, key=lambda k: k["similarity"], reverse=True + ) subject = f"Subject: {subjects[0]['subject']}" similarity = f"Similarity: {subjects[0]['similarity']}" - cv2.putText(self.frame, subject, (box['x_max'], box['y_min'] + 75), - cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) - cv2.putText(self.frame, similarity, (box['x_max'], box['y_min'] + 95), - cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) + cv2.putText( + self.frame, + subject, + (box["x_max"], box["y_min"] + 75), + cv2.FONT_HERSHEY_SIMPLEX, + 0.6, + (0, 255, 0), + 1, + ) + cv2.putText( + self.frame, + similarity, + (box["x_max"], box["y_min"] + 95), + cv2.FONT_HERSHEY_SIMPLEX, + 0.6, + (0, 255, 0), + 1, + ) else: subject = f"No known faces" - cv2.putText(self.frame, subject, (box['x_max'], box['y_min'] + 75), - cv2.FONT_HERSHEY_SIMPLEX, 0.6, (0, 255, 0), 1) - - cv2.imshow('CompreFace demo', self.frame) + cv2.putText( + self.frame, + subject, + (box["x_max"], box["y_min"] + 75), + cv2.FONT_HERSHEY_SIMPLEX, + 0.6, + (0, 255, 0), + 1, + ) + + cv2.imshow("CompreFace demo", self.frame) time.sleep(self.FPS) if cv2.waitKey(1) & 0xFF == 27: self.capture.release() cv2.destroyAllWindows() - self.active=False + self.active = False def is_active(self): return self.active def update(self): - if not hasattr(self, 'frame'): + if not hasattr(self, "frame"): return _, im_buf_arr = cv2.imencode(".jpg", self.frame) byte_im = im_buf_arr.tobytes() data = self.recognition.recognize(byte_im) - self.results = data.get('result') + self.results = data.get("result") -if __name__ == '__main__': +if __name__ == "__main__": args = parseArguments() threaded_camera = ThreadedCamera(args.api_key, args.host, args.port) while threaded_camera.is_active(): - threaded_camera.update() \ No newline at end of file + threaded_camera.update() From 876c7495201d59efcd755dc666689461c090cacd Mon Sep 17 00:00:00 2001 From: ahodkov Date: Thu, 30 Mar 2023 16:21:08 +0200 Subject: [PATCH 2/5] Added compatibility row in matrix --- README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f8da4af..f6dd785 100644 --- a/README.md +++ b/README.md @@ -53,10 +53,11 @@ Before using our SDK make sure you have installed CompreFace and Python on your ## CompreFace compatibility matrix -| CompreFace Python SDK version | CompreFace 0.5.x | CompreFace 0.6.x | -| ------------------------------| ---------------- | ---------------- | -| 0.1.0 | ✔ | :yellow_circle: | -| 0.6.x | :yellow_circle: | ✔ | +| CompreFace Python SDK version | CompreFace 0.5.x | CompreFace 0.6.x | CompreFace 1.2.x | +| ------------------------------| ---------------- | ---------------- | ---------------- | +| 0.1.0 | ✔ | :yellow_circle: | :yellow_circle: | +| 0.6.x | :yellow_circle: | ✔ | :yellow_circle: | +| 1.2.x | :yellow_circle: | :yellow_circle: | ✔ | Explanation: From 3a7c50f1771cb2e8dabc3852ecaa2a4cea823490 Mon Sep 17 00:00:00 2001 From: ahodkov Date: Mon, 3 Apr 2023 10:38:22 +0200 Subject: [PATCH 3/5] Added examples for new features --- README.md | 12 ++-- .../client/recognize_face_from_embeddings.py | 9 +-- .../verification_face_from_embeddings.py | 2 + .../client/verification_face_from_image.py | 2 + .../client/verify_face_from_embeddings.py | 5 +- compreface/client/verify_face_from_image.py | 2 + examples/delete_examples_by_ids.py | 38 ++++++++++++ examples/recognize_face_from_embedding.py | 52 ++++++++++++++++ examples/recognize_face_from_image.py | 12 +++- examples/verification_face_from_embedding.py | 60 +++++++++++++++++++ examples/verification_face_from_image.py | 2 +- examples/verify_face_from_embedding.py | 54 +++++++++++++++++ examples/verify_face_from_image.py | 4 +- setup.py | 2 +- .../compreface_webcam_recognition_demo.py | 2 +- 15 files changed, 240 insertions(+), 18 deletions(-) create mode 100644 examples/delete_examples_by_ids.py create mode 100644 examples/recognize_face_from_embedding.py create mode 100644 examples/verification_face_from_embedding.py create mode 100644 examples/verify_face_from_embedding.py diff --git a/README.md b/README.md index f6dd785..70075d7 100644 --- a/README.md +++ b/README.md @@ -127,7 +127,7 @@ This code snippet shows how to recognize unknown face. ```python image_path: str = 'examples/common/jonathan-petit-unsplash.jpg' -recognition.recognize(image_path=image_path) +recognition.recognize_image(image_path=image_path) ``` ## Webcam demo @@ -279,7 +279,7 @@ class AllOptionsDict(ExpandedOptionsDict): Example of face recognition with object: ```python -recognition.recognize(image_path=image_path, options={ +recognition.recognize_image(image_path=image_path, options={ "limit": 0, "det_prob_threshold": 0.8, "prediction_count": 1, @@ -379,6 +379,8 @@ Response: ### Recognize Faces from a Given Image, Embedding +*[Example](examples/recognize_face_from_embedding.py)* + Determine similarities between input embeddings and embeddings within the Face Collection. ```python @@ -558,7 +560,7 @@ Response: #### Delete Multiple Examples - +*[Example](examples/delete_examples_by_ids.py)* To delete several subject examples ```python @@ -666,7 +668,7 @@ Response: #### Verify Faces from a Given Image, Embedding - +*[Example](examples/verification_face_from_embedding.py)* ```python face_collection.verify_embeddings(embedding, image_id) ``` @@ -1038,7 +1040,7 @@ Response: | plugins_versions | object | contains information about plugin versions | ### Verify Embedding - +*[Example](examples/verify_face_from_embedding.py)* ```python verify.verify_embedding(source_embeddings, targets_embeddings) ``` diff --git a/compreface/client/recognize_face_from_embeddings.py b/compreface/client/recognize_face_from_embeddings.py index a9693ac..4181311 100644 --- a/compreface/client/recognize_face_from_embeddings.py +++ b/compreface/client/recognize_face_from_embeddings.py @@ -14,7 +14,6 @@ permissions and limitations under the License. """ import requests - from compreface.common.typed_dict import ( PredictionCountOptionsDict, check_fields_by_name, @@ -46,7 +45,7 @@ def get(self): :return: json from server. """ - def post(self, embeddings: list = [], options: PredictionCountOptionsDict = {}): + def post(self, embeddings: list = [[]], options: PredictionCountOptionsDict = {}): url: str = self.url + "/recognize?" # Validation loop and adding fields to the url. @@ -55,11 +54,13 @@ def post(self, embeddings: list = [], options: PredictionCountOptionsDict = {}): # key - key field by options. check_fields_by_name(key, options[key]) url += "&" + key + "=" + str(options[key]) - # Sending an input source embedding for recognize faces. result = requests.post( - url, json={"embeddings": embeddings}, headers={"x-api-key": self.api_key} + url, + json={"embeddings": embeddings}, + headers={"x-api-key": self.api_key, "Content-Type": "application/json"}, ) + result.raise_for_status() return result.json() def put(self): diff --git a/compreface/client/verification_face_from_embeddings.py b/compreface/client/verification_face_from_embeddings.py index 4d4f403..c066e56 100644 --- a/compreface/client/verification_face_from_embeddings.py +++ b/compreface/client/verification_face_from_embeddings.py @@ -53,6 +53,8 @@ def post(self, embeddings: list = [], image_id: str = "") -> dict: result = requests.post( url, json={"embeddings": embeddings}, headers={"x-api-key": self.api_key} ) + result.raise_for_status() + return result.json() def put(self): diff --git a/compreface/client/verification_face_from_image.py b/compreface/client/verification_face_from_image.py index c72889b..8faa284 100644 --- a/compreface/client/verification_face_from_image.py +++ b/compreface/client/verification_face_from_image.py @@ -70,6 +70,8 @@ def post( data=m, headers={"Content-Type": m.content_type, "x-api-key": self.api_key}, ) + result.raise_for_status() + return result.json() def put(self): diff --git a/compreface/client/verify_face_from_embeddings.py b/compreface/client/verify_face_from_embeddings.py index edb36ee..c2d7cf3 100644 --- a/compreface/client/verify_face_from_embeddings.py +++ b/compreface/client/verify_face_from_embeddings.py @@ -49,9 +49,12 @@ def post(self, source_embeddings: list, targets_embeddings: list) -> dict: # Sending an input source embedding. result = requests.post( url, - json={"targets": source_embeddings, "source": targets_embeddings}, + json={"source": source_embeddings, "targets": targets_embeddings}, headers={"x-api-key": self.api_key}, ) + + result.raise_for_status() + return result.json() def put(self): diff --git a/compreface/client/verify_face_from_image.py b/compreface/client/verify_face_from_image.py index 8cd4a4c..00ee411 100644 --- a/compreface/client/verify_face_from_image.py +++ b/compreface/client/verify_face_from_image.py @@ -72,6 +72,8 @@ def post( data=m, headers={"Content-Type": m.content_type, "x-api-key": self.api_key}, ) + result.raise_for_status() + return result.json() def put(self): diff --git a/examples/delete_examples_by_ids.py b/examples/delete_examples_by_ids.py new file mode 100644 index 0000000..e8c56f3 --- /dev/null +++ b/examples/delete_examples_by_ids.py @@ -0,0 +1,38 @@ +""" + Copyright(c) 2021 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 compreface import CompreFace +from compreface.service import RecognitionService +from compreface.collections import FaceCollection + + +DOMAIN: str = "http://localhost" +PORT: str = "8000" +RECOGNITION_API_KEY: str = "00000000-0000-0000-0000-000000000002" + +compre_face: CompreFace = CompreFace(DOMAIN, PORT) + +recognition: RecognitionService = compre_face.init_face_recognition(RECOGNITION_API_KEY) + +face_collection: FaceCollection = recognition.get_face_collection() + +faces: list = face_collection.list().get("faces") + +if len(faces) != 0: + image_ids = [face.get("image_id") for face in faces] + print(face_collection.delete_multiple(image_ids)) +else: + print("No subject found") diff --git a/examples/recognize_face_from_embedding.py b/examples/recognize_face_from_embedding.py new file mode 100644 index 0000000..1fa52e2 --- /dev/null +++ b/examples/recognize_face_from_embedding.py @@ -0,0 +1,52 @@ +""" + Copyright(c) 2021 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 compreface import CompreFace +from compreface.service import RecognitionService + +DOMAIN: str = "http://localhost" +PORT: str = "8000" +RECOGNITION_API_KEY: str = "00000000-0000-0000-0000-000000000002" + + +compre_face: CompreFace = CompreFace( + DOMAIN, + PORT, + { + "limit": 0, + "det_prob_threshold": 0.8, + "prediction_count": 1, + "status": "true", + "face_plugins": "calculator", + }, +) + + +recognition: RecognitionService = compre_face.init_face_recognition(RECOGNITION_API_KEY) + +image_path: str = "examples/common/jonathan-petit-unsplash.jpg" + +result = recognition.recognize_image(image_path).get("result") +if len(result) != 0: + last_result: dict = result[len(result) - 1] + embeddings: list = last_result.get("embedding", [[]]) + print( + recognition.recognize_embedding( + embeddings=[embeddings], options={"prediction_count": 1} + ) + ) +else: + print("No embeddings found") diff --git a/examples/recognize_face_from_image.py b/examples/recognize_face_from_image.py index f6b38f2..c445c25 100644 --- a/examples/recognize_face_from_image.py +++ b/examples/recognize_face_from_image.py @@ -25,11 +25,17 @@ compre_face: CompreFace = CompreFace( DOMAIN, PORT, - {"limit": 0, "det_prob_threshold": 0.8, "prediction_count": 1, "status": "true"}, + { + "limit": 0, + "det_prob_threshold": 0.8, + "prediction_count": 1, + "status": "true", + "face_plugins": "calculator", + }, ) recognition: RecognitionService = compre_face.init_face_recognition(RECOGNITION_API_KEY) -image_path: str = "common/jonathan-petit-unsplash.jpg" +image_path: str = "examples/common/jonathan-petit-unsplash.jpg" -print(recognition.recognize(image_path)) +print(recognition.recognize_image(image_path)) diff --git a/examples/verification_face_from_embedding.py b/examples/verification_face_from_embedding.py new file mode 100644 index 0000000..f6e6280 --- /dev/null +++ b/examples/verification_face_from_embedding.py @@ -0,0 +1,60 @@ +""" + Copyright(c) 2021 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 compreface.collections.face_collections import FaceCollection +from compreface import CompreFace +from compreface.service import RecognitionService + +DOMAIN: str = "http://localhost" +PORT: str = "8000" +RECOGNITION_API_KEY: str = "00000000-0000-0000-0000-000000000002" + +compre_face: CompreFace = CompreFace( + DOMAIN, PORT, {"limit": 0, "det_prob_threshold": 0.8, "status": "true"} +) + +recognition: RecognitionService = compre_face.init_face_recognition(RECOGNITION_API_KEY) + +image_path: str = "examples/common/jonathan-petit-unsplash.jpg" + +face_collection: FaceCollection = recognition.get_face_collection() + +print(face_collection.list()) + +result = recognition.recognize_image( + image_path, + options={ + "limit": 0, + "det_prob_threshold": 0.8, + "face_plugins": "calculator", + "status": "true", + }, +).get("result") + +face: dict = next( + item + for item in face_collection.list().get("faces") + if item["subject"] == "Jonathan Petit" +) + +image_id = face.get("image_id") + +if len(result) != 0: + last_result: dict = result[len(result) - 1] + embeddings: list = last_result.get("embedding", []) + print(face_collection.verify_embeddings([embeddings], image_id)) +else: + print("No embedding found") diff --git a/examples/verification_face_from_image.py b/examples/verification_face_from_image.py index 52c68b2..05af142 100644 --- a/examples/verification_face_from_image.py +++ b/examples/verification_face_from_image.py @@ -42,4 +42,4 @@ image_id = face.get("image_id") -print(face_collection.verify(image_path, image_id)) +print(face_collection.verify_image(image_path, image_id)) diff --git a/examples/verify_face_from_embedding.py b/examples/verify_face_from_embedding.py new file mode 100644 index 0000000..ddeaf5c --- /dev/null +++ b/examples/verify_face_from_embedding.py @@ -0,0 +1,54 @@ +""" + Copyright(c) 2021 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 compreface import CompreFace +from compreface.service import VerificationService, RecognitionService + +DOMAIN: str = "http://localhost" +PORT: str = "8000" +VERIFICATION_API_KEY: str = "00000000-0000-0000-0000-000000000001" +RECOGNITION_API_KEY: str = "00000000-0000-0000-0000-000000000002" + +IMAGE_ID = "43257797-fc3e-437e-ad3c-7f3c5bea6f1d" + +compre_face: CompreFace = CompreFace( + DOMAIN, + PORT, + { + "limit": 0, + "det_prob_threshold": 0.8, + "face_plugins": "calculator", + "status": "true", + }, +) + +verify: VerificationService = compre_face.init_face_verification(VERIFICATION_API_KEY) + +recognition: RecognitionService = compre_face.init_face_recognition(RECOGNITION_API_KEY) + +image_path: str = "examples/common/jonathan-petit-unsplash.jpg" + +result = recognition.recognize_image(image_path).get("result") +if len(result) != 0: + last_result: dict = result[len(result) - 1] + embeddings: list = last_result.get("embedding", []) + print( + verify.verify_embedding( + source_embeddings=embeddings, targets_embeddings=[embeddings] + ) + ) +else: + print("No embedding found") diff --git a/examples/verify_face_from_image.py b/examples/verify_face_from_image.py index 19d8f2f..4a2748a 100644 --- a/examples/verify_face_from_image.py +++ b/examples/verify_face_from_image.py @@ -19,7 +19,7 @@ DOMAIN: str = "http://localhost" PORT: str = "8000" -VERIFICATION_API_KEY: str = "5c765423-4192-4fe8-9c60-092f495a332a" +VERIFICATION_API_KEY: str = "00000000-0000-0000-0000-000000000001" compre_face: CompreFace = CompreFace( @@ -37,4 +37,4 @@ image_path: str = "common/jonathan-petit-unsplash.jpg" -print(verify.verify(image_path, image_path)) +print(verify.verify_image(image_path, image_path)) diff --git a/setup.py b/setup.py index afa3e61..548395c 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ setup( name="compreface-sdk", packages=find_packages(exclude=("tests",)), - version="0.6.0", + version="1.2.0", license="Apache License 2.0", description="CompreFace Python SDK makes face recognition into your application even easier.", long_description=long_description, diff --git a/webcam_demo/compreface_webcam_recognition_demo.py b/webcam_demo/compreface_webcam_recognition_demo.py index d2b2149..8934d77 100644 --- a/webcam_demo/compreface_webcam_recognition_demo.py +++ b/webcam_demo/compreface_webcam_recognition_demo.py @@ -181,7 +181,7 @@ def update(self): _, im_buf_arr = cv2.imencode(".jpg", self.frame) byte_im = im_buf_arr.tobytes() - data = self.recognition.recognize(byte_im) + data = self.recognition.recognize_image(byte_im) self.results = data.get("result") From bc1beb91ac400791a79a5e643fd65a1263beba88 Mon Sep 17 00:00:00 2001 From: ahodkov Date: Tue, 4 Apr 2023 12:55:48 +0200 Subject: [PATCH 4/5] Added rose plugin support --- compreface/common/typed_dict.py | 1 + 1 file changed, 1 insertion(+) diff --git a/compreface/common/typed_dict.py b/compreface/common/typed_dict.py index 4720f49..eb09cad 100644 --- a/compreface/common/typed_dict.py +++ b/compreface/common/typed_dict.py @@ -75,6 +75,7 @@ def check_fields_by_name(name: str, value: Any): and row.find("gender") == -1 and row.find("landmarks") == -1 and row.find("mask") == -1 + and row.find("pose") == -1 ): raise IncorrectFieldException( "face_plugins must be only contains calculator,age,gender,landmarks,mask. " From b7d9da563b1bd8a64270032e18e696a6d5860486 Mon Sep 17 00:00:00 2001 From: ahodkov Date: Wed, 5 Apr 2023 10:58:44 +0200 Subject: [PATCH 5/5] Added detect_faces params --- README.md | 13 +++++++++++-- compreface/client/recognize_face_from_image.py | 4 ++-- compreface/common/typed_dict.py | 6 +++++- compreface/service/recognition_service.py | 10 ++++++++-- compreface/use_cases/recognize_face_from_image.py | 4 ++-- examples/recognize_face_from_image.py | 1 + 6 files changed, 29 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 70075d7..cc957e4 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,10 @@ class SavedObjectOptions(TypedDict): class AllOptionsDict(ExpandedOptionsDict): prediction_count: int + +class RecognizeOptionsDict(AllOptionsDict): + detect_faces: bool + ``` | Option | Type | Notes | | --------------------| ------ | ----------------------------------------- | @@ -275,6 +279,10 @@ class AllOptionsDict(ExpandedOptionsDict): | prediction_count | integer | maximum number of subject predictions per face. It returns the most similar subjects. Default value: 1 | | face_plugins | string | comma-separated slugs of face plugins. If empty, no additional information is returned. [Learn more](https://github.com/exadel-inc/CompreFace/tree/master/docs/Face-services-and-plugins.md) | | status | boolean | if true includes system information like execution_time and plugin_version fields. Default value is false | +| detect_faces | boolean | if true the parameter specifies whether to perform image detection or not. Default value is true | +| page | integer | page number of examples to return. Can be used for pagination. Default value is 0. | +| size | integer | faces on page (page size). Can be used for pagination. Default value is 20. | +| subject | string | what subject examples endpoint should return. If empty, return examples for all subjects.| Example of face recognition with object: @@ -284,7 +292,8 @@ recognition.recognize_image(image_path=image_path, options={ "det_prob_threshold": 0.8, "prediction_count": 1, "face_plugins": "calculator,age,gender,landmarks", - "status": "true" + "status": "true", + "detect_faces": True, }) ``` @@ -310,7 +319,7 @@ recognition.recognize_image(image_path, options) | Argument | Type | Required | Notes | | ------------------ | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | | image_path | image | required | Image can pass from url, local path or bytes. Max size is 5Mb | -| options | object | optional | `AllOptionsDict` object can be used in this method. See more [here](#options-structure). | +| options | object | optional | `RecognizeOptionsDict` object can be used in this method. See more [here](#options-structure). | Response: diff --git a/compreface/client/recognize_face_from_image.py b/compreface/client/recognize_face_from_image.py index ac13c5c..d1cb23c 100644 --- a/compreface/client/recognize_face_from_image.py +++ b/compreface/client/recognize_face_from_image.py @@ -16,7 +16,7 @@ import requests from compreface.common.multipart_constructor import multipart_constructor -from compreface.common.typed_dict import AllOptionsDict, check_fields_by_name +from compreface.common.typed_dict import RecognizeOptionsDict, check_fields_by_name from compreface.config.api_list import RECOGNIZE_API from ..common import ClientRequest @@ -44,7 +44,7 @@ def get(self): :return: json from server. """ - def post(self, image: str = "" or bytes, options: AllOptionsDict = {}): + def post(self, image: str = "" or bytes, options: RecognizeOptionsDict = {}): url: str = self.url + "?" # Validation loop and adding fields to the url. diff --git a/compreface/common/typed_dict.py b/compreface/common/typed_dict.py index eb09cad..1546b12 100644 --- a/compreface/common/typed_dict.py +++ b/compreface/common/typed_dict.py @@ -42,6 +42,10 @@ class AllOptionsDict(ExpandedOptionsDict): prediction_count: int +class RecognizeOptionsDict(AllOptionsDict): + detect_faces: bool + + """ Checks fields with necessary rules. :param name: key from dictionary. @@ -78,7 +82,7 @@ def check_fields_by_name(name: str, value: Any): and row.find("pose") == -1 ): raise IncorrectFieldException( - "face_plugins must be only contains calculator,age,gender,landmarks,mask. " + "face_plugins must be only contains calculator,age,gender,landmarks,mask,pose." "Incorrect value {}".format(row) ) if name == "page" or name == "size": diff --git a/compreface/service/recognition_service.py b/compreface/service/recognition_service.py index 32723ee..ae35b3a 100644 --- a/compreface/service/recognition_service.py +++ b/compreface/service/recognition_service.py @@ -14,7 +14,11 @@ permissions and limitations under the License. """ -from compreface.common.typed_dict import AllOptionsDict, PredictionCountOptionsDict +from compreface.common.typed_dict import ( + AllOptionsDict, + PredictionCountOptionsDict, + RecognizeOptionsDict, +) from typing import List from ..common import Service @@ -51,7 +55,9 @@ def get_available_functions(self) -> List[str]: """ return self.available_services - def recognize_image(self, image_path: str, options: AllOptionsDict = {}) -> dict: + def recognize_image( + self, image_path: str, options: RecognizeOptionsDict = {} + ) -> dict: """ Recognize faces from the uploaded image diff --git a/compreface/use_cases/recognize_face_from_image.py b/compreface/use_cases/recognize_face_from_image.py index 31e94d2..b42c31c 100644 --- a/compreface/use_cases/recognize_face_from_image.py +++ b/compreface/use_cases/recognize_face_from_image.py @@ -14,7 +14,7 @@ permissions and limitations under the License. """ -from compreface.common.typed_dict import AllOptionsDict +from compreface.common.typed_dict import RecognizeOptionsDict from dataclasses import dataclass from ..client import RecognizeFaceFromImageClient @@ -30,6 +30,6 @@ def __init__(self, domain: str, port: str, api_key: str): api_key=api_key, domain=domain, port=port ) - def execute(self, request: Request, options: AllOptionsDict = {}) -> dict: + def execute(self, request: Request, options: RecognizeOptionsDict = {}) -> dict: result: dict = self.recognize_face_from_image.post(request.image_path, options) return result diff --git a/examples/recognize_face_from_image.py b/examples/recognize_face_from_image.py index c445c25..4997d04 100644 --- a/examples/recognize_face_from_image.py +++ b/examples/recognize_face_from_image.py @@ -31,6 +31,7 @@ "prediction_count": 1, "status": "true", "face_plugins": "calculator", + "detect_faces": True, }, )