dtyago commited on
Commit
79d7ca9
Β·
1 Parent(s): 843bda3

User registration in chromadb

Browse files
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
- β”‚ β”œβ”€β”€ authentication.py # Integrates MTCNN and Facenet for login authentication
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 # Fundamental package for scientific computing.
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