This commit introduces several new analysis documents, including Auto-Match Load Performance Analysis, Folder Picker Analysis, Monorepo Migration Summary, and various performance analysis documents. Additionally, the installation scripts are updated to reflect changes in backend service paths, ensuring proper integration with the new backend structure. These enhancements provide better documentation and streamline the setup process for users.
349 lines
9.7 KiB
Python
349 lines
9.7 KiB
Python
"""Face processing and identify workflow schemas."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from datetime import date
|
|
from typing import Optional
|
|
|
|
from pydantic import BaseModel, Field, ConfigDict
|
|
|
|
|
|
class ProcessFacesRequest(BaseModel):
|
|
"""Request to process faces in photos."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
batch_size: Optional[int] = Field(
|
|
None,
|
|
ge=1,
|
|
description="Maximum number of photos to process (None = all unprocessed)",
|
|
)
|
|
detector_backend: str = Field(
|
|
"retinaface",
|
|
description="DeepFace detector backend (retinaface, mtcnn, opencv, ssd)",
|
|
)
|
|
model_name: str = Field(
|
|
"ArcFace",
|
|
description="DeepFace model name (ArcFace, Facenet, Facenet512, VGG-Face)",
|
|
)
|
|
|
|
|
|
class ProcessFacesResponse(BaseModel):
|
|
"""Response after initiating face processing."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
job_id: str
|
|
message: str
|
|
batch_size: Optional[int] = None
|
|
detector_backend: str
|
|
model_name: str
|
|
|
|
|
|
class FaceItem(BaseModel):
|
|
"""Minimal face item for list views."""
|
|
|
|
model_config = ConfigDict(from_attributes=True, protected_namespaces=())
|
|
|
|
id: int
|
|
photo_id: int
|
|
quality_score: float
|
|
face_confidence: float
|
|
location: str
|
|
pose_mode: Optional[str] = Field("frontal", description="Pose classification (frontal, profile_left, etc.)")
|
|
excluded: bool = Field(False, description="Whether this face is excluded from identification")
|
|
|
|
|
|
class UnidentifiedFacesQuery(BaseModel):
|
|
"""Query params for listing unidentified faces."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
page: int = 1
|
|
page_size: int = 50
|
|
min_quality: float = 0.0
|
|
date_from: Optional[date] = None
|
|
date_to: Optional[date] = None
|
|
sort_by: str = Field("quality", description="quality|date_taken|date_added")
|
|
sort_dir: str = Field("desc", description="asc|desc")
|
|
|
|
|
|
class UnidentifiedFacesResponse(BaseModel):
|
|
"""Paginated unidentified faces list."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
items: list[FaceItem]
|
|
page: int
|
|
page_size: int
|
|
total: int
|
|
|
|
|
|
class SimilarFaceItem(BaseModel):
|
|
"""Similar face with similarity score (0-1)."""
|
|
|
|
id: int
|
|
photo_id: int
|
|
similarity: float
|
|
location: str
|
|
quality_score: float
|
|
filename: str
|
|
pose_mode: Optional[str] = Field("frontal", description="Pose classification (frontal, profile_left, etc.)")
|
|
|
|
|
|
class SimilarFacesResponse(BaseModel):
|
|
"""Response containing similar faces for a given face."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
base_face_id: int
|
|
items: list[SimilarFaceItem]
|
|
|
|
|
|
class BatchSimilarityRequest(BaseModel):
|
|
"""Request to get similarities between multiple faces."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
face_ids: list[int] = Field(..., description="List of face IDs to calculate similarities for")
|
|
min_confidence: float = Field(60.0, ge=0.0, le=100.0, description="Minimum confidence percentage (0-100)")
|
|
|
|
|
|
class FaceSimilarityPair(BaseModel):
|
|
"""A pair of similar faces with their similarity score."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
face_id_1: int
|
|
face_id_2: int
|
|
similarity: float # 0-1 range
|
|
confidence_pct: float # 0-100 range
|
|
|
|
|
|
class BatchSimilarityResponse(BaseModel):
|
|
"""Response containing similarities between face pairs."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
pairs: list[FaceSimilarityPair] = Field(..., description="List of similar face pairs")
|
|
|
|
|
|
class IdentifyFaceRequest(BaseModel):
|
|
"""Identify a face by selecting existing or creating new person."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
# Either provide person_id or the fields to create new person
|
|
person_id: Optional[int] = None
|
|
first_name: Optional[str] = None
|
|
last_name: Optional[str] = None
|
|
middle_name: Optional[str] = None
|
|
maiden_name: Optional[str] = None
|
|
date_of_birth: Optional[date] = None
|
|
# Optionally identify a batch of face IDs along with this one
|
|
additional_face_ids: Optional[list[int]] = None
|
|
|
|
|
|
class IdentifyFaceResponse(BaseModel):
|
|
"""Result of identify operation."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
identified_face_ids: list[int]
|
|
person_id: int
|
|
created_person: bool
|
|
|
|
|
|
class FaceUnmatchResponse(BaseModel):
|
|
"""Result of unmatch operation."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
face_id: int
|
|
message: str
|
|
|
|
|
|
class BatchUnmatchRequest(BaseModel):
|
|
"""Request to batch unmatch multiple faces."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
face_ids: list[int] = Field(..., min_items=1)
|
|
|
|
|
|
class BatchUnmatchResponse(BaseModel):
|
|
"""Result of batch unmatch operation."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
unmatched_face_ids: list[int]
|
|
count: int
|
|
message: str
|
|
|
|
|
|
class PersonFaceItem(BaseModel):
|
|
"""Face item for person's faces list (includes photo info)."""
|
|
|
|
model_config = ConfigDict(from_attributes=True, protected_namespaces=())
|
|
|
|
id: int
|
|
photo_id: int
|
|
photo_path: str
|
|
photo_filename: str
|
|
location: str
|
|
face_confidence: float
|
|
quality_score: float
|
|
detector_backend: str
|
|
model_name: str
|
|
|
|
|
|
class PersonFacesResponse(BaseModel):
|
|
"""Response containing all faces for a person."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
person_id: int
|
|
items: list[PersonFaceItem]
|
|
total: int
|
|
|
|
|
|
class AutoMatchRequest(BaseModel):
|
|
"""Request to start auto-match process."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
tolerance: float = Field(0.6, ge=0.0, le=1.0, description="Tolerance threshold (lower = stricter matching)")
|
|
auto_accept: bool = Field(False, description="Enable automatic acceptance of matching faces")
|
|
auto_accept_threshold: float = Field(70.0, ge=0.0, le=100.0, description="Similarity threshold for auto-acceptance (0-100%)")
|
|
|
|
|
|
class AutoMatchFaceItem(BaseModel):
|
|
"""Unidentified face match for a person."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
id: int
|
|
photo_id: int
|
|
photo_filename: str
|
|
location: str
|
|
quality_score: float
|
|
similarity: float # Confidence percentage (0-100)
|
|
distance: float
|
|
pose_mode: str = Field("frontal", description="Pose classification (frontal, profile_left, etc.)")
|
|
|
|
|
|
class AutoMatchPersonItem(BaseModel):
|
|
"""Person with matches for auto-match workflow."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
person_id: int
|
|
person_name: str
|
|
reference_face_id: int
|
|
reference_photo_id: int
|
|
reference_photo_filename: str
|
|
reference_location: str
|
|
reference_pose_mode: str = Field("frontal", description="Reference face pose classification")
|
|
face_count: int # Number of faces already identified for this person
|
|
matches: list[AutoMatchFaceItem]
|
|
total_matches: int
|
|
|
|
|
|
class AutoMatchPersonSummary(BaseModel):
|
|
"""Person summary without matches (for fast initial load)."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
person_id: int
|
|
person_name: str
|
|
reference_face_id: int
|
|
reference_photo_id: int
|
|
reference_photo_filename: str
|
|
reference_location: str
|
|
reference_pose_mode: str = Field("frontal", description="Reference face pose classification")
|
|
face_count: int # Number of faces already identified for this person
|
|
total_matches: int = Field(0, description="Total matches (loaded separately)")
|
|
|
|
|
|
class AutoMatchPeopleResponse(BaseModel):
|
|
"""Response containing people list without matches (for fast initial load)."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
people: list[AutoMatchPersonSummary]
|
|
total_people: int
|
|
|
|
|
|
class AutoMatchPersonMatchesResponse(BaseModel):
|
|
"""Response containing matches for a specific person."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
person_id: int
|
|
matches: list[AutoMatchFaceItem]
|
|
total_matches: int
|
|
|
|
|
|
class AutoMatchResponse(BaseModel):
|
|
"""Response from auto-match start operation."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
people: list[AutoMatchPersonItem]
|
|
total_people: int
|
|
total_matches: int
|
|
auto_accepted: bool = Field(False, description="Whether auto-acceptance was performed")
|
|
auto_accepted_faces: int = Field(0, description="Number of faces automatically accepted")
|
|
skipped_persons: int = Field(0, description="Number of persons skipped (non-frontal reference)")
|
|
skipped_matches: int = Field(0, description="Number of matches skipped (didn't meet criteria)")
|
|
|
|
|
|
class AcceptMatchesRequest(BaseModel):
|
|
"""Request to accept matches for a person."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
face_ids: list[int] = Field(..., min_items=0, description="Face IDs to identify with this person")
|
|
|
|
|
|
class MaintenanceFaceItem(BaseModel):
|
|
"""Face item for maintenance view with person info and file path."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
id: int
|
|
photo_id: int
|
|
photo_path: str
|
|
photo_filename: str
|
|
quality_score: float
|
|
person_id: Optional[int] = None
|
|
person_name: Optional[str] = None # Full name if identified
|
|
excluded: bool
|
|
|
|
|
|
class MaintenanceFacesResponse(BaseModel):
|
|
"""Response containing all faces for maintenance."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
items: list[MaintenanceFaceItem]
|
|
total: int
|
|
|
|
|
|
class DeleteFacesRequest(BaseModel):
|
|
"""Request to delete multiple faces."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
face_ids: list[int] = Field(..., min_items=1, description="Face IDs to delete")
|
|
|
|
|
|
class DeleteFacesResponse(BaseModel):
|
|
"""Response after deleting faces."""
|
|
|
|
model_config = ConfigDict(protected_namespaces=())
|
|
|
|
deleted_face_ids: list[int]
|
|
count: int
|
|
message: str
|