feat: Enhance API startup script and add file hash management for photos

This commit improves the `run_api_with_worker.sh` script by ensuring the virtual environment is created if it doesn't exist and dependencies are installed. It also adds a check to ensure the database schema is up to date. Additionally, new functionality has been introduced to calculate and store file hashes for uploaded photos, preventing duplicates. The database schema has been updated to include a `file_hash` column in the `photos` table, along with an index for efficient querying. The frontend has been updated to handle warnings for duplicate photos during the review process. Documentation has been updated to reflect these changes.
This commit is contained in:
tanyar09
2025-11-24 13:16:41 -05:00
parent 93cb4eda5b
commit 661e812193
35 changed files with 593 additions and 5628 deletions
-81
View File
@@ -1,81 +0,0 @@
#!/usr/bin/env python3
"""
Test script to demonstrate the difference between old and new confidence calculations
"""
import sys
import os
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from src.core.config import DEFAULT_FACE_TOLERANCE, USE_CALIBRATED_CONFIDENCE, CONFIDENCE_CALIBRATION_METHOD
from src.core.face_processing import FaceProcessor
from src.core.database import DatabaseManager
def test_confidence_calibration():
"""Test and compare old vs new confidence calculations"""
print("🔍 Confidence Calibration Test")
print("=" * 50)
# Initialize face processor (we don't need database for this test)
db_manager = DatabaseManager(":memory:") # In-memory database for testing
face_processor = FaceProcessor(db_manager, verbose=1)
# Test different distance values
test_distances = [0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0, 1.2, 1.5, 2.0]
tolerance = DEFAULT_FACE_TOLERANCE
print(f"Tolerance threshold: {tolerance}")
print(f"Calibration enabled: {USE_CALIBRATED_CONFIDENCE}")
print(f"Calibration method: {CONFIDENCE_CALIBRATION_METHOD}")
print()
print("Distance | Old Linear | New Calibrated | Difference | Description")
print("-" * 70)
for distance in test_distances:
# Old linear calculation
old_confidence = (1 - distance) * 100
# New calibrated calculation
new_confidence, description = face_processor._get_calibrated_confidence(distance, tolerance)
difference = new_confidence - old_confidence
print(f"{distance:8.1f} | {old_confidence:10.1f}% | {new_confidence:13.1f}% | {difference:+9.1f}% | {description}")
print()
print("📊 Key Differences:")
print("- Old method: Simple linear transformation (1 - distance) * 100")
print("- New method: Empirical calibration based on DeepFace ArcFace characteristics")
print("- New method provides more realistic match probabilities")
print()
# Test different calibration methods
print("🔧 Testing Different Calibration Methods:")
print("-" * 50)
# Temporarily change calibration method to test different approaches
original_method = CONFIDENCE_CALIBRATION_METHOD
test_distance = 0.4 # Example distance
print(f"Distance: {test_distance}, Tolerance: {tolerance}")
print()
methods = ["linear", "sigmoid", "empirical"]
for method in methods:
# Update the global config (this is just for testing)
import src.core.config
src.core.config.CONFIDENCE_CALIBRATION_METHOD = method
confidence, desc = face_processor._get_calibrated_confidence(test_distance, tolerance)
print(f"{method:10}: {confidence:6.1f}% - {desc}")
# Restore original method
src.core.config.CONFIDENCE_CALIBRATION_METHOD = original_method
if __name__ == "__main__":
test_confidence_calibration()
-685
View File
@@ -1,685 +0,0 @@
#!/usr/bin/env python3
"""
DeepFace Integration Test Suite for PunimTag
Tests the complete integration of DeepFace into the application
"""
import os
import sys
from pathlib import Path
# Add parent directory to path
sys.path.insert(0, str(Path(__file__).parent.parent))
# Suppress TensorFlow warnings
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
import warnings
warnings.filterwarnings('ignore')
from src.core.database import DatabaseManager
from src.core.face_processing import FaceProcessor
from src.core.config import DEEPFACE_DETECTOR_BACKEND, DEEPFACE_MODEL_NAME
def test_face_detection():
"""Test 1: Face detection with DeepFace"""
print("\n" + "="*60)
print("Test 1: DeepFace Face Detection")
print("="*60)
try:
db = DatabaseManager(":memory:", verbose=0) # In-memory database for testing
processor = FaceProcessor(db, verbose=1)
# Test with a sample image
test_image = "demo_photos/2019-11-22_0011.jpg"
if not os.path.exists(test_image):
print(f"❌ Test image not found: {test_image}")
print(" Please ensure demo photos are available")
return False
print(f"Testing with image: {test_image}")
# Add photo to database
photo_id = db.add_photo(test_image, Path(test_image).name, None)
print(f"✓ Added photo to database (ID: {photo_id})")
# Process faces
count = processor.process_faces(limit=1)
print(f"✓ Processed {count} photos")
# Verify results
stats = db.get_statistics()
print(f"✓ Found {stats['total_faces']} faces in the photo")
if stats['total_faces'] == 0:
print("❌ FAIL: No faces detected")
return False
# Verify face encodings are 512-dimensional (ArcFace)
with db.get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT encoding FROM faces LIMIT 1")
encoding_blob = cursor.fetchone()[0]
encoding_size = len(encoding_blob)
expected_size = 512 * 8 # 512 floats * 8 bytes per float
print(f"✓ Encoding size: {encoding_size} bytes (expected: {expected_size})")
if encoding_size != expected_size:
print(f"❌ FAIL: Wrong encoding size (expected {expected_size}, got {encoding_size})")
return False
print("\n✅ PASS: Face detection working correctly")
return True
except Exception as e:
print(f"\n❌ FAIL: {e}")
import traceback
traceback.print_exc()
return False
def test_face_matching():
"""Test 2: Face matching with DeepFace"""
print("\n" + "="*60)
print("Test 2: DeepFace Face Matching")
print("="*60)
try:
db = DatabaseManager(":memory:", verbose=0)
processor = FaceProcessor(db, verbose=1)
# Test with multiple images
test_images = [
"demo_photos/2019-11-22_0011.jpg",
"demo_photos/2019-11-22_0012.jpg"
]
# Check if test images exist
available_images = [img for img in test_images if os.path.exists(img)]
if len(available_images) < 2:
print(f"⚠️ Only {len(available_images)} test images available")
print(" Skipping face matching test (need at least 2 images)")
return True # Skip but don't fail
print(f"Testing with {len(available_images)} images")
# Add photos to database
for img in available_images:
photo_id = db.add_photo(img, Path(img).name, None)
print(f"✓ Added {Path(img).name} (ID: {photo_id})")
# Process all faces
count = processor.process_faces(limit=10)
print(f"✓ Processed {count} photos")
# Get statistics
stats = db.get_statistics()
print(f"✓ Found {stats['total_faces']} total faces")
if stats['total_faces'] < 2:
print("⚠️ Not enough faces for matching test")
return True # Skip but don't fail
# Find similar faces
faces = db.get_all_face_encodings()
if len(faces) >= 2:
face_id = faces[0][0]
print(f"✓ Testing similarity for face ID {face_id}")
matches = processor.find_similar_faces(face_id, tolerance=0.4)
print(f"✓ Found {len(matches)} similar faces (within tolerance)")
# Display match details
if matches:
for i, match in enumerate(matches[:3], 1): # Show top 3 matches
confidence_pct = (1 - match['distance']) * 100
print(f" Match {i}: Face {match['face_id']}, Confidence: {confidence_pct:.1f}%")
print("\n✅ PASS: Face matching working correctly")
return True
except Exception as e:
print(f"\n❌ FAIL: {e}")
import traceback
traceback.print_exc()
return False
def test_deepface_metadata():
"""Test 3: DeepFace metadata storage and retrieval"""
print("\n" + "="*60)
print("Test 3: DeepFace Metadata Storage")
print("="*60)
try:
db = DatabaseManager(":memory:", verbose=0)
processor = FaceProcessor(db, verbose=1)
# Test with a sample image
test_image = "demo_photos/2019-11-22_0011.jpg"
if not os.path.exists(test_image):
print(f"⚠️ Test image not found: {test_image}")
return True # Skip but don't fail
# Add photo and process
photo_id = db.add_photo(test_image, Path(test_image).name, None)
processor.process_faces(limit=1)
# Query face metadata
with db.get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT face_confidence, quality_score, detector_backend, model_name
FROM faces
LIMIT 1
""")
result = cursor.fetchone()
if not result:
print("❌ FAIL: No face metadata found")
return False
face_conf, quality, detector, model = result
print(f"✓ Face Confidence: {face_conf}")
print(f"✓ Quality Score: {quality}")
print(f"✓ Detector Backend: {detector}")
print(f"✓ Model Name: {model}")
# Verify metadata is present
if detector is None:
print("❌ FAIL: Detector backend not stored")
return False
if model is None:
print("❌ FAIL: Model name not stored")
return False
# Verify detector matches configuration
if detector != DEEPFACE_DETECTOR_BACKEND:
print(f"⚠️ Warning: Detector mismatch (expected {DEEPFACE_DETECTOR_BACKEND}, got {detector})")
# Verify model matches configuration
if model != DEEPFACE_MODEL_NAME:
print(f"⚠️ Warning: Model mismatch (expected {DEEPFACE_MODEL_NAME}, got {model})")
print("\n✅ PASS: DeepFace metadata stored correctly")
return True
except Exception as e:
print(f"\n❌ FAIL: {e}")
import traceback
traceback.print_exc()
return False
def test_configuration():
"""Test 4: FaceProcessor configuration with different backends"""
print("\n" + "="*60)
print("Test 4: FaceProcessor Configuration")
print("="*60)
try:
db = DatabaseManager(":memory:", verbose=0)
# Test default configuration
processor_default = FaceProcessor(db, verbose=0)
print(f"✓ Default detector: {processor_default.detector_backend}")
print(f"✓ Default model: {processor_default.model_name}")
if processor_default.detector_backend != DEEPFACE_DETECTOR_BACKEND:
print(f"❌ FAIL: Default detector mismatch")
return False
if processor_default.model_name != DEEPFACE_MODEL_NAME:
print(f"❌ FAIL: Default model mismatch")
return False
# Test custom configuration
custom_configs = [
('mtcnn', 'Facenet512'),
('opencv', 'VGG-Face'),
('ssd', 'ArcFace'),
]
for detector, model in custom_configs:
processor = FaceProcessor(db, verbose=0,
detector_backend=detector,
model_name=model)
print(f"✓ Custom config: {detector}/{model}")
if processor.detector_backend != detector:
print(f"❌ FAIL: Custom detector not applied")
return False
if processor.model_name != model:
print(f"❌ FAIL: Custom model not applied")
return False
print("\n✅ PASS: FaceProcessor configuration working correctly")
return True
except Exception as e:
print(f"\n❌ FAIL: {e}")
import traceback
traceback.print_exc()
return False
def test_cosine_similarity():
"""Test 5: Cosine similarity calculation"""
print("\n" + "="*60)
print("Test 5: Cosine Similarity Calculation")
print("="*60)
try:
import numpy as np
db = DatabaseManager(":memory:", verbose=0)
processor = FaceProcessor(db, verbose=0)
# Test with identical encodings
encoding1 = np.random.rand(512).astype(np.float64)
encoding2 = encoding1.copy()
distance = processor._calculate_cosine_similarity(encoding1, encoding2)
print(f"✓ Identical encodings distance: {distance:.6f}")
if distance > 0.01: # Should be very close to 0
print(f"❌ FAIL: Identical encodings should have distance near 0")
return False
# Test with different encodings
encoding3 = np.random.rand(512).astype(np.float64)
distance2 = processor._calculate_cosine_similarity(encoding1, encoding3)
print(f"✓ Different encodings distance: {distance2:.6f}")
if distance2 < 0.1: # Should be significantly different
print(f"⚠️ Warning: Random encodings have low distance (might be coincidence)")
# Test with mismatched lengths
encoding4 = np.random.rand(128).astype(np.float64)
distance3 = processor._calculate_cosine_similarity(encoding1, encoding4)
print(f"✓ Mismatched lengths distance: {distance3:.6f}")
if distance3 != 2.0: # Should return max distance
print(f"❌ FAIL: Mismatched lengths should return 2.0")
return False
print("\n✅ PASS: Cosine similarity calculation working correctly")
return True
except Exception as e:
print(f"\n❌ FAIL: {e}")
import traceback
traceback.print_exc()
return False
def test_database_schema():
"""Test 6: Database schema validation"""
print("\n" + "="*60)
print("Test 6: Database Schema Validation")
print("="*60)
try:
db = DatabaseManager(":memory:", verbose=0)
# Check if new DeepFace columns exist
with db.get_db_connection() as conn:
cursor = conn.cursor()
# Get faces table schema
cursor.execute("PRAGMA table_info(faces)")
columns = {row[1]: row[2] for row in cursor.fetchall()}
print("✓ Faces table columns:")
for col_name in columns:
print(f" - {col_name}")
# Verify DeepFace columns
required_columns = {
'detector_backend': 'TEXT',
'model_name': 'TEXT',
'face_confidence': 'REAL'
}
for col, dtype in required_columns.items():
if col not in columns:
print(f"❌ FAIL: Missing column '{col}' in faces table")
return False
print(f"✓ Column '{col}' exists with type {columns[col]}")
# Check person_encodings table
cursor.execute("PRAGMA table_info(person_encodings)")
pe_columns = {row[1]: row[2] for row in cursor.fetchall()}
print("\n✓ Person_encodings table columns:")
for col_name in pe_columns:
print(f" - {col_name}")
# Verify DeepFace columns in person_encodings
pe_required = {
'detector_backend': 'TEXT',
'model_name': 'TEXT',
}
for col, dtype in pe_required.items():
if col not in pe_columns:
print(f"❌ FAIL: Missing column '{col}' in person_encodings table")
return False
print(f"✓ Column '{col}' exists in person_encodings")
print("\n✅ PASS: Database schema is correct")
return True
except Exception as e:
print(f"\n❌ FAIL: {e}")
import traceback
traceback.print_exc()
return False
def test_face_location_format():
"""Test 7: Face location format validation"""
print("\n" + "="*60)
print("Test 7: Face Location Format")
print("="*60)
try:
import ast
db = DatabaseManager(":memory:", verbose=0)
processor = FaceProcessor(db, verbose=1)
# Test with a sample image
test_image = "demo_photos/2019-11-22_0011.jpg"
if not os.path.exists(test_image):
print(f"⚠️ Test image not found: {test_image}")
return True # Skip but don't fail
# Add photo and process
photo_id = db.add_photo(test_image, Path(test_image).name, None)
processor.process_faces(limit=1)
# Check face location format
with db.get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute("SELECT location FROM faces LIMIT 1")
result = cursor.fetchone()
if not result:
print("⚠️ No faces found")
return True
location_str = result[0]
print(f"✓ Raw location: {location_str}")
# Parse location
try:
location = ast.literal_eval(location_str)
print(f"✓ Parsed location: {location}")
# Check if it's DeepFace format (dict with x, y, w, h)
if isinstance(location, dict):
required_keys = ['x', 'y', 'w', 'h']
for key in required_keys:
if key not in location:
print(f"❌ FAIL: Missing key '{key}' in location dict")
return False
print("✓ Location is in DeepFace dict format {x, y, w, h}")
else:
print(f"❌ FAIL: Location is not a dict, got {type(location)}")
return False
except Exception as e:
print(f"❌ FAIL: Could not parse location: {e}")
return False
print("\n✅ PASS: Face location format is correct")
return True
except Exception as e:
print(f"\n❌ FAIL: {e}")
import traceback
traceback.print_exc()
return False
def test_performance_benchmark():
"""Test 8: Performance benchmarking"""
print("\n" + "="*60)
print("Test 8: Performance Benchmark")
print("="*60)
try:
import time
db = DatabaseManager(":memory:", verbose=0)
processor = FaceProcessor(db, verbose=0)
# Test with multiple images
test_images = [
"demo_photos/2019-11-22_0011.jpg",
"demo_photos/2019-11-22_0012.jpg",
"demo_photos/2019-11-22_0015.jpg",
]
available_images = [img for img in test_images if os.path.exists(img)]
if not available_images:
print("⚠️ No test images available")
return True # Skip but don't fail
print(f"Testing with {len(available_images)} images")
# Add photos to database
for img in available_images:
db.add_photo(img, Path(img).name, None)
# Benchmark face detection
start_time = time.time()
count = processor.process_faces(limit=len(available_images))
detection_time = time.time() - start_time
print(f"✓ Processed {count} photos in {detection_time:.2f}s")
print(f"✓ Average time per photo: {detection_time/max(count, 1):.2f}s")
# Get statistics
stats = db.get_statistics()
total_faces = stats['total_faces']
print(f"✓ Found {total_faces} total faces")
if total_faces > 0:
print(f"✓ Average time per face: {detection_time/total_faces:.2f}s")
# Benchmark similarity calculation
if total_faces >= 2:
faces = db.get_all_face_encodings()
face_id = faces[0][0]
start_time = time.time()
matches = processor.find_similar_faces(face_id, tolerance=0.4)
matching_time = time.time() - start_time
print(f"✓ Similarity search completed in {matching_time:.2f}s")
print(f"✓ Found {len(matches)} matches")
print("\n✅ PASS: Performance benchmark completed")
return True
except Exception as e:
print(f"\n❌ FAIL: {e}")
import traceback
traceback.print_exc()
return False
def test_adaptive_tolerance():
"""Test 9: Adaptive tolerance calculation"""
print("\n" + "="*60)
print("Test 9: Adaptive Tolerance")
print("="*60)
try:
db = DatabaseManager(":memory:", verbose=0)
processor = FaceProcessor(db, verbose=0)
# Test with different quality scores
base_tolerance = 0.4
test_cases = [
(0.1, "Low quality"),
(0.5, "Medium quality"),
(0.9, "High quality"),
]
print(f"Base tolerance: {base_tolerance}")
for quality, desc in test_cases:
tolerance = processor._calculate_adaptive_tolerance(base_tolerance, quality)
print(f"{desc} ({quality:.1f}): tolerance = {tolerance:.3f}")
# Verify tolerance is within bounds
if tolerance < 0.2 or tolerance > 0.6:
print(f"❌ FAIL: Tolerance {tolerance} out of bounds [0.2, 0.6]")
return False
# Test with match confidence
tolerance_with_conf = processor._calculate_adaptive_tolerance(
base_tolerance, 0.7, match_confidence=0.8
)
print(f"✓ With match confidence: tolerance = {tolerance_with_conf:.3f}")
print("\n✅ PASS: Adaptive tolerance working correctly")
return True
except Exception as e:
print(f"\n❌ FAIL: {e}")
import traceback
traceback.print_exc()
return False
def test_multiple_detectors():
"""Test 10: Multiple detector backends"""
print("\n" + "="*60)
print("Test 10: Multiple Detector Backends")
print("="*60)
try:
# Test different detector backends
detectors = ['opencv', 'ssd'] # Skip retinaface and mtcnn for speed
test_image = "demo_photos/2019-11-22_0011.jpg"
if not os.path.exists(test_image):
print("⚠️ Test image not found")
return True # Skip but don't fail
results = {}
for detector in detectors:
print(f"\n Testing with {detector} detector:")
try:
db = DatabaseManager(":memory:", verbose=0)
processor = FaceProcessor(db, verbose=0,
detector_backend=detector,
model_name='ArcFace')
photo_id = db.add_photo(test_image, Path(test_image).name, None)
count = processor.process_faces(limit=1)
stats = db.get_statistics()
faces_found = stats['total_faces']
results[detector] = faces_found
print(f"{detector}: Found {faces_found} faces")
except Exception as e:
print(f"⚠️ {detector} failed: {e}")
results[detector] = 0
# Verify at least one detector worked
if sum(results.values()) == 0:
print("\n❌ FAIL: No detectors found any faces")
return False
print("\n✅ PASS: Multiple detectors tested")
return True
except Exception as e:
print(f"\n❌ FAIL: {e}")
import traceback
traceback.print_exc()
return False
def run_all_tests():
"""Run all DeepFace integration tests"""
print("\n" + "="*70)
print("DEEPFACE INTEGRATION TEST SUITE - PHASE 6")
print("="*70)
print()
print("Testing complete DeepFace integration in PunimTag")
print("This comprehensive test suite validates all aspects of the migration")
print()
tests = [
("Face Detection", test_face_detection),
("Face Matching", test_face_matching),
("Metadata Storage", test_deepface_metadata),
("Configuration", test_configuration),
("Cosine Similarity", test_cosine_similarity),
("Database Schema", test_database_schema),
("Face Location Format", test_face_location_format),
("Performance Benchmark", test_performance_benchmark),
("Adaptive Tolerance", test_adaptive_tolerance),
("Multiple Detectors", test_multiple_detectors),
]
results = []
for test_name, test_func in tests:
try:
result = test_func()
results.append((test_name, result))
except Exception as e:
print(f"\n❌ Test '{test_name}' crashed: {e}")
import traceback
traceback.print_exc()
results.append((test_name, False))
# Print summary
print("\n" + "="*70)
print("TEST SUMMARY")
print("="*70)
passed = 0
failed = 0
for test_name, result in results:
status = "✅ PASS" if result else "❌ FAIL"
print(f"{status}: {test_name}")
if result:
passed += 1
else:
failed += 1
print("="*70)
print(f"Tests passed: {passed}/{len(tests)}")
print(f"Tests failed: {failed}/{len(tests)}")
print("="*70)
if failed == 0:
print("\n🎉 ALL TESTS PASSED! DeepFace integration is working correctly!")
return 0
else:
print(f"\n⚠️ {failed} test(s) failed. Please review the errors above.")
return 1
if __name__ == "__main__":
sys.exit(run_all_tests())
-329
View File
@@ -1,329 +0,0 @@
#!/usr/bin/env python3
"""
Test Phase 1: Database Schema Updates for DeepFace Migration
This test verifies that:
1. Database schema includes new DeepFace columns
2. Method signatures accept new parameters
3. Data can be inserted with DeepFace-specific fields
"""
import os
import sys
import sqlite3
import tempfile
from pathlib import Path
# Add parent directory to path
sys.path.insert(0, str(Path(__file__).parent.parent))
from src.core.database import DatabaseManager
from src.core.config import (
DEEPFACE_DETECTOR_BACKEND,
DEEPFACE_MODEL_NAME,
DEFAULT_FACE_TOLERANCE,
DEEPFACE_SIMILARITY_THRESHOLD
)
def test_schema_has_deepface_columns():
"""Test that database schema includes DeepFace columns"""
print("\n🧪 Test 1: Verify schema has DeepFace columns")
# Create temporary database
with tempfile.NamedTemporaryFile(delete=False, suffix='.db') as tmp:
tmp_db_path = tmp.name
try:
# Initialize database
db = DatabaseManager(tmp_db_path, verbose=0)
# Connect and check schema
conn = sqlite3.connect(tmp_db_path)
cursor = conn.cursor()
# Check faces table
cursor.execute("PRAGMA table_info(faces)")
faces_columns = {row[1]: row[2] for row in cursor.fetchall()}
required_columns = {
'detector_backend': 'TEXT',
'model_name': 'TEXT',
'face_confidence': 'REAL'
}
print(" Checking 'faces' table columns:")
for col_name, col_type in required_columns.items():
if col_name in faces_columns:
print(f"{col_name} ({faces_columns[col_name]})")
else:
print(f"{col_name} - MISSING!")
return False
# Check person_encodings table
cursor.execute("PRAGMA table_info(person_encodings)")
pe_columns = {row[1]: row[2] for row in cursor.fetchall()}
required_pe_columns = {
'detector_backend': 'TEXT',
'model_name': 'TEXT'
}
print(" Checking 'person_encodings' table columns:")
for col_name, col_type in required_pe_columns.items():
if col_name in pe_columns:
print(f"{col_name} ({pe_columns[col_name]})")
else:
print(f"{col_name} - MISSING!")
return False
conn.close()
print(" ✅ All schema columns present")
return True
finally:
# Cleanup
if os.path.exists(tmp_db_path):
os.unlink(tmp_db_path)
def test_add_face_with_deepface_params():
"""Test that add_face() accepts DeepFace parameters"""
print("\n🧪 Test 2: Test add_face() with DeepFace parameters")
# Create temporary database
with tempfile.NamedTemporaryFile(delete=False, suffix='.db') as tmp:
tmp_db_path = tmp.name
try:
# Initialize database
db = DatabaseManager(tmp_db_path, verbose=0)
# Add a test photo
photo_id = db.add_photo(
photo_path="/test/photo.jpg",
filename="photo.jpg",
date_taken="2025-10-16"
)
if not photo_id:
print(" ❌ Failed to add photo")
return False
print(f" ✓ Added test photo (ID: {photo_id})")
# Create dummy 512-dimensional encoding (ArcFace)
import numpy as np
dummy_encoding = np.random.rand(512).astype(np.float64)
encoding_bytes = dummy_encoding.tobytes()
# Add face with DeepFace parameters
face_id = db.add_face(
photo_id=photo_id,
encoding=encoding_bytes,
location="{'x': 100, 'y': 150, 'w': 200, 'h': 200}",
confidence=0.0,
quality_score=0.85,
person_id=None,
detector_backend='retinaface',
model_name='ArcFace',
face_confidence=0.99
)
if not face_id:
print(" ❌ Failed to add face")
return False
print(f" ✓ Added face with DeepFace params (ID: {face_id})")
# Verify data was stored correctly
conn = sqlite3.connect(tmp_db_path)
cursor = conn.cursor()
cursor.execute('''
SELECT detector_backend, model_name, face_confidence, quality_score
FROM faces WHERE id = ?
''', (face_id,))
result = cursor.fetchone()
conn.close()
if not result:
print(" ❌ Face data not found in database")
return False
detector, model, face_conf, quality = result
print(f" ✓ Verified stored data:")
print(f" - detector_backend: {detector}")
print(f" - model_name: {model}")
print(f" - face_confidence: {face_conf}")
print(f" - quality_score: {quality}")
if detector != 'retinaface' or model != 'ArcFace' or face_conf != 0.99:
print(" ❌ Stored data doesn't match input")
return False
print(" ✅ add_face() works with DeepFace parameters")
return True
finally:
# Cleanup
if os.path.exists(tmp_db_path):
os.unlink(tmp_db_path)
def test_add_person_encoding_with_deepface_params():
"""Test that add_person_encoding() accepts DeepFace parameters"""
print("\n🧪 Test 3: Test add_person_encoding() with DeepFace parameters")
# Create temporary database
with tempfile.NamedTemporaryFile(delete=False, suffix='.db') as tmp:
tmp_db_path = tmp.name
try:
# Initialize database
db = DatabaseManager(tmp_db_path, verbose=0)
# Add a test person
person_id = db.add_person(
first_name="Test",
last_name="Person",
middle_name="",
maiden_name="",
date_of_birth=""
)
print(f" ✓ Added test person (ID: {person_id})")
# Add a test photo and face
photo_id = db.add_photo("/test/photo.jpg", "photo.jpg")
import numpy as np
dummy_encoding = np.random.rand(512).astype(np.float64)
encoding_bytes = dummy_encoding.tobytes()
face_id = db.add_face(
photo_id=photo_id,
encoding=encoding_bytes,
location="{'x': 100, 'y': 150, 'w': 200, 'h': 200}",
quality_score=0.85,
detector_backend='retinaface',
model_name='ArcFace'
)
print(f" ✓ Added test face (ID: {face_id})")
# Add person encoding with DeepFace parameters
db.add_person_encoding(
person_id=person_id,
face_id=face_id,
encoding=encoding_bytes,
quality_score=0.85,
detector_backend='retinaface',
model_name='ArcFace'
)
# Verify data was stored
conn = sqlite3.connect(tmp_db_path)
cursor = conn.cursor()
cursor.execute('''
SELECT detector_backend, model_name, quality_score
FROM person_encodings WHERE person_id = ? AND face_id = ?
''', (person_id, face_id))
result = cursor.fetchone()
conn.close()
if not result:
print(" ❌ Person encoding not found in database")
return False
detector, model, quality = result
print(f" ✓ Verified stored data:")
print(f" - detector_backend: {detector}")
print(f" - model_name: {model}")
print(f" - quality_score: {quality}")
if detector != 'retinaface' or model != 'ArcFace':
print(" ❌ Stored data doesn't match input")
return False
print(" ✅ add_person_encoding() works with DeepFace parameters")
return True
finally:
# Cleanup
if os.path.exists(tmp_db_path):
os.unlink(tmp_db_path)
def test_config_constants():
"""Test that config.py has DeepFace constants"""
print("\n🧪 Test 4: Verify DeepFace configuration constants")
print(f" ✓ DEEPFACE_DETECTOR_BACKEND = {DEEPFACE_DETECTOR_BACKEND}")
print(f" ✓ DEEPFACE_MODEL_NAME = {DEEPFACE_MODEL_NAME}")
print(f" ✓ DEFAULT_FACE_TOLERANCE = {DEFAULT_FACE_TOLERANCE}")
print(f" ✓ DEEPFACE_SIMILARITY_THRESHOLD = {DEEPFACE_SIMILARITY_THRESHOLD}")
if DEEPFACE_DETECTOR_BACKEND != 'retinaface':
print(f" ⚠️ Warning: Expected detector 'retinaface', got '{DEEPFACE_DETECTOR_BACKEND}'")
if DEEPFACE_MODEL_NAME != 'ArcFace':
print(f" ⚠️ Warning: Expected model 'ArcFace', got '{DEEPFACE_MODEL_NAME}'")
if DEFAULT_FACE_TOLERANCE != 0.4:
print(f" ⚠️ Warning: Expected tolerance 0.4, got {DEFAULT_FACE_TOLERANCE}")
print(" ✅ Configuration constants loaded")
return True
def run_all_tests():
"""Run all Phase 1 tests"""
print("=" * 70)
print("Phase 1 Schema Tests - DeepFace Migration")
print("=" * 70)
tests = [
("Schema Columns", test_schema_has_deepface_columns),
("add_face() Method", test_add_face_with_deepface_params),
("add_person_encoding() Method", test_add_person_encoding_with_deepface_params),
("Config Constants", test_config_constants)
]
results = []
for test_name, test_func in tests:
try:
result = test_func()
results.append((test_name, result))
except Exception as e:
print(f" ❌ Test failed with exception: {e}")
import traceback
traceback.print_exc()
results.append((test_name, False))
print("\n" + "=" * 70)
print("Test Results Summary")
print("=" * 70)
for test_name, result in results:
status = "✅ PASS" if result else "❌ FAIL"
print(f" {status}: {test_name}")
passed = sum(1 for _, result in results if result)
total = len(results)
print()
print(f"Tests passed: {passed}/{total}")
print("=" * 70)
return all(result for _, result in results)
if __name__ == "__main__":
success = run_all_tests()
sys.exit(0 if success else 1)
-232
View File
@@ -1,232 +0,0 @@
#!/usr/bin/env python3
"""
Test Phase 2: Configuration Updates for DeepFace Migration
This test verifies that:
1. TensorFlow suppression is in place
2. FaceProcessor accepts detector_backend and model_name
3. Configuration constants are accessible
4. Entry points properly suppress warnings
"""
import os
import sys
from pathlib import Path
# Add parent directory to path
sys.path.insert(0, str(Path(__file__).parent.parent))
def test_tensorflow_suppression():
"""Test that TensorFlow warnings are suppressed"""
print("\n🧪 Test 1: Verify TensorFlow suppression in config")
# Import config which sets the environment variable
from src.core import config
# Check environment variable is set (config.py sets it on import)
tf_log_level = os.environ.get('TF_CPP_MIN_LOG_LEVEL')
if tf_log_level == '3':
print(" ✓ TF_CPP_MIN_LOG_LEVEL = 3 (suppressed by config.py)")
print(" ✓ Entry points also set this before imports")
return True
else:
print(f" ❌ TF_CPP_MIN_LOG_LEVEL = {tf_log_level} (expected '3')")
return False
def test_faceprocessor_initialization():
"""Test that FaceProcessor accepts DeepFace parameters"""
print("\n🧪 Test 2: Test FaceProcessor with DeepFace parameters")
import tempfile
from src.core.database import DatabaseManager
from src.core.face_processing import FaceProcessor
try:
# Create temporary database
with tempfile.NamedTemporaryFile(delete=False, suffix='.db') as tmp:
tmp_db_path = tmp.name
# Initialize database and face processor
db = DatabaseManager(tmp_db_path, verbose=0)
# Test with custom detector and model
processor = FaceProcessor(
db,
verbose=0,
detector_backend='mtcnn',
model_name='Facenet'
)
print(f" ✓ FaceProcessor initialized")
print(f" - detector_backend: {processor.detector_backend}")
print(f" - model_name: {processor.model_name}")
if processor.detector_backend != 'mtcnn':
print(" ❌ Detector backend not set correctly")
return False
if processor.model_name != 'Facenet':
print(" ❌ Model name not set correctly")
return False
# Test with defaults
processor2 = FaceProcessor(db, verbose=0)
print(f" ✓ FaceProcessor with defaults:")
print(f" - detector_backend: {processor2.detector_backend}")
print(f" - model_name: {processor2.model_name}")
# Cleanup
if os.path.exists(tmp_db_path):
os.unlink(tmp_db_path)
print(" ✅ FaceProcessor accepts and uses DeepFace parameters")
return True
except Exception as e:
print(f" ❌ Error: {e}")
import traceback
traceback.print_exc()
return False
def test_config_imports():
"""Test that all DeepFace config constants can be imported"""
print("\n🧪 Test 3: Test configuration imports")
try:
from src.core.config import (
DEEPFACE_DETECTOR_BACKEND,
DEEPFACE_MODEL_NAME,
DEEPFACE_DETECTOR_OPTIONS,
DEEPFACE_MODEL_OPTIONS,
DEEPFACE_DISTANCE_METRIC,
DEEPFACE_ENFORCE_DETECTION,
DEEPFACE_ALIGN_FACES,
DEEPFACE_SIMILARITY_THRESHOLD
)
print(" ✓ All DeepFace config constants imported:")
print(f" - DEEPFACE_DETECTOR_BACKEND = {DEEPFACE_DETECTOR_BACKEND}")
print(f" - DEEPFACE_MODEL_NAME = {DEEPFACE_MODEL_NAME}")
print(f" - DEEPFACE_DETECTOR_OPTIONS = {DEEPFACE_DETECTOR_OPTIONS}")
print(f" - DEEPFACE_MODEL_OPTIONS = {DEEPFACE_MODEL_OPTIONS}")
print(f" - DEEPFACE_DISTANCE_METRIC = {DEEPFACE_DISTANCE_METRIC}")
print(f" - DEEPFACE_ENFORCE_DETECTION = {DEEPFACE_ENFORCE_DETECTION}")
print(f" - DEEPFACE_ALIGN_FACES = {DEEPFACE_ALIGN_FACES}")
print(f" - DEEPFACE_SIMILARITY_THRESHOLD = {DEEPFACE_SIMILARITY_THRESHOLD}")
print(" ✅ All configuration constants accessible")
return True
except ImportError as e:
print(f" ❌ Failed to import config: {e}")
return False
def test_entry_point_imports():
"""Test that main entry points can be imported without errors"""
print("\n🧪 Test 4: Test entry point imports (with TF suppression)")
try:
# Desktop GUI has been archived - skip this test
print(" ⚠️ Desktop GUI entry points have been archived")
print(" ⚠️ Skipping desktop entry point import test")
print(" ✓ Web version entry points are available via API")
print(" ✅ Entry point test skipped (desktop archived)")
return True
except Exception as e:
print(f" ❌ Import error: {e}")
import traceback
traceback.print_exc()
return False
def test_gui_config_constants():
"""Test that GUI can access DeepFace options"""
print("\n🧪 Test 5: Test GUI access to DeepFace options")
try:
from src.core.config import DEEPFACE_DETECTOR_OPTIONS, DEEPFACE_MODEL_OPTIONS
# Verify options are lists
if not isinstance(DEEPFACE_DETECTOR_OPTIONS, list):
print(" ❌ DEEPFACE_DETECTOR_OPTIONS is not a list")
return False
if not isinstance(DEEPFACE_MODEL_OPTIONS, list):
print(" ❌ DEEPFACE_MODEL_OPTIONS is not a list")
return False
print(f" ✓ Detector options ({len(DEEPFACE_DETECTOR_OPTIONS)}): {DEEPFACE_DETECTOR_OPTIONS}")
print(f" ✓ Model options ({len(DEEPFACE_MODEL_OPTIONS)}): {DEEPFACE_MODEL_OPTIONS}")
# Verify expected values
expected_detectors = ["retinaface", "mtcnn", "opencv", "ssd"]
expected_models = ["ArcFace", "Facenet", "Facenet512", "VGG-Face"]
if set(DEEPFACE_DETECTOR_OPTIONS) != set(expected_detectors):
print(f" ⚠️ Detector options don't match expected: {expected_detectors}")
if set(DEEPFACE_MODEL_OPTIONS) != set(expected_models):
print(f" ⚠️ Model options don't match expected: {expected_models}")
print(" ✅ GUI can access DeepFace options for dropdowns")
return True
except Exception as e:
print(f" ❌ Error: {e}")
return False
def run_all_tests():
"""Run all Phase 2 tests"""
print("=" * 70)
print("Phase 2 Configuration Tests - DeepFace Migration")
print("=" * 70)
tests = [
("TensorFlow Suppression", test_tensorflow_suppression),
("FaceProcessor Initialization", test_faceprocessor_initialization),
("Config Imports", test_config_imports),
("Entry Point Imports", test_entry_point_imports),
("GUI Config Constants", test_gui_config_constants)
]
results = []
for test_name, test_func in tests:
try:
result = test_func()
results.append((test_name, result))
except Exception as e:
print(f" ❌ Test failed with exception: {e}")
import traceback
traceback.print_exc()
results.append((test_name, False))
print("\n" + "=" * 70)
print("Test Results Summary")
print("=" * 70)
for test_name, result in results:
status = "✅ PASS" if result else "❌ FAIL"
print(f" {status}: {test_name}")
passed = sum(1 for _, result in results if result)
total = len(results)
print()
print(f"Tests passed: {passed}/{total}")
print("=" * 70)
return all(result for _, result in results)
if __name__ == "__main__":
success = run_all_tests()
sys.exit(0 if success else 1)
-326
View File
@@ -1,326 +0,0 @@
#!/usr/bin/env python3
"""
Test Phase 3: Core Face Processing with DeepFace
This test verifies that:
1. DeepFace can be imported and used
2. Face detection works with DeepFace
3. Face encodings are 512-dimensional (ArcFace)
4. Cosine similarity calculation works
5. Location format handling works (dict vs tuple)
6. Full end-to-end processing works
"""
import os
import sys
import tempfile
import numpy as np
from pathlib import Path
# Suppress TensorFlow warnings
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
import warnings
warnings.filterwarnings('ignore')
# Add parent directory to path
sys.path.insert(0, str(Path(__file__).parent.parent))
def test_deepface_import():
"""Test that DeepFace can be imported"""
print("\n🧪 Test 1: DeepFace Import")
try:
from deepface import DeepFace
print(f" ✓ DeepFace imported successfully")
print(f" ✓ Version: {DeepFace.__version__ if hasattr(DeepFace, '__version__') else 'unknown'}")
return True
except ImportError as e:
print(f" ❌ Failed to import DeepFace: {e}")
return False
def test_deepface_detection():
"""Test DeepFace face detection"""
print("\n🧪 Test 2: DeepFace Face Detection")
try:
from deepface import DeepFace
# Check for test images
test_folder = Path("demo_photos/testdeepface")
if not test_folder.exists():
test_folder = Path("demo_photos")
test_images = list(test_folder.glob("*.jpg")) + list(test_folder.glob("*.JPG"))
if not test_images:
print(" ⚠️ No test images found, skipping")
return True
test_image = str(test_images[0])
print(f" Testing with: {Path(test_image).name}")
# Try to detect faces
results = DeepFace.represent(
img_path=test_image,
model_name='ArcFace',
detector_backend='retinaface',
enforce_detection=False,
align=True
)
if results:
print(f" ✓ Found {len(results)} face(s)")
# Check encoding dimensions
encoding = np.array(results[0]['embedding'])
print(f" ✓ Encoding shape: {encoding.shape}")
if len(encoding) == 512:
print(f" ✓ Correct encoding size (512-dimensional for ArcFace)")
else:
print(f" ⚠️ Unexpected encoding size: {len(encoding)}")
# Check facial_area format
facial_area = results[0].get('facial_area', {})
print(f" ✓ Facial area: {facial_area}")
if all(k in facial_area for k in ['x', 'y', 'w', 'h']):
print(f" ✓ Correct facial area format (x, y, w, h)")
else:
print(f" ⚠️ Unexpected facial area format")
return True
else:
print(f" ⚠️ No faces detected (image may have no faces)")
return True # Not a failure, just no faces
except Exception as e:
print(f" ❌ Error: {e}")
import traceback
traceback.print_exc()
return False
def test_cosine_similarity():
"""Test cosine similarity calculation"""
print("\n🧪 Test 3: Cosine Similarity Calculation")
try:
from src.core.database import DatabaseManager
from src.core.face_processing import FaceProcessor
# Create temporary database
with tempfile.NamedTemporaryFile(delete=False, suffix='.db') as tmp:
tmp_db_path = tmp.name
db = DatabaseManager(tmp_db_path, verbose=0)
processor = FaceProcessor(db, verbose=0)
# Test with identical encodings
enc1 = np.random.rand(512)
distance_identical = processor._calculate_cosine_similarity(enc1, enc1)
print(f" ✓ Identical encodings distance: {distance_identical:.6f}")
if distance_identical < 0.01: # Should be very close to 0
print(f" ✓ Identical encodings produce near-zero distance")
else:
print(f" ⚠️ Identical encodings distance higher than expected")
# Test with different encodings
enc2 = np.random.rand(512)
distance_different = processor._calculate_cosine_similarity(enc1, enc2)
print(f" ✓ Different encodings distance: {distance_different:.6f}")
if 0 < distance_different < 2: # Should be in valid range
print(f" ✓ Different encodings produce valid distance")
else:
print(f" ⚠️ Distance out of expected range [0, 2]")
# Test with length mismatch
enc3 = np.random.rand(128) # Different length
distance_mismatch = processor._calculate_cosine_similarity(enc1, enc3)
print(f" ✓ Mismatched length distance: {distance_mismatch:.6f}")
if distance_mismatch == 2.0: # Should return max distance
print(f" ✓ Mismatched lengths handled correctly")
else:
print(f" ⚠️ Mismatch handling unexpected")
# Cleanup
if os.path.exists(tmp_db_path):
os.unlink(tmp_db_path)
print(" ✅ Cosine similarity calculation works correctly")
return True
except Exception as e:
print(f" ❌ Error: {e}")
import traceback
traceback.print_exc()
return False
def test_location_format_handling():
"""Test handling of both dict and tuple location formats"""
print("\n🧪 Test 4: Location Format Handling")
try:
# Test dict format (DeepFace)
location_dict = {'x': 100, 'y': 150, 'w': 200, 'h': 200}
location_str_dict = str(location_dict)
import ast
parsed_dict = ast.literal_eval(location_str_dict)
if isinstance(parsed_dict, dict):
left = parsed_dict.get('x', 0)
top = parsed_dict.get('y', 0)
width = parsed_dict.get('w', 0)
height = parsed_dict.get('h', 0)
right = left + width
bottom = top + height
print(f" ✓ Dict format parsed: {location_dict}")
print(f" ✓ Converted to box: top={top}, right={right}, bottom={bottom}, left={left}")
if (left == 100 and top == 150 and right == 300 and bottom == 350):
print(f" ✓ Dict conversion correct")
else:
print(f" ❌ Dict conversion incorrect")
return False
# Legacy tuple format tests removed - only DeepFace format supported
print(" ✅ DeepFace location format handled correctly")
return True
except Exception as e:
print(f" ❌ Error: {e}")
import traceback
traceback.print_exc()
return False
def test_end_to_end_processing():
"""Test end-to-end face processing with DeepFace"""
print("\n🧪 Test 5: End-to-End Processing")
try:
from src.core.database import DatabaseManager
from src.core.face_processing import FaceProcessor
# Check for test images
test_folder = Path("demo_photos/testdeepface")
if not test_folder.exists():
test_folder = Path("demo_photos")
test_images = list(test_folder.glob("*.jpg")) + list(test_folder.glob("*.JPG"))
if not test_images:
print(" ⚠️ No test images found, skipping")
return True
# Create temporary database
with tempfile.NamedTemporaryFile(delete=False, suffix='.db') as tmp:
tmp_db_path = tmp.name
db = DatabaseManager(tmp_db_path, verbose=0)
processor = FaceProcessor(db, verbose=1,
detector_backend='retinaface',
model_name='ArcFace')
# Add a test photo
test_image = str(test_images[0])
photo_id = db.add_photo(test_image, Path(test_image).name, None)
if not photo_id:
print(f" ❌ Failed to add photo")
return False
print(f" ✓ Added test photo (ID: {photo_id})")
# Process faces
print(f" Processing faces...")
count = processor.process_faces(limit=1)
print(f" ✓ Processed {count} photo(s)")
# Verify results
stats = db.get_statistics()
print(f" ✓ Statistics: {stats['total_faces']} faces found")
if stats['total_faces'] > 0:
# Check encoding size
faces = db.get_all_face_encodings()
if faces:
face_id, encoding_bytes, person_id, quality = faces[0]
encoding = np.frombuffer(encoding_bytes, dtype=np.float64)
print(f" ✓ Encoding size: {len(encoding)} dimensions")
if len(encoding) == 512:
print(f" ✅ Correct encoding size (512-dim ArcFace)")
else:
print(f" ⚠️ Unexpected encoding size: {len(encoding)}")
# Cleanup
if os.path.exists(tmp_db_path):
os.unlink(tmp_db_path)
print(" ✅ End-to-end processing successful")
return True
except Exception as e:
print(f" ❌ Error: {e}")
import traceback
traceback.print_exc()
return False
def run_all_tests():
"""Run all Phase 3 tests"""
print("=" * 70)
print("Phase 3 DeepFace Integration Tests")
print("=" * 70)
tests = [
("DeepFace Import", test_deepface_import),
("DeepFace Detection", test_deepface_detection),
("Cosine Similarity", test_cosine_similarity),
("Location Format Handling", test_location_format_handling),
("End-to-End Processing", test_end_to_end_processing)
]
results = []
for test_name, test_func in tests:
try:
result = test_func()
results.append((test_name, result))
except Exception as e:
print(f" ❌ Test failed with exception: {e}")
import traceback
traceback.print_exc()
results.append((test_name, False))
print("\n" + "=" * 70)
print("Test Results Summary")
print("=" * 70)
for test_name, result in results:
status = "✅ PASS" if result else "❌ FAIL"
print(f" {status}: {test_name}")
passed = sum(1 for _, result in results if result)
total = len(results)
print()
print(f"Tests passed: {passed}/{total}")
print("=" * 70)
return all(result for _, result in results)
if __name__ == "__main__":
success = run_all_tests()
sys.exit(0 if success else 1)
-427
View File
@@ -1,427 +0,0 @@
#!/usr/bin/env python3
"""
Phase 4 Integration Test: GUI Updates for DeepFace
Tests that all GUI panels correctly handle DeepFace metadata and location formats
"""
import os
import sys
import tempfile
import sqlite3
from pathlib import Path
# Add parent directory to path
sys.path.insert(0, str(Path(__file__).parent.parent))
# Suppress TensorFlow warnings
os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
import warnings
warnings.filterwarnings('ignore')
from src.core.database import DatabaseManager
from src.core.face_processing import FaceProcessor
from src.core.config import DEEPFACE_DETECTOR_BACKEND, DEEPFACE_MODEL_NAME
def test_database_schema():
"""Test 1: Verify database schema has DeepFace columns"""
print("\n" + "="*60)
print("Test 1: Database Schema with DeepFace Columns")
print("="*60)
try:
# Create in-memory database
db = DatabaseManager(":memory:", verbose=0)
# Check faces table schema
with db.get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute("PRAGMA table_info(faces)")
columns = {row[1]: row[2] for row in cursor.fetchall()}
# Verify DeepFace columns exist
required_columns = {
'id': 'INTEGER',
'photo_id': 'INTEGER',
'person_id': 'INTEGER',
'encoding': 'BLOB',
'location': 'TEXT',
'confidence': 'REAL',
'quality_score': 'REAL',
'detector_backend': 'TEXT',
'model_name': 'TEXT',
'face_confidence': 'REAL'
}
missing_columns = []
for col_name, col_type in required_columns.items():
if col_name not in columns:
missing_columns.append(col_name)
else:
print(f"✓ Column '{col_name}' exists with type '{columns[col_name]}'")
if missing_columns:
print(f"\n❌ FAIL: Missing columns: {missing_columns}")
return False
print("\n✅ PASS: All DeepFace columns present in database schema")
return True
except Exception as e:
print(f"\n❌ FAIL: {e}")
import traceback
traceback.print_exc()
return False
def test_face_data_retrieval():
"""Test 2: Verify face data retrieval includes DeepFace metadata"""
print("\n" + "="*60)
print("Test 2: Face Data Retrieval with DeepFace Metadata")
print("="*60)
try:
# Create in-memory database
db = DatabaseManager(":memory:", verbose=0)
# Create a test photo
test_photo_path = "/tmp/test_photo.jpg"
photo_id = db.add_photo(test_photo_path, "test_photo.jpg", None)
# Create a test face with DeepFace metadata
import numpy as np
test_encoding = np.random.rand(512).astype(np.float64) # 512-dim for ArcFace
test_location = "{'x': 100, 'y': 100, 'w': 50, 'h': 50}"
face_id = db.add_face(
photo_id=photo_id,
encoding=test_encoding.tobytes(),
location=test_location,
confidence=0.0,
quality_score=0.85,
person_id=None,
detector_backend='retinaface',
model_name='ArcFace',
face_confidence=0.95
)
print(f"✓ Created test face with ID {face_id}")
# Query the face data (simulating GUI panel queries)
with db.get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT f.id, f.photo_id, p.path, p.filename, f.location,
f.face_confidence, f.quality_score, f.detector_backend, f.model_name
FROM faces f
JOIN photos p ON f.photo_id = p.id
WHERE f.id = ?
""", (face_id,))
result = cursor.fetchone()
if not result:
print("\n❌ FAIL: Could not retrieve face data")
return False
# Unpack the result (9 fields)
face_id_ret, photo_id_ret, path, filename, location, face_conf, quality, detector, model = result
print(f"✓ Retrieved face data:")
print(f" - Face ID: {face_id_ret}")
print(f" - Photo ID: {photo_id_ret}")
print(f" - Location: {location}")
print(f" - Face Confidence: {face_conf}")
print(f" - Quality Score: {quality}")
print(f" - Detector: {detector}")
print(f" - Model: {model}")
# Verify the metadata
if face_conf != 0.95:
print(f"\n❌ FAIL: Face confidence mismatch: expected 0.95, got {face_conf}")
return False
if quality != 0.85:
print(f"\n❌ FAIL: Quality score mismatch: expected 0.85, got {quality}")
return False
if detector != 'retinaface':
print(f"\n❌ FAIL: Detector mismatch: expected 'retinaface', got {detector}")
return False
if model != 'ArcFace':
print(f"\n❌ FAIL: Model mismatch: expected 'ArcFace', got {model}")
return False
print("\n✅ PASS: Face data retrieval includes all DeepFace metadata")
return True
except Exception as e:
print(f"\n❌ FAIL: {e}")
import traceback
traceback.print_exc()
return False
def test_location_format_handling():
"""Test 3: Verify both location formats are handled correctly"""
print("\n" + "="*60)
print("Test 3: Location Format Handling (Dict & Tuple)")
print("="*60)
try:
# Test both location formats
deepface_location = "{'x': 100, 'y': 150, 'w': 80, 'h': 90}"
legacy_location = "(150, 180, 240, 100)"
# Parse DeepFace dict format
import ast
deepface_loc = ast.literal_eval(deepface_location)
if not isinstance(deepface_loc, dict):
print(f"❌ FAIL: DeepFace location not parsed as dict")
return False
if 'x' not in deepface_loc or 'y' not in deepface_loc or 'w' not in deepface_loc or 'h' not in deepface_loc:
print(f"❌ FAIL: DeepFace location missing required keys")
return False
print(f"✓ DeepFace format parsed correctly: {deepface_loc}")
# Legacy tuple format tests removed - only DeepFace format supported
print(f"✓ DeepFace format is the only supported format")
print("\n✅ PASS: DeepFace location format handled correctly")
return True
except Exception as e:
print(f"\n❌ FAIL: {e}")
import traceback
traceback.print_exc()
return False
def test_face_processor_configuration():
"""Test 4: Verify FaceProcessor accepts DeepFace configuration"""
print("\n" + "="*60)
print("Test 4: FaceProcessor DeepFace Configuration")
print("="*60)
try:
# Create in-memory database
db = DatabaseManager(":memory:", verbose=0)
# Create FaceProcessor with default config
processor_default = FaceProcessor(db, verbose=0)
print(f"✓ Default detector: {processor_default.detector_backend}")
print(f"✓ Default model: {processor_default.model_name}")
if processor_default.detector_backend != DEEPFACE_DETECTOR_BACKEND:
print(f"❌ FAIL: Default detector mismatch")
return False
if processor_default.model_name != DEEPFACE_MODEL_NAME:
print(f"❌ FAIL: Default model mismatch")
return False
# Create FaceProcessor with custom config
processor_custom = FaceProcessor(db, verbose=0,
detector_backend='mtcnn',
model_name='Facenet512')
print(f"✓ Custom detector: {processor_custom.detector_backend}")
print(f"✓ Custom model: {processor_custom.model_name}")
if processor_custom.detector_backend != 'mtcnn':
print(f"❌ FAIL: Custom detector not applied")
return False
if processor_custom.model_name != 'Facenet512':
print(f"❌ FAIL: Custom model not applied")
return False
print("\n✅ PASS: FaceProcessor correctly configured with DeepFace settings")
return True
except Exception as e:
print(f"\n❌ FAIL: {e}")
import traceback
traceback.print_exc()
return False
def test_gui_panel_compatibility():
"""Test 5: Verify GUI panels can unpack face data correctly"""
print("\n" + "="*60)
print("Test 5: GUI Panel Data Unpacking")
print("="*60)
try:
# Create in-memory database
db = DatabaseManager(":memory:", verbose=0)
# Create test photo and face
test_photo_path = "/tmp/test_photo.jpg"
photo_id = db.add_photo(test_photo_path, "test_photo.jpg", None)
import numpy as np
test_encoding = np.random.rand(512).astype(np.float64)
test_location = "{'x': 100, 'y': 100, 'w': 50, 'h': 50}"
face_id = db.add_face(
photo_id=photo_id,
encoding=test_encoding.tobytes(),
location=test_location,
confidence=0.0,
quality_score=0.85,
person_id=None,
detector_backend='retinaface',
model_name='ArcFace',
face_confidence=0.95
)
# Simulate identify_panel query
with db.get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT f.id, f.photo_id, p.path, p.filename, f.location,
f.face_confidence, f.quality_score, f.detector_backend, f.model_name
FROM faces f
JOIN photos p ON f.photo_id = p.id
WHERE f.person_id IS NULL
""")
faces = cursor.fetchall()
if not faces:
print("❌ FAIL: No faces retrieved")
return False
# Simulate unpacking in identify_panel
for face_tuple in faces:
face_id, photo_id, photo_path, filename, location, face_conf, quality, detector, model = face_tuple
print(f"✓ Unpacked identify_panel data:")
print(f" - Face ID: {face_id}")
print(f" - Photo ID: {photo_id}")
print(f" - Location: {location}")
print(f" - Face Confidence: {face_conf}")
print(f" - Quality: {quality}")
print(f" - Detector/Model: {detector}/{model}")
# Simulate auto_match_panel query
with db.get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT f.id, f.person_id, f.photo_id, f.location, p.filename, f.quality_score,
f.face_confidence, f.detector_backend, f.model_name
FROM faces f
JOIN photos p ON f.photo_id = p.id
""")
faces = cursor.fetchall()
# Simulate unpacking in auto_match_panel (uses tuple indexing)
for face in faces:
face_id = face[0]
person_id = face[1]
photo_id = face[2]
location = face[3]
filename = face[4]
quality = face[5]
face_conf = face[6]
detector = face[7]
model = face[8]
print(f"✓ Unpacked auto_match_panel data (tuple indexing):")
print(f" - Face ID: {face_id}")
print(f" - Quality: {quality}")
print(f" - Face Confidence: {face_conf}")
# Simulate modify_panel query
with db.get_db_connection() as conn:
cursor = conn.cursor()
cursor.execute("""
SELECT f.id, f.photo_id, p.path, p.filename, f.location,
f.face_confidence, f.quality_score, f.detector_backend, f.model_name
FROM faces f
JOIN photos p ON f.photo_id = p.id
""")
faces = cursor.fetchall()
# Simulate unpacking in modify_panel
for face_tuple in faces:
face_id, photo_id, photo_path, filename, location, face_conf, quality, detector, model = face_tuple
print(f"✓ Unpacked modify_panel data:")
print(f" - Face ID: {face_id}")
print(f" - Quality: {quality}")
print("\n✅ PASS: All GUI panels can correctly unpack face data")
return True
except Exception as e:
print(f"\n❌ FAIL: {e}")
import traceback
traceback.print_exc()
return False
def main():
"""Run all Phase 4 tests"""
print("\n" + "="*70)
print("PHASE 4 INTEGRATION TEST SUITE: GUI Updates for DeepFace")
print("="*70)
tests = [
("Database Schema", test_database_schema),
("Face Data Retrieval", test_face_data_retrieval),
("Location Format Handling", test_location_format_handling),
("FaceProcessor Configuration", test_face_processor_configuration),
("GUI Panel Compatibility", test_gui_panel_compatibility),
]
results = []
for test_name, test_func in tests:
try:
result = test_func()
results.append((test_name, result))
except Exception as e:
print(f"\n❌ Test '{test_name}' crashed: {e}")
import traceback
traceback.print_exc()
results.append((test_name, False))
# Print summary
print("\n" + "="*70)
print("TEST SUMMARY")
print("="*70)
passed = 0
failed = 0
for test_name, result in results:
status = "✅ PASS" if result else "❌ FAIL"
print(f"{status}: {test_name}")
if result:
passed += 1
else:
failed += 1
print("="*70)
print(f"Tests passed: {passed}/{len(tests)}")
print(f"Tests failed: {failed}/{len(tests)}")
print("="*70)
if failed == 0:
print("\n🎉 ALL TESTS PASSED! Phase 4 GUI integration is complete!")
return 0
else:
print(f"\n⚠️ {failed} test(s) failed. Please review the errors above.")
return 1
if __name__ == "__main__":
sys.exit(main())