Update project documentation and structure; enhance README, finalize project reorganization, and improve testing standards.

This commit is contained in:
2025-09-03 17:07:17 -04:00
parent f4a83b3c40
commit 0e66b2253f
56 changed files with 1267 additions and 14713 deletions
-160
View File
@@ -1,160 +0,0 @@
#!/usr/bin/env python3
"""
Cleanup script for PunimTag project organization.
This script removes redundant test files and organizes the project structure.
"""
import os
import shutil
from pathlib import Path
def cleanup_old_tests():
"""Remove old test files that are now consolidated."""
old_test_files = [
"test_syntax_fix.html",
"test_js_validator.html",
"test_direct_error_check.html",
"test_console_tracker.html",
"test_syntax_check.html",
"test_progressive.html",
"test_simple_main.html",
"test_diagnostic.html",
"test_minimal.html",
"debug_ui.html",
"test_backend.py",
"test_punimtag.py",
"test_web_api.py"
]
removed_count = 0
for file_name in old_test_files:
file_path = Path(file_name)
if file_path.exists():
try:
file_path.unlink()
print(f"✅ Removed: {file_name}")
removed_count += 1
except Exception as e:
print(f"❌ Failed to remove {file_name}: {e}")
print(f"\n📊 Cleanup complete: {removed_count} files removed")
def verify_structure():
"""Verify that the new structure is properly organized."""
required_structure = {
"src/backend/app.py": "Main Flask application",
"src/backend/db_manager.py": "Database manager",
"src/backend/visual_identifier.py": "Face recognition",
"src/utils/tag_manager.py": "Tag management",
"config/settings.py": "Configuration settings",
"data/punimtag_simple.db": "Main database",
"tests/test_main.py": "Main test suite",
"docs/product.md": "Product vision",
"docs/structure.md": "Project structure",
"docs/tech.md": "Technical architecture",
"docs/api-standards.md": "API standards",
"docs/testing-standards.md": "Testing standards",
"docs/code-conventions.md": "Code conventions"
}
print("\n🔍 Verifying project structure:")
print("=" * 50)
all_good = True
for file_path, description in required_structure.items():
if Path(file_path).exists():
print(f"✅ {file_path} - {description}")
else:
print(f"❌ {file_path} - {description} (MISSING)")
all_good = False
return all_good
def create_gitignore():
"""Create or update .gitignore file."""
gitignore_content = """# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# Virtual environments
venv/
env/
ENV/
# Database files (keep structure, ignore content)
data/*.db
data/*.sqlite
# Temporary files
*.tmp
*.temp
temp_face_crop_*.jpg
# IDE
.vscode/
.idea/
*.swp
*.swo
# OS
.DS_Store
Thumbs.db
# Logs
*.log
# Environment variables
.env
"""
with open(".gitignore", "w") as f:
f.write(gitignore_content)
print("✅ Updated .gitignore file")
def main():
"""Main cleanup function."""
print("🧹 PunimTag Project Cleanup")
print("=" * 50)
# Clean up old test files
cleanup_old_tests()
# Verify structure
structure_ok = verify_structure()
# Update gitignore
create_gitignore()
print("\n" + "=" * 50)
if structure_ok:
print("🎉 Project cleanup completed successfully!")
print("\n📋 Next steps:")
print("1. Review the steering documents in docs/")
print("2. Run tests: python tests/test_main.py")
print("3. Start the app: python main.py")
else:
print("⚠️ Project cleanup completed with issues.")
print("Please check the missing files above.")
if __name__ == "__main__":
main()
-436
View File
@@ -1,436 +0,0 @@
#!/usr/bin/env python3
"""
Face Clustering GUI for PunimTag
Visual interface for viewing and identifying clustered unknown faces
"""
import tkinter as tk
from tkinter import ttk, messagebox, simpledialog
from PIL import Image, ImageTk
import os
import sqlite3
from typing import List, Dict
import pickle
import numpy as np
class FaceClusterGUI:
def __init__(self, db_path: str = 'punimtag_simple.db'):
self.db_path = db_path
self.root = tk.Tk()
self.root.title("PunimTag - Face Clustering")
self.root.geometry("1200x800")
# Current cluster data
self.clusters = []
self.current_cluster_index = 0
# Image cache
self.image_cache = {}
self.setup_ui()
self.load_clusters()
def setup_ui(self):
"""Setup the user interface"""
# Main frame
main_frame = ttk.Frame(self.root, padding="10")
main_frame.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Configure grid weights
self.root.columnconfigure(0, weight=1)
self.root.rowconfigure(0, weight=1)
main_frame.columnconfigure(1, weight=1)
main_frame.rowconfigure(1, weight=1)
# Title
title_label = ttk.Label(main_frame, text="Unknown Face Clusters",
font=('Arial', 16, 'bold'))
title_label.grid(row=0, column=0, columnspan=3, pady=(0, 10))
# Left panel - cluster list
left_frame = ttk.LabelFrame(main_frame, text="Clusters", padding="5")
left_frame.grid(row=1, column=0, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(0, 10))
# Cluster listbox
self.cluster_listbox = tk.Listbox(left_frame, width=30)
self.cluster_listbox.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
self.cluster_listbox.bind('<<ListboxSelect>>', self.on_cluster_select)
scrollbar = ttk.Scrollbar(left_frame, orient="vertical", command=self.cluster_listbox.yview)
scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
self.cluster_listbox.configure(yscrollcommand=scrollbar.set)
left_frame.columnconfigure(0, weight=1)
left_frame.rowconfigure(0, weight=1)
# Center panel - face display
center_frame = ttk.LabelFrame(main_frame, text="Faces in Cluster", padding="5")
center_frame.grid(row=1, column=1, sticky=(tk.W, tk.E, tk.N, tk.S))
# Canvas for face thumbnails
self.canvas = tk.Canvas(center_frame, bg='white')
self.canvas.grid(row=0, column=0, sticky=(tk.W, tk.E, tk.N, tk.S))
# Scrollbars for canvas
v_scrollbar = ttk.Scrollbar(center_frame, orient="vertical", command=self.canvas.yview)
v_scrollbar.grid(row=0, column=1, sticky=(tk.N, tk.S))
h_scrollbar = ttk.Scrollbar(center_frame, orient="horizontal", command=self.canvas.xview)
h_scrollbar.grid(row=1, column=0, sticky=(tk.W, tk.E))
self.canvas.configure(yscrollcommand=v_scrollbar.set, xscrollcommand=h_scrollbar.set)
center_frame.columnconfigure(0, weight=1)
center_frame.rowconfigure(0, weight=1)
# Right panel - actions
right_frame = ttk.LabelFrame(main_frame, text="Actions", padding="5")
right_frame.grid(row=1, column=2, sticky=(tk.W, tk.E, tk.N, tk.S), padx=(10, 0))
# Cluster info
self.info_label = ttk.Label(right_frame, text="Select a cluster", wraplength=200)
self.info_label.grid(row=0, column=0, pady=(0, 10), sticky=(tk.W, tk.E))
# Identify button
self.identify_button = ttk.Button(right_frame, text="Identify as Person",
command=self.identify_cluster, state='disabled')
self.identify_button.grid(row=1, column=0, pady=5, sticky=(tk.W, tk.E))
# Skip button
self.skip_button = ttk.Button(right_frame, text="Skip Cluster",
command=self.skip_cluster, state='disabled')
self.skip_button.grid(row=2, column=0, pady=5, sticky=(tk.W, tk.E))
# Refresh button
self.refresh_button = ttk.Button(right_frame, text="Refresh Clusters",
command=self.refresh_clusters)
self.refresh_button.grid(row=3, column=0, pady=5, sticky=(tk.W, tk.E))
# Statistics
self.stats_label = ttk.Label(right_frame, text="", wraplength=200)
self.stats_label.grid(row=4, column=0, pady=(20, 0), sticky=(tk.W, tk.E))
right_frame.columnconfigure(0, weight=1)
# Status bar
self.status_bar = ttk.Label(main_frame, text="Ready", relief=tk.SUNKEN)
self.status_bar.grid(row=2, column=0, columnspan=3, sticky=(tk.W, tk.E), pady=(10, 0))
def load_clusters(self):
"""Load face clusters from database"""
try:
self.status_bar.config(text="Loading clusters...")
self.root.update()
# Get clusters using simple clustering (without sklearn)
clusters = self.get_simple_clusters()
self.clusters = clusters
self.populate_cluster_list()
self.update_statistics()
self.status_bar.config(text=f"Loaded {len(clusters)} clusters")
except Exception as e:
messagebox.showerror("Error", f"Failed to load clusters: {e}")
self.status_bar.config(text="Error loading clusters")
def get_simple_clusters(self) -> List[Dict]:
"""Simple clustering without sklearn - group by face encoding similarity"""
if not os.path.exists(self.db_path):
return []
conn = sqlite3.connect(self.db_path)
c = conn.cursor()
try:
# Get unidentified faces
c.execute('''SELECT f.id, f.image_id, i.path, f.top, f.right, f.bottom, f.left, f.encoding
FROM faces f
JOIN images i ON f.image_id = i.id
WHERE f.person_id IS NULL''')
faces = c.fetchall()
if len(faces) < 2:
return []
# Simple clustering by face encoding similarity
clusters = []
used_faces = set()
for i, face1 in enumerate(faces):
if face1[0] in used_faces:
continue
cluster_faces = [face1]
used_faces.add(face1[0])
encoding1 = pickle.loads(face1[7])
# Find similar faces
for j, face2 in enumerate(faces[i+1:], i+1):
if face2[0] in used_faces:
continue
encoding2 = pickle.loads(face2[7])
# Calculate similarity (simple distance)
try:
import face_recognition
distance = face_recognition.face_distance([encoding1], encoding2)[0]
if distance < 0.5: # Similar faces
cluster_faces.append(face2)
used_faces.add(face2[0])
except:
# Fallback to numpy distance if face_recognition not available
distance = np.linalg.norm(encoding1 - encoding2)
if distance < 0.8:
cluster_faces.append(face2)
used_faces.add(face2[0])
# Only create cluster if it has multiple faces
if len(cluster_faces) >= 2:
cluster_data = {
'cluster_id': len(clusters),
'face_count': len(cluster_faces),
'faces': []
}
for face in cluster_faces:
cluster_data['faces'].append({
'face_id': face[0],
'image_id': face[1],
'image_path': face[2],
'location': (face[3], face[4], face[5], face[6])
})
clusters.append(cluster_data)
# Sort by face count (largest clusters first)
clusters.sort(key=lambda x: x['face_count'], reverse=True)
return clusters
except Exception as e:
print(f"Error in simple clustering: {e}")
return []
finally:
conn.close()
def populate_cluster_list(self):
"""Populate the cluster list"""
self.cluster_listbox.delete(0, tk.END)
for i, cluster in enumerate(self.clusters):
label = f"Cluster {i+1} ({cluster['face_count']} faces)"
self.cluster_listbox.insert(tk.END, label)
def on_cluster_select(self, event):
"""Handle cluster selection"""
selection = self.cluster_listbox.curselection()
if not selection:
return
self.current_cluster_index = selection[0]
self.display_cluster_faces()
self.identify_button.config(state='normal')
self.skip_button.config(state='normal')
def display_cluster_faces(self):
"""Display faces in the selected cluster"""
if not self.clusters or self.current_cluster_index >= len(self.clusters):
return
cluster = self.clusters[self.current_cluster_index]
# Update info
info_text = f"Cluster {self.current_cluster_index + 1}\n"
info_text += f"{cluster['face_count']} faces\n"
info_text += f"Click 'Identify as Person' to name these faces"
self.info_label.config(text=info_text)
# Clear canvas
self.canvas.delete("all")
# Display face thumbnails
x, y = 10, 10
max_width = 0
row_height = 0
for i, face in enumerate(cluster['faces']):
try:
thumbnail = self.get_face_thumbnail(face)
if thumbnail:
# Create image on canvas
image_id = self.canvas.create_image(x, y, anchor=tk.NW, image=thumbnail)
# Add image path as text below
filename = os.path.basename(face['image_path'])
self.canvas.create_text(x + 50, y + 110, text=filename,
width=100, font=('Arial', 8))
x += 120
max_width = max(max_width, x)
row_height = max(row_height, 130)
# New row after 4 images
if (i + 1) % 4 == 0:
x = 10
y += row_height
row_height = 0
except Exception as e:
print(f"Error displaying face thumbnail: {e}")
# Update canvas scroll region
self.canvas.configure(scrollregion=self.canvas.bbox("all"))
def get_face_thumbnail(self, face: Dict) -> ImageTk.PhotoImage:
"""Get thumbnail image of a face"""
cache_key = f"{face['face_id']}"
if cache_key in self.image_cache:
return self.image_cache[cache_key]
try:
# Load image
image_path = face['image_path']
if not os.path.exists(image_path):
return None
img = Image.open(image_path)
# Crop face region
top, right, bottom, left = face['location']
# Add some padding
padding = 20
left = max(0, left - padding)
top = max(0, top - padding)
right = min(img.width, right + padding)
bottom = min(img.height, bottom + padding)
face_img = img.crop((left, top, right, bottom))
# Resize to thumbnail
face_img.thumbnail((100, 100), Image.Resampling.LANCZOS)
# Convert to PhotoImage
photo = ImageTk.PhotoImage(face_img)
# Cache it
self.image_cache[cache_key] = photo
return photo
except Exception as e:
print(f"Error creating thumbnail: {e}")
return None
def identify_cluster(self):
"""Identify all faces in cluster as a person"""
if not self.clusters or self.current_cluster_index >= len(self.clusters):
return
cluster = self.clusters[self.current_cluster_index]
# Get person name
name = simpledialog.askstring("Identify Person",
f"Enter name for {cluster['face_count']} faces:")
if not name or not name.strip():
return
name = name.strip()
try:
# Add person to database and assign faces
conn = sqlite3.connect(self.db_path)
c = conn.cursor()
# Add person
c.execute('INSERT OR IGNORE INTO people (name) VALUES (?)', (name,))
c.execute('SELECT id FROM people WHERE name = ?', (name,))
person_id = c.fetchone()[0]
# Assign all faces in cluster
for face in cluster['faces']:
c.execute('''UPDATE faces
SET person_id = ?, is_confirmed = 1
WHERE id = ?''',
(person_id, face['face_id']))
conn.commit()
conn.close()
messagebox.showinfo("Success", f"Identified {cluster['face_count']} faces as {name}")
# Refresh clusters
self.refresh_clusters()
except Exception as e:
messagebox.showerror("Error", f"Failed to identify cluster: {e}")
def skip_cluster(self):
"""Skip current cluster"""
if self.current_cluster_index < len(self.clusters) - 1:
self.cluster_listbox.selection_set(self.current_cluster_index + 1)
self.on_cluster_select(None)
else:
messagebox.showinfo("Info", "This is the last cluster")
def refresh_clusters(self):
"""Reload clusters from database"""
# Clear cache
self.image_cache.clear()
# Reload
self.load_clusters()
# Reset selection
self.identify_button.config(state='disabled')
self.skip_button.config(state='disabled')
self.canvas.delete("all")
self.info_label.config(text="Select a cluster")
def update_statistics(self):
"""Update statistics display"""
if not os.path.exists(self.db_path):
return
try:
conn = sqlite3.connect(self.db_path)
c = conn.cursor()
c.execute("SELECT COUNT(*) FROM faces WHERE person_id IS NULL")
unidentified = c.fetchone()[0]
c.execute("SELECT COUNT(*) FROM faces WHERE person_id IS NOT NULL")
identified = c.fetchone()[0]
conn.close()
stats_text = f"Statistics:\n"
stats_text += f"Unidentified: {unidentified}\n"
stats_text += f"Identified: {identified}\n"
stats_text += f"Clusters: {len(self.clusters)}"
self.stats_label.config(text=stats_text)
except Exception as e:
print(f"Error updating statistics: {e}")
def run(self):
"""Start the GUI"""
self.root.mainloop()
def main():
"""Main entry point"""
app = FaceClusterGUI()
app.run()
if __name__ == "__main__":
main()
-193
View File
@@ -1,193 +0,0 @@
#!/usr/bin/env python3
"""
Interactive Face Identifier for PunimTag
Allows users to identify unknown faces in the database
"""
import os
import cv2
import numpy as np
from punimtag import PunimTag
from typing import Optional
import sys
class InteractiveFaceIdentifier:
def __init__(self, db_path: str = 'punimtag.db'):
self.tagger = PunimTag(db_path=db_path)
self.window_name = 'Face Identifier'
def display_face(self, image_path: str, location: tuple) -> np.ndarray:
"""Load and display image with face highlighted"""
img = cv2.imread(image_path)
if img is None:
print(f"Error: Could not load image {image_path}")
return None
# Get face coordinates
top, right, bottom, left = location
# Draw rectangle around face
cv2.rectangle(img, (left, top), (right, bottom), (0, 255, 0), 3)
# Calculate display size (max 800x600)
height, width = img.shape[:2]
max_height, max_width = 600, 800
if height > max_height or width > max_width:
scale = min(max_height/height, max_width/width)
new_width = int(width * scale)
new_height = int(height * scale)
img = cv2.resize(img, (new_width, new_height))
return img
def get_user_input(self, face_info: dict) -> Optional[str]:
"""Get user input for face identification"""
print("\n" + "="*50)
print(f"Image: {face_info['image_path']}")
print(f"Face ID: {face_info['face_id']}")
print("\nOptions:")
print("1. Enter person's name")
print("2. Skip this face (press Enter)")
print("3. Quit (press 'q')")
print("="*50)
user_input = input("\nEnter person's name (or press Enter to skip): ").strip()
if user_input.lower() == 'q':
return 'QUIT'
elif user_input == '':
return None
else:
return user_input
def run(self):
"""Run the interactive identification process"""
print("PunimTag Interactive Face Identifier")
print("=" * 50)
# Get unidentified faces
unidentified = self.tagger.get_unidentified_faces()
if not unidentified:
print("No unidentified faces found!")
return
print(f"Found {len(unidentified)} unidentified faces")
# Create window
cv2.namedWindow(self.window_name, cv2.WINDOW_NORMAL)
identified_count = 0
for i, face_info in enumerate(unidentified):
print(f"\nProcessing face {i+1} of {len(unidentified)}")
# Display the face
img = self.display_face(face_info['image_path'], face_info['location'])
if img is None:
continue
cv2.imshow(self.window_name, img)
cv2.waitKey(1) # Allow window to update
# Get user input
name = self.get_user_input(face_info)
if name == 'QUIT':
print("\nQuitting...")
break
elif name:
# Add person and assign face
person_id = self.tagger.add_person(name)
self.tagger.assign_face_to_person(face_info['face_id'], person_id, is_confirmed=True)
identified_count += 1
print(f"✓ Identified as: {name}")
else:
print("⊘ Skipped")
cv2.destroyAllWindows()
print(f"\n{'='*50}")
print(f"Identification complete!")
print(f"Identified {identified_count} faces")
print(f"Skipped {len(unidentified) - identified_count} faces")
self.tagger.close()
class CLIFaceIdentifier:
"""Command-line only face identifier (no OpenCV required)"""
def __init__(self, db_path: str = 'punimtag.db'):
self.tagger = PunimTag(db_path=db_path)
def run(self):
"""Run CLI-based identification"""
print("PunimTag CLI Face Identifier")
print("=" * 50)
# Get unidentified faces
unidentified = self.tagger.get_unidentified_faces()
if not unidentified:
print("No unidentified faces found!")
return
print(f"Found {len(unidentified)} unidentified faces\n")
identified_count = 0
for i, face_info in enumerate(unidentified):
print(f"\n{'='*50}")
print(f"Face {i+1} of {len(unidentified)}")
print(f"Image: {face_info['image_path']}")
print(f"Location in image: top={face_info['location'][0]}, right={face_info['location'][1]}, "
f"bottom={face_info['location'][2]}, left={face_info['location'][3]}")
print(f"Face ID: {face_info['face_id']}")
name = input("\nEnter person's name (or press Enter to skip, 'q' to quit): ").strip()
if name.lower() == 'q':
print("\nQuitting...")
break
elif name:
# Add person and assign face
person_id = self.tagger.add_person(name)
self.tagger.assign_face_to_person(face_info['face_id'], person_id, is_confirmed=True)
identified_count += 1
print(f"✓ Identified as: {name}")
else:
print("⊘ Skipped")
print(f"\n{'='*50}")
print(f"Identification complete!")
print(f"Identified {identified_count} faces")
print(f"Skipped {len(unidentified) - identified_count} faces")
self.tagger.close()
def main():
"""Main entry point"""
# Check if OpenCV is available
try:
import cv2
print("OpenCV available - using visual identifier")
identifier = InteractiveFaceIdentifier()
except ImportError:
print("OpenCV not available - using CLI identifier")
print("Install opencv-python for visual identification: pip install opencv-python")
identifier = CLIFaceIdentifier()
try:
identifier.run()
except KeyboardInterrupt:
print("\n\nInterrupted by user")
sys.exit(0)
if __name__ == "__main__":
main()
-118
View File
@@ -1,118 +0,0 @@
#!/usr/bin/env python3
"""
Simple Face Identifier for PunimTag
Works with the punimtag_simple.db database
"""
import sqlite3
import os
from PIL import Image
import pickle
class SimpleFaceIdentifier:
def __init__(self, db_path='punimtag_simple.db'):
self.db_path = db_path
def get_unidentified_faces(self, limit=10):
"""Get a limited number of unidentified faces"""
conn = sqlite3.connect(self.db_path)
c = conn.cursor()
c.execute('''SELECT f.id, f.image_id, i.path, i.filename, f.top, f.right, f.bottom, f.left
FROM faces f
JOIN images i ON f.image_id = i.id
WHERE f.person_id IS NULL
LIMIT ?''', (limit,))
faces = c.fetchall()
conn.close()
return faces
def add_person(self, name):
"""Add a new person"""
conn = sqlite3.connect(self.db_path)
c = conn.cursor()
c.execute('INSERT OR IGNORE INTO people (name) VALUES (?)', (name,))
c.execute('SELECT id FROM people WHERE name = ?', (name,))
person_id = c.fetchone()[0]
conn.commit()
conn.close()
return person_id
def assign_face(self, face_id, person_id):
"""Assign a face to a person"""
conn = sqlite3.connect(self.db_path)
c = conn.cursor()
c.execute('UPDATE faces SET person_id = ?, is_confirmed = 1 WHERE id = ?',
(person_id, face_id))
conn.commit()
conn.close()
def run_cli_identifier(self):
"""Run command line identifier"""
print("\n🏷️ Simple Face Identifier")
print("=" * 50)
faces = self.get_unidentified_faces(50) # Get first 50 faces
if not faces:
print("No unidentified faces found!")
return
print(f"Found {len(faces)} unidentified faces to process...")
print("For each face, enter the person's name or 's' to skip\n")
for i, (face_id, image_id, path, filename, top, right, bottom, left) in enumerate(faces):
print(f"\nFace {i+1}/{len(faces)}")
print(f"📁 File: {filename}")
print(f"📍 Location: top={top}, right={right}, bottom={bottom}, left={left}")
# Try to display basic info about the image
try:
if os.path.exists(path):
with Image.open(path) as img:
print(f"🖼️ Image size: {img.size}")
else:
print("⚠️ Image file not found")
except Exception as e:
print(f"⚠️ Could not read image: {e}")
while True:
name = input(f"👤 Who is this person? (or 's' to skip): ").strip()
if name.lower() == 's':
print("⏭️ Skipped")
break
elif name:
try:
person_id = self.add_person(name)
self.assign_face(face_id, person_id)
print(f"✅ Identified as '{name}'")
break
except Exception as e:
print(f"❌ Error: {e}")
else:
print("Please enter a name or 's' to skip")
print(f"\n🎉 Completed processing {len(faces)} faces!")
# Show remaining count
remaining = self.get_remaining_count()
if remaining > 0:
print(f"📊 {remaining} unidentified faces remaining")
print("Run the script again to continue identifying faces")
else:
print("🏆 All faces have been identified!")
def get_remaining_count(self):
"""Get count of remaining unidentified faces"""
conn = sqlite3.connect(self.db_path)
c = conn.cursor()
c.execute('SELECT COUNT(*) FROM faces WHERE person_id IS NULL')
count = c.fetchone()[0]
conn.close()
return count
if __name__ == "__main__":
identifier = SimpleFaceIdentifier()
identifier.run_cli_identifier()
-224
View File
@@ -1,224 +0,0 @@
#!/usr/bin/env python3
"""
PunimTag GUI Starter
Simple script to demonstrate the system and guide next steps
"""
import os
import subprocess
import sys
def check_requirements():
"""Check if all requirements are met"""
print("🔍 Checking requirements...")
# Check if photos directory exists
if not os.path.exists('photos'):
print("❌ Photos directory not found")
print(" Creating photos/ directory...")
os.makedirs('photos', exist_ok=True)
print(" ✅ Created photos/ directory")
else:
print("✅ Photos directory exists")
# Check if database exists
if os.path.exists('punimtag_simple.db'):
print("✅ Database exists")
# Get basic stats
try:
import sqlite3
conn = sqlite3.connect('punimtag_simple.db')
c = conn.cursor()
c.execute("SELECT COUNT(*) FROM images")
image_count = c.fetchone()[0]
c.execute("SELECT COUNT(*) FROM faces")
face_count = c.fetchone()[0]
c.execute("SELECT COUNT(*) FROM faces WHERE person_id IS NULL")
unidentified_count = c.fetchone()[0]
conn.close()
print(f" 📊 {image_count} images, {face_count} faces, {unidentified_count} unidentified")
return image_count, face_count, unidentified_count
except Exception as e:
print(f" ⚠️ Error reading database: {e}")
return 0, 0, 0
else:
print("❌ Database not found")
print(" Run 'python punimtag_simple.py' first to process images")
return 0, 0, 0
def show_menu(image_count, face_count, unidentified_count):
"""Show main menu"""
print("\n" + "="*60)
print("🏷️ PUNIMTAG - NEXT STEPS")
print("="*60)
if image_count == 0:
print("📝 GETTING STARTED:")
print(" 1. Add photos to the 'photos/' directory")
print(" 2. Run: python punimtag_simple.py")
print(" 3. Come back here for face identification")
print("\n💡 TIP: Start with 10-20 photos for testing")
return
print(f"📊 CURRENT STATUS:")
print(f" Images processed: {image_count}")
print(f" Faces detected: {face_count}")
print(f" Unidentified faces: {unidentified_count}")
print(f"\n🎯 AVAILABLE ACTIONS:")
print(f" 1. Process more images")
print(f" 2. Identify unknown faces (CLI)")
print(f" 3. Manage database")
print(f" 4. View statistics")
if unidentified_count > 0:
print(f" 5. Start simple web interface (coming soon)")
print(f" 6. Exit")
def process_images():
"""Process images"""
print("\n📷 Processing images...")
try:
result = subprocess.run([sys.executable, 'punimtag_simple.py'],
capture_output=True, text=True)
print(result.stdout)
if result.stderr:
print("Errors:", result.stderr)
except Exception as e:
print(f"Error: {e}")
def identify_faces():
"""Identify faces using CLI tool"""
print("\n👥 Starting face identification...")
try:
subprocess.run([sys.executable, 'interactive_identifier.py'])
except Exception as e:
print(f"Error: {e}")
def manage_database():
"""Manage database"""
print("\n🗄️ Starting database manager...")
try:
subprocess.run([sys.executable, 'db_manager.py'])
except Exception as e:
print(f"Error: {e}")
def show_statistics():
"""Show detailed statistics"""
print("\n📊 DETAILED STATISTICS")
print("="*40)
try:
from db_manager import DatabaseManager
manager = DatabaseManager('punimtag_simple.db')
manager.inspect_database()
except Exception as e:
print(f"Error: {e}")
def show_next_gui_steps():
"""Show what's coming next for GUI development"""
print("\n🚀 NEXT GUI DEVELOPMENT STEPS")
print("="*50)
print("We have a working backend! Next steps for GUI:")
print()
print("✅ COMPLETED:")
print(" • Face recognition and clustering")
print(" • Database management")
print(" • Jewish organization features")
print(" • Search functionality")
print(" • CLI tools for identification")
print()
print("🔄 IN PROGRESS:")
print(" • Web-based GUI with Flask")
print(" • Face clustering visualization")
print(" • Interactive face identification")
print()
print("📋 PLANNED:")
print(" • Advanced search interface")
print(" • Tag management GUI")
print(" • Statistics dashboard")
print(" • Photo gallery with face highlights")
print()
print("💡 TO TEST THE WEB GUI:")
print(" 1. Make sure you have processed some images")
print(" 2. Run: python web_gui.py (when ready)")
print(" 3. Open http://localhost:5000 in browser")
def main():
"""Main function"""
print("🏷️ PunimTag GUI Starter")
print("Welcome to the face recognition and photo tagging system!")
# Check requirements
image_count, face_count, unidentified_count = check_requirements()
while True:
show_menu(image_count, face_count, unidentified_count)
try:
choice = input(f"\n➤ Select option: ").strip()
if choice == '1':
process_images()
# Refresh stats
image_count, face_count, unidentified_count = check_requirements()
elif choice == '2':
if face_count == 0:
print("❌ No faces to identify. Process images first.")
else:
identify_faces()
# Refresh stats
image_count, face_count, unidentified_count = check_requirements()
elif choice == '3':
manage_database()
# Refresh stats
image_count, face_count, unidentified_count = check_requirements()
elif choice == '4':
show_statistics()
elif choice == '5':
if unidentified_count > 0:
print("🌐 Web interface is in development!")
print("For now, use option 2 for CLI identification.")
else:
print("✅ All faces are identified!")
elif choice == '6':
break
else:
print("❌ Invalid option")
input("\nPress Enter to continue...")
except KeyboardInterrupt:
print("\n\n👋 Goodbye!")
break
except Exception as e:
print(f"Error: {e}")
input("Press Enter to continue...")
print("\n🎉 Thank you for using PunimTag!")
show_next_gui_steps()
if __name__ == "__main__":
main()