Spaces:
Runtime error
Runtime error
User registration in chromadb
Browse files- Dockerfile +9 -0
- README.md +1 -3
- app/admin/admin_functions.py +46 -21
- app/admin/templates/admin_login.html +1 -1
- app/admin/templates/registration_success.html +1 -0
- app/admin/templates/user_registration.html +4 -2
- app/main.py +21 -1
- app/utils/__init__.py +2 -0
- app/utils/ec_image_utils.py +105 -0
- requirements.txt +5 -3
Dockerfile
CHANGED
@@ -1,6 +1,11 @@
|
|
1 |
# Use an official Python runtime as a parent image
|
2 |
FROM python:3.9
|
3 |
|
|
|
|
|
|
|
|
|
|
|
4 |
# Create a non-root user with a specified user ID
|
5 |
RUN useradd -m -u 1000 user
|
6 |
|
@@ -29,5 +34,9 @@ RUN pip install --no-cache-dir --user -r requirements.txt
|
|
29 |
# Make port 7860 available to the world outside this container
|
30 |
EXPOSE 7860
|
31 |
|
|
|
|
|
|
|
|
|
32 |
# Run the FastAPI application using Uvicorn, binding to port 7860
|
33 |
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
|
|
1 |
# Use an official Python runtime as a parent image
|
2 |
FROM python:3.9
|
3 |
|
4 |
+
# Install system dependencies required by OpenCV
|
5 |
+
RUN apt-get update && apt-get install -y \
|
6 |
+
libgl1-mesa-glx \
|
7 |
+
&& rm -rf /var/lib/apt/lists/*
|
8 |
+
|
9 |
# Create a non-root user with a specified user ID
|
10 |
RUN useradd -m -u 1000 user
|
11 |
|
|
|
34 |
# Make port 7860 available to the world outside this container
|
35 |
EXPOSE 7860
|
36 |
|
37 |
+
# Indicate that a volume is expected at /home/user/data
|
38 |
+
# This directory is intended for persistent storage
|
39 |
+
VOLUME /home/user/data
|
40 |
+
|
41 |
# Run the FastAPI application using Uvicorn, binding to port 7860
|
42 |
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "7860"]
|
README.md
CHANGED
@@ -18,7 +18,6 @@ EduConnect/
|
|
18 |
β βββ dependencies.py # Dependency utilities for JWT token verification, etc.
|
19 |
β βββ models.py # Database models for ORM
|
20 |
β βββ schemas.py # Pydantic schemas for request and response validation
|
21 |
-
β βββ crud.py # CRUD operations interfacing with the database
|
22 |
β βββ api/
|
23 |
β β βββ __init__.py
|
24 |
β β βββ userlogin.py # Endpoint for user login functionality
|
@@ -33,8 +32,7 @@ EduConnect/
|
|
33 |
β β βββ user_registration.html # Template for user registration page
|
34 |
β βββ utils/
|
35 |
β βββ __init__.py
|
36 |
-
β
|
37 |
-
β βββ database.py # ChromaDB integration and database utilities
|
38 |
βββ static/
|
39 |
β βββ css/
|
40 |
β βββ js/
|
|
|
18 |
β βββ dependencies.py # Dependency utilities for JWT token verification, etc.
|
19 |
β βββ models.py # Database models for ORM
|
20 |
β βββ schemas.py # Pydantic schemas for request and response validation
|
|
|
21 |
β βββ api/
|
22 |
β β βββ __init__.py
|
23 |
β β βββ userlogin.py # Endpoint for user login functionality
|
|
|
32 |
β β βββ user_registration.html # Template for user registration page
|
33 |
β βββ utils/
|
34 |
β βββ __init__.py
|
35 |
+
β βββ ec_image_utils.py # Integrates MTCNN and Facenet for login authentication
|
|
|
36 |
βββ static/
|
37 |
β βββ css/
|
38 |
β βββ js/
|
app/admin/admin_functions.py
CHANGED
@@ -2,6 +2,52 @@ from fastapi import HTTPException, UploadFile, File, Form
|
|
2 |
from typing import Optional
|
3 |
import bcrypt
|
4 |
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
|
6 |
# Admin Authentication
|
7 |
def verify_admin_password(submitted_user: str, submitted_password: str) -> bool:
|
@@ -21,27 +67,6 @@ def verify_admin_password(submitted_user: str, submitted_password: str) -> bool:
|
|
21 |
|
22 |
return False
|
23 |
|
24 |
-
|
25 |
-
# User Registration
|
26 |
-
async def register_user(email: str, name: str, role: str, file: UploadFile = File(...)) -> Optional[str]:
|
27 |
-
"""
|
28 |
-
Registers a new user with the provided details and stores the profile picture.
|
29 |
-
|
30 |
-
:param email: The user's email address.
|
31 |
-
:param name: The user's full name.
|
32 |
-
:param role: The user's role (e.g., Student, Teacher).
|
33 |
-
:param file: The profile picture file.
|
34 |
-
:return: User ID of the newly registered user or None if registration failed.
|
35 |
-
"""
|
36 |
-
# Here, you would include logic to:
|
37 |
-
# 1. Process and validate the input data.
|
38 |
-
# 2. Use MTCNN and Facenet (or similar) to process the profile picture.
|
39 |
-
# 3. Store the user's details and the processed picture in ChromaDB.
|
40 |
-
# 4. Return the user ID or None if the registration fails.
|
41 |
-
|
42 |
-
# This is a placeholder for the implementation.
|
43 |
-
pass
|
44 |
-
|
45 |
# Additional Admin Functions
|
46 |
# You could include other administrative functionalities here, such as:
|
47 |
# - Listing all registered users.
|
|
|
2 |
from typing import Optional
|
3 |
import bcrypt
|
4 |
import os
|
5 |
+
from ..utils import get_user_cropped_image_from_photo
|
6 |
+
|
7 |
+
|
8 |
+
|
9 |
+
# Registrering a face
|
10 |
+
async def register_user(db, email: str, name: str, role: str, file: UploadFile = File(...)):
|
11 |
+
"""
|
12 |
+
Processes and stores the image uploaded into vectordb as image embeddings.
|
13 |
+
|
14 |
+
:param db: The vector db collection handle to which the image embedding with email id as key will be upserted
|
15 |
+
:param email: The email id of the user being registered, this is assumed to be unique per user record
|
16 |
+
:param name: The user name (different from email) for display
|
17 |
+
:param role: The role associated with the user, it can only be student or teacher
|
18 |
+
:param file: The facial image of the user being registered, the first recognized face image would be used.
|
19 |
+
|
20 |
+
:return: email
|
21 |
+
"""
|
22 |
+
unique_filename = f"{email}.jpg" # Use the email as the filename
|
23 |
+
file_path = f"/home/user/data/tmp/{unique_filename}" # Specify your upload directory
|
24 |
+
|
25 |
+
# Ensure the directory exists
|
26 |
+
os.makedirs(os.path.dirname(file_path), exist_ok=True)
|
27 |
+
|
28 |
+
# Then, proceed to open the file
|
29 |
+
with open(file_path, "wb") as buffer:
|
30 |
+
contents = await file.read()
|
31 |
+
buffer.write(contents)
|
32 |
+
|
33 |
+
# Process the image to extract the face
|
34 |
+
cropped_face = get_user_cropped_image_from_photo(file_path)
|
35 |
+
|
36 |
+
if cropped_face is not None:
|
37 |
+
|
38 |
+
# Here you can store the embeddings along with user details in ChromaDB
|
39 |
+
# chroma_db.save_embeddings(user_id, embeddings)
|
40 |
+
db.upsert(images=[cropped_face], ids=[email], metadatas=[{"name":name, "role":role}])
|
41 |
+
return {"status": "User registered successfully", "image": cropped_face}
|
42 |
+
|
43 |
+
else:
|
44 |
+
|
45 |
+
return {"error": "No faces detected"}
|
46 |
+
|
47 |
+
#os.remove(file_path) # Optionally remove the file after processing, if not needed
|
48 |
+
|
49 |
+
|
50 |
+
|
51 |
|
52 |
# Admin Authentication
|
53 |
def verify_admin_password(submitted_user: str, submitted_password: str) -> bool:
|
|
|
67 |
|
68 |
return False
|
69 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
70 |
# Additional Admin Functions
|
71 |
# You could include other administrative functionalities here, such as:
|
72 |
# - Listing all registered users.
|
app/admin/templates/admin_login.html
CHANGED
@@ -1,6 +1,6 @@
|
|
1 |
<html>
|
2 |
<head>
|
3 |
-
<title>EduConnect Administration- login page</title>
|
4 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
5 |
<link href="static/css/mvp.css" rel="stylesheet" media="screen">
|
6 |
</head>
|
|
|
1 |
<html>
|
2 |
<head>
|
3 |
+
<title>EduConnect Administration - login page</title>
|
4 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
5 |
<link href="static/css/mvp.css" rel="stylesheet" media="screen">
|
6 |
</head>
|
app/admin/templates/registration_success.html
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
Success!
|
app/admin/templates/user_registration.html
CHANGED
@@ -1,11 +1,12 @@
|
|
1 |
<!DOCTYPE html>
|
2 |
<html>
|
3 |
<head>
|
4 |
-
<title>User Registration</title>
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
<link href="static/css/mvp.css" rel="stylesheet" media="screen">
|
7 |
</head>
|
8 |
<body>
|
|
|
9 |
<h2>User Registration</h2>
|
10 |
<form action="/admin/register_user" method="post" enctype="multipart/form-data">
|
11 |
<label for="email">User ID (Email):</label>
|
@@ -23,10 +24,11 @@
|
|
23 |
<label for="file">Profile Picture (.jpg):</label>
|
24 |
<input type="file" id="file" name="file" accept=".jpg" required><br>
|
25 |
|
26 |
-
<button type="submit">Register</button>
|
27 |
</form>
|
28 |
{% if error %}
|
29 |
<p class="error"><strong>Error:</strong> {{ error }}
|
30 |
{% endif %}
|
|
|
31 |
</body>
|
32 |
</html>
|
|
|
1 |
<!DOCTYPE html>
|
2 |
<html>
|
3 |
<head>
|
4 |
+
<title>Econnect User Registration</title>
|
5 |
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6 |
<link href="static/css/mvp.css" rel="stylesheet" media="screen">
|
7 |
</head>
|
8 |
<body>
|
9 |
+
<div class=""container">
|
10 |
<h2>User Registration</h2>
|
11 |
<form action="/admin/register_user" method="post" enctype="multipart/form-data">
|
12 |
<label for="email">User ID (Email):</label>
|
|
|
24 |
<label for="file">Profile Picture (.jpg):</label>
|
25 |
<input type="file" id="file" name="file" accept=".jpg" required><br>
|
26 |
|
27 |
+
<button type="submit" class="btn btn-default" >Register</button>
|
28 |
</form>
|
29 |
{% if error %}
|
30 |
<p class="error"><strong>Error:</strong> {{ error }}
|
31 |
{% endif %}
|
32 |
+
</div>
|
33 |
</body>
|
34 |
</html>
|
app/main.py
CHANGED
@@ -1,3 +1,5 @@
|
|
|
|
|
|
1 |
from fastapi import FastAPI, Request, Form, File, UploadFile, Depends
|
2 |
from fastapi.middleware.cors import CORSMiddleware
|
3 |
from fastapi.staticfiles import StaticFiles
|
@@ -5,9 +7,21 @@ from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
|
|
5 |
from fastapi.templating import Jinja2Templates
|
6 |
|
7 |
from .admin import admin_functions as admin
|
|
|
8 |
|
9 |
app = FastAPI()
|
10 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
11 |
# Add middleware
|
12 |
# Set all origins to wildcard for simplicity, but you should limit this in production
|
13 |
app.add_middleware(
|
@@ -39,10 +53,16 @@ async def handle_admin_login(request: Request, username: str = Form(...), passwo
|
|
39 |
# Reload login page with error message
|
40 |
return templates.TemplateResponse("admin_login.html", {"request": request, "error": "Invalid password"})
|
41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
42 |
# User Registration Handler
|
43 |
@app.post("/admin/register_user", response_class=HTMLResponse)
|
44 |
async def handle_user_registration(request: Request, email: str = Form(...), name: str = Form(...), role: str = Form(...), file: UploadFile = File(...)):
|
45 |
-
user_id = await admin.register_user(email, name, role, file)
|
46 |
if user_id:
|
47 |
# Redirect or display a success message
|
48 |
return templates.TemplateResponse("registration_success.html", {"request": request})
|
|
|
1 |
+
import chromadb
|
2 |
+
|
3 |
from fastapi import FastAPI, Request, Form, File, UploadFile, Depends
|
4 |
from fastapi.middleware.cors import CORSMiddleware
|
5 |
from fastapi.staticfiles import StaticFiles
|
|
|
7 |
from fastapi.templating import Jinja2Templates
|
8 |
|
9 |
from .admin import admin_functions as admin
|
10 |
+
from .utils.ec_image_utils import UserFaceEmbeddingFunction
|
11 |
|
12 |
app = FastAPI()
|
13 |
|
14 |
+
# Persitent storage for chromadb setup in /data volume
|
15 |
+
ec_client = chromadb.PersistentClient("/home/user/data/chromadb")
|
16 |
+
user_faces_db = ec_client.get_or_create_collection(name="user_faces_db", embedding_function=UserFaceEmbeddingFunction())
|
17 |
+
|
18 |
+
@app.on_event("startup")
|
19 |
+
async def startup_event():
|
20 |
+
# Perform any necessary ChromaDB setup or checks here
|
21 |
+
ec_client.heartbeat()
|
22 |
+
print("ChromaDB setup completed.")
|
23 |
+
|
24 |
+
|
25 |
# Add middleware
|
26 |
# Set all origins to wildcard for simplicity, but you should limit this in production
|
27 |
app.add_middleware(
|
|
|
53 |
# Reload login page with error message
|
54 |
return templates.TemplateResponse("admin_login.html", {"request": request, "error": "Invalid password"})
|
55 |
|
56 |
+
# To display the register user page
|
57 |
+
@app.get("/admin/register_user", response_class=HTMLResponse)
|
58 |
+
async def get_user_registration(request: Request):
|
59 |
+
# Render the registration form
|
60 |
+
return templates.TemplateResponse("user_registration.html", {"request": request})
|
61 |
+
|
62 |
# User Registration Handler
|
63 |
@app.post("/admin/register_user", response_class=HTMLResponse)
|
64 |
async def handle_user_registration(request: Request, email: str = Form(...), name: str = Form(...), role: str = Form(...), file: UploadFile = File(...)):
|
65 |
+
user_id = await admin.register_user(user_faces_db, email, name, role, file)
|
66 |
if user_id:
|
67 |
# Redirect or display a success message
|
68 |
return templates.TemplateResponse("registration_success.html", {"request": request})
|
app/utils/__init__.py
CHANGED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
# In utils/__init__.py
|
2 |
+
from .ec_image_utils import get_user_cropped_image_from_photo
|
app/utils/ec_image_utils.py
ADDED
@@ -0,0 +1,105 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import cv2
|
2 |
+
from mtcnn.mtcnn import MTCNN
|
3 |
+
import numpy as np
|
4 |
+
from keras_facenet import FaceNet
|
5 |
+
from chromadb.api.types import EmbeddingFunction, Embeddings, Image, Images
|
6 |
+
from typing import List
|
7 |
+
|
8 |
+
|
9 |
+
def load_image(filename):
|
10 |
+
"""Load an image from a file path."""
|
11 |
+
img = cv2.imread(filename)
|
12 |
+
return cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
|
13 |
+
|
14 |
+
|
15 |
+
def normalize(img):
|
16 |
+
"""Normalize the given image array."""
|
17 |
+
mean, std = img.mean(), img.std()
|
18 |
+
return (img - mean) / std
|
19 |
+
|
20 |
+
|
21 |
+
def detect_faces_with_mtcnn(img):
|
22 |
+
"""Detect faces in an image using MTCNN."""
|
23 |
+
detector = MTCNN()
|
24 |
+
bounding_boxes = []
|
25 |
+
detected_faces = detector.detect_faces(img)
|
26 |
+
for detected_face in detected_faces:
|
27 |
+
bounding_boxes.append(detected_face["box"])
|
28 |
+
return bounding_boxes
|
29 |
+
|
30 |
+
|
31 |
+
# Crops out parts of an image based on a list of bounding
|
32 |
+
# boxes. The cropped faces are also resized to 160x160 in
|
33 |
+
# preparation for passing it to FaceNet to compute the
|
34 |
+
# face embeddings.
|
35 |
+
#
|
36 |
+
def crop_faces_to_160x160(img, bounding_boxes):
|
37 |
+
"""Crop faces from an image based on detections."""
|
38 |
+
cropped_faces = []
|
39 |
+
|
40 |
+
for (x,y,w,h) in bounding_boxes:
|
41 |
+
cropped_face = img[y:y+h, x:x+w]
|
42 |
+
normalize(cropped_face)
|
43 |
+
cropped_face = cv2.resize(cropped_face, (160, 160), interpolation=cv2.INTER_CUBIC)
|
44 |
+
cropped_faces.append(cropped_face)
|
45 |
+
|
46 |
+
return np.array(cropped_faces)
|
47 |
+
|
48 |
+
|
49 |
+
# Extract cropped user face image
|
50 |
+
#
|
51 |
+
def get_user_cropped_image_from_photo(filename):
|
52 |
+
|
53 |
+
# Load the image.
|
54 |
+
#...#
|
55 |
+
img = load_image(filename)
|
56 |
+
|
57 |
+
|
58 |
+
# Detect faces and extract all bounding boxes
|
59 |
+
#...#
|
60 |
+
bounding_boxes = detect_faces_with_mtcnn(img)
|
61 |
+
|
62 |
+
|
63 |
+
# Crop out the faces from the image
|
64 |
+
#...#
|
65 |
+
cropped_faces = crop_faces_to_160x160(img, bounding_boxes)
|
66 |
+
|
67 |
+
if cropped_faces.shape[0] == 0:
|
68 |
+
return
|
69 |
+
|
70 |
+
|
71 |
+
# Take the image of only the first detected face
|
72 |
+
#...#
|
73 |
+
cropped_face = cropped_faces[0:1, :, :, :]
|
74 |
+
|
75 |
+
|
76 |
+
# Get the face embeddings using FaceNet and return
|
77 |
+
# the results.
|
78 |
+
#...#
|
79 |
+
return cropped_face[0]
|
80 |
+
|
81 |
+
|
82 |
+
###### Class implementing Custom Embedding function for chroma db
|
83 |
+
#
|
84 |
+
class UserFaceEmbeddingFunction(EmbeddingFunction[Images]):
|
85 |
+
def __init__(self):
|
86 |
+
# Intitialize the FaceNet model
|
87 |
+
self.facenet = FaceNet()
|
88 |
+
|
89 |
+
def __call__(self, input: Images) -> Embeddings:
|
90 |
+
# Since the input images are assumed to be `numpy.ndarray` objects already,
|
91 |
+
# we can directly use them for embeddings extraction without additional processing.
|
92 |
+
# Ensure the input images are pre-cropped face images ready for embedding extraction.
|
93 |
+
|
94 |
+
# Extract embeddings using FaceNet for the pre-cropped face images.
|
95 |
+
embeddings_array = self.facenet.embeddings(input)
|
96 |
+
|
97 |
+
# Convert numpy array of embeddings to list of lists, as expected by Chroma.
|
98 |
+
return embeddings_array.tolist()
|
99 |
+
|
100 |
+
|
101 |
+
# Usage example:
|
102 |
+
# user_face_embedding_function = UserFaceEmbeddingFunction()
|
103 |
+
# Assuming `images` is a list of `numpy.ndarray` objects where each represents a pre-cropped face image ready for embedding extraction.
|
104 |
+
# embeddings = user_face_embedding_function(images)
|
105 |
+
|
requirements.txt
CHANGED
@@ -2,13 +2,15 @@ fastapi==0.95.2 # Core framework for building APIs.
|
|
2 |
uvicorn[standard]==0.18.3 # ASGI server for FastAPI, supports live reloading.
|
3 |
requests==2.28.* # For making HTTP requests, if needed by your app.
|
4 |
torch==1.11.* # PyTorch, for handling deep learning models.
|
5 |
-
transformers==4.* # From Hugging Face, for working with pre-trained LLMs.
|
6 |
sentencepiece==0.1.* # For chat text processing
|
7 |
mtcnn==0.1.1 # For face detection in images.
|
8 |
python-jose[cryptography]==3.3.* # For creating, parsing, and verifying JWT tokens.
|
9 |
python-multipart==0.0.5 # Necessary for form data handling, including file uploads.
|
10 |
-
numpy
|
11 |
chromadb==0.4.22 # Vector database interaction libraries.
|
12 |
keras-facenet==0.3.2 # For face recognition and embedding, used alongside MTCNN.
|
13 |
jinja2==3.0.* # For Admin site redndering
|
14 |
-
bcrypt==4.1.* # For hashing secrets
|
|
|
|
|
|
|
|
2 |
uvicorn[standard]==0.18.3 # ASGI server for FastAPI, supports live reloading.
|
3 |
requests==2.28.* # For making HTTP requests, if needed by your app.
|
4 |
torch==1.11.* # PyTorch, for handling deep learning models.
|
|
|
5 |
sentencepiece==0.1.* # For chat text processing
|
6 |
mtcnn==0.1.1 # For face detection in images.
|
7 |
python-jose[cryptography]==3.3.* # For creating, parsing, and verifying JWT tokens.
|
8 |
python-multipart==0.0.5 # Necessary for form data handling, including file uploads.
|
9 |
+
numpy==1.23.0 # Updated to resolve conflict
|
10 |
chromadb==0.4.22 # Vector database interaction libraries.
|
11 |
keras-facenet==0.3.2 # For face recognition and embedding, used alongside MTCNN.
|
12 |
jinja2==3.0.* # For Admin site redndering
|
13 |
+
bcrypt==4.1.* # For hashing secrets
|
14 |
+
opencv-python-headless==4.5.5.* # For image handling images
|
15 |
+
tensorflow # Tensorflow is needed by MTCNN for facial recognition
|
16 |
+
scipy # The scipy is required for keras-facenet
|