From c9106f2c4d909cb0cdca17921f693fecbc711f7a Mon Sep 17 00:00:00 2001 From: Devasy Patel <110348311+Devasy23@users.noreply.github.com> Date: Fri, 8 Mar 2024 16:58:39 +0530 Subject: [PATCH 1/4] Add support for multiple images in create_new_faceEntry API --- API/route.py | 71 +++++++++++++++++--------------------- API/utils.py | 41 ++++++++++++++++++++++ testing/test_face_cycle.py | 8 ++--- 3 files changed, 76 insertions(+), 44 deletions(-) create mode 100644 API/utils.py diff --git a/API/route.py b/API/route.py index b0efca9..2d64aaf 100644 --- a/API/route.py +++ b/API/route.py @@ -5,6 +5,8 @@ from datetime import datetime from io import BytesIO +from API.utils import init_logging_config +from typing import List from bson import ObjectId from deepface import DeepFace from fastapi import APIRouter, Form, HTTPException, Response @@ -15,23 +17,11 @@ from API.database import Database -# Create a logger object -logger = logging.getLogger(__name__) -# Create a file handler -handler = logging.FileHandler("log.log") -handler.setLevel(logging.INFO) -# Create a logging format -formatter = logging.Formatter("%(asctime)s - %(name)s - %(levelname)s - %(message)s") -handler.setFormatter(formatter) +init_logging_config() router = APIRouter() -# To create connection with Mongodb -# mongodb_uri = "mongodb://localhost:27017/" -# port = 8000 -# client = MongoClient(mongodb_uri, port) -# db = client["ImageDB"] client = Database() collection = "faceEntries" @@ -43,14 +33,14 @@ class Employee(BaseModel): Name: str gender: str Department: str - Image: str + Images: List[str] class UpdateEmployee(BaseModel): Name: str gender: str Department: str - Image: str + Images: List[str] # To create new entries of employee @@ -60,25 +50,29 @@ async def create_new_faceEntry(Employee: Employee): EmployeeCode = Employee.EmployeeCode gender = Employee.gender Department = Employee.Department - encoded_image = Employee.Image + encoded_images = Employee.Images time = datetime.now() - img_recovered = base64.b64decode(encoded_image) # decode base64string - # print(img_recovered) - pil_image = Image.open(BytesIO(img_recovered)) - image_filename = f"{Name}.png" - pil_image.save(image_filename) - # print path of the current working directory - pil_image.save(f"Images\dbImages\{Name}.jpg") - # Extract the face from the image - face_image_data = DeepFace.extract_faces( - image_filename, detector_backend="mtcnn", enforce_detection=False - ) - # Calculate the embeddings of the face image - plt.imsave(f"Images/Faces/{Name}.jpg", face_image_data[0]["face"]) - embeddings = DeepFace.represent( - image_filename, model_name="Facenet", detector_backend="mtcnn" - ) - os.remove(image_filename) + + embeddings = [] + for encoded_image in encoded_images: + img_recovered = base64.b64decode(encoded_image) # decode base64string + # print(img_recovered) + pil_image = Image.open(BytesIO(img_recovered)) + image_filename = f"{Name}.png" + pil_image.save(image_filename) + # print path of the current working directory + pil_image.save(f"Images\dbImages\{Name}.jpg") + # Extract the face from the image + face_image_data = DeepFace.extract_faces( + image_filename, detector_backend="mtcnn", enforce_detection=False + ) + # Calculate the embeddings of the face image + plt.imsave(f"Images/Faces/{Name}.jpg", face_image_data[0]["face"]) + embedding = DeepFace.represent( + image_filename, model_name="Facenet", detector_backend="mtcnn" + ) + embeddings.append(embedding) + os.remove(image_filename) # Store the data in the database client.insert_one( collection, @@ -89,12 +83,9 @@ async def create_new_faceEntry(Employee: Employee): "Department": Department, "time": time, "embeddings": embeddings, - "Image": encoded_image, + "Images": encoded_images, }, ) - # db.faceEntries.insert_one( - - # ) return {"message": "Face entry created successfully"} @@ -108,7 +99,7 @@ async def get_employees(): Name=employee.get("Name", "N/A"), gender=employee.get("gender", "N/A"), Department=employee.get("Department", "N/A"), - Image=employee.get("Image", "N/A"), + Images=employee.get("Images", "N/A"), ) for employee in employees_mongo ] @@ -119,7 +110,7 @@ async def get_employees(): @router.get("/read/{EmployeeCode}", response_class=Response) async def read_employee(EmployeeCode: int): try: - # logger.info(f"Start {EmployeeCode}") + logging.info(f"Start {EmployeeCode}") items = client.find_one( collection, filter={"EmployeeCode": EmployeeCode}, @@ -127,7 +118,7 @@ async def read_employee(EmployeeCode: int): "Name": True, "gender": True, "Department": True, - "Image": True, + "Images": True, "_id": False, }, ) diff --git a/API/utils.py b/API/utils.py new file mode 100644 index 0000000..226c873 --- /dev/null +++ b/API/utils.py @@ -0,0 +1,41 @@ +import logging + + +def init_logging_config(): + class CustomFormatter(logging.Formatter): + def __init__(self, file=False): + super().__init__() + yellow = "\x1b[36;10m" if not file else "" + blue = "\x1b[35;10m" if not file else "" + green = "\x1b[32;10m" if not file else "" + red = "\x1b[31;10m" if not file else "" + bold_red = "\x1b[31;1m" if not file else "" + reset = "\x1b[0m" if not file else "" + log = "%(asctime)s (%(filename)s:%(lineno)d) - %(levelname)s: " + msg = reset + "%(message)s" + + self.FORMATS = { + logging.DEBUG: blue + log + msg, + logging.INFO: green + log + msg, + logging.WARNING: yellow + log + msg, + logging.ERROR: red + log + msg, + logging.CRITICAL: bold_red + log + msg, + } + + def format(self, record): + log_fmt = self.FORMATS.get(record.levelno) + formatter = logging.Formatter(log_fmt) + return formatter.format(record) + + logger = logging.getLogger() + logger.setLevel(logging.DEBUG) + + stderr_handler = logging.StreamHandler() + stderr_handler.setLevel(logging.DEBUG) + stderr_handler.setFormatter(CustomFormatter()) + logger.addHandler(stderr_handler) + + file_handler = logging.FileHandler("app.log", mode="w") + file_handler.setLevel(logging.DEBUG) + file_handler.setFormatter(CustomFormatter(True)) + logger.addHandler(file_handler) \ No newline at end of file diff --git a/testing/test_face_cycle.py b/testing/test_face_cycle.py index 93562db..6083aba 100644 --- a/testing/test_face_cycle.py +++ b/testing/test_face_cycle.py @@ -28,7 +28,7 @@ def test_face_lifecycle( "Name": "Devansh", "gender": "Male", "Department": "IT", - "Image": "encoded_string1", + "Images": ["encoded_string1", "encoded_string2"], } # Configure the mock to return the mock document when find() is called @@ -46,7 +46,7 @@ def test_face_lifecycle( "Name": "Devansh", "gender": "Male", "Department": "IT", - "Image": encoded_string1, + "Images": [encoded_string1, encoded_string1], }, ) assert response1.status_code == 200 @@ -61,7 +61,7 @@ def test_face_lifecycle( "Name": "test", "gender": "Female", "Department": "IT", - "Image": encoded_string2, + "Images": [encoded_string2, encoded_string2], }, ) assert response2.status_code == 200 @@ -79,7 +79,7 @@ def test_face_lifecycle( "Name": "Test", "gender": "Male", "Department": "IT_Test", - "Image": "estring", + "Images": ["estring", "estring2"], }, ) assert response.status_code == 200 From 4e65f8d4abfe06acbd50c961267af8d2cf623c93 Mon Sep 17 00:00:00 2001 From: Devasy Patel <110348311+Devasy23@users.noreply.github.com> Date: Fri, 8 Mar 2024 17:02:03 +0530 Subject: [PATCH 2/4] Add API documentation for create_new_faceEntry, get_employees, read_employee, update_employees, and delete_employees endpoints --- API/route.py | 66 +++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 58 insertions(+), 8 deletions(-) diff --git a/API/route.py b/API/route.py index 2d64aaf..107d98d 100644 --- a/API/route.py +++ b/API/route.py @@ -46,6 +46,18 @@ class UpdateEmployee(BaseModel): # To create new entries of employee @router.post("/create_new_faceEntry") async def create_new_faceEntry(Employee: Employee): + """ + Create a new face entry for an employee. + + Args: + Employee (Employee): The employee object containing the employee details. + + Returns: + dict: A dictionary with a success message. + + Raises: + None + """ Name = Employee.Name EmployeeCode = Employee.EmployeeCode gender = Employee.gender @@ -56,23 +68,20 @@ async def create_new_faceEntry(Employee: Employee): embeddings = [] for encoded_image in encoded_images: img_recovered = base64.b64decode(encoded_image) # decode base64string - # print(img_recovered) pil_image = Image.open(BytesIO(img_recovered)) image_filename = f"{Name}.png" pil_image.save(image_filename) - # print path of the current working directory pil_image.save(f"Images\dbImages\{Name}.jpg") - # Extract the face from the image face_image_data = DeepFace.extract_faces( image_filename, detector_backend="mtcnn", enforce_detection=False ) - # Calculate the embeddings of the face image plt.imsave(f"Images/Faces/{Name}.jpg", face_image_data[0]["face"]) embedding = DeepFace.represent( image_filename, model_name="Facenet", detector_backend="mtcnn" ) embeddings.append(embedding) os.remove(image_filename) + # Store the data in the database client.insert_one( collection, @@ -86,12 +95,19 @@ async def create_new_faceEntry(Employee: Employee): "Images": encoded_images, }, ) + return {"message": "Face entry created successfully"} # To display all records @router.get("/Data/", response_model=list[Employee]) async def get_employees(): + """ + Retrieve a list of employees from the database. + + Returns: + list[Employee]: A list of Employee objects containing employee information. + """ employees_mongo = client.find(collection) employees = [ Employee( @@ -109,6 +125,19 @@ async def get_employees(): # To display specific record info @router.get("/read/{EmployeeCode}", response_class=Response) async def read_employee(EmployeeCode: int): + """ + Retrieve employee information based on the provided EmployeeCode. + + Args: + EmployeeCode (int): The unique code of the employee. + + Returns: + Response: A response object containing the employee information in JSON format. + + Raises: + HTTPException: If the employee is not found. + + """ try: logging.info(f"Start {EmployeeCode}") items = client.find_one( @@ -140,8 +169,22 @@ async def read_employee(EmployeeCode: int): # For updating existing record @router.put("/update/{EmployeeCode}", response_model=str) async def update_employees(EmployeeCode: int, Employee: UpdateEmployee): + """ + Update employee information based on the provided EmployeeCode. + + Args: + EmployeeCode (int): The unique code of the employee to be updated. + Employee (UpdateEmployee): The updated employee data. + + Returns: + str: A message indicating the success of the update operation. + + Raises: + HTTPException: If the employee with the given EmployeeCode is not found. + HTTPException: If no data was updated during the update operation. + HTTPException: If an internal server error occurs. + """ try: - # logger.warning("Updating Start") user_id = client.find_one( collection, {"EmployeeCode": EmployeeCode}, projection={"_id": True} ) @@ -149,7 +192,6 @@ async def update_employees(EmployeeCode: int, Employee: UpdateEmployee): if not user_id: raise HTTPException(status_code=404, detail="Employee not found") Employee_data = Employee.model_dump(by_alias=True, exclude_unset=True) - # logger.info(f"Employee data to update: {Employee_data}") try: update_result = client.update_one( collection, @@ -160,16 +202,24 @@ async def update_employees(EmployeeCode: int, Employee: UpdateEmployee): raise HTTPException(status_code=400, detail="No data was updated") return "Updated Successfully" except Exception as e: - # logger.error(f"Error while updating: {e}") raise HTTPException(status_code=500, detail="Internal server error") except Exception as e: - # logger.error(f"Error while fetching user_id: {e}") raise HTTPException(status_code=500, detail="Internal server error") # To delete employee record @router.delete("/delete/{EmployeeCode}") async def delete_employees(EmployeeCode: int): + """ + Delete an employee from the collection based on the provided EmployeeCode. + + Args: + EmployeeCode (int): The unique code of the employee to be deleted. + + Returns: + dict: A dictionary containing a success message. + + """ print(EmployeeCode) client.find_one_and_delete(collection, {"EmployeeCode": EmployeeCode}) From f4a4c4848bf58fdcdd07357a503ecdc84de01623 Mon Sep 17 00:00:00 2001 From: Devasy Patel <110348311+Devasy23@users.noreply.github.com> Date: Fri, 8 Mar 2024 17:08:45 +0530 Subject: [PATCH 3/4] Updated create_new_faceEntry function to handle multiple images --- API/route.py | 14 +++++++++----- API/utils.py | 4 ++-- CHANGELOG.md | 17 +++++++++++++++-- test.ipynb | 39 +++++++++++++++++++++++---------------- 4 files changed, 49 insertions(+), 25 deletions(-) diff --git a/API/route.py b/API/route.py index 107d98d..f26f56b 100644 --- a/API/route.py +++ b/API/route.py @@ -4,9 +4,8 @@ import os from datetime import datetime from io import BytesIO - -from API.utils import init_logging_config from typing import List + from bson import ObjectId from deepface import DeepFace from fastapi import APIRouter, Form, HTTPException, Response @@ -16,6 +15,7 @@ from pymongo import MongoClient from API.database import Database +from API.utils import init_logging_config init_logging_config() @@ -64,11 +64,12 @@ async def create_new_faceEntry(Employee: Employee): Department = Employee.Department encoded_images = Employee.Images time = datetime.now() - + embeddings = [] for encoded_image in encoded_images: img_recovered = base64.b64decode(encoded_image) # decode base64string pil_image = Image.open(BytesIO(img_recovered)) + logging.info(f"Image opened {Name}") image_filename = f"{Name}.png" pil_image.save(image_filename) pil_image.save(f"Images\dbImages\{Name}.jpg") @@ -76,12 +77,15 @@ async def create_new_faceEntry(Employee: Employee): image_filename, detector_backend="mtcnn", enforce_detection=False ) plt.imsave(f"Images/Faces/{Name}.jpg", face_image_data[0]["face"]) + logging.info(f"Face saved {Name}") embedding = DeepFace.represent( image_filename, model_name="Facenet", detector_backend="mtcnn" ) embeddings.append(embedding) + logging.info(f"Embedding created Embeddings for {Name}") os.remove(image_filename) - + + logging.debug(f"About to insert Embeddings: {embeddings}") # Store the data in the database client.insert_one( collection, @@ -95,7 +99,7 @@ async def create_new_faceEntry(Employee: Employee): "Images": encoded_images, }, ) - + return {"message": "Face entry created successfully"} diff --git a/API/utils.py b/API/utils.py index 226c873..1003f38 100644 --- a/API/utils.py +++ b/API/utils.py @@ -35,7 +35,7 @@ def format(self, record): stderr_handler.setFormatter(CustomFormatter()) logger.addHandler(stderr_handler) - file_handler = logging.FileHandler("app.log", mode="w") + file_handler = logging.FileHandler("app.log", mode="w") file_handler.setLevel(logging.DEBUG) file_handler.setFormatter(CustomFormatter(True)) - logger.addHandler(file_handler) \ No newline at end of file + logger.addHandler(file_handler) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0f99b4..a20115a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,18 @@ -## [Unreleased] +## [0.0.1] - 2024-03-08 - 2:30 ### Added - Implemented all test cases in `test_face_cycle` -- Implemented mock test cases for `test_face_cycle` to work on online runners \ No newline at end of file +- Implemented mock test cases for `test_face_cycle` to work on online runners + +## [0.1.0] - 2024-03-08 - 17:10 + +### Added +- Updated `create_new_faceEntry` function in [`route.py`](route/route.py) to handle multiple images for each employee. +- Updated `test_face_lifecycle` function in [`test_face_cycle.py`](testing/test_face_cycle.py) to handle multiple images for each employee in the test data. + +### Changed +- Modified the `Employee` and `UpdateEmployee` models in [`route.py`](route/route.py) to include a list of images instead of a single image. +- Adjusted the mock data and assertions in [`test_face_cycle.py`](testing/test_face_cycle.py) to handle multiple images for each employee. + +### Fixed +- Resolved an issue where the `create_new_faceEntry` function in [`route.py`](route/route.py) was not correctly processing multiple images for each employee. \ No newline at end of file diff --git a/test.ipynb b/test.ipynb index 556d392..622d675 100644 --- a/test.ipynb +++ b/test.ipynb @@ -631,13 +631,16 @@ "outputs": [], "source": [ "url = \"http://127.0.0.1:8000/create_new_faceEntry\"\n", - "resp = requests.post(url=url, json={\n", - " \"EmployeeCode\": 134,\n", - " \"Name\": \"Name\",\n", - " \"gender\": \"gender\",\n", - " \"Department\": \"Department\",\n", - " \"Image\": \"your_image\",\n", - "})" + "resp = requests.post(\n", + " url=url,\n", + " json={\n", + " \"EmployeeCode\": 134,\n", + " \"Name\": \"Name\",\n", + " \"gender\": \"gender\",\n", + " \"Department\": \"Department\",\n", + " \"Image\": \"your_image\",\n", + " },\n", + ")" ] }, { @@ -676,16 +679,20 @@ ], "source": [ "from API.database import Database\n", + "\n", "EmployeeCode = 1\n", "client = Database()\n", - "client.find_one(\"faceEntries\", filter={\"EmployeeCode\": EmployeeCode},\n", - " projection={\n", - " \"Name\": True,\n", - " \"gender\": True,\n", - " \"Department\": True,\n", - " \"Image\": True,\n", - " \"_id\": False,\n", - " })" + "client.find_one(\n", + " \"faceEntries\",\n", + " filter={\"EmployeeCode\": EmployeeCode},\n", + " projection={\n", + " \"Name\": True,\n", + " \"gender\": True,\n", + " \"Department\": True,\n", + " \"Image\": True,\n", + " \"_id\": False,\n", + " },\n", + ")" ] } ], @@ -705,7 +712,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.13" + "version": "3.11.8" } }, "nbformat": 4, From be9f4032feb53edcf120ad57c9f97f84d3475866 Mon Sep 17 00:00:00 2001 From: Devasy Patel <110348311+Devasy23@users.noreply.github.com> Date: Fri, 8 Mar 2024 17:18:19 +0530 Subject: [PATCH 4/4] Remove Pylint workflow --- .github/workflows/pylint.yml | 24 ------------------------ 1 file changed, 24 deletions(-) delete mode 100644 .github/workflows/pylint.yml diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml deleted file mode 100644 index aa7c3c3..0000000 --- a/.github/workflows/pylint.yml +++ /dev/null @@ -1,24 +0,0 @@ -name: Pylint - -on: [pull_request] - -jobs: - build: - runs-on: ubuntu-latest - strategy: - matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] - steps: - - uses: actions/checkout@v3 - - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v3 - with: - python-version: ${{ matrix.python-version }} - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install pylint - pip install -r requirements.txt - - name: Analysing the code with pylint - run: | - pylint $(git ls-files '*.py')