Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add Frontend Support for vector search function and improve face recognition #43

Merged
merged 12 commits into from
Jul 29, 2024

Conversation

Devasy23
Copy link
Owner

This pull request adds a new vector_search function to the database.py file, which performs a pipeline aggregation vector search on the MongoDB Atlas database. It also improves the face recognition code by refactoring it to use the Facenet512 model for better accuracy. Additionally, error handling has been added for cases where no match is found in face recognition. The pull request also includes code for the recognize_face API.

Copy link

@senior-dev-bot senior-dev-bot bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Feedback from Senior Dev Bot

API/database.py Outdated
Comment on lines 22 to 50

def update_one(self, collection, query, update):
return self.db[collection].update_one(query, update)

# add a function for pipeline aggregation vector search
def vector_search(self, collection, embedding):

result = self.db[collection].aggregate([
{
"$vectorSearch": {
"index": "vector_index",
"path": "face_embedding",
"queryVector": embedding,
"numCandidates": 20,
"limit": 20
}
}, {
'$project': {
'_id': 0,
'Name': 1,
'Image': 1,
'score': {
'$meta': 'vectorSearchScore'
}
}
}
])
result_arr = [i for i in result]
return result_arr

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CODE REVIEW

The vector_search function lacks input validation and error handling, and the function name could be more descriptive. Consider validating the inputs and handling potential exceptions.

def search_by_embedding(self, collection, embedding):
    if not collection or not embedding:
        raise ValueError("Collection and embedding are required.")
    # rest of the code...

Comment on lines 20 to 46
});
});

$(document).ready(function () {
$("#captureButton1").on("click", function () {
$.ajax({
type: "POST",
url: "/capturing",
success: function (response) {
console.log(response)
updateImage();
enableImage();
},
error: function (error) {
console.error(error);
}
});
});
});






function updateImage(){
var img = document.getElementById('Image');

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CODE REVIEW

The code changes are functional, but they lack readability. It's better to remove the excessive empty lines. Also, consider using arrow functions for brevity and consistency. Ensure error handling is consistent throughout the codebase.

$(document).ready(() => {
    $("#captureButton1").on("click", () => {
        $.ajax({
            type: "POST",
            url: "/capturing",
            success: (response) => {
                console.log(response);
                updateImage();
                enableImage();
            },
            error: (error) => {
                console.error(error);
            }
        });
    });
});

function updateImage() {
    var img = document.getElementById('Image');
}

Comment on lines 204 to 210
<div style="padding-top: 10px;">
<select id="Gender" name="Gender" class="form-select form-select-lg mb-3 bg-primary.bg-gradient" aria-label=".form-select-lg example" value="{{employee_data.gender}}" required>

<option value="Male" class="dropdown-item active">Male</option>
<option value="Male" class="dropdown-item ">Male</option>
<option value="Female" class="dropdown-item" >Female</option>
<option value="Other" class="dropdown-item">Other</option>
</select><br><br></div>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CODE REVIEW

The use of inline styles is not ideal. It's better to separate the CSS from the HTML for maintainability. Also, the use of classes like "dropdown-item" appears to be Bootstrap specific, but it's being used in a select element. Consider using appropriate classes or styles for the select element.

<style>
  .select-wrapper {
    padding-top: 10px;
  }
</style>

<div class="select-wrapper">
  <select id="Gender" name="Gender" class="form-select form-select-lg mb-3 bg-primary.bg-gradient" aria-label=".form-select-lg example" value="{{employee_data.gender}}" required>
    <option value="Male">Male</option>
    <option value="Female">Female</option>
    <option value="Other">Other</option>
  </select>
</div>

Comment on lines 54 to 59
var uploadElement = document.getElementById('Upload');
uploadElement.removeAttribute('hidden');
}


myButton.addEventListener("click", function () {
myPopup.classList.add("show");
});

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CODE REVIEW

The code could be improved by using modern JavaScript conventions such as const and addEventListener for event handling. Additionally, consistent spacing and indentation will improve readability.

const uploadElement = document.getElementById('Upload');
uploadElement.removeAttribute('hidden');

myButton.addEventListener('click', () => {
  myPopup.classList.add('show');
});

Comment on lines 218 to 224
<div style="padding-top: 10px;">

<select id="Department" name="Department" class="form-select form-select-lg mb-3 bg-primary.bg-gradient" aria-label=".form-select-lg example"value={{employee_data.Department}} required>
<option value="Software" class="dropdown-item active">Software</option>
<option value="Software" class="dropdown-item ">Software</option>
<option value="BPO/KPO" class="dropdown-item" >BPO/KPO</option>
<option value="Other" class="dropdown-item">Other</option>
</select><br><br></div>

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CODE REVIEW

