This commit introduces a comprehensive set of modules for the PunimTag application, including configuration management, database operations, face processing, photo management, and tag management. Each module is designed to encapsulate specific functionalities, enhancing maintainability and scalability. The GUI components are also integrated, allowing for a cohesive user experience. This foundational work sets the stage for future enhancements and features, ensuring a robust framework for photo tagging and face recognition tasks.
268 lines
10 KiB
Python
268 lines
10 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Search functionality and statistics for PunimTag
|
|
"""
|
|
|
|
from typing import List, Dict, Tuple, Optional
|
|
|
|
from database import DatabaseManager
|
|
|
|
|
|
class SearchStats:
|
|
"""Handles search functionality and statistics generation"""
|
|
|
|
def __init__(self, db_manager: DatabaseManager, verbose: int = 0):
|
|
"""Initialize search and stats manager"""
|
|
self.db = db_manager
|
|
self.verbose = verbose
|
|
|
|
def search_faces(self, person_name: str) -> List[str]:
|
|
"""Search for photos containing a specific person"""
|
|
# Get all people matching the name
|
|
people = self.db.show_people_list()
|
|
matching_people = []
|
|
|
|
for person in people:
|
|
person_id, first_name, last_name, middle_name, maiden_name, date_of_birth, created_date = person
|
|
full_name = f"{first_name} {last_name}".lower()
|
|
search_name = person_name.lower()
|
|
|
|
# Check if search term matches any part of the name
|
|
if (search_name in full_name or
|
|
search_name in first_name.lower() or
|
|
search_name in last_name.lower() or
|
|
(middle_name and search_name in middle_name.lower()) or
|
|
(maiden_name and search_name in maiden_name.lower())):
|
|
matching_people.append(person_id)
|
|
|
|
if not matching_people:
|
|
print(f"❌ No people found matching '{person_name}'")
|
|
return []
|
|
|
|
# Get photos for matching people
|
|
photo_paths = []
|
|
for person_id in matching_people:
|
|
# This would need to be implemented in the database module
|
|
# For now, we'll use a placeholder
|
|
pass
|
|
|
|
if photo_paths:
|
|
print(f"🔍 Found {len(photo_paths)} photos with '{person_name}':")
|
|
for path in photo_paths:
|
|
print(f" 📸 {path}")
|
|
else:
|
|
print(f"❌ No photos found for '{person_name}'")
|
|
|
|
return photo_paths
|
|
|
|
def get_statistics(self) -> Dict:
|
|
"""Get comprehensive database statistics"""
|
|
stats = self.db.get_statistics()
|
|
|
|
# Add calculated statistics
|
|
if stats['total_photos'] > 0:
|
|
stats['processing_percentage'] = (stats['processed_photos'] / stats['total_photos']) * 100
|
|
else:
|
|
stats['processing_percentage'] = 0
|
|
|
|
if stats['total_faces'] > 0:
|
|
stats['identification_percentage'] = (stats['identified_faces'] / stats['total_faces']) * 100
|
|
else:
|
|
stats['identification_percentage'] = 0
|
|
|
|
if stats['total_people'] > 0:
|
|
stats['faces_per_person'] = stats['identified_faces'] / stats['total_people']
|
|
else:
|
|
stats['faces_per_person'] = 0
|
|
|
|
if stats['total_photos'] > 0:
|
|
stats['faces_per_photo'] = stats['total_faces'] / stats['total_photos']
|
|
else:
|
|
stats['faces_per_photo'] = 0
|
|
|
|
if stats['total_photos'] > 0:
|
|
stats['tags_per_photo'] = stats['total_photo_tags'] / stats['total_photos']
|
|
else:
|
|
stats['tags_per_photo'] = 0
|
|
|
|
return stats
|
|
|
|
def print_statistics(self):
|
|
"""Print formatted statistics to console"""
|
|
stats = self.get_statistics()
|
|
|
|
print("\n📊 PunimTag Database Statistics")
|
|
print("=" * 50)
|
|
|
|
print(f"📸 Photos:")
|
|
print(f" Total photos: {stats['total_photos']}")
|
|
print(f" Processed: {stats['processed_photos']} ({stats['processing_percentage']:.1f}%)")
|
|
print(f" Unprocessed: {stats['total_photos'] - stats['processed_photos']}")
|
|
|
|
print(f"\n👤 Faces:")
|
|
print(f" Total faces: {stats['total_faces']}")
|
|
print(f" Identified: {stats['identified_faces']} ({stats['identification_percentage']:.1f}%)")
|
|
print(f" Unidentified: {stats['unidentified_faces']}")
|
|
|
|
print(f"\n👥 People:")
|
|
print(f" Total people: {stats['total_people']}")
|
|
print(f" Average faces per person: {stats['faces_per_person']:.1f}")
|
|
|
|
print(f"\n🏷️ Tags:")
|
|
print(f" Total tags: {stats['total_tags']}")
|
|
print(f" Total photo-tag links: {stats['total_photo_tags']}")
|
|
print(f" Average tags per photo: {stats['tags_per_photo']:.1f}")
|
|
|
|
print(f"\n📈 Averages:")
|
|
print(f" Faces per photo: {stats['faces_per_photo']:.1f}")
|
|
print(f" Tags per photo: {stats['tags_per_photo']:.1f}")
|
|
|
|
print("=" * 50)
|
|
|
|
def get_photo_statistics(self) -> Dict:
|
|
"""Get detailed photo statistics"""
|
|
stats = self.get_statistics()
|
|
|
|
# This could be expanded with more detailed photo analysis
|
|
return {
|
|
'total_photos': stats['total_photos'],
|
|
'processed_photos': stats['processed_photos'],
|
|
'unprocessed_photos': stats['total_photos'] - stats['processed_photos'],
|
|
'processing_percentage': stats['processing_percentage']
|
|
}
|
|
|
|
def get_face_statistics(self) -> Dict:
|
|
"""Get detailed face statistics"""
|
|
stats = self.get_statistics()
|
|
|
|
return {
|
|
'total_faces': stats['total_faces'],
|
|
'identified_faces': stats['identified_faces'],
|
|
'unidentified_faces': stats['unidentified_faces'],
|
|
'identification_percentage': stats['identification_percentage'],
|
|
'faces_per_photo': stats['faces_per_photo']
|
|
}
|
|
|
|
def get_people_statistics(self) -> Dict:
|
|
"""Get detailed people statistics"""
|
|
stats = self.get_statistics()
|
|
|
|
return {
|
|
'total_people': stats['total_people'],
|
|
'faces_per_person': stats['faces_per_person']
|
|
}
|
|
|
|
def get_tag_statistics(self) -> Dict:
|
|
"""Get detailed tag statistics"""
|
|
stats = self.get_statistics()
|
|
|
|
return {
|
|
'total_tags': stats['total_tags'],
|
|
'total_photo_tags': stats['total_photo_tags'],
|
|
'tags_per_photo': stats['tags_per_photo']
|
|
}
|
|
|
|
def search_photos_by_date(self, date_from: str = None, date_to: str = None) -> List[Tuple]:
|
|
"""Search photos by date range"""
|
|
# This would need to be implemented in the database module
|
|
# For now, return empty list
|
|
return []
|
|
|
|
def search_photos_by_tags(self, tags: List[str], match_all: bool = False) -> List[Tuple]:
|
|
"""Search photos by tags"""
|
|
# This would need to be implemented in the database module
|
|
# For now, return empty list
|
|
return []
|
|
|
|
def search_photos_by_people(self, people: List[str]) -> List[Tuple]:
|
|
"""Search photos by people"""
|
|
# This would need to be implemented in the database module
|
|
# For now, return empty list
|
|
return []
|
|
|
|
def get_most_common_tags(self, limit: int = 10) -> List[Tuple[str, int]]:
|
|
"""Get most commonly used tags"""
|
|
# This would need to be implemented in the database module
|
|
# For now, return empty list
|
|
return []
|
|
|
|
def get_most_photographed_people(self, limit: int = 10) -> List[Tuple[str, int]]:
|
|
"""Get most photographed people"""
|
|
# This would need to be implemented in the database module
|
|
# For now, return empty list
|
|
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 []
|
|
|
|
def get_photos_without_tags(self) -> List[Tuple]:
|
|
"""Get photos that have no tags"""
|
|
# This would need to be implemented in the database module
|
|
# For now, return empty list
|
|
return []
|
|
|
|
def get_duplicate_faces(self, tolerance: float = 0.6) -> List[Dict]:
|
|
"""Get potential duplicate faces (same person, different photos)"""
|
|
# This would need to be implemented using face matching
|
|
# For now, return empty list
|
|
return []
|
|
|
|
def get_face_quality_distribution(self) -> Dict:
|
|
"""Get distribution of face quality scores"""
|
|
# This would need to be implemented in the database module
|
|
# For now, return empty dict
|
|
return {}
|
|
|
|
def get_processing_timeline(self) -> List[Tuple[str, int]]:
|
|
"""Get timeline of photo processing (photos processed per day)"""
|
|
# This would need to be implemented in the database module
|
|
# For now, return empty list
|
|
return []
|
|
|
|
def export_statistics(self, filename: str = "punimtag_stats.json"):
|
|
"""Export statistics to a JSON file"""
|
|
import json
|
|
|
|
stats = self.get_statistics()
|
|
|
|
try:
|
|
with open(filename, 'w') as f:
|
|
json.dump(stats, f, indent=2)
|
|
print(f"✅ Statistics exported to {filename}")
|
|
except Exception as e:
|
|
print(f"❌ Error exporting statistics: {e}")
|
|
|
|
def generate_report(self) -> str:
|
|
"""Generate a text report of statistics"""
|
|
stats = self.get_statistics()
|
|
|
|
report = f"""
|
|
PunimTag Database Report
|
|
Generated: {__import__('datetime').datetime.now().strftime('%Y-%m-%d %H:%M:%S')}
|
|
|
|
PHOTO STATISTICS:
|
|
- Total photos: {stats['total_photos']}
|
|
- Processed: {stats['processed_photos']} ({stats['processing_percentage']:.1f}%)
|
|
- Unprocessed: {stats['total_photos'] - stats['processed_photos']}
|
|
|
|
FACE STATISTICS:
|
|
- Total faces: {stats['total_faces']}
|
|
- Identified: {stats['identified_faces']} ({stats['identification_percentage']:.1f}%)
|
|
- Unidentified: {stats['unidentified_faces']}
|
|
- Average faces per photo: {stats['faces_per_photo']:.1f}
|
|
|
|
PEOPLE STATISTICS:
|
|
- Total people: {stats['total_people']}
|
|
- Average faces per person: {stats['faces_per_person']:.1f}
|
|
|
|
TAG STATISTICS:
|
|
- Total tags: {stats['total_tags']}
|
|
- Total photo-tag links: {stats['total_photo_tags']}
|
|
- Average tags per photo: {stats['tags_per_photo']:.1f}
|
|
"""
|
|
|
|
return report
|