Skip to content

Commit

Permalink
Merge pull request #5 from devansh-shah-11:merge-update
Browse files Browse the repository at this point in the history
Merge update
  • Loading branch information
Devasy23 authored Mar 9, 2024
2 parents c971abf + 5ade3b0 commit e479a14
Show file tree
Hide file tree
Showing 6 changed files with 89 additions and 109 deletions.
46 changes: 43 additions & 3 deletions API/route.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ async def create_new_faceEntry(Employee: Employee):
Raises:
None
"""
logging.info("Creating new face entry")
Name = Employee.Name
EmployeeCode = Employee.EmployeeCode
gender = Employee.gender
Expand Down Expand Up @@ -112,7 +113,9 @@ async def get_employees():
Returns:
list[Employee]: A list of Employee objects containing employee information.
"""
logging.info("Displaying all employees")
employees_mongo = client.find(collection)
logging.info(f"Employees found {employees_mongo}")
employees = [
Employee(
EmployeeCode=int(employee.get("EmployeeCode", 0)),
Expand Down Expand Up @@ -142,6 +145,7 @@ async def read_employee(EmployeeCode: int):
HTTPException: If the employee is not found.
"""
logging.info(f"Display information for {EmployeeCode}")
try:
logging.info(f"Start {EmployeeCode}")
items = client.find_one(
Expand Down Expand Up @@ -170,11 +174,14 @@ async def read_employee(EmployeeCode: int):
print(e)


# 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.
Whenever user clicks on update employee button, in the frontend part, all the images will be visible - they can be deleted or new images can be added.
Accordingly, the embeddings will be recalculated and updated in the database.
Args:
EmployeeCode (int): The unique code of the employee to be updated.
Expand All @@ -188,6 +195,7 @@ async def update_employees(EmployeeCode: int, Employee: UpdateEmployee):
HTTPException: If no data was updated during the update operation.
HTTPException: If an internal server error occurs.
"""
logging.info(f"Updating for EmployeeCode: {EmployeeCode}")
try:
user_id = client.find_one(
collection, {"EmployeeCode": EmployeeCode}, projection={"_id": True}
Expand All @@ -196,12 +204,34 @@ 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)
logging.info(f"Employee data {Employee_data}")
# Calculate and store embeddings for the updated image array
encoded_images = Employee.Images
embeddings = []
for encoded_image in encoded_images:
img_recovered = base64.b64decode(encoded_image) # decode base64string
pil_image = Image.open(BytesIO(img_recovered))
image_filename = f"{Employee.Name}.png"
pil_image.save(image_filename)
logging.info(f"Image saved {Employee.Name}")
face_image_data = DeepFace.extract_faces(
image_filename, detector_backend="mtcnn", enforce_detection=False
)
embedding = DeepFace.represent(
image_filename, model_name="Facenet", detector_backend="mtcnn"
)
logging.info(f"Embedding created {Employee.Name}")
embeddings.append(embedding)
os.remove(image_filename)
Employee_data["embeddings"] = embeddings

try:
update_result = client.update_one(
collection,
filter={"_id": ObjectId(user_id["_id"])},
update={"$set": Employee_data},
)
logging.info(f"Update result {update_result}")
if update_result.modified_count == 0:
raise HTTPException(status_code=400, detail="No data was updated")
return "Updated Successfully"
Expand All @@ -210,7 +240,6 @@ async def update_employees(EmployeeCode: int, Employee: UpdateEmployee):
except Exception as e:
raise HTTPException(status_code=500, detail="Internal server error")


# To delete employee record
@router.delete("/delete/{EmployeeCode}")
async def delete_employees(EmployeeCode: int):
Expand All @@ -224,7 +253,18 @@ async def delete_employees(EmployeeCode: int):
dict: A dictionary containing a success message.
"""
print(EmployeeCode)
"""
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.
"""
logging.info("Deleting Employee")
logging.info(f"Deleting for EmployeeCode: {EmployeeCode}")
client.find_one_and_delete(collection, {"EmployeeCode": EmployeeCode})

return {"Message": "Successfully Deleted"}
22 changes: 20 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,26 @@
- 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.
- Modified the `Employee` model 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.
- Resolved an issue where the `create_new_faceEntry` function in [`route.py`](route/route.py) was not correctly processing multiple images for each employee.

## [0.1.1] - 2024-03-09 - 01:00

### Added
- Added logging statements to all the API endpoints in [`route.py`](route/route.py) for easier debugging.

### Changed
- Modified the `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 for update data in [`test_face_cycle.py`](testing/test_face_cycle.py) to handle multiple images for each employee.

## [0.1.2] - 2024-03-09 - 22:00

### Changed
- Merged code in [`route.py`](route/route.py) and [`test_face_cycle.py`](testing/test_face_cycle.py) to improve code organization and readability. Changes made by @Devasy23.
- Split `test_face_lifecycle` function in [`test_face_cycle.py`](testing/test_face_cycle.py) into multiple smaller test cases that execute in a particularly specified order. Changes made by @Devasy23.

### Fixed
- Resolved issues in the test cases of [`test_face_cycle.py`](testing/test_face_cycle.py) to ensure they pass with the updated code structure. Fixes made by @Devasy23.
64 changes: 1 addition & 63 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ The database contains a `faceEntries` collection with the following schema:
- `Department`: The department of the person
- `time`: The time the face entry was created.
- `embeddings`: The embeddings of the face image.
- `Image`: Base64 encoded image file.
- `Images`: Base64 encoded image file.

## Function Flow

Expand All @@ -79,68 +79,6 @@ The database contains a `faceEntries` collection with the following schema:
5. `delete()` : This function is used to delete the specific Employee Data.
# Face Recognition Project
This project uses FastAPI and DeepFace to create a face recognition system. It allows users to register faces with associated metadata, and then recognizes faces in new images.
## Getting Started
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
### Prerequisites
This project requires Python 3.7 or later.
### Installing
1. Clone the repository:
```bash
git clone https://gitlab.com/Devasy23/FaceRec.git
```

2. Navigate to the project directory:

```bash
cd FaceRec
```

3. Install the required packages:

```bash
pip install -r requirements.txt
```

### Running the Server

To start the FastAPI server, run the following command:

```bash
uvicorn main:app --reload
```

## Project Structure

- `main/`: Contains the main FastAPI application.
- `Images/`: Contains the original images and extracted faces.
- `testing/`: Contains the test cases for the application.
- `requirements.txt`: Contains the Python dependencies for the project.

## Database Schema

The database contains a `faceEntries` collection with the following schema:

- `id`: A unique identifier for the face entry.
- `age`: The age of the person.
- `gender`: The gender of the person.
- `time`: The time the face entry was created.
- `embeddings`: The embeddings of the face image.

## Function Flow

1. `create_new_faceEntry()`: This function receives a POST request with an image and metadata (age and gender). It extracts the face from the image, calculates the embeddings, and stores the data in the database.
2. `register_face()`: This function is used to store face images in the database, it is currently deprecated.
## Testing
To run the tests, use the following command:
Expand Down
31 changes: 20 additions & 11 deletions test.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -545,18 +545,27 @@
},
{
"cell_type": "code",
"execution_count": 1,
"execution_count": 4,
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"{'message': 'Face entry created successfully'}"
]
},
"execution_count": 1,
"metadata": {},
"output_type": "execute_result"
"ename": "JSONDecodeError",
"evalue": "Expecting value: line 1 column 1 (char 0)",
"output_type": "error",
"traceback": [
"\u001b[1;31m---------------------------------------------------------------------------\u001b[0m",
"\u001b[1;31mJSONDecodeError\u001b[0m Traceback (most recent call last)",
"File \u001b[1;32md:\\Projects and Hackathon\\FaceRec\\venv\\lib\\site-packages\\requests\\models.py:971\u001b[0m, in \u001b[0;36mResponse.json\u001b[1;34m(self, **kwargs)\u001b[0m\n\u001b[0;32m 970\u001b[0m \u001b[38;5;28;01mtry\u001b[39;00m:\n\u001b[1;32m--> 971\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m complexjson\u001b[38;5;241m.\u001b[39mloads(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtext, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m 972\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m JSONDecodeError \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[0;32m 973\u001b[0m \u001b[38;5;66;03m# Catch JSON-related errors and raise as requests.JSONDecodeError\u001b[39;00m\n\u001b[0;32m 974\u001b[0m \u001b[38;5;66;03m# This aliases json.JSONDecodeError and simplejson.JSONDecodeError\u001b[39;00m\n",
"File \u001b[1;32m~\\anaconda3\\lib\\json\\__init__.py:346\u001b[0m, in \u001b[0;36mloads\u001b[1;34m(s, cls, object_hook, parse_float, parse_int, parse_constant, object_pairs_hook, **kw)\u001b[0m\n\u001b[0;32m 343\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m (\u001b[38;5;28mcls\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m object_hook \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m\n\u001b[0;32m 344\u001b[0m parse_int \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m parse_float \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m\n\u001b[0;32m 345\u001b[0m parse_constant \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m object_pairs_hook \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m \u001b[38;5;129;01mand\u001b[39;00m \u001b[38;5;129;01mnot\u001b[39;00m kw):\n\u001b[1;32m--> 346\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m \u001b[43m_default_decoder\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mdecode\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 347\u001b[0m \u001b[38;5;28;01mif\u001b[39;00m \u001b[38;5;28mcls\u001b[39m \u001b[38;5;129;01mis\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m:\n",
"File \u001b[1;32m~\\anaconda3\\lib\\json\\decoder.py:337\u001b[0m, in \u001b[0;36mJSONDecoder.decode\u001b[1;34m(self, s, _w)\u001b[0m\n\u001b[0;32m 333\u001b[0m \u001b[38;5;250m\u001b[39m\u001b[38;5;124;03m\"\"\"Return the Python representation of ``s`` (a ``str`` instance\u001b[39;00m\n\u001b[0;32m 334\u001b[0m \u001b[38;5;124;03mcontaining a JSON document).\u001b[39;00m\n\u001b[0;32m 335\u001b[0m \n\u001b[0;32m 336\u001b[0m \u001b[38;5;124;03m\"\"\"\u001b[39;00m\n\u001b[1;32m--> 337\u001b[0m obj, end \u001b[38;5;241m=\u001b[39m \u001b[38;5;28;43mself\u001b[39;49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mraw_decode\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[43midx\u001b[49m\u001b[38;5;241;43m=\u001b[39;49m\u001b[43m_w\u001b[49m\u001b[43m(\u001b[49m\u001b[43ms\u001b[49m\u001b[43m,\u001b[49m\u001b[43m \u001b[49m\u001b[38;5;241;43m0\u001b[39;49m\u001b[43m)\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mend\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\u001b[43m)\u001b[49m\n\u001b[0;32m 338\u001b[0m end \u001b[38;5;241m=\u001b[39m _w(s, end)\u001b[38;5;241m.\u001b[39mend()\n",
"File \u001b[1;32m~\\anaconda3\\lib\\json\\decoder.py:355\u001b[0m, in \u001b[0;36mJSONDecoder.raw_decode\u001b[1;34m(self, s, idx)\u001b[0m\n\u001b[0;32m 354\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m \u001b[38;5;167;01mStopIteration\u001b[39;00m \u001b[38;5;28;01mas\u001b[39;00m err:\n\u001b[1;32m--> 355\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m JSONDecodeError(\u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mExpecting value\u001b[39m\u001b[38;5;124m\"\u001b[39m, s, err\u001b[38;5;241m.\u001b[39mvalue) \u001b[38;5;28;01mfrom\u001b[39;00m \u001b[38;5;28;01mNone\u001b[39;00m\n\u001b[0;32m 356\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m obj, end\n",
"\u001b[1;31mJSONDecodeError\u001b[0m: Expecting value: line 1 column 1 (char 0)",
"\nDuring handling of the above exception, another exception occurred:\n",
"\u001b[1;31mJSONDecodeError\u001b[0m Traceback (most recent call last)",
"Cell \u001b[1;32mIn[4], line 62\u001b[0m\n\u001b[0;32m 54\u001b[0m data \u001b[38;5;241m=\u001b[39m {\n\u001b[0;32m 55\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mEmployeeCode\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124m1234\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[0;32m 56\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mName\u001b[39m\u001b[38;5;124m\"\u001b[39m: \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mDevanshu\u001b[39m\u001b[38;5;124m\"\u001b[39m,\n\u001b[1;32m (...)\u001b[0m\n\u001b[0;32m 59\u001b[0m \u001b[38;5;124m\"\u001b[39m\u001b[38;5;124mImage\u001b[39m\u001b[38;5;124m\"\u001b[39m: encoded_string,\n\u001b[0;32m 60\u001b[0m }\n\u001b[0;32m 61\u001b[0m response \u001b[38;5;241m=\u001b[39m requests\u001b[38;5;241m.\u001b[39mpost(url, json\u001b[38;5;241m=\u001b[39mdata)\n\u001b[1;32m---> 62\u001b[0m \u001b[43mresponse\u001b[49m\u001b[38;5;241;43m.\u001b[39;49m\u001b[43mjson\u001b[49m\u001b[43m(\u001b[49m\u001b[43m)\u001b[49m\n",
"File \u001b[1;32md:\\Projects and Hackathon\\FaceRec\\venv\\lib\\site-packages\\requests\\models.py:975\u001b[0m, in \u001b[0;36mResponse.json\u001b[1;34m(self, **kwargs)\u001b[0m\n\u001b[0;32m 971\u001b[0m \u001b[38;5;28;01mreturn\u001b[39;00m complexjson\u001b[38;5;241m.\u001b[39mloads(\u001b[38;5;28mself\u001b[39m\u001b[38;5;241m.\u001b[39mtext, \u001b[38;5;241m*\u001b[39m\u001b[38;5;241m*\u001b[39mkwargs)\n\u001b[0;32m 972\u001b[0m \u001b[38;5;28;01mexcept\u001b[39;00m JSONDecodeError \u001b[38;5;28;01mas\u001b[39;00m e:\n\u001b[0;32m 973\u001b[0m \u001b[38;5;66;03m# Catch JSON-related errors and raise as requests.JSONDecodeError\u001b[39;00m\n\u001b[0;32m 974\u001b[0m \u001b[38;5;66;03m# This aliases json.JSONDecodeError and simplejson.JSONDecodeError\u001b[39;00m\n\u001b[1;32m--> 975\u001b[0m \u001b[38;5;28;01mraise\u001b[39;00m RequestsJSONDecodeError(e\u001b[38;5;241m.\u001b[39mmsg, e\u001b[38;5;241m.\u001b[39mdoc, e\u001b[38;5;241m.\u001b[39mpos)\n",
"\u001b[1;31mJSONDecodeError\u001b[0m: Expecting value: line 1 column 1 (char 0)"
]
}
],
"source": [
Expand Down Expand Up @@ -614,8 +623,8 @@
"\n",
"encoded_string = encoded_string.decode(\"utf-8\")\n",
"data = {\n",
" \"EmployeeCode\": \"123\",\n",
" \"Name\": \"Devansh\",\n",
" \"EmployeeCode\": \"1234\",\n",
" \"Name\": \"Devanshu\",\n",
" \"gender\": \"Male\",\n",
" \"Department\": \"IT\",\n",
" \"Image\": encoded_string,\n",
Expand Down
6 changes: 5 additions & 1 deletion testing/test_face_cycle.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
@patch("API.database.Database.find_one")
@patch("API.database.Database.find")
@patch("API.database.Database.insert_one")

def test_face_lifecycle(
mock_insert_one: MagicMock,
mock_find: MagicMock,
Expand Down Expand Up @@ -72,14 +73,17 @@ def test_face_lifecycle(
assert response.status_code == 200
assert len(response.json()) == 2

with open("./test-faces/devansh.jpg", "rb") as image_file:
encoded_string2 = base64.b64encode(image_file.read()).decode("utf-8")

# Update a face
response = client.put(
"/update/1",
json={
"Name": "Test",
"gender": "Male",
"Department": "IT_Test",
"Images": ["estring", "estring2"],
"Images": [encoded_string2, encoded_string2],
},
)
assert response.status_code == 200
Expand Down
29 changes: 0 additions & 29 deletions testing/test_register_face.py

This file was deleted.

0 comments on commit e479a14

Please sign in to comment.