From bca01a5ac37e0f496163dd911dbfb1b161841b21 Mon Sep 17 00:00:00 2001 From: tanyar09 Date: Thu, 11 Dec 2025 13:16:58 -0500 Subject: [PATCH] feat: Enhance getSimilar API and UI with excluded faces functionality This commit updates the `getSimilar` API to include an optional parameter for excluding faces in the results. The Identify component is modified to utilize this new parameter, allowing users to filter out unwanted faces during identification. Additionally, the Help documentation is updated to reflect changes in the identification process, including new filtering options and user instructions for managing excluded faces. Overall, these enhancements improve the user experience and provide more control over face identification. --- frontend/src/api/faces.ts | 6 ++++-- frontend/src/pages/Help.tsx | 31 +++++++++++++++++++++---------- frontend/src/pages/Identify.tsx | 2 +- src/web/api/faces.py | 12 ++++++++---- src/web/services/face_service.py | 14 ++++++++++++-- 5 files changed, 46 insertions(+), 19 deletions(-) diff --git a/frontend/src/api/faces.ts b/frontend/src/api/faces.ts index 63498ac..e173cd1 100644 --- a/frontend/src/api/faces.ts +++ b/frontend/src/api/faces.ts @@ -217,8 +217,10 @@ export const facesApi = { }) return response.data }, - getSimilar: async (faceId: number): Promise => { - const response = await apiClient.get(`/api/v1/faces/${faceId}/similar`) + getSimilar: async (faceId: number, includeExcluded?: boolean): Promise => { + const response = await apiClient.get(`/api/v1/faces/${faceId}/similar`, { + params: { include_excluded: includeExcluded || false }, + }) return response.data }, batchSimilarity: async (request: BatchSimilarityRequest): Promise => { diff --git a/frontend/src/pages/Help.tsx b/frontend/src/pages/Help.tsx index c678b30..9e7b39f 100644 --- a/frontend/src/pages/Help.tsx +++ b/frontend/src/pages/Help.tsx @@ -73,11 +73,11 @@ function NavigationOverview({ onPageClick }: { onPageClick: (page: PageId) => vo const mainNavItems = [ { id: 'scan' as PageId, icon: '🗂️', label: 'Scan', description: 'Import photos from folders or upload files' }, { id: 'process' as PageId, icon: '⚙️', label: 'Process', description: 'Detect and process faces in photos' }, - { id: 'identify' as PageId, icon: '👤', label: 'Identify', description: 'Manually identify people in faces' }, + { id: 'identify' as PageId, icon: '👤', label: 'Identify People', description: 'Manually identify people in faces' }, { id: 'auto-match' as PageId, icon: '🤖', label: 'Auto-Match', description: 'Automatically match similar faces to previously identified faces' }, - { id: 'search' as PageId, icon: '🔍', label: 'Search', description: 'Search and filter photos' }, - { id: 'modify' as PageId, icon: '✏️', label: 'Modify', description: 'Edit person information' }, - { id: 'tags' as PageId, icon: '🏷️', label: 'Tags', description: 'Tag photos and manage photo tags' }, + { id: 'search' as PageId, icon: '🔍', label: 'Search Photos', description: 'Search and filter photos' }, + { id: 'modify' as PageId, icon: '✏️', label: 'Modify People', description: 'Edit person information' }, + { id: 'tags' as PageId, icon: '🏷️', label: 'Tag Photos', description: 'Tag photos and manage photo tags' }, ] const maintenanceNavItems = [ @@ -257,7 +257,7 @@ function ProcessPageHelp({ onBack }: { onBack: () => void }) { function IdentifyPageHelp({ onBack }: { onBack: () => void }) { return ( - +

Purpose

@@ -270,11 +270,11 @@ function IdentifyPageHelp({ onBack }: { onBack: () => void }) {

Features:

  • Face Navigation: Browse through unidentified faces using Prev/Next buttons
  • -
  • Filters: Filter by date taken, tags
  • +
  • Filters: Filter by date taken, tags, quality, and excluded faces
  • Unique Faces Only: Hide similar faces (faces with 60%+ similarity to others)
  • Batch Size: Control how many faces to load at once
  • Similar Faces Panel: View and identify multiple similar faces at once
  • - +
  • Exclude Faces: Block faces from identification (e.g., for false detections or unwanted faces)
@@ -288,11 +288,20 @@ function IdentifyPageHelp({ onBack }: { onBack: () => void }) {
  • Click "Filters" to expand filter options
  • Set date range, minimum quality, or select tags
  • Choose sort order (Quality, Date Taken, or Date Processed)
  • +
  • Check "Include excluded faces" to show faces that have been excluded from identification
  • Click "Apply Filters" to load faces
  • Toggle "Unique faces only" checkbox to hide duplicate faces (recommended)
  • View the current face on the left panel
  • +
  • To exclude a face from identification: +
      +
    • Click the 🚫 button next to the face counter
    • +
    • The face will be marked as excluded and automatically skipped
    • +
    • Excluded faces are hidden by default (check "Include excluded faces" in filters to see them)
    • +
    • Click the 🚫 button again to include the face back in identification
    • +
    +
  • Choose how to identify:
    • Select existing person: Choose from the dropdown at the top
    • @@ -370,6 +379,8 @@ function IdentifyPageHelp({ onBack }: { onBack: () => void }) {
    • Tag filtering lets you identify faces only from photos with specific tags
    • Click on face images to open the full photo in a new window
    • For videos, you can identify multiple people in the same video
    • +
    • Use the exclude (🚫) button to exclude unknown, unwanted faces, or low-quality faces from appearing in your identification workflow
    • +
    • Excluded faces are hidden by default, but you can view them by checking "Include excluded faces" in the filters
  • @@ -468,7 +479,7 @@ function AutoMatchPageHelp({ onBack }: { onBack: () => void }) { function SearchPageHelp({ onBack }: { onBack: () => void }) { return ( - +

    Purpose

    @@ -580,7 +591,7 @@ function SearchPageHelp({ onBack }: { onBack: () => void }) { function ModifyPageHelp({ onBack }: { onBack: () => void }) { return ( - +

    Purpose

    @@ -701,7 +712,7 @@ function ModifyPageHelp({ onBack }: { onBack: () => void }) { function TagsPageHelp({ onBack }: { onBack: () => void }) { return ( - +

    Purpose

    diff --git a/frontend/src/pages/Identify.tsx b/frontend/src/pages/Identify.tsx index 6c8e478..699f93c 100644 --- a/frontend/src/pages/Identify.tsx +++ b/frontend/src/pages/Identify.tsx @@ -348,7 +348,7 @@ export default function Identify() { return } try { - const res = await facesApi.getSimilar(faceId) + const res = await facesApi.getSimilar(faceId, includeExcludedFaces) setSimilar(res.items || []) setSelectedSimilar({}) } catch (error) { diff --git a/src/web/api/faces.py b/src/web/api/faces.py index d28caf1..784913b 100644 --- a/src/web/api/faces.py +++ b/src/web/api/faces.py @@ -192,11 +192,15 @@ def get_unidentified_faces( @router.get("/{face_id}/similar", response_model=SimilarFacesResponse) -def get_similar_faces(face_id: int, db: Session = Depends(get_db)) -> SimilarFacesResponse: +def get_similar_faces( + face_id: int, + include_excluded: bool = Query(False, description="Include excluded faces in results"), + db: Session = Depends(get_db) +) -> SimilarFacesResponse: """Return similar unidentified faces for a given face.""" import logging logger = logging.getLogger(__name__) - logger.info(f"API: get_similar_faces called for face_id={face_id}") + logger.info(f"API: get_similar_faces called for face_id={face_id}, include_excluded={include_excluded}") # Validate face exists base = db.query(Face).filter(Face.id == face_id).first() @@ -204,8 +208,8 @@ def get_similar_faces(face_id: int, db: Session = Depends(get_db)) -> SimilarFac logger.warning(f"API: Face {face_id} not found") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=f"Face {face_id} not found") - logger.info(f"API: Calling find_similar_faces for face_id={face_id}") - results = find_similar_faces(db, face_id) + logger.info(f"API: Calling find_similar_faces for face_id={face_id}, include_excluded={include_excluded}") + results = find_similar_faces(db, face_id, include_excluded=include_excluded) logger.info(f"API: find_similar_faces returned {len(results)} results") items = [ diff --git a/src/web/services/face_service.py b/src/web/services/face_service.py index c30b0a0..619c436 100644 --- a/src/web/services/face_service.py +++ b/src/web/services/face_service.py @@ -1512,6 +1512,7 @@ def find_similar_faces( limit: int = 20000, # Very high default limit - effectively unlimited tolerance: float = 0.6, # DEFAULT_FACE_TOLERANCE from desktop filter_frontal_only: bool = False, # New: Only return frontal or tilted faces (not profile) + include_excluded: bool = False, # Include excluded faces in results ) -> List[Tuple[Face, float, float]]: # Returns (face, distance, confidence_pct) """Find similar faces matching desktop logic exactly. @@ -1528,6 +1529,7 @@ def find_similar_faces( Args: filter_frontal_only: Only return frontal or tilted faces (not profile) + include_excluded: Include excluded faces in results (default: False) """ from src.web.db.models import Photo @@ -1586,6 +1588,10 @@ def find_similar_faces( is_unidentified = f.person_id is None if is_unidentified and confidence_pct >= 40: + # Filter by excluded status if not including excluded faces + if not include_excluded and getattr(f, "excluded", False): + continue + # Filter by pose_mode if requested (only frontal or tilted faces) if filter_frontal_only and not _is_acceptable_pose_for_auto_match(f.pose_mode): continue @@ -1848,9 +1854,11 @@ def find_auto_match_matches( # Desktop: similar_faces = self.face_processor._get_filtered_similar_faces( # reference_face_id, tolerance, include_same_photo=False, face_status=None) # This filters by: person_id is None (unidentified), confidence >= 40%, sorts by distance + # Auto-match always excludes excluded faces similar_faces = find_similar_faces( db, reference_face_id, tolerance=tolerance, - filter_frontal_only=filter_frontal_only + filter_frontal_only=filter_frontal_only, + include_excluded=False # Auto-match always excludes excluded faces ) if similar_faces: @@ -1994,9 +2002,11 @@ def get_auto_match_person_matches( return [] # Find similar faces using existing function + # Auto-match always excludes excluded faces similar_faces = find_similar_faces( db, reference_face.id, tolerance=tolerance, - filter_frontal_only=filter_frontal_only + filter_frontal_only=filter_frontal_only, + include_excluded=False # Auto-match always excludes excluded faces ) return similar_faces