iris-backend / stats.py
trretretret's picture
Updated app.py and added stats.py
f5d0cea
Raw
History Blame Contribute Delete
8.73 kB
"""
stats.py β€” Centralized statistics tracker for Iris Recognition App
Tracks live events in memory + persists to a JSON file on disk.
Call update_*() functions from your routes in app.py.
Call get_dashboard_stats() from a new /stats endpoint.
"""
import os
import json
import threading
from datetime import datetime, date
from collections import defaultdict
# ─────────────────────────────────────────
# Config
# ─────────────────────────────────────────
STATS_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "stats_data.json")
_lock = threading.Lock() # Thread-safe writes
# ─────────────────────────────────────────
# Default stats structure
# ─────────────────────────────────────────
def _default_stats():
return {
# ── Login stats
"total_logins": 0,
"successful_logins": 0,
"failed_logins": 0, # identity not recognised
"login_scores": [], # list of float scores (last 100)
# ── Registration stats
"total_registrations": 0,
"registrations_by_date": {}, # {"2025-07-01": 3, ...}
# ── Phase 1 (Iris detection)
"phase1_total": 0,
"phase1_iris": 0,
"phase1_non_iris": 0,
# ── Phase 2 (PAD β€” Presentation Attack Detection)
"phase2_total": 0,
"phase2_real": 0,
"phase2_fake": 0, # attacks blocked
"phase2_confidences": [], # list of float (last 100)
# ── GAN generation
"total_generated": 0,
# ── Daily activity (last 14 days)
"daily_logins": {}, # {"2025-07-01": 5, ...}
"daily_attacks": {}, # {"2025-07-01": 2, ...}
}
# ─────────────────────────────────────────
# Load / Save helpers
# ─────────────────────────────────────────
def _load():
if os.path.exists(STATS_FILE):
try:
with open(STATS_FILE, "r") as f:
data = json.load(f)
# Backfill any missing keys (in case file is from older version)
defaults = _default_stats()
for key, val in defaults.items():
data.setdefault(key, val)
return data
except Exception:
pass
return _default_stats()
def _save(data):
try:
with open(STATS_FILE, "w") as f:
json.dump(data, f, indent=2)
except Exception as e:
print(f"⚠️ Stats save failed: {e}")
def _today():
return date.today().isoformat() # "2025-07-01"
# ─────────────────────────────────────────
# Public update functions β€” call from app.py
# ─────────────────────────────────────────
def update_login(success: bool, score: float):
"""Call this every time /login is hit."""
with _lock:
data = _load()
today = _today()
data["total_logins"] += 1
if success:
data["successful_logins"] += 1
else:
data["failed_logins"] += 1
# Keep only last 100 scores (to avoid file bloat)
data["login_scores"].append(round(score, 4))
data["login_scores"] = data["login_scores"][-100:]
# Daily activity
data["daily_logins"][today] = data["daily_logins"].get(today, 0) + 1
_save(data)
def update_registration(person_id: str):
"""Call this every time /register succeeds."""
with _lock:
data = _load()
today = _today()
data["total_registrations"] += 1
data["registrations_by_date"][today] = \
data["registrations_by_date"].get(today, 0) + 1
_save(data)
def update_phase1(is_iris: bool):
"""Call this every time /phase1 runs."""
with _lock:
data = _load()
data["phase1_total"] += 1
if is_iris:
data["phase1_iris"] += 1
else:
data["phase1_non_iris"] += 1
_save(data)
def update_phase2(is_real: bool, confidence: float):
"""Call this every time /phase2 runs."""
with _lock:
data = _load()
today = _today()
data["phase2_total"] += 1
if is_real:
data["phase2_real"] += 1
else:
data["phase2_fake"] += 1
data["daily_attacks"][today] = data["daily_attacks"].get(today, 0) + 1
data["phase2_confidences"].append(round(confidence, 4))
data["phase2_confidences"] = data["phase2_confidences"][-100:]
_save(data)
def update_generation(count: int):
"""Call this every time /generate runs."""
with _lock:
data = _load()
data["total_generated"] += count
_save(data)
# ─────────────────────────────────────────
# Main dashboard endpoint data
# ─────────────────────────────────────────
def get_dashboard_stats(gallery: dict) -> dict:
"""
Returns all stats needed for frontend charts + cards.
Pass in the live `gallery` dict from iris_recognition.py.
"""
with _lock:
data = _load()
today = _today()
# ── Gallery breakdown: samples per person
gallery_sizes = {
person: int(embeddings.shape[0])
for person, embeddings in gallery.items()
}
# ── Last 14 days of login + attack activity
from datetime import timedelta
last_14 = [
(date.today() - timedelta(days=i)).isoformat()
for i in range(13, -1, -1)
]
daily_login_trend = [data["daily_logins"].get(d, 0) for d in last_14]
daily_attack_trend = [data["daily_attacks"].get(d, 0) for d in last_14]
# ── Average login score
scores = data["login_scores"]
avg_score = round(sum(scores) / len(scores), 4) if scores else 0.0
# ── PAD confidence average
confs = data["phase2_confidences"]
avg_pad_conf = round(sum(confs) / len(confs), 4) if confs else 0.0
return {
# ── Summary cards
"gallery": {
"total_persons": len(gallery),
"total_samples": sum(gallery_sizes.values()),
"samples_per_person": gallery_sizes, # for bar chart
},
"logins": {
"total": data["total_logins"],
"successful": data["successful_logins"],
"failed": data["failed_logins"],
"success_rate": round(
data["successful_logins"] / data["total_logins"] * 100, 1
) if data["total_logins"] else 0,
"avg_score": avg_score,
"recent_scores": data["login_scores"][-20:], # for line chart
},
"registrations": {
"total": data["total_registrations"],
"by_date": data["registrations_by_date"],
},
"phase1": {
"total": data["phase1_total"],
"iris_detected": data["phase1_iris"],
"non_iris": data["phase1_non_iris"],
},
"phase2": {
"total": data["phase2_total"],
"real": data["phase2_real"],
"fake_blocked": data["phase2_fake"],
"attack_rate": round(
data["phase2_fake"] / data["phase2_total"] * 100, 1
) if data["phase2_total"] else 0,
"avg_confidence": avg_pad_conf,
},
"gan": {
"total_generated": data["total_generated"],
},
# ── Time-series for charts (last 14 days)
"trends": {
"dates": last_14,
"daily_logins": daily_login_trend,
"daily_attacks": daily_attack_trend,
},
"today": {
"date": today,
"logins_today": data["daily_logins"].get(today, 0),
"attacks_today": data["daily_attacks"].get(today, 0),
}
}