The changes look good, but let's separate the styling from the HTML. Also, let's ensure proper indentation and follow accessibility best practices by providing labels for the select element.

<div style="padding-top: 10px;">
  <label for="Department">Department:</label>
  <select id="Department" name="Department" class="form-select form-select-lg mb-3 bg-primary.bg-gradient" aria-label=".form-select-lg example" value={{employee_data.Department}} required>
    <option value="Software" class="dropdown-item">Software</option>
    <option value="BPO/KPO" class="dropdown-item">BPO/KPO</option>
    <option value="Other" class="dropdown-item">Other</option>
  </select>
</div>

Comment on lines 38 to 64
# logger.info(resp.status_code)
# logger.info(resp.json())
employees = resp.json()

except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
return render_template("table.html", employees=employees)


# To add employee record
@flk_blueprint.route("/Add_employee")
def add_employee():
return render_template("index.html")


# To submit the form data to server and save it in database
@flk_blueprint.route("/submit_form", methods=["POST"])
def submit_form():

Employee_Code = request.form["EmployeeCode"]
Name = request.form["Name"]
gender = request.form["Gender"]
Department = request.form["Department"]

if request.files["File"]:
if "File" not in request.files:
return jsonify({"message": "No file part"}), 400

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CODE REVIEW

It's important to handle exceptions and errors properly. Consider logging the exception instead of printing it and returning a generic 500 status code.

except requests.exceptions.RequestException as e:
    logger.error(f"Request failed: {e}")
    return render_template("error.html"), 500

Also, the file upload handling logic seems incorrect. It's better to reorganize the condition and return the response for the missing file error outside the file existence check.

if "File" not in request.files:
    return jsonify({"message": "No file part"}), 400

if request.files["File"]:
    # handle file upload

Comment on lines 96 to 134
"Image": encoded_image,
}
url = "http://127.0.0.1:8000/create_new_faceEntry"
try:
resp = requests.post(
url=url,
json={
"EmployeeCode": 134,
"Name": "Name",
"gender": "gender",
"Department": "Department",
"Image": "your_image",
},
)
resp.status_code
except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
payload.status_code
# try:
# resp = requests.post(
# url=url,
# json={
# "EmployeeCode": 134,
# "Name": "Name",
# "gender": "gender",
# "Department": "Department",
# "Image": "your_image",
# },
# )
# resp.status_code
# except requests.exceptions.RequestException as e:
# print(f"Request failed: {e}")
jsonify({"message": "Successfully executed"})
print("Executed.")
if resp.status_code == 200:
if payload.status_code == 200:
return redirect("DisplayingEmployees")
else:
return jsonify({"message": "Failed to execute"})


# To edit an employee details
@flk_blueprint.route("/edit/<int:EmployeeCode>", methods=["POST", "GET"])
def edit(EmployeeCode):
if request.method == "POST":
Name = request.form["Name"]
gender = request.form["Gender"]
Department = request.form["Department"]
with open(Config.image_data_file, "r") as file:
image_data = json.load(file)
encoded_image = image_data.get("base64_image", "")
payload = {
"Name": Name,
"gender": gender,
"Department": Department,
"Image": encoded_image,
}
# logger.info(payload)
try:
url = requests.put(
f"http://127.0.0.1:8000/update/{EmployeeCode}", json=payload
)
url.status_code
# logger.info(url.json())

return redirect("/")

except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
response = requests.get(f"http://127.0.0.1:8000/read/{EmployeeCode}")
# logger.info(response.status_code)
# logger.info(response.json())
if response.status_code == 200:
employee_data = response.json()
return render_template("edit.html", employee_data=employee_data)
else:
return f"Error {response.status_code}: Failed to retrieve employee data."




# To delete employee details
@flk_blueprint.route("/Delete/<int:EmployeeCode>", methods=["DELETE", "GET"])
def Delete(EmployeeCode):
# logger.info(employees)
response = requests.delete(f"http://127.0.0.1:8000/delete/{EmployeeCode}")
jsonify(response.json())

return redirect("/DisplayingEmployees")


Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CODE REVIEW

In the first block, the commented-out code for making a POST request should be removed entirely. Besides, the second block uses 'url.status_code' instead of 'requests.put' for making a PUT request. Avoid unnecessary comments and handle exceptions appropriately. Also, consider using the response directly rather than calling 'response.json()' and 'jsonify(response.json())'.

