Implement photos without faces feature in Search GUI and SearchStats

This commit enhances the SearchGUI and SearchStats classes by adding functionality to search for and display photos that do not have detected faces. The SearchGUI now includes logic to handle the visibility of relevant columns based on the selected search type, ensuring a streamlined user experience. Additionally, the SearchStats class has been updated to retrieve photos without faces from the database, improving the overall search capabilities of the application.
This commit is contained in:
tanyar09 2025-10-08 14:19:58 -04:00
parent 40ffc0692e
commit 8a9834b056
2 changed files with 79 additions and 5 deletions

View File

@ -25,7 +25,7 @@ class SearchGUI:
"Search photos by multiple people (planned)",
"Most common tags (planned)",
"Most photographed people (planned)",
"Photos without faces (planned)",
"Photos without faces",
"Photos without tags (planned)",
"Duplicate faces (planned)",
"Face quality distribution (planned)",
@ -197,6 +197,9 @@ class SearchGUI:
# Show person column for name search
tree.column("person", width=180, minwidth=50, anchor="w")
tree.heading("person", text="Person", command=lambda: sort_treeview("person"))
# Restore people icon column for name search
tree.column("open_photo", width=50, minwidth=50, anchor="center")
tree.heading("open_photo", text="👤")
# Restore all columns to display
tree["displaycolumns"] = ("select", "person", "tags", "open_dir", "open_photo", "path")
elif choice == self.SEARCH_TYPES[2]: # Search photos by tags
@ -208,8 +211,22 @@ class SearchGUI:
# Hide person column completely for tag search
tree.column("person", width=0, minwidth=0, anchor="w")
tree.heading("person", text="")
# Restore people icon column for tag search
tree.column("open_photo", width=50, minwidth=50, anchor="center")
tree.heading("open_photo", text="👤")
# Also hide the column from display
tree["displaycolumns"] = ("select", "tags", "open_dir", "open_photo", "path")
elif choice == self.SEARCH_TYPES[6]: # Photos without faces
# No input needed for this search type
search_btn.configure(state="normal")
# Hide person column since photos without faces won't have person info
tree.column("person", width=0, minwidth=0, anchor="w")
tree.heading("person", text="")
# Hide the people icon column since there are no faces/people
tree.column("open_photo", width=0, minwidth=0, anchor="center")
tree.heading("open_photo", text="")
# Also hide the columns from display
tree["displaycolumns"] = ("select", "tags", "open_dir", "path")
else:
planned_label.pack(anchor="w")
name_entry.configure(state="disabled")
@ -219,6 +236,9 @@ class SearchGUI:
# Show person column for other search types
tree.column("person", width=180, minwidth=50, anchor="w")
tree.heading("person", text="Person", command=lambda: sort_treeview("person"))
# Restore people icon column for other search types
tree.column("open_photo", width=50, minwidth=50, anchor="center")
tree.heading("open_photo", text="👤")
# Restore all columns to display
tree["displaycolumns"] = ("select", "person", "tags", "open_dir", "open_photo", "path")
@ -244,6 +264,11 @@ class SearchGUI:
path, tag_info = row
photo_tags = get_photo_tags_for_display(path)
tree.insert("", tk.END, values=("", "", photo_tags, "📁", "👤", path))
elif search_type_var.get() == self.SEARCH_TYPES[6]: # Photos without faces
# For photos without faces: (path, tag_info) - hide person and people icon columns
path, tag_info = row
photo_tags = get_photo_tags_for_display(path)
tree.insert("", tk.END, values=("", "", photo_tags, "📁", "", path))
else:
# For name search: (full_name, path) - show person column
full_name, p = row
@ -256,6 +281,9 @@ class SearchGUI:
if search_type_var.get() == self.SEARCH_TYPES[2]: # Tag search
# Sort by tags column for tag search
self.sort_column = "tags"
elif search_type_var.get() == self.SEARCH_TYPES[6]: # Photos without faces
# Sort by path column for photos without faces
self.sort_column = "path"
else:
# Sort by person column for name search
self.sort_column = "person"
@ -302,6 +330,15 @@ class SearchGUI:
mode_text = "all" if match_all else "any"
messagebox.showinfo("Search", f"No photos found with {mode_text} of the tags: {', '.join(tags)}", parent=root)
add_results(rows)
elif choice == self.SEARCH_TYPES[6]: # Photos without faces
rows = self.search_stats.get_photos_without_faces()
if not rows:
messagebox.showinfo("Search", "No photos without faces found.", parent=root)
else:
# Convert to the format expected by add_results: (path, tag_info)
# For photos without faces, we don't have person info, so we use empty string
formatted_rows = [(path, "") for path, filename in rows]
add_results(formatted_rows)
def open_dir(path: str):
try:
@ -861,6 +898,7 @@ class SearchGUI:
# Determine column offsets based on search type
is_tag_search = (search_type_var.get() == self.SEARCH_TYPES[2])
is_photos_without_faces = (search_type_var.get() == self.SEARCH_TYPES[6])
if is_tag_search:
# Tag search: person column is hidden, select is first
select_col = "#1" # select is now column 1
@ -868,6 +906,13 @@ class SearchGUI:
face_col = "#4" # open_photo is now column 4
path_col = "#5" # path is now column 5
path_index = 5 # path is at index 5 in values array
elif is_photos_without_faces:
# Photos without faces: person and people icon columns are hidden
select_col = "#1" # select is now column 1
open_dir_col = "#3" # open_dir is now column 3
face_col = "#4" # open_photo is now column 4 (but hidden)
path_col = "#4" # path is now column 4 (since people icon is hidden)
path_index = 5 # path is at index 5 in values array
else:
# Name search: all columns visible, select is first
select_col = "#1" # select is now column 1
@ -931,6 +976,7 @@ class SearchGUI:
# Determine column offsets based on search type
is_tag_search = (search_type_var.get() == self.SEARCH_TYPES[2])
is_photos_without_faces = (search_type_var.get() == self.SEARCH_TYPES[6])
if is_tag_search:
# Tag search: person column is hidden, select is first
tags_col = "#2" # tags is now column 2
@ -938,6 +984,13 @@ class SearchGUI:
face_col = "#4" # open_photo is now column 4
path_col = "#5" # path is now column 5
path_index = 5 # path is at index 5 in values array
elif is_photos_without_faces:
# Photos without faces: person and people icon columns are hidden
tags_col = "#2" # tags is now column 2
open_dir_col = "#3" # open_dir is now column 3
face_col = "#4" # open_photo is now column 4 (but hidden)
path_col = "#4" # path is now column 4 (since people icon is hidden)
path_index = 5 # path is at index 5 in values array
else:
# Name search: all columns visible, select is first
tags_col = "#3" # tags is column 3

View File

@ -276,10 +276,31 @@ class SearchStats:
return []
def get_photos_without_faces(self) -> List[Tuple]:
"""Get photos that have no detected faces"""
# This would need to be implemented in the database module
# For now, return empty list
return []
"""Get photos that have no detected faces
Returns:
List of tuples: (photo_path, filename)
"""
results = []
try:
with self.db.get_db_connection() as conn:
cursor = conn.cursor()
# Find photos that have no faces associated with them
cursor.execute('''
SELECT p.path, p.filename
FROM photos p
LEFT JOIN faces f ON p.id = f.photo_id
WHERE f.photo_id IS NULL
ORDER BY p.filename
''')
for row in cursor.fetchall():
if row and row[0]:
results.append((row[0], row[1]))
except Exception as e:
if self.verbose > 0:
print(f"Error searching photos without faces: {e}")
return results
def get_photos_without_tags(self) -> List[Tuple]:
"""Get photos that have no tags"""