-
-
Notifications
You must be signed in to change notification settings - Fork 623
[Feat] - Webcam Feature #593
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
Changes from all commits
8b2c12b
0129f7c
52a8a57
7b73b34
4fc47c7
5c05baf
1e4b900
5d06b19
b9a36dd
2c037c8
79c9b67
f655d50
994b943
ed5168b
b083616
4fba2f2
ec95615
14f55fc
efe79db
fd18420
dbc0820
5b216a6
1f7113d
eb99b0e
7c8bef7
7297b59
a12a7c9
340b1e9
aed41b4
aa01490
8967c03
e74e37e
06f3c53
66b22a5
7c3eeb0
f7c4a97
ac41b8b
8a1a930
8d7a082
e39321e
6fbb39d
d1c9131
acadd54
5372fbe
19d11d3
2dfa314
73278fc
df38c90
58fcafc
d45a4b1
501552b
0d56ea4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,10 +1,16 @@ | ||||||||||||||||||||||||||||||||||
| from enum import Enum | ||||||||||||||||||||||||||||||||||
| from pydantic import BaseModel | ||||||||||||||||||||||||||||||||||
| from typing import Optional, List, Union | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| # Request Model | ||||||||||||||||||||||||||||||||||
| class AddSingleImageRequest(BaseModel): | ||||||||||||||||||||||||||||||||||
| path: str | ||||||||||||||||||||||||||||||||||
| class InputType(str, Enum): | ||||||||||||||||||||||||||||||||||
| path = "path" | ||||||||||||||||||||||||||||||||||
| base64 = "base64" | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| class FaceSearchRequest(BaseModel): | ||||||||||||||||||||||||||||||||||
| path: Optional[str] = None | ||||||||||||||||||||||||||||||||||
| base64_data: Optional[str] = None | ||||||||||||||||||||||||||||||||||
|
Comment on lines
+11
to
+13
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add validation to ensure at least one input field is provided. Both Apply this diff to add validation using Pydantic's model validator: +from pydantic import model_validator
+
class FaceSearchRequest(BaseModel):
path: Optional[str] = None
base64_data: Optional[str] = None
+
+ @model_validator(mode='after')
+ def check_at_least_one_field(self):
+ if not self.path and not self.base64_data:
+ raise ValueError("Either 'path' or 'base64_data' must be provided")
+ if self.path and self.base64_data:
+ raise ValueError("Only one of 'path' or 'base64_data' should be provided")
+ return self📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| class AddMultipleImagesRequest(BaseModel): | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,106 @@ | ||
| import uuid | ||
| from typing import Optional, List, Dict, Any | ||
| from pydantic import BaseModel | ||
| from app.config.settings import CONFIDENCE_PERCENT, DEFAULT_FACENET_MODEL | ||
| from app.database.faces import get_all_face_embeddings | ||
| from app.models.FaceDetector import FaceDetector | ||
| from app.models.FaceNet import FaceNet | ||
| from app.utils.FaceNet import FaceNet_util_cosine_similarity | ||
|
|
||
|
|
||
| class BoundingBox(BaseModel): | ||
| x: float | ||
| y: float | ||
| width: float | ||
| height: float | ||
|
|
||
|
|
||
| class ImageData(BaseModel): | ||
| id: str | ||
| path: str | ||
| folder_id: str | ||
| thumbnailPath: str | ||
| metadata: Dict[str, Any] | ||
| isTagged: bool | ||
| tags: Optional[List[str]] = None | ||
| bboxes: BoundingBox | ||
|
|
||
|
|
||
| class GetAllImagesResponse(BaseModel): | ||
| success: bool | ||
| message: str | ||
| data: List[ImageData] | ||
|
|
||
|
Comment on lines
+11
to
+33
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix schema mismatch and avoid duplicate models. -from pydantic import BaseModel
-from app.config.settings import CONFIDENCE_PERCENT, DEFAULT_FACENET_MODEL
+from app.config.settings import CONFIDENCE_PERCENT, DEFAULT_FACENET_MODEL
+from app.schemas.images import ImageData, GetAllImagesResponse
@@
-class BoundingBox(BaseModel):
- x: float
- y: float
- width: float
- height: float
-
-
-class ImageData(BaseModel):
- id: str
- path: str
- folder_id: str
- thumbnailPath: str
- metadata: Dict[str, Any]
- isTagged: bool
- tags: Optional[List[str]] = None
- bboxes: BoundingBox
-
-
-class GetAllImagesResponse(BaseModel):
- success: bool
- message: str
- data: List[ImageData]
+# Reuse shared schemas to prevent drift with OpenAPI.
@@
matches.append(
ImageData(
id=image["id"],
path=image["path"],
folder_id=image["folder_id"],
thumbnailPath=image["thumbnailPath"],
metadata=image["metadata"],
isTagged=image["isTagged"],
tags=image["tags"],
- bboxes=image["bbox"],
+ # Note: bbox/bboxes omitted here to match ImageData schema.
)
)Follow-up: If bbox must be returned, extend the shared ImageData schema and OpenAPI accordingly instead of redefining models locally. Based on learnings Also applies to: 18-27, 29-33, 84-93 🤖 Prompt for AI Agents |
||
|
|
||
| def perform_face_search(image_path: str) -> GetAllImagesResponse: | ||
| """ | ||
| Performs face detection, embedding generation, and similarity search. | ||
|
|
||
| Args: | ||
| image_path (str): Path to the image file to process. | ||
|
|
||
| Returns: | ||
| GetAllImagesResponse: Search result containing matched images. | ||
| """ | ||
| fd = FaceDetector() | ||
| fn = FaceNet(DEFAULT_FACENET_MODEL) | ||
|
|
||
| try: | ||
| matches = [] | ||
| image_id = str(uuid.uuid4()) | ||
|
|
||
| try: | ||
| result = fd.detect_faces(image_id, image_path, forSearch=True) | ||
| except Exception as e: | ||
| return GetAllImagesResponse( | ||
| success=False, | ||
| message=f"Failed to process image: {str(e)}", | ||
| data=[], | ||
| ) | ||
| if not result or result["num_faces"] == 0: | ||
| return GetAllImagesResponse( | ||
| success=True, | ||
| message="No faces detected in the image.", | ||
| data=[], | ||
| ) | ||
|
|
||
| process_face = result["processed_faces"][0] | ||
| new_embedding = fn.get_embedding(process_face) | ||
|
|
||
| images = get_all_face_embeddings() | ||
| if not images: | ||
| return GetAllImagesResponse( | ||
| success=True, | ||
| message="No face embeddings available for comparison.", | ||
| data=[], | ||
| ) | ||
|
|
||
| for image in images: | ||
| similarity = FaceNet_util_cosine_similarity( | ||
| new_embedding, image["embeddings"] | ||
| ) | ||
| if similarity >= CONFIDENCE_PERCENT: | ||
| matches.append( | ||
| ImageData( | ||
| id=image["id"], | ||
| path=image["path"], | ||
| folder_id=image["folder_id"], | ||
| thumbnailPath=image["thumbnailPath"], | ||
| metadata=image["metadata"], | ||
| isTagged=image["isTagged"], | ||
| tags=image["tags"], | ||
| bboxes=image["bbox"], | ||
| ) | ||
| ) | ||
|
|
||
| return GetAllImagesResponse( | ||
| success=True, | ||
| message=f"Successfully retrieved {len(matches)} matching images.", | ||
| data=matches, | ||
| ) | ||
|
|
||
| finally: | ||
| if "fd" in locals() and fd is not None: | ||
| fd.close() | ||
| if "fn" in locals() and fn is not None: | ||
| fn.close() | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Decode first, enforce byte-size, and use a secure temp file.
Current length check is on the base64 string (imprecise), and writes to a predictable path. Decode, validate bytes, respond with 413, and use tempfile.NamedTemporaryFile.
Also applies to: 277-294
🤖 Prompt for AI Agents