# Remove commented-out code

# Replace 'url.status_code' with requests.put
resp = requests.put(
    f"http://127.0.0.1:8000/update/{EmployeeCode}", json=payload
)

# Use response directly
return jsonify(response.json())

API/route.py Outdated
Comment on lines 84 to 99
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"
image_filename, model_name="Facenet512", 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,
client2.insert_one(
collection2,
{
"EmployeeCode": EmployeeCode,
"Name": Name,

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CODE REVIEW

The code changes look good. Consider refactoring the code for better readability and modularity. Also, it's a good practice to handle potential errors when saving the face image and inserting into the database.

# Refactored code for better readability and error handling
save_face_image(f"Images/Faces/{Name}.jpg", face_image_data[0]["face"])
log_info(f"Face saved {Name}")
embedding = create_embedding(image_filename)
embeddings.append(embedding)
log_info(f"Embedding created for {Name}")
delete_temp_image(image_filename)
log_debug(f"About to insert Embeddings: {embeddings}")
client2.insert_one(collection2, {"EmployeeCode": EmployeeCode, "Name": Name, ...})

API/route.py Outdated
Comment on lines 8 to 34

from bson import ObjectId
from deepface import DeepFace
from fastapi import APIRouter, HTTPException, Response
from fastapi import APIRouter, HTTPException, Response, UploadFile, File
from matplotlib import pyplot as plt
from PIL import Image
from pydantic import BaseModel

from API.database import Database
from API.utils import init_logging_config
from dotenv import load_dotenv

load_dotenv()
init_logging_config()

MONGO_URI = os.getenv("MONGO_URL1")
router = APIRouter()


client = Database()
client2 = Database("mongodb+srv://devansh:[email protected]/?retryWrites=true&w=majority&appName=Devasy23", "FaceRec")

collection = "faceEntries"
collection2 = "ImageDB"


# Models for the data to be sent and received by the server

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CODE REVIEW

The addition of the dotenv package and loading environment variables is a good practice. However, it's important to handle the import of the os package. Also, using descriptive names for variables is essential for readability.

import os
from dotenv import load_dotenv

load_dotenv()

MONGO_URI = os.getenv("MONGO_URL1")
client = Database()
second_client = Database("mongodb+srv://devansh:[email protected]/?retryWrites=true&w=majority&appName=Devasy23", "FaceRec")

API/route.py Outdated
Comment on lines 272 to 310
client.find_one_and_delete(collection, {"EmployeeCode": EmployeeCode})

return {"Message": "Successfully Deleted"}


@router.post("/recognize_face", response_class=Response)
async def recognize_face(Face: UploadFile = File(...)):
"""
Recognize a face from the provided image.

Args:
Face (UploadFile): The image file to be recognized.

Returns:
Response: A response object containing the recognized employee information in JSON format.

Raises:
HTTPException: If an internal server error occurs.
"""
logging.info("Recognizing Face")
try:
img_data = await Face.read()
with open("temp.png", "wb") as f:
f.write(img_data)

embedding = DeepFace.represent(img_path="temp.png", model_name="Facenet512", detector_backend="mtcnn")
result = client2.vector_search(collection2, embedding[0]['embedding'])
logging.info(f"Result: {result[0]['Name']}, {result[0]['score']}")
os.remove("temp.png")
if result[0]['score'] < 0.5:
return Response(status_code=404, content=json.dumps({"message": "No match found"}))
except Exception as e:
logging.error(f"Error: {e}")
os.remove("temp.png")
raise HTTPException(status_code=500, detail="Internal server error")
return Response(
content=bytes(json.dumps(result[0], default=str), "utf-8"),
media_type="application/json",
)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CODE REVIEW

The recognize_face function should be refactored to improve readability and maintainability. Consider breaking down the logic into smaller functions, using descriptive variable names, and applying error handling for file removal. Also, consider using context managers for file operations.

async def recognize_face(Face: UploadFile = File(...)):
    """
    Recognize a face from the provided image.
    Args:
        Face (UploadFile): The image file to be recognized.
    Returns:
        Response: A response object containing the recognized employee information in JSON format.
    Raises:
        HTTPException: If an internal server error occurs.
    """
    try:
        temp_image_path = "temp.png"
        await save_uploaded_image(Face, temp_image_path)
        embedding = extract_embedding(temp_image_path)
        result = search_for_matching_vector(embedding)
        remove_temp_image(temp_image_path)
        handle_matching_result(result)
    except Exception as e:
        handle_recognition_error(e)


async def save_uploaded_image(file: UploadFile, file_path: str) -> None:
    async with aiofiles.open(file_path, "wb") as temp_image:
        data = await file.read()
        await temp_image.write(data)

def extract_embedding(image_path: str):
    with open(image_path, "rb") as image_file:
        embedding = DeepFace.represent(img_path=image_path, model_name="Facenet512", detector_backend="mtcnn")
    return embedding


def search_for_matching_vector(embedding):
    result = client2.vector_search(collection2, embedding[0]['embedding'])
    return result

def remove_temp_image(file_path: str):
    os.remove(file_path)

def handle_matching_result(result):
    if result[0]['score'] < 0.5:
        return Response(status_code=404, content=json.dumps({"message": "No match found"}))

def handle_recognition_error(error):
    remove_temp_image("temp.png")
    raise HTTPException(status_code=500, detail="Internal server error")

Copy link

gitguardian bot commented May 30, 2024

⚠️ GitGuardian has uncovered 2 secrets following the scan of your pull request.

Please consider investigating the findings and remediating the incidents. Failure to do so may lead to compromising the associated services or software components.

🔎 Detected hardcoded secrets in your pull request
GitGuardian id GitGuardian status Secret Commit Filename
11383748 Triggered MongoDB Credentials 096aba7 API/route.py View secret
11383748 Triggered MongoDB Credentials 1362215 API/route.py View secret
🛠 Guidelines to remediate hardcoded secrets
  1. Understand the implications of revoking this secret by investigating where it is used in your code.
  2. Replace and store your secrets safely. Learn here the best practices.
  3. Revoke and rotate these secrets.
  4. If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.

To avoid such incidents in the future consider


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@Devasy23 Devasy23 added enhancement New feature or request dependencies Pull requests that update a dependency file Testing Perform Testing labels May 30, 2024
API/route.py Outdated
router = APIRouter()


client = Database()
client2 = Database("mongodb+srv://devansh:[email protected]/?retryWrites=true&w=majority&appName=Devasy23", "FaceRec")

Check failure

Code scanning / SonarCloud

MongoDB database passwords should not be disclosed

<!--SONAR_ISSUE_KEY:AY_KC2Kgrd-O9X3PiZ-P-->Make sure this MongoDB database password gets changed and removed from the code. <p>See more on <a href="https://sonarcloud.io/project/issues?id=Devasy23_FaceRec&issues=AY_KC2Kgrd-O9X3PiZ-P&open=AY_KC2Kgrd-O9X3PiZ-P&pullRequest=43">SonarCloud</a></p>
<title>Capture image</title>
<link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-T3c6CoIi6uLrA9TneNEoa7RxnatzjcDSCmG1MXxSR1GAsXEV/Dwwykc2MPK8M2HN" crossorigin="anonymous">
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js" integrity="sha384-C6RzsynM9kWDrMNeT87bh95OGNyZPhcTNXj1NW7RuBCsyN/o0jlpcV8Qyq46cDfL" crossorigin="anonymous"></script>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>

Check warning

Code scanning / CodeQL

Inclusion of functionality from an untrusted source Medium

Script loaded from content delivery network with no integrity check.
Comment on lines +114 to +116
url = requests.put(
f"http://127.0.0.1:8000/update/{EmployeeCode}", json=payload
)

Check failure

Code scanning / CodeQL

Partial server-side request forgery Critical

Part of the URL of this request depends on a
user-provided value
.

except requests.exceptions.RequestException as e:
print(f"Request failed: {e}")
response = requests.get(f"http://127.0.0.1:8000/read/{EmployeeCode}")

Check failure

Code scanning / CodeQL

Partial server-side request forgery Critical

Part of the URL of this request depends on a
user-provided value
.
Copy link

sonarcloud bot commented Jun 8, 2024

Quality Gate Failed Quality Gate failed

Failed conditions
7 Security Hotspots
0.0% Coverage on New Code (required ≥ 80%)
6.2% Duplication on New Code (required ≤ 3%)
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarCloud

Catch issues before they fail your Quality Gate with our IDE extension SonarLint

@Devasy23 Devasy23 merged commit 2f0711f into main Jul 29, 2024
5 of 8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dependencies Pull requests that update a dependency file enhancement New feature or request Testing Perform Testing
Projects
Status: Done
Development

Successfully merging this pull request may close these issues.

3 participants