punimtag/scripts/analyze_poses.py
tanyar09 e74ade9278 feat: Add pose mode analysis and face width detection for improved profile classification
This commit introduces a comprehensive analysis of pose modes and face width detection to enhance profile classification accuracy. New scripts have been added to analyze pose data in the database, check identified faces for pose information, and validate yaw angles. The PoseDetector class has been updated to calculate face width from landmarks, which serves as an additional indicator for profile detection. The frontend and API have been modified to include pose mode in responses, ensuring better integration with existing functionalities. Documentation has been updated to reflect these changes, improving user experience and accuracy in face processing.
2025-11-06 13:26:25 -05:00

193 lines
6.2 KiB
Python

#!/usr/bin/env python3
"""
Analyze pose_mode values in the faces table
"""
import sqlite3
import sys
import os
from collections import Counter
from typing import Dict, List, Tuple
# Default database path
DEFAULT_DB_PATH = "data/photos.db"
def analyze_poses(db_path: str) -> None:
"""Analyze pose_mode values in faces table"""
if not os.path.exists(db_path):
print(f"❌ Database not found: {db_path}")
return
print(f"📊 Analyzing poses in database: {db_path}\n")
try:
conn = sqlite3.connect(db_path)
conn.row_factory = sqlite3.Row
cursor = conn.cursor()
# Get total number of faces
cursor.execute("SELECT COUNT(*) FROM faces")
total_faces = cursor.fetchone()[0]
print(f"Total faces in database: {total_faces}\n")
if total_faces == 0:
print("No faces found in database.")
conn.close()
return
# Get pose_mode distribution
cursor.execute("""
SELECT pose_mode, COUNT(*) as count
FROM faces
GROUP BY pose_mode
ORDER BY count DESC
""")
pose_modes = cursor.fetchall()
print("=" * 60)
print("POSE_MODE DISTRIBUTION")
print("=" * 60)
for row in pose_modes:
pose_mode = row['pose_mode'] or 'NULL'
count = row['count']
percentage = (count / total_faces) * 100
print(f" {pose_mode:30s} : {count:6d} ({percentage:5.1f}%)")
print("\n" + "=" * 60)
print("ANGLE STATISTICS")
print("=" * 60)
# Yaw angle statistics
cursor.execute("""
SELECT
COUNT(*) as total,
COUNT(yaw_angle) as with_yaw,
MIN(yaw_angle) as min_yaw,
MAX(yaw_angle) as max_yaw,
AVG(yaw_angle) as avg_yaw
FROM faces
WHERE yaw_angle IS NOT NULL
""")
yaw_stats = cursor.fetchone()
# Pitch angle statistics
cursor.execute("""
SELECT
COUNT(*) as total,
COUNT(pitch_angle) as with_pitch,
MIN(pitch_angle) as min_pitch,
MAX(pitch_angle) as max_pitch,
AVG(pitch_angle) as avg_pitch
FROM faces
WHERE pitch_angle IS NOT NULL
""")
pitch_stats = cursor.fetchone()
# Roll angle statistics
cursor.execute("""
SELECT
COUNT(*) as total,
COUNT(roll_angle) as with_roll,
MIN(roll_angle) as min_roll,
MAX(roll_angle) as max_roll,
AVG(roll_angle) as avg_roll
FROM faces
WHERE roll_angle IS NOT NULL
""")
roll_stats = cursor.fetchone()
print(f"\nYaw Angle:")
print(f" Faces with yaw data: {yaw_stats['with_yaw']}")
if yaw_stats['with_yaw'] > 0:
print(f" Min: {yaw_stats['min_yaw']:.1f}°")
print(f" Max: {yaw_stats['max_yaw']:.1f}°")
print(f" Avg: {yaw_stats['avg_yaw']:.1f}°")
print(f"\nPitch Angle:")
print(f" Faces with pitch data: {pitch_stats['with_pitch']}")
if pitch_stats['with_pitch'] > 0:
print(f" Min: {pitch_stats['min_pitch']:.1f}°")
print(f" Max: {pitch_stats['max_pitch']:.1f}°")
print(f" Avg: {pitch_stats['avg_pitch']:.1f}°")
print(f"\nRoll Angle:")
print(f" Faces with roll data: {roll_stats['with_roll']}")
if roll_stats['with_roll'] > 0:
print(f" Min: {roll_stats['min_roll']:.1f}°")
print(f" Max: {roll_stats['max_roll']:.1f}°")
print(f" Avg: {roll_stats['avg_roll']:.1f}°")
# Sample faces with different poses
print("\n" + "=" * 60)
print("SAMPLE FACES BY POSE")
print("=" * 60)
for row in pose_modes[:10]: # Top 10 pose modes
pose_mode = row['pose_mode']
cursor.execute("""
SELECT id, photo_id, pose_mode, yaw_angle, pitch_angle, roll_angle
FROM faces
WHERE pose_mode = ?
LIMIT 3
""", (pose_mode,))
samples = cursor.fetchall()
print(f"\n{pose_mode}:")
for sample in samples:
yaw_str = f"{sample['yaw_angle']:.1f}°" if sample['yaw_angle'] is not None else "N/A"
pitch_str = f"{sample['pitch_angle']:.1f}°" if sample['pitch_angle'] is not None else "N/A"
roll_str = f"{sample['roll_angle']:.1f}°" if sample['roll_angle'] is not None else "N/A"
print(f" Face ID {sample['id']}: "
f"yaw={yaw_str} "
f"pitch={pitch_str} "
f"roll={roll_str}")
conn.close()
except sqlite3.Error as e:
print(f"❌ Database error: {e}")
except Exception as e:
print(f"❌ Error: {e}")
def check_web_database() -> None:
"""Check if web database exists and analyze it"""
# Common web database locations
web_db_paths = [
"data/punimtag.db", # Default web database
"data/web_photos.db",
"data/photos_web.db",
"web_photos.db",
]
for db_path in web_db_paths:
if os.path.exists(db_path):
print(f"\n{'='*60}")
print(f"WEB DATABASE: {db_path}")
print(f"{'='*60}\n")
analyze_poses(db_path)
break
if __name__ == "__main__":
# Check desktop database
desktop_db = DEFAULT_DB_PATH
if os.path.exists(desktop_db):
analyze_poses(desktop_db)
# Check web database
check_web_database()
# If no database found, list what we tried
if not os.path.exists(desktop_db):
print(f"❌ Desktop database not found: {desktop_db}")
print("\nTrying to find database files...")
for root, dirs, files in os.walk("data"):
for file in files:
if file.endswith(('.db', '.sqlite', '.sqlite3')):
print(f" Found: {os.path.join(root, file)}")