diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8dea6c9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,100 @@ +# Linux trash folder which might appear on any partition or disk +.Trash-* + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +pytestdebug.log + +# PyBuilder +target/ + +# IPython +profile_default/ +ipython_config.py + +# Environments +# .env +.env/ +.venv/ +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +pythonenv* + +# operating system-related files +# file properties cache/storage on macOS +*.DS_Store +# thumbnail cache on Windows +Thumbs.db + +# profiling data +.prof + + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +*.code-workspace + +### VisualStudioCode Patch ### +# Ignore all local history of files +.history +.ionide + +# Vscode +.vscode + +#Idea +.idea +.idea/* \ No newline at end of file diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..3d33284 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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 + + http://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. \ No newline at end of file diff --git a/README.md b/README.md index 58beef7..a121d8c 100644 --- a/README.md +++ b/README.md @@ -1 +1,747 @@ -# compreface-python-sdk \ No newline at end of file +# CompreFace Python SDK + +CompreFace Python SDK makes face recognition into your application even easier. + +## Table of content +- [Requirements](#requirements) +- [Installation](#installation) +- [Usage](#usage) + - [Initialization](#initialization) + - [Example Add an Example of a Subject](#example-add-an-example-of-a-subject) + - [Example Recognize Faces from a Given Image](#example-recognize-faces-from-a-given-image) + - [Example List of All Saved Subjects](#example-list-of-all-saved-subjects) + - [Example Delete All Examples of the Subject by Name](#example-delete-all-examples-of-the-subject-by-name) + - [Example Delete an Example of the Subject by ID](#example-delete-an-example-of-the-subject-by-id) + - [Example Compare Faces from a Given Image](#example-compare-faces-from-a-given-image) + - [Example Detect Faces from a Given Image](#example-detect-faces-from-a-given-image) + - [Example Verify Face from a Given Images](#example-verify-face-from-a-given-images) +- [Reference](#reference) + - [CompreFace Global Object](#compreFace-global-object) + - [Options structure](#options-structure) + - [Face Recognition Service](#face-recognition-service) + - [Add an Example of a Subject](#add-an-example-of-a-subject) + - [Recognize Faces from a Given Image](#recognize-faces-from-a-given-image) + - [List of All Saved Subjects](#list-of-all-saved-subjects) + - [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) + - [Verify Faces from a Given Image](#verify-faces-from-a-given-image) + - [Face Detection Service](#face-detection-service) + - [Face Verification Service](#face-verification-service) + +## Requirements + +Before using our SDK make sure you have installed CompreFace and Python on your machine. + +1. [CompreFace](https://github.com/exadel-inc/CompreFace#getting-started-with-compreface) +2. [Python](https://www.python.org/downloads/) (Version 3.7+) + +### CompreFace compatibility matrix + +| CompreFace Python SDK version | CompreFace 0.5.x | +| ------------------------------| ---------------- | +| 0.1.0 | ✔ | + + +## Installation + +It can be installed through pip: + +```shell +pip install compreface-sdk +``` + +## Usage + +All these examples you can find in repository inside [examples](/examples) folder. + +### Initialization + +To start using Python SDK you need to import `CompreFace` object from 'compreface-sdk' dependency. + +Then you need to init it with `url` and `port`. By default, if you run CompreFace on your local machine, it's `http://localhost` and `8000` respectively. +You can pass optional `options` object when call method to set default parameters, see reference for [more information](#options-structure). + +After you initialized `CompreFace` object you need to init the service object with the `api key` of your face service. You can use this service object to recognize faces. + +However, before recognizing you need first to add faces into the face collection. To do this, get the face collection object from the service object. + +```python +from compreface import CompreFace +from compreface.service import RecognitionService +from compreface.collections import FaceCollection + +DOMAIN: str = 'http://localhost' +PORT: str = '8000' +API_KEY: str = 'your_face_recognition_key' + +compre_face: CompreFace = CompreFace(DOMAIN, PORT) + +recognition: RecognitionService = compre_face.init_face_recognition(API_KEY) + +face_collection: FaceCollection = recognition.get_face_collection() +``` + +### Example. Add an Example of a Subject + +Here is example that shows how to add an image to your face collection from your file system: + +```python +image_path: str = 'examples/common/jonathan-petit-unsplash.jpg' +subject: str = 'Jonathan Petit' + +face_collection.add(image_path=image_path, subject=subject) +``` + +### Example. Recognize Faces from a Given Image + +This code snippet shows how to recognize unknown face. + +```python +image_path: "str or bytes" + +recognition.recognize(image_path=image_path) +``` + +### Example. List of All Saved Subjects + +This code shows how to retrieve a list of subject examples saved in a Face Collection: + +```python +face_collection.list() +``` + +### Example. Delete All Examples of the Subject by Name + +This code shows how to delete all image examples of the subject: + +```python +subject: str = 'Jonathan Petit' + +print(face_collection.delete_all(subject)) + +``` + +### Example. Delete an Example of the Subject by ID + +This example to delete an image by ID: + +```python +faces: list = face_collection.list().get('faces') + +if(len(faces) != 0): + last_face: dict = faces[len(faces) - 1] + print(face_collection.delete(last_face.get('image_id'))) +else: + print('No subject found') + +``` + +### Example. Compare Faces from a Given Image + +This example shows how to compare unknown face with existing face in face collection: + +```python +image_path: "str or bytes" + +face: dict = next(item for item in face_collection.list().get('faces') if item['subject'] == + 'Jonathan Petit') + +image_id = face.get('image_id') + +face_collection.verify(image_path=image_path, image_id=image_id) +``` + +### Example. Detect Faces from a Given Image + +Here is example to detect faces from a given image. + +```python +from compreface import CompreFace +from compreface.service import DetectionService + +DOMAIN: str = 'http://localhost' +PORT: str = '8000' +DETECTION_API_KEY: str = 'your_face_detection_key' + +compre_face: CompreFace = CompreFace(DOMAIN, PORT) + +detection: DetectionService = compre_face.init_face_detection(DETECTION_API_KEY) + +image_path: str = 'examples/common/jonathan-petit-unsplash.jpg' + +detection.detect(image_path=image_path) +``` + +### Example. Verify Face from a Given Images +Here is example to verify face from a given images. + +```python +from compreface import CompreFace +from compreface.service import VerificationService + +DOMAIN: str = 'http://localhost' +PORT: str = '8000' +VERIFICATION_API_KEY: str = 'your_face_verification_key' + +compre_face: CompreFace = CompreFace(DOMAIN, PORT) + +verify: VerificationService = compre_face.init_face_verification(VERIFICATION_API_KEY) + +image_path: str = 'examples/common/jonathan-petit-unsplash.jpg' + +verify.verify(source_image_path=image_path, target_image_path=image_path) +``` + +## Reference + +### CompreFace Global Object + +Global CompreFace Object is used for initializing connection to CompreFace and setting default values for options. +Default values will be used in every service method if applicable. +If the option’s value is set in the global object and passed as a function argument then the function argument value will be used. + +**Constructor:** + +```CompreFace(domain, port, options)``` + +| Argument | Type | Required | Notes | +| ---------| ------ | -------- | ----------------------------------------- | +| url | string | required | URL with protocol where CompreFace is located. E.g. `http://localhost` | +| port | string | required | CompreFace port. E.g. `8000` | +| options | object | optional | Default values for face recognition services. See more [here](#options-structure). `AllOptionsDict` object can be used in this method | + +Possible options: + +| Option | Type | Notes | +| --------------------| ------ | ----------------------------------------- | +| det_prob_threshold | string | minimum required confidence that a recognized face is actually a face. Value is between 0.0 and 1.0 | +| limit | integer | maximum number of faces on the image to be recognized. It recognizes the biggest faces first. Value of 0 represents no limit. Default value: 0 | +| 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 | + +Example: + +```python +from compreface import CompreFace + +DOMAIN: str = 'http://localhost' +PORT: str = '8000' + +compre_face: CompreFace = CompreFace(domain=DOMAIN, port=PORT, options={ + "limit": 0, + "det_prob_threshold": 0.8, + "prediction_count": 1, + "face_plugins": "calculator,age,gender,landmarks", + "status": "true" +}) +``` + +**Methods:** + +1. ```CompreFace.init_face_recognition(api_key)``` + +Inits face recognition service object. + +| Argument | Type | Required | Notes | +| ---------| ------ | -------- | ----------------------------------------- | +| api_key | string | required | Face Recognition Api Key in UUID format | + +Example: + +```python +from compreface.service import RecognitionService + +API_KEY: str = 'your_face_recognition_key' + +recognition: RecognitionService = compre_face.init_face_recognition(API_KEY) +``` + +2. ```CompreFace.init_face_detection(api_key)``` + +Inits face detection service object. + +| Argument | Type | Required | Notes | +| ---------| ------ | -------- | ----------------------------------------- | +| api_key | string | required | Face Detection Api Key in UUID format | + +Example: + +```python +from compreface.service import DetectionService + +DETECTION_API_KEY: str = 'your_face_detection_key' + +detection: DetectionService = compre_face.init_face_detection(DETECTION_API_KEY) +``` + +3. ```CompreFace.init_face_verification(api_key)``` + +Inits face verification service object. + +| Argument | Type | Required | Notes | +| ---------| ------ | -------- | ----------------------------------------- | +| api_key | string | required | Face Verification Api Key in UUID format | + +Example: + +```python +from compreface.service import VerificationService + +VERIFICATION_API_KEY: str = 'your_face_verification_key' + +verify: VerificationService = compre_face.init_face_verification(VERIFICATION_API_KEY) +``` + +### Options structure + +Options is optional field in every request that contains an image. +If the option’s value is set in the global object and passed as a function argument then the function argument value will be used. + +```python + +class DetProbOptionsDict(TypedDict): + det_prob_threshold: float + + +class ExpandedOptionsDict(DetProbOptionsDict): + limit: int + status: bool + face_plugins: str + + +class AllOptionsDict(ExpandedOptionsDict): + prediction_count: int + +``` +| Option | Type | Notes | +| --------------------| ------ | ----------------------------------------- | +| det_prob_threshold | string | minimum required confidence that a recognized face is actually a face. Value is between 0.0 and 1.0 | +| limit | integer | maximum number of faces on the image to be recognized. It recognizes the biggest faces first. Value of 0 represents no limit. Default value: 0 | +| 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 | + +Example of face recognition with object: + +```python +recognition.recognize(image_path=image_path, options={ + "limit": 0, + "det_prob_threshold": 0.8, + "prediction_count": 1, + "face_plugins": "calculator,age,gender,landmarks", + "status": "true" +}) +``` + +### Face Recognition Service + +Face recognition service is used for face identification. +This means that you first need to upload known faces to face collection and then recognize unknown faces among them. +When you upload an unknown face, the service returns the most similar faces to it. +Also, face recognition service supports verify endpoint to check if this person from face collection is the correct one. +For more information, see [CompreFace page](https://github.com/exadel-inc/CompreFace). + +#### Add an Example of a Subject + +This creates an example of the subject by saving images. You can add as many images as you want to train the system. + +```python +FaceCollection.add(image_path, subject, options) +``` + +| Argument | Type | Required | Notes | +| ------------------ | ------ | -------- | ---------------------------------------------------------------------------------------------------- | +| image_path | image | required | Image can pass from url, local path or bytes. Max size is 5Mb | +| subject | string | required | is the name you assign to the image you save | +| options | object | optional | `DetProbOptionsDict` object can be used in this method. See more [here](#options-structure). | + +Response body on success: + +```json +{ + "image_id": "6b135f5b-a365-4522-b1f1-4c9ac2dd0728", + "subject": "subject1" +} +``` + +| Element | Type | Description | +| -------- | ------ | -------------------------- | +| image_id | UUID | UUID of uploaded image | +| subject | string | Subject of the saved image | + +### Recognize Faces from a Given Image + +Recognizes faces from the uploaded image. + +```python +RecognitionService.recognize(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). | + +Response body on success: + +```json +{ + "result": [ + { + "age": [25, 32], + "gender": "female", + "embedding": [9.424854069948196e-4, "...", -0.011415496468544006], + "box": { + "probability": 1.0, + "x_max": 1420, + "y_max": 1368, + "x_min": 548, + "y_min": 295 + }, + "landmarks": [ + [814, 713], + [1104, 829], + [832, 937], + [704, 1030], + [1017, 1133] + ], + "subjects": [ + { + "similarity": 0.97858, + "subject": "subject1" + } + ], + "execution_time": { + "age": 28.0, + "gender": 26.0, + "detector": 117.0, + "calculator": 45.0 + } + } + ], + "plugins_versions": { + "age": "agegender.AgeDetector", + "gender": "agegender.GenderDetector", + "detector": "facenet.FaceDetector", + "calculator": "facenet.Calculator" + } +} +``` + +| Element | Type | Description | +| -------------------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | +| age | array | detected age range. Return only if [age plugin](https://github.com/exadel-inc/CompreFace/blob/master/docs/Face-services-and-plugins.md) is enabled | +| gender | string | detected gender. Return only if [gender plugin](https://github.com/exadel-inc/CompreFace/blob/master/docs/Face-services-and-plugins.md) is enabled | +| embedding | array | face embeddings. Return only if [calculator plugin](https://github.com/exadel-inc/CompreFace/blob/master/docs/Face-services-and-plugins.md) is enabled | +| box | object | list of parameters of the bounding box for this face | +| probability | float | probability that a found face is actually a face | +| x_max, y_max, x_min, y_min | integer | coordinates of the frame containing the face | +| landmarks | array | list of the coordinates of the frame containing the face-landmarks.| +| subjects | list | list of similar subjects with size of order by similarity | +| similarity | float | similarity that on that image predicted person | +| subject | string | name of the subject in Face Collection | +| execution_time | object | execution time of all plugins | +| plugins_versions | object | contains information about plugin versions | + + +### List of All Saved Subjects + +To retrieve a list of subjects saved in a Face Collection: + +```python +FaceCollection.list() +``` + +Response body on success: + +``` +{ + "faces": [ + { + "image_id": , + "subject": + }, + ... + ] +} +``` + +| Element | Type | Description | +| -------- | ------ | ----------------------------------------------------------------- | +| image_id | UUID | UUID of the face | +| subject | string | of the person, whose picture was saved for this api key | + +### Delete All Examples of the Subject by Name + +To delete all image examples of the : + +```python +FaceCollection.delete_all(subject) +``` + +| Argument | Type | Required | Notes | +| --------- | ------ | -------- | ------------------------------------------------------------------------------------------------------------------------------------ | +| subject | string | optional | is the name you assign to the image you save. If this parameter is absent, all faces in Face Collection will be removed | + +Response body on success: + +``` +[ + { + "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 | + +### Delete an Example of the Subject by ID + +To delete an image by ID: + +```python +FaceCollection.delete(image_id) +``` +| Argument | Type | Required | Notes | +| --------- | ------ | -------- | ------------------------------------------------------------ +| image_id | UUID | required | UUID of the removing face | + +Response body on success: + +``` +{ + "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 | + +### Verify Faces from a Given Image + +```python +FaceCollection.verify(image_path, image_id, options) +``` + +Compares similarities of given image with image from your face collection. + + +| Argument | Type | Required | Notes | +| ------------------ | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| image_path | image | required | Image can pass from url, local path or bytes. Max size is 5Mb | +| image_id | UUID | required | UUID of the verifying face | +| options | string | Object | `ExpandedOptionsDict` object can be used in this method. See more [here](#options-structure). | + +Response body on success: + +``` +{ + "result": [ + { + "box": { + "probability": , + "x_max": , + "y_max": , + "x_min": , + "y_min": + }, + "similarity": + }, + ... + ] +} +``` + +| Element | Type | Description | +| -------------------------- | ------- | ---------------------------------------------------- | +| box | object | list of parameters of the bounding box for this face | +| probability | float | probability that a found face is actually a face | +| x_max, y_max, x_min, y_min | integer | coordinates of the frame containing the face | +| similarity | float | similarity that on that image predicted person | +| subject | string | name of the subject in Face Collection | + +### Face Detection Service + +Face detection service is used for detecting faces in the image. + +**Methods:** + +#### Detect + +```python +DetectionService.detect(image_path, options) +``` + +Finds all faces on the image. + +| Argument | Type | Required | Notes | +| ----------------- | ------- | -------- | ---------------------------------------------------------------------------------------------------------------------------------------------- | +| image_path | image | required | image where to detect faces. Image can pass from url, local path or bytes. Max size is 5Mb | +| options | string | Object | `ExpandedOptionsDict` object can be used in this method. See more [here](#options-structure). | + +Response body on success: + +```json +{ + "result": [ + { + "age": [25, 32], + "gender": "female", + "embedding": [-0.03027934394776821, "...", -0.05117142200469971], + "box": { + "probability": 0.9987509250640869, + "x_max": 376, + "y_max": 479, + "x_min": 68, + "y_min": 77 + }, + "landmarks": [ + [156, 245], + [277, 253], + [202, 311], + [148, 358], + [274, 365] + ], + "execution_time": { + "age": 30.0, + "gender": 26.0, + "detector": 130.0, + "calculator": 49.0 + } + } + ], + "plugins_versions": { + "age": "agegender.AgeDetector", + "gender": "agegender.GenderDetector", + "detector": "facenet.FaceDetector", + "calculator": "facenet.Calculator" + } +} +``` + +## Face Verification Service + +Face verification service is used for comparing two images. +A source image should contain only one face which will be compared to all faces on the target image. + +**Methods:** + +```python +VerificationService.verify(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. + +| Argument | Type | Required | Notes | +| ------------------ | ------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | +| image_id | UUID | required | UUID of the verifying face | +| source_image_path | image | required | file to be verified. Image can pass from url, local path or bytes. Max size is 5Mb | +| target_image_path | image | required | reference file to check the source file. Image can pass from url, local path or bytes. Max size is 5Mb | +| options | string | Object | `ExpandedOptionsDict` object can be used in this method. See more [here](#options-structure). | + +Response body on success: + +```json +{ + "source_image_face" : { + "age" : [ 25, 32 ], + "gender" : "female", + "embedding" : [ -0.0010271212086081505, "...", -0.008746841922402382 ], + "box" : { + "probability" : 0.9997453093528748, + "x_max" : 205, + "y_max" : 167, + "x_min" : 48, + "y_min" : 0 + }, + "landmarks" : [ [ 92, 44 ], [ 130, 68 ], [ 71, 76 ], [ 60, 104 ], [ 95, 125 ] ], + "execution_time" : {}, + "face_matches": [ + { + "age" : [ 25, 32 ], + "gender" : "female", + "embedding" : [ -0.049007344990968704, "...", -0.01753818802535534 ], + "box" : { + "probability" : 0.99975, + "x_max" : 308, + "y_max" : 180, + "x_min" : 235, + "y_min" : 98 + }, + "landmarks" : [ [ 260, 129 ], [ 273, 127 ], [ 258, 136 ], [ 257, 150 ], [ 269, 148 ] ], + "similarity" : 0.97858, + "execution_time" : { + "age" : 59.0, + "gender" : 30.0, + "detector" : 177.0, + "calculator" : 70.0 + } + }], + "plugins_versions" : { + "age" : "agegender.AgeDetector", + "gender" : "agegender.GenderDetector", + "detector" : "facenet.FaceDetector", + "calculator" : "facenet.Calculator" + } + } +} +``` + +| Element | Type | Description | +| -------------------------- | ------- | ---------------------------------------------------------------------------------- | +| source_image_face | object | additional info about source image face | +| face_matches | array | result of face verification | +| age | array | detected age range. | +| gender | string | detected gender. Return only if | +| embedding | array | face embeddings. Return only if | +| box | object | list of parameters of the bounding box for this face | +| probability | float | probability that a found face is actually a face | +| x_max, y_max, x_min, y_min | integer | coordinates of the frame containing the face | +| landmarks | array | list of the coordinates of the frame containing the face-landmarks. Return only if | +| similarity | float | similarity between this face and the face on the source image | +| execution_time | object | execution time of all plugins | +| plugins_versions | object | contains information about plugin versions | + +# 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. + +1. Fork the Project +2. Create your Feature Branch (`git checkout -b feature/AmazingFeature`) +3. Commit your Changes (`git commit -m 'Add some AmazingFeature'`) +4. Push to the Branch (`git push origin feature/AmazingFeature`) +5. Open a Pull Request + +After creating your first contributing pull request, you will receive a request to sign our Contributor License Agreement by commenting your pull request with a special message. + +### Report Bugs + +Please report any bugs [here](https://github.com/exadel-inc/compreface-python-sdk/issues). + +If you are reporting a bug, please specify: + +- Your operating system name and version +- Any details about your local setup that might be helpful in troubleshooting +- Detailed steps to reproduce the bug + +### Submit Feedback + +The best way to send us feedback is to file an issue at https://github.com/exadel-inc/compreface-python-sdk/issues. + +If you are proposing a feature, please: + +- Explain in detail how it should work. +- Keep the scope as narrow as possible to make it easier to implement. + +# License info + +CompreFace Python SDK is open-source facial recognition SDK released under the [Apache 2.0 license](https://www.apache.org/licenses/LICENSE-2.0.html). diff --git a/compreface/__init__.py b/compreface/__init__.py new file mode 100644 index 0000000..837c223 --- /dev/null +++ b/compreface/__init__.py @@ -0,0 +1,17 @@ +""" + 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 .core import CompreFace diff --git a/compreface/client/__init__.py b/compreface/client/__init__.py new file mode 100644 index 0000000..a60963c --- /dev/null +++ b/compreface/client/__init__.py @@ -0,0 +1,22 @@ +""" + 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 .verification_face_from_image import VerificationFaceFromImageClient +from .add_example_of_subject import AddExampleOfSubjectClient +from .delete_example_by_id import DeleteExampleByIdClient +from .recognize_face_from_image import RecognizeFaceFromImageClient +from .detect_face_from_image import DetectFaceFromImageClient +from .verify_face_from_image import VerifyFaceFromImageClient diff --git a/compreface/client/add_example_of_subject.py b/compreface/client/add_example_of_subject.py new file mode 100644 index 0000000..ef4ce9e --- /dev/null +++ b/compreface/client/add_example_of_subject.py @@ -0,0 +1,81 @@ +""" + 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.multipart_constructor import multipart_constructor +from compreface.common.typed_dict import DetProbOptionsDict, 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 + + """ + GET request for get all subjects. + + :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}) + return result.json() + + """ + POST request for add subject from his face in image. + + :param image_path: path to image in file system. + :param subject: fullname + :param options: dictionary with options for server. + + :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 + # 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]) + m = multipart_constructor(image) + result = requests.post(url, data=m, headers={'Content-Type': m.content_type, + 'x-api-key': self.api_key}) + return result.json() + + def put(self): + pass + + """ + Delete request to CompreFace server. + + :param subject: fullname + + :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}) + return result.json() diff --git a/compreface/client/delete_example_by_id.py b/compreface/client/delete_example_by_id.py new file mode 100644 index 0000000..7e84f9d --- /dev/null +++ b/compreface/client/delete_example_by_id.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 requests + +from compreface.config.api_list import RECOGNIZE_CRUD_API +from ..common import ClientRequest + + +class DeleteExampleByIdClient(ClientRequest): + + """ + 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 + + def get(self): + pass + + def post(self): + pass + + def put(self): + pass + + """ + DELETE request to CompreFace server. Delete example by id from image_id. + + :param image_id: + + :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}) + return result.json() diff --git a/compreface/client/detect_face_from_image.py b/compreface/client/detect_face_from_image.py new file mode 100644 index 0000000..d6da005 --- /dev/null +++ b/compreface/client/detect_face_from_image.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.multipart_constructor import multipart_constructor +from compreface.common.typed_dict import ExpandedOptionsDict, check_fields_by_name +from ..common import ClientRequest +from compreface.config.api_list import DETECTION_API + + +class DetectFaceFromImageClient(ClientRequest): + """ + 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 + + def get(self): + pass + + """ + POST request for detection faces in image. + + :param image_path: Path to image in file system. + :param options: dictionary with options for server. + + :return: json from server. + """ + + 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]) + + # 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}) + 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 new file mode 100644 index 0000000..09f8d31 --- /dev/null +++ b/compreface/client/recognize_face_from_image.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.multipart_constructor import multipart_constructor +from compreface.common.typed_dict import AllOptionsDict, check_fields_by_name +from compreface.config.api_list import RECOGNIZE_API +from ..common import ClientRequest + + +class RecognizeFaceFromImageClient(ClientRequest): + """ + 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 + + def get(self): + pass + + """ + POST request for recognize faces in image. + + :param image_path: Path to image in file system. + :param options: dictionary with options for server. + + :return: json from server. + """ + + def post(self, image: str = '' or bytes, options: AllOptionsDict = {}): + 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]) + + # 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}) + 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 new file mode 100644 index 0000000..022dd5b --- /dev/null +++ b/compreface/client/verification_face_from_image.py @@ -0,0 +1,75 @@ +""" + 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.multipart_constructor import multipart_constructor +from compreface.common.typed_dict import ExpandedOptionsDict, check_fields_by_name +from compreface.config.api_list import RECOGNIZE_CRUD_API +from ..common import ClientRequest + + +class VerificationFaceFromImageClient(ClientRequest): + """ + 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 + + def get(self): + pass + + """ + POST request for compare face in image using image id. + + :param image_path: Path to image in file system. + :param image_id: subject id from previously added image. + :param options: dictionary with options for server. + + :return: json from server. + """ + + 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]) + + # 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}) + 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 new file mode 100644 index 0000000..4b7b196 --- /dev/null +++ b/compreface/client/verify_face_from_image.py @@ -0,0 +1,75 @@ +""" + 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.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 +from compreface.common.client import ClientRequest + + +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. + """ + + 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 + + def get(self): + pass + + """ + POST request for verify face in image using source and target images. + + :param source_image: Path to source image in file system. + :param target_image: Path to target image in file system. + :param image_id: subject id from previously added image. + :param options: dictionary with options for server. + + :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?' + # 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]) + + # 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}) + return result.json() + + def put(self): + pass + + def delete(self): + pass diff --git a/compreface/collections/__init__.py b/compreface/collections/__init__.py new file mode 100644 index 0000000..b03f9b8 --- /dev/null +++ b/compreface/collections/__init__.py @@ -0,0 +1,17 @@ +""" + 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 .face_collections import FaceCollection diff --git a/compreface/collections/face_collections.py b/compreface/collections/face_collections.py new file mode 100644 index 0000000..ae00c73 --- /dev/null +++ b/compreface/collections/face_collections.py @@ -0,0 +1,116 @@ +""" + 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 AllOptionsDict, ExpandedOptionsDict, DetProbOptionsDict, pass_dict +from ..use_cases import ( + AddExampleOfSubject, + ListOfAllSavedSubjects, + DeleteAllExamplesOfSubjectByName, + DeleteExampleById, + VerificationFaceFromImage +) + + +class FaceCollection: + 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 + ) + 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 + ) + self.delete_all_examples_by_id: DeleteExampleById = DeleteExampleById( + domain=domain, + port=port, + api_key=api_key + ) + self.verify_face_from_image: VerificationFaceFromImage = VerificationFaceFromImage( + domain=domain, + port=port, + api_key=api_key + ) + + def add(self, image_path: str, subject: str, options: DetProbOptionsDict = {}) -> dict: + """ + Add example to collection + :param image_path: + :param subject: + :return: + """ + request = AddExampleOfSubject.Request( + 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) + + def list(self) -> dict: + """ + Get list of collections + :return: + """ + return self.list_of_all_saved_subjects.execute() + + def delete_all(self, subject: str) -> dict: + """ + Delete all examples of subject + :param subject: + :return: + """ + request = DeleteAllExamplesOfSubjectByName.Request( + api_key=self.api_key, + subject=subject + ) + return self.delete_all_examples_of_subject_by_name.execute(request) + + def delete(self, image_id: str) -> dict: + """ + Delete example by Id + :param image_id: + :return: + """ + request = DeleteExampleById.Request( + api_key=self.api_key, + image_id=image_id + ) + return self.delete_all_examples_by_id.execute(request) + + def verify(self, image_path: str, image_id: str, options: ExpandedOptionsDict = {}) -> dict: + """ + Compare image + :param image_path: + :param image_id: + :return: + """ + request = VerificationFaceFromImage.Request( + 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) diff --git a/compreface/common/__init__.py b/compreface/common/__init__.py new file mode 100644 index 0000000..46b0078 --- /dev/null +++ b/compreface/common/__init__.py @@ -0,0 +1,18 @@ +""" + 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 .client import ClientRequest +from .service import Service diff --git a/compreface/common/client.py b/compreface/common/client.py new file mode 100644 index 0000000..2af0b70 --- /dev/null +++ b/compreface/common/client.py @@ -0,0 +1,41 @@ +""" + 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 abc import ABC, abstractmethod + + +class ClientRequest(ABC): + """The best class of all requests""" + + @abstractmethod + def __init__(self): + pass + + @abstractmethod + def get(self): + pass + + @abstractmethod + def post(self): + pass + + @abstractmethod + def put(self): + pass + + @abstractmethod + def delete(self): + pass diff --git a/compreface/common/multipart_constructor.py b/compreface/common/multipart_constructor.py new file mode 100644 index 0000000..ba3fbf0 --- /dev/null +++ b/compreface/common/multipart_constructor.py @@ -0,0 +1,33 @@ +import os +import requests + +from requests_toolbelt.multipart.encoder import MultipartEncoder + + +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) + else: + name_img: str = os.path.basename(image) + file = (name_img, open(image, 'rb')) + return file + + +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)} + ) + + +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)} + ) diff --git a/compreface/common/service.py b/compreface/common/service.py new file mode 100644 index 0000000..35ae362 --- /dev/null +++ b/compreface/common/service.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 abc import ABC, abstractmethod +from compreface.common.typed_dict import AllOptionsDict + + +class Service(ABC): + """The best class of all services""" + + @abstractmethod + def __init__(self, api_key: str, options: AllOptionsDict): + self._api_key = api_key + self._options = options + + @property + def api_key(self): + return self._api_key + + @property + def options(self): + return self._options + + @abstractmethod + def get_available_functions(self): + pass diff --git a/compreface/common/typed_dict.py b/compreface/common/typed_dict.py new file mode 100644 index 0000000..aa3f14e --- /dev/null +++ b/compreface/common/typed_dict.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. + """ + +from compreface.exceptions import IncorrectFieldException +from typing import Any, TypedDict + + +class DetProbOptionsDict(TypedDict): + det_prob_threshold: float + + +class ExpandedOptionsDict(DetProbOptionsDict): + limit: int + status: bool + face_plugins: str + + +class AllOptionsDict(ExpandedOptionsDict): + prediction_count: int + + +""" + Checks fields with necessary rules. + :param name: key from dictionary. + :param value: value from dictionary. + + raise exception when value break necessary rules. +""" + + +def check_fields_by_name(name: str, value: Any): + 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': + 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)) + if name == "face_plugins": + values = value.strip() + 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: + raise IncorrectFieldException( + "face_plugins must be only contains calculator,age,gender,landmarks. Incorrect value {}".format(row)) + + +def pass_dict(options: AllOptionsDict, type: DetProbOptionsDict or ExpandedOptionsDict): + converted_options: ExpandedOptionsDict or DetProbOptionsDict = {} + for key in type.__annotations__.keys(): + value = options.get(key) + if value != None: + converted_options[key] = value + return converted_options diff --git a/compreface/config/__init__.py b/compreface/config/__init__.py new file mode 100644 index 0000000..e82733f --- /dev/null +++ b/compreface/config/__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/compreface/config/api_list.py b/compreface/config/api_list.py new file mode 100644 index 0000000..884b2df --- /dev/null +++ b/compreface/config/api_list.py @@ -0,0 +1,25 @@ +""" + 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. + """ + + +RECOGNITION_ROOT_API: str = '/api/v1/recognition' + +RECOGNIZE_API: str = RECOGNITION_ROOT_API + '/recognize' +RECOGNIZE_CRUD_API: str = RECOGNITION_ROOT_API + '/faces' + +DETECTION_API: str = '/api/v1/detection/detect' + +VERIFICATION_API: str = '/api/v1/verification' diff --git a/compreface/core/__init__.py b/compreface/core/__init__.py new file mode 100644 index 0000000..63b00d4 --- /dev/null +++ b/compreface/core/__init__.py @@ -0,0 +1,16 @@ +""" + 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 .model import CompreFace diff --git a/compreface/core/model.py b/compreface/core/model.py new file mode 100644 index 0000000..73be277 --- /dev/null +++ b/compreface/core/model.py @@ -0,0 +1,97 @@ +""" + 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 AllOptionsDict +from typing import Optional +from ..service import ( + RecognitionService, + VerificationService, + DetectionService +) + + +class CompreFace(object): + """ + Main class + """ + + def __init__(self, domain: str, port: str, options: AllOptionsDict = {}): + self._domain: str = domain + self._port: str = port + self._options: AllOptionsDict = options + self.recognition: Optional[RecognitionService] = None + self.verification: Optional[VerificationService] = None + self.detection: Optional[DetectionService] = None + + @property + def domain(self): + return self._domain + + @domain.setter + def domain(self, domain: str): + self._domain = domain + + @property + def port(self): + return self._port + + @port.setter + def port(self, port: str): + self._port = port + + @property + def options(self): + return self._options + + @options.setter + def options(self, options: AllOptionsDict): + self._options = options + + def init_face_recognition(self, api_key: str) -> RecognitionService: + """ + Init Face Recognition Service + :param api_key: + :return: + """ + 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: + """ + Init Face Verification Service + :param api_key: + :return: + """ + 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: + """ + Init Face Detection Service + :param api_key: + :return: + """ + self.detection = DetectionService(api_key=api_key, + domain=self.domain, + port=self.port, + options=self.options) + return self.detection diff --git a/compreface/exceptions/__init__.py b/compreface/exceptions/__init__.py new file mode 100644 index 0000000..eedff41 --- /dev/null +++ b/compreface/exceptions/__init__.py @@ -0,0 +1,17 @@ +""" + 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 .field_exception import IncorrectFieldException diff --git a/compreface/exceptions/field_exception.py b/compreface/exceptions/field_exception.py new file mode 100644 index 0000000..81d3664 --- /dev/null +++ b/compreface/exceptions/field_exception.py @@ -0,0 +1,19 @@ +""" + 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. + """ + + +class IncorrectFieldException(BaseException): + pass diff --git a/compreface/service/__init__.py b/compreface/service/__init__.py new file mode 100644 index 0000000..ef427ec --- /dev/null +++ b/compreface/service/__init__.py @@ -0,0 +1,19 @@ +""" + 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 .detection_service import DetectionService +from .verification_service import VerificationService +from .recognition_service import RecognitionService diff --git a/compreface/service/detection_service.py b/compreface/service/detection_service.py new file mode 100644 index 0000000..9ff6e33 --- /dev/null +++ b/compreface/service/detection_service.py @@ -0,0 +1,56 @@ +""" + 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 AllOptionsDict, ExpandedOptionsDict, pass_dict +from compreface.use_cases.detect_face_from_image import DetectFaceFromImage +from typing import List + +from ..common import Service + + +class DetectionService(Service): + """Detection service""" + + 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 + ) + + def get_available_functions(self) -> List[str]: + """ + Get List of available functions in service + :return: + """ + return self.available_services + + def detect(self, image_path: str, options: ExpandedOptionsDict = {}) -> dict: + """ + Detect face in image + :param image_path: + :param options: + :return: + """ + request = DetectFaceFromImage.Request( + 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) diff --git a/compreface/service/recognition_service.py b/compreface/service/recognition_service.py new file mode 100644 index 0000000..44a3293 --- /dev/null +++ b/compreface/service/recognition_service.py @@ -0,0 +1,71 @@ +""" + 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 AllOptionsDict +from typing import List + +from ..common import Service +from ..collections import FaceCollection +from ..use_cases import ( + RecognizeFaceFromImage +) + + +class RecognitionService(Service): + """Recognition service""" + + 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.face_collection: FaceCollection = FaceCollection( + domain=domain, + port=port, + api_key=api_key, + options=options + ) + + def get_available_functions(self) -> List[str]: + """ + Get List of available functions in service + :return: + """ + return self.available_services + + def recognize(self, image_path: str, options: AllOptionsDict = {}) -> dict: + """ + Recognize image + :param image_path: + :param options: + :return: + """ + request = RecognizeFaceFromImage.Request( + api_key=self.api_key, + image_path=image_path + ) + return self.recognize_face_from_images.execute(request, self.options if options == {} else options) + + def get_face_collection(self) -> FaceCollection: + """ + Get face collection + :return: + """ + return self.face_collection diff --git a/compreface/service/verification_service.py b/compreface/service/verification_service.py new file mode 100644 index 0000000..68de5d4 --- /dev/null +++ b/compreface/service/verification_service.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. + """ + +from compreface.use_cases.verifiy_face_from_images import VerifyFaceFromImage +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 + + +class VerificationService(Service): + """Verification service""" + + 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 + ) + + def get_available_functions(self) -> List[str]: + """ + Get List of available functions in service + :return: + """ + return self.available_services + + def verify(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: + """ + 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 + ) diff --git a/compreface/use_cases/__init__.py b/compreface/use_cases/__init__.py new file mode 100644 index 0000000..887e53f --- /dev/null +++ b/compreface/use_cases/__init__.py @@ -0,0 +1,23 @@ +""" + 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 .add_example_of_subject import AddExampleOfSubject +from .delete_all_examples_of_subject_by_name import DeleteAllExamplesOfSubjectByName +from .delete_example_by_id import DeleteExampleById +from .list_of_all_saved_subjects import ListOfAllSavedSubjects +from .recognize_face_from_image import RecognizeFaceFromImage +from .verification_face_from_image import VerificationFaceFromImage +from .detect_face_from_image import DetectFaceFromImage diff --git a/compreface/use_cases/add_example_of_subject.py b/compreface/use_cases/add_example_of_subject.py new file mode 100644 index 0000000..0a0bfec --- /dev/null +++ b/compreface/use_cases/add_example_of_subject.py @@ -0,0 +1,40 @@ +""" + 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 DetProbOptionsDict +from dataclasses import dataclass +from ..client import AddExampleOfSubjectClient + + +class AddExampleOfSubject: + + @dataclass + class Request: + api_key: str + image_path: str + subject: str + + def __init__(self, domain: str, port: str, api_key: str): + self.add_example_of_subject = AddExampleOfSubjectClient( + 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) + return result 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 new file mode 100644 index 0000000..1703844 --- /dev/null +++ b/compreface/use_cases/delete_all_examples_of_subject_by_name.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 AddExampleOfSubjectClient + + +class DeleteAllExamplesOfSubjectByName: + + @dataclass + class Request: + api_key: str + subject: str + + def __init__(self, domain: str, port: str, api_key: str): + self.add_example_of_subject = AddExampleOfSubjectClient( + api_key=api_key, + domain=domain, + port=port + ) + + def execute(self, request: Request) -> dict: + result: dict = self.add_example_of_subject.delete(request.subject) + return result diff --git a/compreface/use_cases/delete_example_by_id.py b/compreface/use_cases/delete_example_by_id.py new file mode 100644 index 0000000..05eed2e --- /dev/null +++ b/compreface/use_cases/delete_example_by_id.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 DeleteExampleByIdClient + + +class DeleteExampleById: + + @dataclass + class Request: + api_key: str + image_id: str + + 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.delete(request.image_id) + return result diff --git a/compreface/use_cases/detect_face_from_image.py b/compreface/use_cases/detect_face_from_image.py new file mode 100644 index 0000000..11bfe74 --- /dev/null +++ b/compreface/use_cases/detect_face_from_image.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 ExpandedOptionsDict +from dataclasses import dataclass +from ..client import DetectFaceFromImageClient + + +class DetectFaceFromImage: + + @dataclass + class Request: + api_key: str + image_path: str + + def __init__(self, domain: str, port: str, api_key: str): + self.detect_face_from_image = DetectFaceFromImageClient( + 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) + return result diff --git a/compreface/use_cases/list_of_all_saved_subjects.py b/compreface/use_cases/list_of_all_saved_subjects.py new file mode 100644 index 0000000..22e265d --- /dev/null +++ b/compreface/use_cases/list_of_all_saved_subjects.py @@ -0,0 +1,36 @@ +""" + 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 AddExampleOfSubjectClient + + +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 + ) + + def execute(self) -> dict: + result: dict = self.add_example_of_subject.get() + return result diff --git a/compreface/use_cases/recognize_face_from_image.py b/compreface/use_cases/recognize_face_from_image.py new file mode 100644 index 0000000..8cc916c --- /dev/null +++ b/compreface/use_cases/recognize_face_from_image.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 AllOptionsDict +from dataclasses import dataclass +from ..client import RecognizeFaceFromImageClient + + +class RecognizeFaceFromImage: + + @dataclass + class Request: + api_key: str + image_path: str + + def __init__(self, domain: str, port: str, api_key: str): + self.recognize_face_from_image = RecognizeFaceFromImageClient( + 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) + return result diff --git a/compreface/use_cases/verification_face_from_image.py b/compreface/use_cases/verification_face_from_image.py new file mode 100644 index 0000000..0d29c2e --- /dev/null +++ b/compreface/use_cases/verification_face_from_image.py @@ -0,0 +1,40 @@ +""" + 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 ExpandedOptionsDict +from dataclasses import dataclass +from ..client import VerificationFaceFromImageClient + + +class VerificationFaceFromImage: + + @dataclass + class Request: + api_key: str + image_path: str + image_id: str + + def __init__(self, domain: str, port: str, api_key: str): + self.verify_face_from_image = VerificationFaceFromImageClient( + 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) + return result diff --git a/compreface/use_cases/verifiy_face_from_images.py b/compreface/use_cases/verifiy_face_from_images.py new file mode 100644 index 0000000..8184ad4 --- /dev/null +++ b/compreface/use_cases/verifiy_face_from_images.py @@ -0,0 +1,41 @@ +""" + 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_image import VerifyFaceFromImageClient +from compreface.common.typed_dict import ExpandedOptionsDict +from dataclasses import dataclass + + +class VerifyFaceFromImage: + + @dataclass + class Request: + api_key: str + source_image_path: str + target_image_path: str + + def __init__(self, domain: str, port: str, api_key: str): + self.verify_face_from_image = VerifyFaceFromImageClient( + 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) + return result diff --git a/examples/add_example_of_a_subject.py b/examples/add_example_of_a_subject.py new file mode 100644 index 0000000..94acea5 --- /dev/null +++ b/examples/add_example_of_a_subject.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 ExpandedOptionsDict +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 = 'b97fbc0a-518a-4b1d-a93a-581b1d3814cc' + +compre_face: CompreFace = CompreFace(DOMAIN, PORT, { + "det_prob_threshold": 0.8 +}) + +recognition: RecognitionService = compre_face.init_face_recognition( + RECOGNITION_API_KEY) + +face_collection: FaceCollection = recognition.get_face_collection() + +# Image from local path. +image: str = 'examples/common/jonathan-petit-unsplash.jpg' +subject: str = 'Jonathan Petit' + +print(face_collection.add(image, subject)) diff --git a/examples/common/jonathan-petit-unsplash.jpg b/examples/common/jonathan-petit-unsplash.jpg new file mode 100644 index 0000000..2d498be Binary files /dev/null and b/examples/common/jonathan-petit-unsplash.jpg differ diff --git a/examples/delete_all_examples_of_subject.py b/examples/delete_all_examples_of_subject.py new file mode 100644 index 0000000..ea9cba4 --- /dev/null +++ b/examples/delete_all_examples_of_subject.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 compreface import CompreFace +from compreface.service import RecognitionService +from compreface.collections import FaceCollection + +DOMAIN: str = 'http://localhost' +PORT: str = '8000' +RECOGNITION_API_KEY: str = 'b97fbc0a-518a-4b1d-a93a-581b1d3814cc' + + +compre_face: CompreFace = CompreFace(DOMAIN, PORT) + +recognition: RecognitionService = compre_face.init_face_recognition( + RECOGNITION_API_KEY) +subject: str = 'Jonathan Petit' + +face_collection: FaceCollection = recognition.get_face_collection() + +print(face_collection.delete_all(subject)) diff --git a/examples/delete_example_by_id.py b/examples/delete_example_by_id.py new file mode 100644 index 0000000..ee424a6 --- /dev/null +++ b/examples/delete_example_by_id.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 import CompreFace +from compreface.service import RecognitionService +from compreface.collections import FaceCollection + +DOMAIN: str = 'http://localhost' +PORT: str = '8000' +RECOGNITION_API_KEY: str = 'b97fbc0a-518a-4b1d-a93a-581b1d3814cc' + + +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): + last_face: dict = faces[len(faces) - 1] + print(face_collection.delete(last_face.get('image_id'))) +else: + print('No subject found') diff --git a/examples/detect_face_from_image.py b/examples/detect_face_from_image.py new file mode 100644 index 0000000..e1a1468 --- /dev/null +++ b/examples/detect_face_from_image.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 compreface import CompreFace +from compreface.service import DetectionService + + +DOMAIN: str = 'http://localhost' +PORT: str = '8000' +DETECTION_API_KEY: str = '444dc40f-3168-43d5-8cca-108ab401ff5c' + +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 = 'examples/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 new file mode 100644 index 0000000..4f12326 --- /dev/null +++ b/examples/get_list_of_all_subjects.py @@ -0,0 +1,33 @@ +""" + 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 = 'b97fbc0a-518a-4b1d-a93a-581b1d3814cc' + + +compre_face: CompreFace = CompreFace(DOMAIN, PORT) + +recognition: RecognitionService = compre_face.init_face_recognition( + RECOGNITION_API_KEY) + +face_collection: FaceCollection = recognition.get_face_collection() + +print(face_collection.list()) diff --git a/examples/recognize_face_from_image.py b/examples/recognize_face_from_image.py new file mode 100644 index 0000000..d216b1b --- /dev/null +++ b/examples/recognize_face_from_image.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 compreface import CompreFace +from compreface.service import RecognitionService + +DOMAIN: str = 'http://localhost' +PORT: str = '8000' +RECOGNITION_API_KEY: str = 'b97fbc0a-518a-4b1d-a93a-581b1d3814cc' + + +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) + +image_path: str = 'examples/common/jonathan-petit-unsplash.jpg' + +print(recognition.recognize(image_path)) diff --git a/examples/verification_face_from_image.py b/examples/verification_face_from_image.py new file mode 100644 index 0000000..4a763fb --- /dev/null +++ b/examples/verification_face_from_image.py @@ -0,0 +1,45 @@ +""" + 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 = 'b97fbc0a-518a-4b1d-a93a-581b1d3814cc' + +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()) + +face: dict = next(item for item in face_collection.list().get('faces') if item['subject'] == + 'Jonathan Petit') + +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 new file mode 100644 index 0000000..6c93cca --- /dev/null +++ b/examples/verify_face_from_image.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 VerificationService + +DOMAIN: str = 'http://localhost' +PORT: str = '8000' +VERIFICATION_API_KEY: str = '7aa288a2-9082-4fa2-a975-e175deba1ad2' + + +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) + +image_path: str = 'examples/common/jonathan-petit-unsplash.jpg' + + +print(verify.verify(image_path, image_path)) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..0499089 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,5 @@ +[bdist_wheel] +universal = 1 + +[metadata] +description-file = README.md \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..c3f9729 --- /dev/null +++ b/setup.py @@ -0,0 +1,50 @@ +""" + 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 setuptools import find_packages, setup + + +setup( + name='compreface-sdk', + packages=find_packages(where='compreface', include=['examples']), + version='0.1.0', + license='apache-2.0', + description='CompreFace Python SDK makes face recognition into your application even easier.', + author='Artsiom Liubymov aliubymov@exadel.com, Artsiom Khadzkou akhadzkou@exadel.com', + author_email='aliubymov@exadel.com, akhadzkou@exadel.com', + url='https://exadel.com/solutions/compreface/', + download_url='https://github.com/exadel-inc/compreface-python-sdk/0.1.0.tar.gz', + keywords=[ + "CompreFace", + "Face Recognition", + "Face Detection", + "Face Verification", + "Face Identification", + "Computer Vision", + "SDK" + ], + install_requires=[ + 'requests-toolbelt==0.9.1' + ], + classifiers=[ + 'Development Status :: 5 - Production/Stable', + 'Intended Audience :: Developers', + 'Topic :: Software Development :: Build Tools', + 'License :: OSI Approved :: Apache license 2.0', + 'Programming Language :: Python :: 3.7+', + ], +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e82733f --- /dev/null +++ b/tests/__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/client/__init__.py b/tests/client/__init__.py new file mode 100644 index 0000000..160057e --- /dev/null +++ b/tests/client/__init__.py @@ -0,0 +1,16 @@ + +""" +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/client/const_config.py b/tests/client/const_config.py new file mode 100644 index 0000000..774cb79 --- /dev/null +++ b/tests/client/const_config.py @@ -0,0 +1,26 @@ +""" + 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.config.api_list import DETECTION_API + + +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' diff --git a/tests/client/test_add_example_of_subject_client.py b/tests/client/test_add_example_of_subject_client.py new file mode 100644 index 0000000..be3991b --- /dev/null +++ b/tests/client/test_add_example_of_subject_client.py @@ -0,0 +1,165 @@ + +""" + 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 os +import httpretty +import requests +from requests_toolbelt.multipart.encoder import MultipartEncoder +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 +""" +url: str = DOMAIN + ":" + PORT + RECOGNIZE_CRUD_API + + +@httpretty.activate(verbose=True, allow_net_connect=False) +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"}]}' + ) + test_subject: AddExampleOfSubjectClient = AddExampleOfSubjectClient( + RECOGNIZE_API_KEY, DOMAIN, PORT) + response: dict = requests.get( + url=url, headers={'x-api-key': RECOGNIZE_API_KEY}).json() + test_response: dict = test_subject.get() + assert response == test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +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"}]}' + ) + response: dict = requests.delete( + url=url, headers={'x-api-key': RECOGNIZE_API_KEY}).json() + + test_subject: AddExampleOfSubjectClient = AddExampleOfSubjectClient( + 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"}' + ) + + 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() + + test_subject: AddExampleOfSubjectClient = AddExampleOfSubjectClient( + 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_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"}' + ) + + 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() + + 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"}' + ) + test_subject: AddExampleOfSubjectClient = AddExampleOfSubjectClient( + 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_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"}]}' + ) + test_subject: AddExampleOfSubjectClient = AddExampleOfSubjectClient( + RECOGNIZE_API_KEY, DOMAIN, PORT) + response: dict = requests.get( + 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": []}' + ) + test_response: dict = test_subject.get() + 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"}]}' + ) + response: dict = requests.delete( + 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": []}' + ) + test_subject: AddExampleOfSubjectClient = AddExampleOfSubjectClient( + RECOGNIZE_API_KEY, DOMAIN, PORT) + test_response: dict = test_subject.delete("Subject") + assert response != test_response diff --git a/tests/client/test_delete_example_by_id_client.py b/tests/client/test_delete_example_by_id_client.py new file mode 100644 index 0000000..30a1410 --- /dev/null +++ b/tests/client/test_delete_example_by_id_client.py @@ -0,0 +1,64 @@ +""" + 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.delete_example_by_id import DeleteExampleByIdClient +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 + + +@httpretty.activate(verbose=True, allow_net_connect=False) +def test_delete(): + httpretty.register_uri( + httpretty.DELETE, + url, + 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() + + test_subject: DeleteExampleByIdClient = DeleteExampleByIdClient( + RECOGNIZE_API_KEY, DOMAIN, PORT) + test_response: dict = test_subject.delete(IMAGE_ID) + assert response == test_response + + +@httpretty.activate(verbose=True, allow_net_connect=False) +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"}' + ) + response: dict = requests.delete( + url=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 diff --git a/tests/client/test_detect_face_from_image.py b/tests/client/test_detect_face_from_image.py new file mode 100644 index 0000000..82b1db9 --- /dev/null +++ b/tests/client/test_detect_face_from_image.py @@ -0,0 +1,76 @@ +""" + 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 httpretty +import requests +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 + +url: str = DOMAIN + ":" + PORT + DETECTION_API + + +@httpretty.activate(verbose=True, allow_net_connect=False) +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"}]}' + ) + + 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() + test_subject: DetectFaceFromImageClient = DetectFaceFromImageClient( + 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_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"}]}' + ) + + 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() + test_subject: DetectFaceFromImageClient = DetectFaceFromImageClient( + 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"}]}' + ) + test_response: dict = test_subject.post(FILE_PATH) + assert response != test_response diff --git a/tests/client/test_recognize_face_from_image.py b/tests/client/test_recognize_face_from_image.py new file mode 100644 index 0000000..2b07d6e --- /dev/null +++ b/tests/client/test_recognize_face_from_image.py @@ -0,0 +1,78 @@ +""" + 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.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 +""" +url: str = DOMAIN + ":" + PORT + RECOGNIZE_API + + +@httpretty.activate(verbose=True, allow_net_connect=False) +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"}]}' + ) + 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() + + test_subject: RecognizeFaceFromImageClient = RecognizeFaceFromImageClient( + 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_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"}]}' + ) + 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() + 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"}]}' + ) + test_subject: RecognizeFaceFromImageClient = RecognizeFaceFromImageClient( + RECOGNIZE_API_KEY, DOMAIN, PORT) + test_response: dict = test_subject.post(FILE_PATH) + assert response != test_response diff --git a/tests/client/test_verification_face_from_image.py b/tests/client/test_verification_face_from_image.py new file mode 100644 index 0000000..e06cbea --- /dev/null +++ b/tests/client/test_verification_face_from_image.py @@ -0,0 +1,78 @@ +""" + 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 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 + +url: str = DOMAIN + ":" + PORT + RECOGNIZE_CRUD_API + '/' + IMAGE_ID + '/verify' + + +@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='{"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}).json() + + test_subject: VerificationFaceFromImageClient = VerificationFaceFromImageClient( + 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_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"}]}' + ) + + 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}).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"}]}' + ) + test_subject: VerificationFaceFromImageClient = VerificationFaceFromImageClient( + RECOGNIZE_API_KEY, DOMAIN, PORT) + test_response: dict = test_subject.post(FILE_PATH, IMAGE_ID) + assert response != test_response diff --git a/tests/client/test_verify_face_from_image.py b/tests/client/test_verify_face_from_image.py new file mode 100644 index 0000000..865b00b --- /dev/null +++ b/tests/client/test_verify_face_from_image.py @@ -0,0 +1,83 @@ +""" + 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 os +import httpretty +import requests +from requests_toolbelt.multipart.encoder import MultipartEncoder +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' + + +@httpretty.activate(verbose=True, allow_net_connect=False) +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"}]}' + ) + + 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, data=m, headers={'x-api-key': VERIFICATION_API_KEY, 'Content-Type': m.content_type}).json() + test_subject: VerifyFaceFromImageClient = VerifyFaceFromImageClient( + VERIFICATION_API_KEY, DOMAIN, PORT) + 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_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"}]}' + ) + + 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, data=m, headers={'x-api-key': VERIFICATION_API_KEY, 'Content-Type': m.content_type}).json() + test_subject: VerifyFaceFromImageClient = VerifyFaceFromImageClient( + 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"}]}' + ) + test_response: dict = test_subject.post(FILE_PATH, FILE_PATH) + assert response != test_response diff --git a/tests/common/jonathan-petit-unsplash.jpg b/tests/common/jonathan-petit-unsplash.jpg new file mode 100644 index 0000000..2d498be Binary files /dev/null and b/tests/common/jonathan-petit-unsplash.jpg differ diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..e69de29