UAIDE / video_bundle /app.py
ATS-27's picture
Upload folder using huggingface_hub
af980d7 verified
Raw
History Blame Contribute Delete
95.9 kB
<<<<<<< HEAD
import io
import json
from PIL import Image
import numpy as np
import gradio as gr
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import joblib
from pathlib import Path
from datetime import datetime
from sklearn.metrics import accuracy_score, roc_auc_score, classification_report, roc_curve
import torch
import torch.nn as nn
import torch.nn.functional as F
from torchvision import transforms, models
import torchvision.transforms.functional as TF
import cv2
import tempfile
import os
from detector import sliding_patch_scores, reconstruct_heatmap, rgb_to_gray, extract_residual, fft_stats, lbp_entropy
from ethical_assessment import EthicalAssessment, format_ethical_report, get_simple_status
from video_model import ResNetLSTM, GradCAM, overlay_cam
from video_data import read_video_frames, FaceCropper
def get_enhanced_ethical_status(assessment):
"""Generate enhanced status string with prominent flag display"""
status = assessment.get('status', 'UNKNOWN')
risk = assessment.get('risk_score', 0)
flags = assessment.get('flags', [])
checks = assessment.get('checks', {})
lines = []
# Main status line
if assessment.get('is_ethical'):
lines.append(f"STATUS: {status}")
else:
lines.append(f"STATUS: {status}")
lines.append(f"Risk Score: {risk:.1%}")
# Show flags prominently
if flags:
lines.append("")
lines.append("FLAGS RAISED:")
for flag in flags:
flag_desc = {
"NSFW_CONTENT": "Explicit/NSFW content detected",
"POTENTIAL_MINOR": "CRITICAL: Potential minor detected",
"POTENTIAL_CELEBRITY": "Celebrity impersonation risk",
"EMOTIONAL_MANIPULATION": "High emotional manipulation",
"AI_METADATA_MARKERS": "AI generation markers in metadata",
"WATERMARK_REMOVAL": "Signs of watermark removal",
"POTENTIAL_HATE_SYMBOL": "Potential hate symbol detected",
"MISLEADING_TEXT": "Misleading text overlay",
"DOCUMENT_DETECTED": "Document/ID forgery risk"
}.get(flag, flag)
lines.append(f" - {flag_desc}")
# Key check results
if checks:
lines.append("")
if 'nsfw' in checks and checks['nsfw'].get('nsfw_score', 0) > 0.3:
lines.append(f"NSFW: {checks['nsfw'].get('severity', 'N/A')}")
if 'age_estimation' in checks and checks['age_estimation'].get('is_minor_risk'):
lines.append(f"Age: {checks['age_estimation'].get('estimated_age_range', 'N/A')}")
if 'document' in checks and checks['document'].get('is_document'):
lines.append(f"Document: {checks['document'].get('document_type', 'detected')}")
return "\n".join(lines)
# Try to import timm for EfficientNet
try:
import timm
HAS_TIMM = True
except ImportError:
HAS_TIMM = False
# ──────────────────────────────────────────────────────────────────────────────
# EfficientNet + FFT Model (from train_adv.py)
# ──────────────────────────────────────────────────────────────────────────────
class FFTFeatureExtractor(nn.Module):
"""Extract FFT features for frequency domain analysis"""
def __init__(self, output_dim=512):
super().__init__()
self.fft_processor = nn.Sequential(
nn.Linear(12, 64),
nn.BatchNorm1d(64),
nn.ReLU(inplace=True),
nn.Dropout(0.1),
nn.Linear(64, 128),
nn.BatchNorm1d(128),
nn.ReLU(inplace=True),
nn.Linear(128, output_dim),
)
@torch.no_grad()
def _extract_fft_features(self, x):
B, C, H, W = x.shape
device = x.device
x_f32 = x.float()
if C == 3:
gray = 0.299 * x_f32[:, 0] + 0.587 * x_f32[:, 1] + 0.114 * x_f32[:, 2]
else:
gray = x_f32[:, 0]
fft_img = torch.fft.fft2(gray)
fft_shift = torch.fft.fftshift(fft_img)
mag = torch.abs(fft_shift) + 1e-8
mag = mag / (mag.max(dim=-1, keepdim=True)[0].max(dim=-2, keepdim=True)[0] + 1e-8)
fft_features = []
for i in range(B):
m = mag[i].flatten()
feat = torch.stack([
m.mean(), m.std().clamp(min=1e-8), m.max(), m.min(),
(m > m.mean()).float().mean(), m.median(),
mag[i][:H//4, :].mean(), mag[i][H//4:H//2, :].mean(),
mag[i][H//2:3*H//4, :].mean(), mag[i][3*H//4:, :].mean(),
(m > 0.5).float().mean(), (m > 0.1).float().mean(),
])
feat = torch.clamp(feat, min=-10, max=10)
fft_features.append(feat)
return torch.stack(fft_features, dim=0)
def forward(self, x):
fft_feat = self._extract_fft_features(x)
fft_feat = fft_feat.to(x.dtype).detach()
fft_feat.requires_grad_(True)
return self.fft_processor(fft_feat)
class EfficientNetFFTFusion(nn.Module):
"""EfficientNet-B4 backbone with FFT feature fusion"""
def __init__(self, num_classes=2, dropout=0.4, backbone='efficientnet_b4'):
super().__init__()
if HAS_TIMM:
self.backbone = timm.create_model(backbone, pretrained=False, num_classes=0)
backbone_dim = self.backbone.num_features
else:
self.backbone = models.efficientnet_b4(weights=None)
backbone_dim = self.backbone.classifier[1].in_features
self.backbone.classifier = nn.Identity()
fft_dim = 512
self.fft_extractor = FFTFeatureExtractor(output_dim=fft_dim)
fusion_dim = backbone_dim + fft_dim
self.fusion = nn.Sequential(
nn.Linear(fusion_dim, 1024),
nn.LayerNorm(1024),
nn.GELU(),
nn.Dropout(dropout),
nn.Linear(1024, 512),
nn.LayerNorm(512),
nn.GELU(),
nn.Dropout(dropout * 0.5),
)
self.classifier = nn.Linear(512, num_classes)
def forward(self, x):
backbone_feat = self.backbone(x)
fft_feat = self.fft_extractor(x)
fused = torch.cat([backbone_feat, fft_feat], dim=1)
fused = self.fusion(fused)
return self.classifier(fused)
def make_overlay_pil(img_arr, heatmap, alpha=0.5, cmap='jet'):
# img_arr: HxWx3 in [0,1]
plt.figure(figsize=(6, 6), dpi=100)
plt.imshow(np.clip(img_arr, 0, 1))
plt.imshow(heatmap, cmap=cmap, alpha=alpha, vmin=0, vmax=1)
plt.axis('off')
buf = io.BytesIO()
plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0)
plt.close()
buf.seek(0)
return Image.open(buf).convert('RGB')
def pad_to_min_size(img, size):
w, h = img.size
pad_w = max(0, size - w)
pad_h = max(0, size - h)
if pad_w or pad_h:
left = pad_w // 2
right = pad_w - left
top = pad_h // 2
bottom = pad_h - top
img = TF.pad(img, [left, top, right, bottom], padding_mode='reflect')
return img
# Load the trained model (try different model files)
MODEL_PATH = None
MODEL = None
MODEL_INFO = None
# Fixed threshold for AI detection
# Auto-calibrated based on recent model performance (94.33% accuracy)
# 0.50 = Balanced threshold (equal precision/recall for fake detection)
# Lower = More sensitive to fakes (higher recall, lower precision)
# Higher = More strict (lower recall, higher precision)
AUTO_THRESHOLD = 0.50
# Temperature scaling to reduce model overconfidence
# T > 1 spreads probabilities, reducing extreme confidence
TEMPERATURE_SCALE = 1.2
def apply_temperature_scaling(prob):
"""Apply temperature scaling to reduce overconfidence"""
import math
prob = max(1e-7, min(1 - 1e-7, prob))
logit = math.log(prob / (1 - prob))
scaled_logit = logit / TEMPERATURE_SCALE
return 1 / (1 + math.exp(-scaled_logit))
# Video model variables
VIDEO_MODEL = None
VIDEO_MODEL_CONFIG = None
VIDEO_MODEL_PATH = 'video_resnet_lstm.pt'
TARGET_REAL_FPR = 0.02 # 2% false positive rate on real images (stricter)
MAX_CALIB_IMAGES = 200
def _pick_dataset_root():
"""Pick the first validation dataset that exists on disk."""
candidates = [
Path('C:/Users/DESHNA/Downloads/UAIDE_enhanced/CIFAKE'),
Path('DeepfakeVsReal/Dataset'),
Path('AI vs Real img'),
]
for cand in candidates:
if (cand / 'Validation').exists():
return str(cand)
return None
def _get_validation_files(dataset_root, max_val_images=None):
val_root = Path(dataset_root) / 'Validation'
if not val_root.exists():
return None, None
real_files = list((val_root / 'Real').rglob('*.jpg')) + list((val_root / 'Real').rglob('*.png'))
fake_files = list((val_root / 'Fake').rglob('*.jpg')) + list((val_root / 'Fake').rglob('*.png'))
real_files = sorted([str(x) for x in real_files])
fake_files = sorted([str(x) for x in fake_files])
if max_val_images:
real_files = real_files[:max_val_images]
fake_files = fake_files[:max_val_images]
files = real_files + fake_files
labels = [0] * len(real_files) + [1] * len(fake_files)
if len(files) == 0:
return None, None
return files, labels
def _get_transform(model_type):
if model_type in ['resnet', 'fusion', 'fusion_improved']:
size = 224
else:
size = 128
return transforms.Compose([
transforms.Lambda(lambda img: pad_to_min_size(img, size)),
transforms.CenterCrop(size),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
def _load_video_model(checkpoint_path):
"""Load video ResNetLSTM model from checkpoint."""
if not Path(checkpoint_path).exists():
return None, None
try:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
ckpt = torch.load(checkpoint_path, map_location=device)
config = ckpt.get('config', {})
model = ResNetLSTM(
hidden_size=config.get('hidden_size', 256),
num_layers=config.get('num_layers', 1),
bidirectional=config.get('bidirectional', True),
temporal_pool=config.get('temporal_pool', 'mean'),
pretrained=config.get('pretrained', False),
)
model.load_state_dict(ckpt['model_state'], strict=True)
model.to(device)
model.eval()
print(f'=== Loaded Video Model ===')
print(f"Checkpoint: {checkpoint_path}")
print(f"Config: {config}")
return model, config
except Exception as e:
print(f'Failed to load video model from {checkpoint_path}: {e}')
return None, None
def _build_video_transform():
"""Build transform for video frames."""
return transforms.Compose([
transforms.Resize((224, 224)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225]),
])
def _extract_video_frames(video_path, frames_per_video=16, frame_stride=4, face_detection=False):
"""Extract frames from video file."""
try:
face_cropper = FaceCropper() if face_detection else None
frames = read_video_frames(
Path(video_path),
frames_per_video=frames_per_video,
frame_stride=frame_stride,
face_cropper=face_cropper,
)
return frames
except Exception as e:
print(f'Video frame extraction failed: {e}')
return None
def _predict_video_model(model, config, video_path, return_frames=False):
"""Predict deepfake probability using video model."""
if model is None:
return None, None, None
try:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
frames_per_video = config.get('frames_per_video', 16)
frame_stride = config.get('frame_stride', 4)
face_detection = config.get('face_detection', True)
frames = _extract_video_frames(video_path, frames_per_video, frame_stride, face_detection)
if frames is None:
return None, None, None
transform = _build_video_transform()
video_tensor = torch.stack([transform(Image.fromarray(f)) for f in frames], dim=0)
video_tensor = video_tensor.unsqueeze(0).to(device)
model.eval()
with torch.no_grad():
frame_logits, video_logits = model(video_tensor)
video_probs = torch.softmax(video_logits, dim=1).cpu().numpy()[0]
frame_probs = torch.softmax(frame_logits.squeeze(0), dim=1).cpu().numpy()
prob_fake = float(video_probs[1])
if return_frames:
return prob_fake, frame_probs, frames
return prob_fake, frame_probs, None
except Exception as e:
print(f'Video model prediction failed: {e}')
return None, None, None
def _evaluate_deep_model(model, model_type, dataset_root, max_val_images=None):
"""Evaluate deep learning models on validation set."""
from train import ImageDataset
from torch.utils.data import DataLoader
files, labels = _get_validation_files(dataset_root, max_val_images=max_val_images)
if files is None:
return None
transform = _get_transform(model_type)
dataset = ImageDataset(files, labels, transform=transform)
dataloader = DataLoader(dataset, batch_size=8, shuffle=False, num_workers=0)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.eval()
all_probs = []
all_labels = []
with torch.no_grad():
for inputs, lbls in dataloader:
inputs = inputs.to(device)
outputs = model(inputs)
probs = torch.softmax(outputs, dim=1)[:, 1].cpu().numpy()
all_probs.extend(probs.tolist())
all_labels.extend(lbls)
if len(all_labels) == 0:
return None
y_true = np.array(all_labels)
y_prob = np.array(all_probs)
y_pred = (y_prob >= 0.5).astype(int)
acc = accuracy_score(y_true, y_pred)
try:
auc = roc_auc_score(y_true, y_prob)
except Exception:
auc = None
report = classification_report(y_true, y_pred, zero_division=0)
return {
'accuracy': acc,
'auc': auc,
'report': report,
'count': len(y_true)
}
def _evaluate_ml_model(model, dataset_root, max_val_images=None, patch_size=128, n_patches=4):
from train import collect_features
val_root = Path(dataset_root) / 'Validation'
if not val_root.exists():
return None
real_val = val_root / 'Real'
fake_val = val_root / 'Fake'
Xrv, yrv = collect_features(real_val, 0, max_images=max_val_images, patch_size=patch_size, n_patches=n_patches)
Xfv, yfv = collect_features(fake_val, 1, max_images=max_val_images, patch_size=patch_size, n_patches=n_patches)
if len(Xrv) + len(Xfv) == 0:
return None
Xv = np.array(Xrv + Xfv)
yv = np.array(yrv + yfv)
if hasattr(model, 'predict_proba'):
probs = model.predict_proba(Xv)[:, 1]
else:
probs = model.predict(Xv).astype(float)
preds = (probs >= 0.5).astype(int)
acc = accuracy_score(yv, preds)
try:
auc = roc_auc_score(yv, probs)
except Exception:
auc = None
report = classification_report(yv, preds, zero_division=0)
return {
'accuracy': acc,
'auc': auc,
'report': report,
'count': len(yv)
}
def _calibrate_threshold(model, model_info, dataset_root, target_real_fpr=0.05, max_val_images=200):
"""Calibrate detection threshold using ROC/Youden J; fallback to real-FPR quantile."""
if model is None or model_info is None:
return None
val_root = Path(dataset_root) / 'Validation'
if not val_root.exists():
return None
mtype = model_info.get('model_type', 'unknown') if isinstance(model_info, dict) else 'unknown'
all_probs = []
all_labels = []
try:
if mtype in ['cnn', 'cnn_kfold', 'resnet', 'fusion', 'fusion_improved']:
from train import ImageDataset
from torch.utils.data import DataLoader
files, labels = _get_validation_files(dataset_root, max_val_images=max_val_images)
if files is None:
return None
transform = _get_transform(mtype)
dataset = ImageDataset(files, labels, transform=transform)
dataloader = DataLoader(dataset, batch_size=16, shuffle=False, num_workers=0)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.eval()
with torch.no_grad():
for inputs, lbls in dataloader:
inputs = inputs.to(device)
outputs = model(inputs)
probs = torch.softmax(outputs, dim=1)[:, 1].cpu().numpy()
all_probs.extend(probs.tolist())
all_labels.extend(lbls.tolist())
else:
from train import collect_features
real_val = val_root / 'Real'
fake_val = val_root / 'Fake'
Xrv, yrv = collect_features(real_val, 0, max_images=max_val_images, patch_size=128, n_patches=4)
Xfv, yfv = collect_features(fake_val, 1, max_images=max_val_images, patch_size=128, n_patches=4)
X = np.array(Xrv + Xfv)
y = np.array(yrv + yfv)
if len(X) == 0:
return None
if hasattr(model, 'predict_proba'):
probs = model.predict_proba(X)[:, 1]
else:
probs = model.predict(X).astype(float)
all_probs.extend(probs.tolist())
all_labels.extend(y.tolist())
if len(all_labels) == 0 or len(np.unique(all_labels)) < 2:
return None
# Youden's J: maximize TPR - FPR
fpr, tpr, thresholds = roc_curve(np.array(all_labels), np.array(all_probs))
j_scores = tpr - fpr
best_idx = int(np.argmax(j_scores))
best_thresh = float(thresholds[best_idx])
print(f'Calibrated threshold via Youden J (balanced): {best_thresh:.3f} (TPR={tpr[best_idx]:.3f}, FPR={fpr[best_idx]:.3f})')
return best_thresh
except Exception as e:
print(f'Youden calibration failed, falling back to real-FPR quantile: {e}')
# Fallback: match target_real_fpr on real images only
real_probs = []
if mtype in ['cnn', 'cnn_kfold', 'resnet', 'fusion', 'fusion_improved']:
from train import ImageDataset
from torch.utils.data import DataLoader
files, labels = _get_validation_files(dataset_root, max_val_images=max_val_images)
if files is None:
return None
real_files = [f for f, lbl in zip(files, labels) if lbl == 0]
if len(real_files) == 0:
return None
transform = _get_transform(mtype)
dataset = ImageDataset(real_files, [0] * len(real_files), transform=transform)
dataloader = DataLoader(dataset, batch_size=16, shuffle=False, num_workers=0)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.eval()
with torch.no_grad():
for inputs, _ in dataloader:
inputs = inputs.to(device)
outputs = model(inputs)
probs = torch.softmax(outputs, dim=1)[:, 1].cpu().numpy()
real_probs.extend(probs.tolist())
else:
from train import collect_features
real_val = val_root / 'Real'
Xrv, _ = collect_features(real_val, 0, max_images=max_val_images, patch_size=128, n_patches=4)
if len(Xrv) == 0:
return None
X = np.array(Xrv)
if hasattr(model, 'predict_proba'):
probs = model.predict_proba(X)[:, 1]
else:
probs = model.predict(X).astype(float)
real_probs.extend(probs.tolist())
if len(real_probs) == 0:
return None
real_probs = np.array(real_probs)
target = max(0.0, min(1.0, float(target_real_fpr)))
if target <= 0.0:
return float(np.max(real_probs))
return float(np.quantile(real_probs, 1.0 - target))
def _load_model_from_info(info_path):
info = joblib.load(str(info_path))
if not isinstance(info, dict) or 'model_type' not in info:
return None, None
def _model_base_from_info_path(path_obj):
path_str = str(path_obj)
if path_str.endswith('_improved_info.pkl'):
return path_str[:-len('_improved_info.pkl')]
if path_str.endswith('_info.pkl'):
return path_str[:-len('_info.pkl')]
return path_str
mtype = info['model_type']
model = None
if mtype in ['cnn', 'cnn_kfold', 'resnet', 'fusion', 'fusion_improved']:
if mtype == 'resnet':
from train import DeepfakeResNet as _ModelClass
elif mtype in ['fusion', 'fusion_improved']:
from train import DeepfakeFeatureFusion as _ModelClass
else:
from train import DeepfakeCNN as _ModelClass
model = _ModelClass()
state_path = info.get('state_dict_path')
if state_path is None:
base = _model_base_from_info_path(info_path)
candidates = [base + '_best_improved', base + '_best', base]
for c in candidates:
if Path(c).exists():
state_path = c
break
if state_path is None:
raise FileNotFoundError('state_dict_path not found in model info and no candidate file exists')
model.load_state_dict(torch.load(state_path, map_location='cpu'))
model.eval()
if torch.cuda.is_available():
model.to(torch.device('cuda'))
else:
base = _model_base_from_info_path(info_path)
joblib_candidates = [base, base + '.joblib'] if base.endswith('.joblib') else [base + '.joblib', base]
for joblib_path in joblib_candidates:
if Path(joblib_path).exists():
model = joblib.load(joblib_path)
break
if model is None:
model = info
return model, info
def load_model_fast():
"""Load a model quickly without validation evaluation for faster startup."""
# Prioritize improved models
info_candidates = list(Path('.').glob('*_improved_info.pkl')) + list(Path('.').glob('*_info.pkl'))
info_candidates = sorted(info_candidates, key=lambda p: p.name, reverse=True)
if not info_candidates:
return None, None, None
# Try to load the first valid model without evaluation
for info_path in info_candidates:
try:
model, info = _load_model_from_info(info_path)
mtype = info.get('model_type', 'unknown') if isinstance(info, dict) else 'unknown'
print(f'=== Loaded Model (fast mode) ===')
print(f"Model: {info_path.name}")
print(f"Type: {mtype}")
return model, info, str(info_path)
except Exception as e:
print(f"Skipping {info_path.name}: failed to load model ({e})")
continue
return None, None, None
def select_best_model(dataset_root='C:\\Users\\DESHNA\\Downloads\\UAIDE_enhanced\\CIFAKE', max_val_images=None):
info_candidates = list(Path('.').glob('*_improved_info.pkl')) + list(Path('.').glob('*_info.pkl'))
info_candidates = sorted(info_candidates, key=lambda p: p.name, reverse=True)
if not info_candidates:
return None, None, None
scored = []
for info_path in info_candidates:
try:
model, info = _load_model_from_info(info_path)
except Exception as e:
print(f"Skipping {info_path.name}: failed to load model ({e})")
continue
mtype = info.get('model_type', 'unknown') if isinstance(info, dict) else 'unknown'
stats = None
if mtype in ['cnn', 'cnn_kfold', 'resnet', 'fusion', 'fusion_improved']:
try:
stats = _evaluate_deep_model(model, mtype, dataset_root, max_val_images=max_val_images)
except Exception as e:
print(f"Failed to evaluate deep model: {e}")
stats = None
else:
patch_size = info.get('patch_size', 128) if isinstance(info, dict) else 128
n_patches = info.get('patches_per_image', 4) if isinstance(info, dict) else 4
stats = _evaluate_ml_model(model, dataset_root, max_val_images=max_val_images, patch_size=patch_size, n_patches=n_patches)
if stats is None:
print(f"Skipping {info_path.name}: no validation stats")
continue
scored.append((info_path, model, info, stats))
auc = stats.get('auc')
acc = stats.get('accuracy')
print('\n=== Model Evaluation ===')
print(f"Model: {info_path.name} | type: {mtype}")
print(f"Val samples: {stats.get('count', 0)}")
print(f"Accuracy: {acc:.4f}")
if auc is not None:
print(f"ROC AUC: {auc:.4f}")
print('Classification report:')
print(stats.get('report', 'N/A'))
if not scored:
return None, None, None
def _score_key(item):
_, _, _, st = item
auc = st.get('auc')
acc = st.get('accuracy')
return (auc if auc is not None else -1.0, acc)
scored.sort(key=_score_key, reverse=True)
best_path, best_model, best_info, best_stats = scored[0]
print('\n=== Selected Best Model ===')
print(f"Model: {best_path.name}")
print(f"Type: {best_info.get('model_type', 'unknown') if isinstance(best_info, dict) else 'unknown'}")
print(f"Accuracy: {best_stats.get('accuracy'):.4f}")
if best_stats.get('auc') is not None:
print(f"ROC AUC: {best_stats.get('auc'):.4f}")
return best_model, best_info, str(best_path)
# ──────────────────────────────────────────────────────────────────────────────
# ROC-BASED OPTIMAL THRESHOLD CALCULATION
# ──────────────────────────────────────────────────────────────────────────────
def calculate_optimal_threshold_from_metrics(metrics):
"""
Calculate optimal threshold using ROC curve analysis (Youden's J statistic).
Uses sensitivity and specificity from the classification report.
Youden's J = Sensitivity + Specificity - 1
When model is biased (sensitivity >> specificity), we need to raise threshold
aggressively to reduce false positives (real images detected as AI).
"""
if not metrics:
return 0.5
sensitivity = metrics.get('sensitivity', 0.5) # TPR (ability to detect fakes)
specificity = metrics.get('specificity', 0.5) # TNR (ability to recognize real)
fpr = metrics.get('fpr', 0.5) # False Positive Rate (real images wrongly called fake)
fnr = metrics.get('fnr', 0.5) # False Negative Rate (fakes wrongly called real)
# Calculate Youden's J at current threshold (evaluated at 0.5)
youden_j = sensitivity + specificity - 1
# Calculate imbalance: positive means biased towards detecting fake
imbalance = sensitivity - specificity
# Method 1: FPR-based adjustment (AGGRESSIVE for high FPR)
# High FPR means we're classifying too many real images as fake
# Use exponential scaling for high FPR values
if fpr > 0.15:
# Aggressive adjustment for high FPR
fpr_adjustment = fpr * 1.2 + (fpr - 0.15) * 0.5
else:
fpr_adjustment = fpr * 0.8
# Method 2: Imbalance-based adjustment
# Large positive imbalance means sensitivity >> specificity
if imbalance > 0:
# Progressive scaling: larger imbalance = more aggressive adjustment
imbalance_adjustment = imbalance * (0.4 + 0.3 * imbalance)
else:
imbalance_adjustment = imbalance * 0.3
# Method 3: Target specificity adjustment
# If specificity is below 85%, push threshold higher
if specificity < 0.85:
specificity_boost = (0.85 - specificity) * 0.5
else:
specificity_boost = 0.0
# Combine adjustments
adjustment = 0.5 * fpr_adjustment + 0.3 * imbalance_adjustment + 0.2 * specificity_boost
optimal_threshold = 0.5 + adjustment
# Clamp to reasonable range
optimal_threshold = max(0.40, min(0.85, optimal_threshold))
print(f" ROC Analysis (Youden's J Method):")
print(f" Sensitivity (TPR): {sensitivity:.3f} (detect fakes)")
print(f" Specificity (TNR): {specificity:.3f} (recognize real)")
print(f" False Positive Rate: {fpr:.3f} (real -> fake error)")
print(f" False Negative Rate: {fnr:.3f} (fake -> real error)")
print(f" Youden's J: {youden_j:.3f}")
print(f" Imbalance: {imbalance:+.3f}")
print(f" FPR Adjustment: +{fpr_adjustment:.3f}")
print(f" Imbalance Adjustment: +{imbalance_adjustment:.3f}")
print(f" Specificity Boost: +{specificity_boost:.3f}")
print(f" Combined Adjustment: +{adjustment:.3f}")
print(f" Optimal Threshold: {optimal_threshold:.3f}")
return optimal_threshold
def get_confidence_tier(probability, threshold):
"""
Get confidence tier instead of binary decision.
Returns tier name and confidence level.
"""
if probability >= threshold + 0.25:
return "HIGH_CONFIDENCE_AI", "Very likely AI-generated"
elif probability >= threshold + 0.10:
return "MEDIUM_CONFIDENCE_AI", "Likely AI-generated"
elif probability >= threshold:
return "LOW_CONFIDENCE_AI", "Possibly AI-generated (borderline)"
elif probability >= threshold - 0.15:
return "UNCERTAIN", "Uncertain - could be either"
elif probability >= threshold - 0.30:
return "LOW_CONFIDENCE_REAL", "Likely authentic"
else:
return "HIGH_CONFIDENCE_REAL", "Very likely authentic"
# Auto-detect and load the latest trained model
# Priority order: models_adv > models_v2 > fallback
MODEL = None
MODEL_PATH = None
MODEL_INFO = None
# ─────────────────────────────────────────────────────────────────────────────
# PRIORITY 1: Load Recently Trained Model from models_adv (NEWEST)
# ─────────────────────────────────────────────────────────────────────────────
ADV_MODEL_PATH = Path('models_adv/best_model_weights.pt')
ADV_CONFIG_PATH = Path('models_adv/config.json')
if ADV_MODEL_PATH.exists():
try:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Load config
adv_config = {}
if ADV_CONFIG_PATH.exists():
with open(ADV_CONFIG_PATH) as f:
adv_config = json.load(f)
# Load state_dict first to detect correct backbone
state_dict = torch.load(ADV_MODEL_PATH, map_location=device, weights_only=False)
# Detect backbone from fusion layer input dimension
# backbone_dim + 512 (FFT) = fusion input
fusion_in_dim = state_dict['fusion.0.weight'].shape[1]
backbone_dim = fusion_in_dim - 512
# Map backbone_dim to backbone name
backbone_map = {1280: 'efficientnet_b0', 1408: 'efficientnet_b2', 1792: 'efficientnet_b4'}
backbone = backbone_map.get(backbone_dim, 'efficientnet_b2')
# Require timm for this model
if not HAS_TIMM:
raise ImportError("timm is required for EfficientNet models. Install with: pip install timm")
# Create model architecture and load weights
MODEL = EfficientNetFFTFusion(num_classes=2, dropout=0.4, backbone=backbone)
MODEL.load_state_dict(state_dict)
MODEL.to(device)
MODEL.eval()
MODEL_PATH = str(ADV_MODEL_PATH)
MODEL_INFO = {
'model_type': 'efficientnet_fft',
'backbone': backbone,
'accuracy': adv_config.get('best_metrics', {}).get('accuracy', 86.0),
'auc': adv_config.get('best_metrics', {}).get('auc_roc', 0.9394),
'optimal_threshold': 0.50,
**adv_config
}
mod_time = datetime.fromtimestamp(ADV_MODEL_PATH.stat().st_mtime).strftime('%Y-%m-%d %H:%M:%S')
print(f'\n=== Loaded Recent Model (PRIORITY 1) ===')
print(f"Model: {ADV_MODEL_PATH.name}")
print(f"Backbone: {MODEL_INFO.get('backbone', 'unknown')}")
print(f"Accuracy: {MODEL_INFO.get('accuracy', 'unknown')}%")
print(f"AUC: {MODEL_INFO.get('auc', 'unknown')}")
print(f"Modified: {mod_time}")
print(f"Status: Ready for inference ✓")
except Exception as e:
print(f'Failed to load models_adv model: {e}')
MODEL = None
# ─────────────────────────────────────────────────────────────────────────────
# PRIORITY 2: Load EfficientNet+FFT model from models_v2 (only if PRIORITY 1 failed)
# ─────────────────────────────────────────────────────────────────────────────
EFFICIENTNET_MODEL_PATH = Path('models_v2/best_model.pt')
EFFICIENTNET_WEIGHTS_PATH = Path('models_v2/best_model_weights.pt')
EFFICIENTNET_CONFIG_PATH = Path('models_v2/config.json')
EFFICIENTNET_REPORT_PATH = Path('models_v2/classification_report.json')
if MODEL is None and (EFFICIENTNET_MODEL_PATH.exists() or EFFICIENTNET_WEIGHTS_PATH.exists()):
try:
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Load config to get model settings
config = {}
if EFFICIENTNET_CONFIG_PATH.exists():
with open(EFFICIENTNET_CONFIG_PATH) as f:
config = json.load(f)
# Load classification report for threshold
report = {}
if EFFICIENTNET_REPORT_PATH.exists():
with open(EFFICIENTNET_REPORT_PATH) as f:
report = json.load(f)
# Create model
backbone = config.get('backbone', 'efficientnet_b4')
MODEL = EfficientNetFFTFusion(num_classes=2, dropout=0.4, backbone=backbone)
# Load weights (weights_only=False for PyTorch 2.6+ compatibility)
if EFFICIENTNET_MODEL_PATH.exists():
checkpoint = torch.load(EFFICIENTNET_MODEL_PATH, map_location=device, weights_only=False)
if 'model_state_dict' in checkpoint:
MODEL.load_state_dict(checkpoint['model_state_dict'])
else:
MODEL.load_state_dict(checkpoint)
else:
MODEL.load_state_dict(torch.load(EFFICIENTNET_WEIGHTS_PATH, map_location=device, weights_only=False))
MODEL.to(device)
MODEL.eval()
# Set model info
metrics = report.get('metrics', {})
# Calculate optimal threshold using ROC curve analysis (Youden's J)
AUTO_THRESHOLD = calculate_optimal_threshold_from_metrics(metrics)
MODEL_INFO = {
'model_type': 'efficientnet_fft',
'backbone': backbone,
'image_size': config.get('image_size', 224),
'accuracy': metrics.get('accuracy', 0.89),
'auc_roc': metrics.get('auc_roc', 0.96),
'optimal_threshold': AUTO_THRESHOLD,
}
MODEL_PATH = str(EFFICIENTNET_MODEL_PATH)
print('=' * 60)
print(' LOADED: EfficientNet-B4 + FFT Fusion Model')
print('=' * 60)
print(f" Path: {MODEL_PATH}")
print(f" Backbone: {backbone}")
print(f" Accuracy: {metrics.get('accuracy', 'N/A'):.2%}" if metrics.get('accuracy') else " Accuracy: N/A")
print(f" AUC-ROC: {metrics.get('auc_roc', 'N/A'):.4f}" if metrics.get('auc_roc') else " AUC-ROC: N/A")
print(f" Threshold (ROC-optimized): {AUTO_THRESHOLD:.3f}")
print('=' * 60)
except Exception as e:
print(f'Failed to load EfficientNet model: {e}')
MODEL = None
# ─────────────────────────────────────────────────────────────────────────────
# PRIORITY 2: Fall back to other models
# ─────────────────────────────────────────────────────────────────────────────
if MODEL is None:
# Find all model files sorted by modification time (newest first)
model_candidates = sorted(
list(Path('.').glob('model*.joblib')) +
list(Path('.').glob('model*.pt')) +
[Path('model_fusion_best_improved')] +
[Path('model_fusion_best_improved_best_improved')],
key=lambda p: p.stat().st_mtime if p.exists() else 0,
reverse=True
)
if model_candidates:
for model_path in model_candidates:
try:
# Try loading as joblib first (traditional ML models)
try:
MODEL = joblib.load(str(model_path))
print(f'[OK] Loaded joblib model: {model_path.name}')
except Exception as joblib_err:
# If joblib fails, try loading as PyTorch state dict
try:
state_dict = torch.load(str(model_path), map_location='cpu')
# Check model info to determine the correct class
model_info_path = Path(str(model_path).rsplit('.', 1)[0] + '_info.pkl')
if model_info_path.exists():
MODEL_INFO = joblib.load(str(model_info_path))
else:
MODEL_INFO = {'model_type': 'fusion_improved'}
model_type = MODEL_INFO.get('model_type', 'fusion_improved')
# Reconstruct model based on type
if model_type in ['fusion', 'fusion_improved']:
from train import DeepfakeFeatureFusion
MODEL = DeepfakeFeatureFusion()
MODEL.load_state_dict(state_dict)
MODEL.eval()
if torch.cuda.is_available():
MODEL.cuda()
print(f'[OK] Loaded PyTorch fusion model: {model_path.name}')
elif model_type == 'resnet':
from train import DeepfakeResNet
MODEL = DeepfakeResNet()
MODEL.load_state_dict(state_dict)
MODEL.eval()
print(f'[OK] Loaded PyTorch ResNet model: {model_path.name}')
elif model_type == 'cnn':
from train import DeepfakeCNN
MODEL = DeepfakeCNN()
MODEL.load_state_dict(state_dict)
MODEL.eval()
print(f'[OK] Loaded PyTorch CNN model: {model_path.name}')
else:
raise ValueError(f"Unknown model type: {model_type}")
except Exception as torch_err:
raise Exception(f"Joblib failed: {joblib_err}; PyTorch failed: {torch_err}")
MODEL_PATH = str(model_path)
# Try to load metadata if not already loaded
if MODEL_INFO is None:
model_info_path = Path(str(model_path).rsplit('.', 1)[0] + '_info.pkl')
if not model_info_path.exists():
model_base = str(model_path).replace('.joblib', '').replace('_best_improved', '').replace('_best', '')
for info_candidate in [model_base + '_info.pkl', model_base + '_improved_info.pkl']:
if Path(info_candidate).exists():
model_info_path = Path(info_candidate)
break
if model_info_path.exists():
MODEL_INFO = joblib.load(str(model_info_path))
else:
MODEL_INFO = {'model_type': 'unknown'}
# Display which model was loaded
mod_time = datetime.fromtimestamp(model_path.stat().st_mtime).strftime('%Y-%m-%d %H:%M:%S')
print(f'=== Loaded Latest Model ===')
print(f"Model: {model_path.name}")
print(f"Modified: {mod_time}")
model_type = MODEL_INFO.get("model_type", "unknown") if isinstance(MODEL_INFO, dict) else "unknown"
print(f"Type: {model_type}")
print(f"Path: {model_path.resolve()}")
break
except Exception as e:
print(f'Failed to load {model_path.name}: {e}')
continue
if MODEL is None:
print('No valid model found; falling back to heuristic detection.')
MODEL_INFO = None
MODEL_PATH = None
else:
# Using fixed threshold of 0.80 (disabled auto-calibration)
# Higher threshold = fewer false positives (real images classified as AI)
print(f'Using fixed threshold: {AUTO_THRESHOLD:.3f} (high confidence mode)')
# NOTE: Auto-calibration disabled - uncomment below to re-enable
# try:
# dataset_root = _pick_dataset_root()
# if dataset_root is None:
# raise FileNotFoundError('No validation dataset found')
# AUTO_THRESHOLD = _calibrate_threshold(
# MODEL, MODEL_INFO, dataset_root=dataset_root,
# target_real_fpr=TARGET_REAL_FPR, max_val_images=MAX_CALIB_IMAGES,
# )
# except Exception as e:
# print(f'Auto-threshold calibration failed: {e}')
# Load video model
if Path(VIDEO_MODEL_PATH).exists():
VIDEO_MODEL, VIDEO_MODEL_CONFIG = _load_video_model(VIDEO_MODEL_PATH)
else:
print(f'Video model not found: {VIDEO_MODEL_PATH}')
def extract_image_features_from_array(img_arr, patch_size=128, n_patches=8, random_state=None):
# sample random patches (similar to training script) and pool mean/std
H, W, _ = img_arr.shape
patches = []
rng = np.random.RandomState(random_state)
for _ in range(n_patches):
if H <= patch_size or W <= patch_size:
y0 = max(0, (H - patch_size) // 2)
x0 = max(0, (W - patch_size) // 2)
else:
y0 = int(rng.randint(0, H - patch_size + 1))
x0 = int(rng.randint(0, W - patch_size + 1))
patch = img_arr[y0:y0 + patch_size, x0:x0 + patch_size]
if patch.shape[0] != patch_size or patch.shape[1] != patch_size:
ph = np.zeros((patch_size, patch_size, 3), dtype=patch.dtype)
ph[:patch.shape[0], :patch.shape[1]] = patch
patch = ph
patches.append(patch)
feats = []
for p in patches:
g = rgb_to_gray(p)
res = extract_residual(g)
res_std = float(np.std(res))
_, hf = fft_stats(g)
# LBP expects integer images; convert to uint8 for stability
ent = lbp_entropy((g * 255).astype(np.uint8))
feats.append([res_std, hf, ent])
feats = np.array(feats)
mean = feats.mean(axis=0)
std = feats.std(axis=0)
return np.concatenate([mean, std])[None, :]
def evaluate_model_on_validation(model, dataset_root='C:\\Users\\DESHNA\\Downloads\\UAIDE_enhanced\\CIFAKE'):
p = Path(dataset_root)
val_root = p / 'Validation'
if not val_root.exists():
return 'Validation folder not found under ' + str(p)
real_folder = val_root / 'Real'
fake_folder = val_root / 'Fake'
files_real = sorted([str(x) for x in real_folder.rglob('*.jpg')] + [str(x) for x in real_folder.rglob('*.png')])
files_fake = sorted([str(x) for x in fake_folder.rglob('*.jpg')] + [str(x) for x in fake_folder.rglob('*.png')])
files = [(f, 0) for f in files_real] + [(f, 1) for f in files_fake]
X = []
y = []
for f, lbl in files:
try:
pil = Image.open(f).convert('RGB')
arr = np.asarray(pil).astype(np.float32) / 255.0
feat = extract_image_features_from_array(arr, patch_size=128, n_patches=8, random_state=123)
X.append(feat[0])
y.append(lbl)
except Exception as e:
continue
if len(X) == 0:
return 'No validation images found or feature extraction failed.'
X = np.array(X)
y = np.array(y)
try:
if hasattr(model, 'predict_proba'):
probs = model.predict_proba(X)[:, 1]
else:
probs = model.predict(X).astype(float)
preds = (probs >= 0.5).astype(int)
acc = accuracy_score(y, preds)
try:
auc = roc_auc_score(y, probs)
except Exception:
auc = None
report = classification_report(y, preds)
lines = [f'Val accuracy: {acc:.4f}']
if auc is not None:
lines.append(f'Val ROC AUC: {auc:.4f}')
lines.append('\n'+report)
return '\n'.join(lines)
except Exception as e:
return f'Evaluation failed: {e}'
def predict_video_gradio(video_file, ethical_threshold=0.5, show_raw_features=False):
"""Predict deepfake probability for video input."""
ethical_status = "N/A"
ethical_report = ""
if VIDEO_MODEL is None or VIDEO_MODEL_CONFIG is None:
return "Model Error", 0.0, None, "Video model not loaded", "Please ensure video_resnet_lstm.pt exists"
try:
# Get video path
if isinstance(video_file, str):
video_path = video_file
else:
video_path = video_file.name if hasattr(video_file, 'name') else str(video_file)
# Predict using video model
prob_fake, frame_probs, frames = _predict_video_model(
VIDEO_MODEL,
VIDEO_MODEL_CONFIG,
video_path,
return_frames=True
)
if prob_fake is None:
return "Error", 0.0, None, "Failed to process video", "Video processing failed"
# Apply temperature scaling to reduce overconfidence
prob_fake = apply_temperature_scaling(prob_fake)
# Determine label and threshold
threshold = AUTO_THRESHOLD if AUTO_THRESHOLD is not None else VIDEO_MODEL_CONFIG.get('optimal_threshold', 0.85)
is_ai = prob_fake >= threshold
label = 'AI-generated' if is_ai else 'Real (camera)'
# Create visualization: overlay top suspicious frame
visualization = None
if frames is not None and len(frames) > 0:
try:
# Find frame with highest fake probability
top_frame_idx = int(np.argmax(frame_probs[:, 1]))
frame = frames[top_frame_idx]
# Apply Grad-CAM on this frame
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
target_layer = VIDEO_MODEL.backbone.layer4[-1].conv3
grad_cam = GradCAM(VIDEO_MODEL, target_layer)
transform = _build_video_transform()
frame_tensor = transform(Image.fromarray(frame)).unsqueeze(0).unsqueeze(0).to(device)
cam = grad_cam.generate(frame_tensor, class_idx=1) # Focus on fake class
# Create overlay visualization
visualization = overlay_cam(frame, cam, alpha=0.5)
except Exception as e:
print(f"Grad-CAM visualization failed: {e}")
# Fallback: just use first frame
if frames and len(frames) > 0:
visualization = frames[0]
# Convert to PIL if we have visualization
if visualization is not None:
overlay_pil = Image.fromarray(visualization)
else:
# Create a simple fallback image
overlay_pil = Image.new('RGB', (224, 224), color=(128, 128, 128))
# Perform ethical assessment if AI-generated detected
if is_ai and frames and len(frames) > 0:
# Use first frame for ethical assessment
img_arr = np.asarray(frames[0]).astype(np.float32) / 255.0
assessment = EthicalAssessment.assess(img_arr, threshold=ethical_threshold)
ethical_status = get_enhanced_ethical_status(assessment)
ethical_report = format_ethical_report(assessment)
if not show_raw_features:
idx = ethical_report.find('\nRAW FEATURES:')
if idx != -1:
ethical_report = ethical_report[:idx]
return label, prob_fake, overlay_pil, ethical_status, ethical_report
except Exception as e:
print(f"Video prediction failed: {e}")
return "Error", 0.0, None, "Prediction failed", str(e)
def predict_gradio(pil_img, ethical_threshold=0.5, show_raw_features=False):
# pil_img is a PIL.Image
img = np.asarray(pil_img).astype(np.float32) / 255.0
# Initialize ethical status
ethical_status = "N/A"
ethical_report = ""
if MODEL is not None and MODEL_INFO is not None:
try:
if MODEL_INFO['model_type'] in ['cnn', 'cnn_kfold', 'resnet', 'fusion', 'fusion_improved', 'efficientnet_fft']:
# Deep learning model prediction with Grad-CAM
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Prepare image transform based on model type
if MODEL_INFO['model_type'] == 'efficientnet_fft':
# EfficientNet uses 224x224 (can be higher but 224 works well)
img_size = MODEL_INFO.get('image_size', 224)
transform = transforms.Compose([
transforms.Resize((img_size, img_size)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
elif MODEL_INFO['model_type'] in ['resnet', 'fusion', 'fusion_improved']:
transform = transforms.Compose([
transforms.Lambda(lambda img: pad_to_min_size(img, 224)),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
else:
transform = transforms.Compose([
transforms.Lambda(lambda img: pad_to_min_size(img, 128)),
transforms.CenterCrop(128),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
input_tensor = transform(pil_img).unsqueeze(0).to(device)
# Get prediction
with torch.no_grad():
outputs = MODEL(input_tensor)
probs = torch.softmax(outputs, dim=1)
prob_fake_raw = float(probs[0, 1])
# Apply temperature scaling to reduce overconfidence
prob_fake = apply_temperature_scaling(prob_fake_raw)
# Use fixed threshold to reduce false positives
threshold = AUTO_THRESHOLD if AUTO_THRESHOLD is not None else MODEL_INFO.get('optimal_threshold', 0.85)
pred_class = 1 if prob_fake >= threshold else 0
label = 'AI-generated' if pred_class == 1 else 'Real (camera)'
is_ai = pred_class == 1
# Generate Grad-CAM visualization
try:
overlay_img = apply_gradcam_overlay_from_pil(pil_img, MODEL, MODEL_INFO['model_type'])
overlay_pil = Image.fromarray(overlay_img)
except Exception as e:
print(f"Grad-CAM failed: {e}")
# Fallback to traditional heatmap
patch_scores, coords, img_shape, ps, st = sliding_patch_scores(img, patch_size=128, stride=64)
heat = reconstruct_heatmap(patch_scores, coords, img_shape, ps, st)
overlay_pil = make_overlay_pil(img, heat)
# Perform ethical assessment if AI-generated detected
if is_ai:
assessment = EthicalAssessment.assess(img, threshold=ethical_threshold)
ethical_status = get_enhanced_ethical_status(assessment)
ethical_report = format_ethical_report(assessment)
if not show_raw_features:
idx = ethical_report.find('\nRAW FEATURES:')
if idx != -1:
ethical_report = ethical_report[:idx]
return label, prob_fake, overlay_pil, ethical_status, ethical_report
else:
# Traditional ML model
X = extract_image_features_from_array(img, patch_size=128, n_patches=8, random_state=0)
if hasattr(MODEL, 'predict_proba'):
prob_raw = float(MODEL.predict_proba(X)[:, 1][0])
else:
pred = MODEL.predict(X)[0]
prob_raw = float(pred)
# Apply temperature scaling to reduce overconfidence
prob = apply_temperature_scaling(prob_raw)
# Use fixed threshold to reduce false positives
threshold = AUTO_THRESHOLD if AUTO_THRESHOLD is not None else MODEL_INFO.get('optimal_threshold', 0.85)
is_ai = prob >= threshold
label = 'AI-generated' if is_ai else 'Real (camera)'
# Traditional heatmap
patch_scores, coords, img_shape, ps, st = sliding_patch_scores(img, patch_size=128, stride=64)
heat = reconstruct_heatmap(patch_scores, coords, img_shape, ps, st)
overlay = make_overlay_pil(img, heat)
# Perform ethical assessment if AI-generated detected
if is_ai:
assessment = EthicalAssessment.assess(img, threshold=ethical_threshold)
ethical_status = get_enhanced_ethical_status(assessment)
ethical_report = format_ethical_report(assessment)
if not show_raw_features:
idx = ethical_report.find('\nRAW FEATURES:')
if idx != -1:
ethical_report = ethical_report[:idx]
return label, prob, overlay, ethical_status, ethical_report
except Exception as e:
print(f"Model prediction failed: {e}")
# Fall back to heuristic
# Fallback heuristic
patch_scores, coords, img_shape, ps, st = sliding_patch_scores(img, patch_size=128, stride=64)
heat = reconstruct_heatmap(patch_scores, coords, img_shape, ps, st)
overlay = make_overlay_pil(img, heat)
ai_score_raw = float(np.mean(heat))
# Apply temperature scaling to reduce overconfidence
ai_score = apply_temperature_scaling(ai_score_raw)
threshold = AUTO_THRESHOLD if AUTO_THRESHOLD is not None else 0.85
is_ai = ai_score >= threshold
label = 'AI-generated' if is_ai else 'Real (camera)'
# Perform ethical assessment if AI-generated detected
if is_ai:
assessment = EthicalAssessment.assess(img, threshold=ethical_threshold)
ethical_status = get_enhanced_ethical_status(assessment)
ethical_report = format_ethical_report(assessment)
if not show_raw_features:
idx = ethical_report.find('\nRAW FEATURES:')
if idx != -1:
ethical_report = ethical_report[:idx]
return label, ai_score, overlay, ethical_status, ethical_report
def apply_gradcam_overlay_from_pil(pil_img, model, model_type):
"""Apply Grad-CAM to PIL image for deep learning models"""
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Prepare transform based on model type
if model_type == 'efficientnet_fft':
img_size = MODEL_INFO.get('image_size', 224) if MODEL_INFO else 224
transform = transforms.Compose([
transforms.Resize((img_size, img_size)),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
target_size = (img_size, img_size)
elif model_type in ['resnet', 'fusion', 'fusion_improved']:
transform = transforms.Compose([
transforms.Lambda(lambda img: pad_to_min_size(img, 224)),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
target_size = (224, 224)
else:
transform = transforms.Compose([
transforms.Lambda(lambda img: pad_to_min_size(img, 128)),
transforms.CenterCrop(128),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
target_size = (128, 128)
input_tensor = transform(pil_img).unsqueeze(0).to(device)
# Get target layer based on model type
if model_type == 'efficientnet_fft':
# For EfficientNet, use the last convolutional block
if HAS_TIMM:
# timm EfficientNet structure
target_layer = model.backbone.conv_head
else:
# torchvision EfficientNet structure
target_layer = model.backbone.features[-1]
elif model_type == 'resnet':
target_layer = model.resnet.layer4[-1].conv3
elif model_type in ['fusion', 'fusion_improved']:
target_layer = model.resnet[7][-1].conv3
else:
target_layer = model.conv4
# Use custom simple Grad-CAM for EfficientNet
if model_type == 'efficientnet_fft':
# Simple Grad-CAM implementation
activations = []
gradients = []
def forward_hook(module, input, output):
activations.append(output)
def backward_hook(module, grad_in, grad_out):
gradients.append(grad_out[0])
handle_f = target_layer.register_forward_hook(forward_hook)
handle_b = target_layer.register_full_backward_hook(backward_hook)
model.eval()
output = model(input_tensor)
model.zero_grad()
output[0, 1].backward() # Focus on fake class
handle_f.remove()
handle_b.remove()
# Compute CAM
act = activations[0].detach()
grad = gradients[0].detach()
weights = grad.mean(dim=(2, 3), keepdim=True)
cam = (weights * act).sum(dim=1, keepdim=True)
cam = F.relu(cam)
cam = cam - cam.min()
cam = cam / (cam.max() + 1e-8)
cam = F.interpolate(cam, size=target_size, mode='bilinear', align_corners=False)
cam = cam.squeeze().cpu().numpy()
else:
# Use existing GradCAM from train
from train import GradCAM
grad_cam = GradCAM(model, target_layer)
cam = grad_cam.generate_cam(input_tensor, target_class=1)
# Create overlay
heatmap = cv2.applyColorMap(np.uint8(255 * cam), cv2.COLORMAP_JET)
heatmap = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGB)
# Resize original image
original = cv2.resize(np.array(pil_img), target_size)
# Overlay
overlay = cv2.addWeighted(original, 0.6, heatmap, 0.4, 0)
return overlay
title = "Advanced Deepfake Detection System with Ethical Assessment"
# Create balanced layout using Blocks with tabs for image and video
with gr.Blocks(title=title) as iface:
gr.Markdown(f"""
# {title}
Upload an image or video to detect if it's AI-generated and assess its ethical status.
**Image Model:** {MODEL_INFO['model_type'].upper() if MODEL_INFO else 'Heuristic-based'}
**Video Model:** {'ResNetLSTM (Video)' if VIDEO_MODEL else 'Not loaded'}
""")
with gr.Tabs():
# ===== IMAGE TAB =====
with gr.Tab("Image Detection"):
with gr.Row():
# Left Column - Inputs
with gr.Column(scale=1):
gr.Markdown("### Input")
input_image = gr.Image(type='pil', label='Upload Image', height=400)
gr.Markdown("### Settings")
ethical_threshold_img = gr.Slider(
minimum=0.0,
maximum=1.0,
step=0.01,
value=0.5,
label='Ethical Risk Threshold',
info='Lower = more strict classification'
)
show_raw_features_img = gr.Checkbox(
label='Show raw feature values',
value=False
)
analyze_img_btn = gr.Button("Analyze Image", variant="primary", size="lg")
gr.Markdown("""
---
**Features:**
- Deep Learning: CNN/ResNet with transfer learning
- Grad-CAM visualization highlights suspicious regions
- Ethical assessment evaluates privacy and misuse risks
- Real-time GPU-accelerated inference
""")
# Right Column - Outputs
with gr.Column(scale=1):
gr.Markdown("### Detection Results")
with gr.Row():
detection_result_img = gr.Label(num_top_classes=2, label='Classification')
ai_score_img = gr.Number(label='AI-likelihood Score', precision=4)
heatmap_img = gr.Image(label='Detection Heatmap', height=400)
gr.Markdown("### Ethical Assessment")
ethical_status_img = gr.Textbox(label='Status', lines=2)
with gr.Accordion("Full Report", open=False):
ethical_report_img = gr.Textbox(
label='Detailed Assessment',
lines=30
)
# Connect image button to function
analyze_img_btn.click(
fn=predict_gradio,
inputs=[input_image, ethical_threshold_img, show_raw_features_img],
outputs=[detection_result_img, ai_score_img, heatmap_img, ethical_status_img, ethical_report_img]
)
# ===== VIDEO TAB =====
with gr.Tab("Video Detection"):
with gr.Row():
# Left Column - Inputs
with gr.Column(scale=1):
gr.Markdown("### Input")
input_video = gr.Video(label='Upload Video', height=400)
gr.Markdown("### Settings")
ethical_threshold_vid = gr.Slider(
minimum=0.0,
maximum=1.0,
step=0.01,
value=0.5,
label='Ethical Risk Threshold',
info='Lower = more strict classification'
)
show_raw_features_vid = gr.Checkbox(
label='Show raw feature values',
value=False
)
analyze_vid_btn = gr.Button("Analyze Video", variant="primary", size="lg")
gr.Markdown("""
---
**Features:**
- ResNetLSTM: temporal modeling with LSTM
- Frame-level & video-level predictions
- Grad-CAM on most suspicious frame
- Ethical assessment from frame content
- Supports MP4, AVI, MOV, MKV, WebM
""")
# Right Column - Outputs
with gr.Column(scale=1):
gr.Markdown("### Detection Results")
with gr.Row():
detection_result_vid = gr.Label(num_top_classes=2, label='Classification')
ai_score_vid = gr.Number(label='AI-likelihood Score', precision=4)
heatmap_vid = gr.Image(label='Suspicious Frame (with Grad-CAM)', height=400)
gr.Markdown("### Ethical Assessment")
ethical_status_vid = gr.Textbox(label='Status', lines=2)
with gr.Accordion("Full Report", open=False):
ethical_report_vid = gr.Textbox(
label='Detailed Assessment',
lines=30
)
# Connect video button to function
analyze_vid_btn.click(
fn=predict_video_gradio,
inputs=[input_video, ethical_threshold_vid, show_raw_features_vid],
outputs=[detection_result_vid, ai_score_vid, heatmap_vid, ethical_status_vid, ethical_report_vid]
)
gr.Markdown("""
---
**How it works:**
- **Image**: The heatmap overlay shows regions the model considers suspicious for deepfake artifacts.
- **Video**: Frames are processed temporally, and the most suspicious frame is highlighted with Grad-CAM.
- Ethical classification is based on artifact detectability and human face presence.
*Powered by FHIBE Dataset concepts for face authenticity verification.*
""")
if __name__ == '__main__':
iface.launch()
=======
import io
from PIL import Image
import numpy as np
import gradio as gr
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import joblib
from pathlib import Path
from sklearn.metrics import accuracy_score, roc_auc_score, classification_report
import torch
import torch.nn as nn
from torchvision import transforms
import torchvision.transforms.functional as TF
import cv2
from detector import sliding_patch_scores, reconstruct_heatmap, rgb_to_gray, extract_residual, fft_stats, lbp_entropy
from ethical_assessment import EthicalAssessment, format_ethical_report, get_simple_status
def make_overlay_pil(img_arr, heatmap, alpha=0.5, cmap='jet'):
# img_arr: HxWx3 in [0,1]
plt.figure(figsize=(6, 6), dpi=100)
plt.imshow(np.clip(img_arr, 0, 1))
plt.imshow(heatmap, cmap=cmap, alpha=alpha, vmin=0, vmax=1)
plt.axis('off')
buf = io.BytesIO()
plt.savefig(buf, format='png', bbox_inches='tight', pad_inches=0)
plt.close()
buf.seek(0)
return Image.open(buf).convert('RGB')
def pad_to_min_size(img, size):
w, h = img.size
pad_w = max(0, size - w)
pad_h = max(0, size - h)
if pad_w or pad_h:
left = pad_w // 2
right = pad_w - left
top = pad_h // 2
bottom = pad_h - top
img = TF.pad(img, [left, top, right, bottom], padding_mode='reflect')
return img
# Load the trained model (try different model files)
MODEL_PATH = None
MODEL = None
MODEL_INFO = None
AUTO_THRESHOLD = None
TARGET_REAL_FPR = 0.05
MAX_CALIB_IMAGES = 200
def _get_validation_files(dataset_root, max_val_images=None):
val_root = Path(dataset_root) / 'Validation'
if not val_root.exists():
return None, None
real_files = list((val_root / 'Real').rglob('*.jpg')) + list((val_root / 'Real').rglob('*.png'))
fake_files = list((val_root / 'Fake').rglob('*.jpg')) + list((val_root / 'Fake').rglob('*.png'))
real_files = sorted([str(x) for x in real_files])
fake_files = sorted([str(x) for x in fake_files])
if max_val_images:
real_files = real_files[:max_val_images]
fake_files = fake_files[:max_val_images]
files = real_files + fake_files
labels = [0] * len(real_files) + [1] * len(fake_files)
if len(files) == 0:
return None, None
return files, labels
def _get_transform(model_type):
if model_type in ['resnet', 'fusion', 'fusion_improved', 'fusion_dual']:
size = 224
else:
size = 128
return transforms.Compose([
transforms.Lambda(lambda img: pad_to_min_size(img, size)),
transforms.CenterCrop(size),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
def _evaluate_deep_model(model, model_type, dataset_root, max_val_images=None):
from train import ImageDataset
from torch.utils.data import DataLoader
files, labels = _get_validation_files(dataset_root, max_val_images=max_val_images)
if files is None:
return None
transform = _get_transform(model_type)
dataset = ImageDataset(files, labels, transform=transform)
dataloader = DataLoader(dataset, batch_size=8, shuffle=False, num_workers=0)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.eval()
all_probs = []
all_labels = []
with torch.no_grad():
for inputs, lbls in dataloader:
inputs = inputs.to(device)
outputs = model(inputs)
probs = torch.softmax(outputs, dim=1)[:, 1].cpu().numpy()
all_probs.extend(probs.tolist())
all_labels.extend(lbls)
if len(all_labels) == 0:
return None
y_true = np.array(all_labels)
y_prob = np.array(all_probs)
y_pred = (y_prob >= 0.5).astype(int)
acc = accuracy_score(y_true, y_pred)
try:
auc = roc_auc_score(y_true, y_prob)
except Exception:
auc = None
report = classification_report(y_true, y_pred, zero_division=0)
return {
'accuracy': acc,
'auc': auc,
'report': report,
'count': len(y_true)
}
def _evaluate_ml_model(model, dataset_root, max_val_images=None, patch_size=128, n_patches=4):
from train import collect_features
val_root = Path(dataset_root) / 'Validation'
if not val_root.exists():
return None
real_val = val_root / 'Real'
fake_val = val_root / 'Fake'
Xrv, yrv = collect_features(real_val, 0, max_images=max_val_images, patch_size=patch_size, n_patches=n_patches)
Xfv, yfv = collect_features(fake_val, 1, max_images=max_val_images, patch_size=patch_size, n_patches=n_patches)
if len(Xrv) + len(Xfv) == 0:
return None
Xv = np.array(Xrv + Xfv)
yv = np.array(yrv + yfv)
if hasattr(model, 'predict_proba'):
probs = model.predict_proba(Xv)[:, 1]
else:
probs = model.predict(Xv).astype(float)
preds = (probs >= 0.5).astype(int)
acc = accuracy_score(yv, preds)
try:
auc = roc_auc_score(yv, probs)
except Exception:
auc = None
report = classification_report(yv, preds, zero_division=0)
return {
'accuracy': acc,
'auc': auc,
'report': report,
'count': len(yv)
}
def _calibrate_threshold(model, model_info, dataset_root, target_real_fpr=0.05, max_val_images=200):
if model is None or model_info is None:
return None
val_root = Path(dataset_root) / 'Validation'
if not val_root.exists():
return None
mtype = model_info.get('model_type', 'unknown') if isinstance(model_info, dict) else 'unknown'
real_probs = []
if mtype in ['cnn', 'cnn_kfold', 'resnet', 'fusion', 'fusion_improved', 'fusion_dual']:
from train import ImageDataset
from torch.utils.data import DataLoader
files, labels = _get_validation_files(dataset_root, max_val_images=max_val_images)
if files is None:
return None
real_files = [f for f, lbl in zip(files, labels) if lbl == 0]
if len(real_files) == 0:
return None
transform = _get_transform(mtype)
dataset = ImageDataset(real_files, [0] * len(real_files), transform=transform)
dataloader = DataLoader(dataset, batch_size=8, shuffle=False, num_workers=0)
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.eval()
with torch.no_grad():
for inputs, _ in dataloader:
inputs = inputs.to(device)
outputs = model(inputs)
probs = torch.softmax(outputs, dim=1)[:, 1].cpu().numpy()
real_probs.extend(probs.tolist())
else:
from train import collect_features
real_val = val_root / 'Real'
Xrv, _ = collect_features(real_val, 0, max_images=max_val_images, patch_size=128, n_patches=4)
if len(Xrv) == 0:
return None
X = np.array(Xrv)
if hasattr(model, 'predict_proba'):
probs = model.predict_proba(X)[:, 1]
else:
probs = model.predict(X).astype(float)
real_probs.extend(probs.tolist())
if len(real_probs) == 0:
return None
real_probs = np.array(real_probs)
target = max(0.0, min(1.0, float(target_real_fpr)))
if target <= 0.0:
return float(np.max(real_probs))
return float(np.quantile(real_probs, 1.0 - target))
def _load_model_from_info(info_path):
info = joblib.load(str(info_path))
if not isinstance(info, dict) or 'model_type' not in info:
return None, None
mtype = info['model_type']
model = None
if mtype in ['cnn', 'cnn_kfold', 'resnet', 'fusion', 'fusion_improved', 'fusion_dual']:
if mtype == 'resnet':
from train import DeepfakeResNet as _ModelClass
elif mtype == 'fusion_dual':
from train import DeepfakeDualStream as _ModelClass
elif mtype in ['fusion', 'fusion_improved']:
from train import DeepfakeFeatureFusion as _ModelClass
else:
from train import DeepfakeCNN as _ModelClass
model = _ModelClass()
state_path = info.get('state_dict_path')
if state_path is None:
base = str(info_path).replace('_improved_info.pkl', '').replace('_info.pkl', '')
candidates = [base + '_best_improved', base + '_best', base]
for c in candidates:
if Path(c).exists():
state_path = c
break
if state_path is None:
raise FileNotFoundError('state_dict_path not found in model info and no candidate file exists')
model.load_state_dict(torch.load(state_path, map_location='cpu'))
model.eval()
if torch.cuda.is_available():
model.to(torch.device('cuda'))
else:
base = str(info_path).replace('_improved_info.pkl', '').replace('_info.pkl', '')
joblib_path = base + '.joblib'
if Path(joblib_path).exists():
model = joblib.load(joblib_path)
else:
model = info
return model, info
def load_model_fast():
"""Load a model quickly without validation evaluation for faster startup."""
# Prioritize improved models
info_candidates = list(Path('.').glob('*_improved_info.pkl')) + list(Path('.').glob('*_info.pkl'))
info_candidates = sorted(info_candidates, key=lambda p: p.name, reverse=True)
if not info_candidates:
return None, None, None
# Try to load the first valid model without evaluation
for info_path in info_candidates:
try:
model, info = _load_model_from_info(info_path)
mtype = info.get('model_type', 'unknown') if isinstance(info, dict) else 'unknown'
print(f'=== Loaded Model (fast mode) ===')
print(f"Model: {info_path.name}")
print(f"Type: {mtype}")
return model, info, str(info_path)
except Exception as e:
print(f"Skipping {info_path.name}: failed to load model ({e})")
continue
return None, None, None
def select_best_model(dataset_root='DeepfakeVsReal/Dataset', max_val_images=None):
info_candidates = list(Path('.').glob('*_improved_info.pkl')) + list(Path('.').glob('*_info.pkl'))
info_candidates = sorted(info_candidates, key=lambda p: p.name, reverse=True)
if not info_candidates:
return None, None, None
scored = []
for info_path in info_candidates:
try:
model, info = _load_model_from_info(info_path)
except Exception as e:
print(f"Skipping {info_path.name}: failed to load model ({e})")
continue
mtype = info.get('model_type', 'unknown') if isinstance(info, dict) else 'unknown'
stats = None
if mtype in ['cnn', 'cnn_kfold', 'resnet', 'fusion', 'fusion_improved', 'fusion_dual']:
stats = _evaluate_deep_model(model, mtype, dataset_root, max_val_images=max_val_images)
else:
patch_size = info.get('patch_size', 128) if isinstance(info, dict) else 128
n_patches = info.get('patches_per_image', 4) if isinstance(info, dict) else 4
stats = _evaluate_ml_model(model, dataset_root, max_val_images=max_val_images, patch_size=patch_size, n_patches=n_patches)
if stats is None:
print(f"Skipping {info_path.name}: no validation stats")
continue
scored.append((info_path, model, info, stats))
auc = stats.get('auc')
acc = stats.get('accuracy')
print('\n=== Model Evaluation ===')
print(f"Model: {info_path.name} | type: {mtype}")
print(f"Val samples: {stats.get('count', 0)}")
print(f"Accuracy: {acc:.4f}")
if auc is not None:
print(f"ROC AUC: {auc:.4f}")
print('Classification report:')
print(stats.get('report', 'N/A'))
if not scored:
return None, None, None
def _score_key(item):
_, _, _, st = item
auc = st.get('auc')
acc = st.get('accuracy')
return (auc if auc is not None else -1.0, acc)
scored.sort(key=_score_key, reverse=True)
best_path, best_model, best_info, best_stats = scored[0]
print('\n=== Selected Best Model ===')
print(f"Model: {best_path.name}")
print(f"Type: {best_info.get('model_type', 'unknown') if isinstance(best_info, dict) else 'unknown'}")
print(f"Accuracy: {best_stats.get('accuracy'):.4f}")
if best_stats.get('auc') is not None:
print(f"ROC AUC: {best_stats.get('auc'):.4f}")
return best_model, best_info, str(best_path)
# Use fast loading for Gradio startup - no validation evaluation
MODEL_INFO_PATH = Path('model_fusion_best.joblib_info.pkl')
if MODEL_INFO_PATH.exists():
try:
MODEL, MODEL_INFO = _load_model_from_info(MODEL_INFO_PATH)
MODEL_PATH = str(MODEL_INFO_PATH)
print(f'=== Loaded Fixed Model ===')
print(f"Model: {MODEL_INFO_PATH.name}")
print(f"Type: {MODEL_INFO.get('model_type', 'unknown') if isinstance(MODEL_INFO, dict) else 'unknown'}")
except Exception as e:
print(f'Failed to load fixed model {MODEL_INFO_PATH.name}: {e}')
MODEL = None
MODEL_INFO = None
MODEL_PATH = None
else:
print(f'Fixed model info not found: {MODEL_INFO_PATH.name}')
if MODEL is None:
print('No valid model loaded; falling back to heuristic detection.')
else:
try:
AUTO_THRESHOLD = _calibrate_threshold(
MODEL,
MODEL_INFO,
dataset_root='DeepfakeVsReal/Dataset',
target_real_fpr=TARGET_REAL_FPR,
max_val_images=MAX_CALIB_IMAGES,
)
if AUTO_THRESHOLD is not None:
print(f'Auto-threshold (target real FPR {TARGET_REAL_FPR:.2%}): {AUTO_THRESHOLD:.3f}')
except Exception as e:
print(f'Auto-threshold calibration failed: {e}')
def extract_image_features_from_array(img_arr, patch_size=128, n_patches=8, random_state=None):
# sample random patches (similar to training script) and pool mean/std
H, W, _ = img_arr.shape
patches = []
rng = np.random.RandomState(random_state)
for _ in range(n_patches):
if H <= patch_size or W <= patch_size:
y0 = max(0, (H - patch_size) // 2)
x0 = max(0, (W - patch_size) // 2)
else:
y0 = int(rng.randint(0, H - patch_size + 1))
x0 = int(rng.randint(0, W - patch_size + 1))
patch = img_arr[y0:y0 + patch_size, x0:x0 + patch_size]
if patch.shape[0] != patch_size or patch.shape[1] != patch_size:
ph = np.zeros((patch_size, patch_size, 3), dtype=patch.dtype)
ph[:patch.shape[0], :patch.shape[1]] = patch
patch = ph
patches.append(patch)
feats = []
for p in patches:
g = rgb_to_gray(p)
res = extract_residual(g)
res_std = float(np.std(res))
_, hf = fft_stats(g)
# LBP expects integer images; convert to uint8 for stability
ent = lbp_entropy((g * 255).astype(np.uint8))
feats.append([res_std, hf, ent])
feats = np.array(feats)
mean = feats.mean(axis=0)
std = feats.std(axis=0)
return np.concatenate([mean, std])[None, :]
def evaluate_model_on_validation(model, dataset_root='DeepfakeVsReal/Dataset'):
p = Path(dataset_root)
val_root = p / 'Validation'
if not val_root.exists():
return 'Validation folder not found under ' + str(p)
real_folder = val_root / 'Real'
fake_folder = val_root / 'Fake'
files_real = sorted([str(x) for x in real_folder.rglob('*.jpg')] + [str(x) for x in real_folder.rglob('*.png')])
files_fake = sorted([str(x) for x in fake_folder.rglob('*.jpg')] + [str(x) for x in fake_folder.rglob('*.png')])
files = [(f, 0) for f in files_real] + [(f, 1) for f in files_fake]
X = []
y = []
for f, lbl in files:
try:
pil = Image.open(f).convert('RGB')
arr = np.asarray(pil).astype(np.float32) / 255.0
feat = extract_image_features_from_array(arr, patch_size=128, n_patches=8, random_state=123)
X.append(feat[0])
y.append(lbl)
except Exception as e:
continue
if len(X) == 0:
return 'No validation images found or feature extraction failed.'
X = np.array(X)
y = np.array(y)
try:
if hasattr(model, 'predict_proba'):
probs = model.predict_proba(X)[:, 1]
else:
probs = model.predict(X).astype(float)
preds = (probs >= 0.5).astype(int)
acc = accuracy_score(y, preds)
try:
auc = roc_auc_score(y, probs)
except Exception:
auc = None
report = classification_report(y, preds)
lines = [f'Val accuracy: {acc:.4f}']
if auc is not None:
lines.append(f'Val ROC AUC: {auc:.4f}')
lines.append('\n'+report)
return '\n'.join(lines)
except Exception as e:
return f'Evaluation failed: {e}'
def predict_gradio(pil_img, ethical_threshold=0.5, show_raw_features=False):
# pil_img is a PIL.Image
img = np.asarray(pil_img).astype(np.float32) / 255.0
# Initialize ethical status
ethical_status = "N/A"
ethical_report = ""
if MODEL is not None and MODEL_INFO is not None:
try:
if MODEL_INFO['model_type'] in ['cnn', 'cnn_kfold', 'resnet', 'fusion', 'fusion_improved', 'fusion_dual']:
# Deep learning model prediction with Grad-CAM
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Prepare image transform
if MODEL_INFO['model_type'] in ['resnet', 'fusion', 'fusion_improved']:
transform = transforms.Compose([
transforms.Lambda(lambda img: pad_to_min_size(img, 224)),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
else:
transform = transforms.Compose([
transforms.Lambda(lambda img: pad_to_min_size(img, 128)),
transforms.CenterCrop(128),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
input_tensor = transform(pil_img).unsqueeze(0).to(device)
# Get prediction
with torch.no_grad():
outputs = MODEL(input_tensor)
probs = torch.softmax(outputs, dim=1)
prob_fake = float(probs[0, 1])
# Use auto-calibrated threshold to reduce false positives
threshold = AUTO_THRESHOLD if AUTO_THRESHOLD is not None else MODEL_INFO.get('optimal_threshold', 0.5)
pred_class = 1 if prob_fake >= threshold else 0
label = 'AI-generated' if pred_class == 1 else 'Real (camera)'
is_ai = pred_class == 1
# Generate Grad-CAM visualization
try:
overlay_img = apply_gradcam_overlay_from_pil(pil_img, MODEL, MODEL_INFO['model_type'])
overlay_pil = Image.fromarray(overlay_img)
except Exception as e:
print(f"Grad-CAM failed: {e}")
# Fallback to traditional heatmap
patch_scores, coords, img_shape, ps, st = sliding_patch_scores(img, patch_size=128, stride=64)
heat = reconstruct_heatmap(patch_scores, coords, img_shape, ps, st)
overlay_pil = make_overlay_pil(img, heat)
# Perform ethical assessment if AI-generated detected
if is_ai:
assessment = EthicalAssessment.assess(img, threshold=ethical_threshold)
ethical_status = get_simple_status(assessment)
ethical_report = format_ethical_report(assessment)
if not show_raw_features:
idx = ethical_report.find('\nRAW FEATURES:')
if idx != -1:
ethical_report = ethical_report[:idx]
return label, prob_fake, overlay_pil, ethical_status, ethical_report
else:
# Traditional ML model
X = extract_image_features_from_array(img, patch_size=128, n_patches=8, random_state=0)
if hasattr(MODEL, 'predict_proba'):
prob = float(MODEL.predict_proba(X)[:, 1][0])
else:
pred = MODEL.predict(X)[0]
prob = float(pred)
# Use auto-calibrated threshold to reduce false positives
threshold = AUTO_THRESHOLD if AUTO_THRESHOLD is not None else MODEL_INFO.get('optimal_threshold', 0.5)
is_ai = prob >= threshold
label = 'AI-generated' if is_ai else 'Real (camera)'
# Traditional heatmap
patch_scores, coords, img_shape, ps, st = sliding_patch_scores(img, patch_size=128, stride=64)
heat = reconstruct_heatmap(patch_scores, coords, img_shape, ps, st)
overlay = make_overlay_pil(img, heat)
# Perform ethical assessment if AI-generated detected
if is_ai:
assessment = EthicalAssessment.assess(img, threshold=ethical_threshold)
ethical_status = get_simple_status(assessment)
ethical_report = format_ethical_report(assessment)
if not show_raw_features:
idx = ethical_report.find('\nRAW FEATURES:')
if idx != -1:
ethical_report = ethical_report[:idx]
return label, prob, overlay, ethical_status, ethical_report
except Exception as e:
print(f"Model prediction failed: {e}")
# Fall back to heuristic
# Fallback heuristic
patch_scores, coords, img_shape, ps, st = sliding_patch_scores(img, patch_size=128, stride=64)
heat = reconstruct_heatmap(patch_scores, coords, img_shape, ps, st)
overlay = make_overlay_pil(img, heat)
ai_score = float(np.mean(heat))
threshold = AUTO_THRESHOLD if AUTO_THRESHOLD is not None else 0.5
is_ai = ai_score >= threshold
label = 'AI-generated' if is_ai else 'Real (camera)'
# Perform ethical assessment if AI-generated detected
if is_ai:
assessment = EthicalAssessment.assess(img, threshold=ethical_threshold)
ethical_status = get_simple_status(assessment)
ethical_report = format_ethical_report(assessment)
if not show_raw_features:
idx = ethical_report.find('\nRAW FEATURES:')
if idx != -1:
ethical_report = ethical_report[:idx]
return label, ai_score, overlay, ethical_status, ethical_report
def apply_gradcam_overlay_from_pil(pil_img, model, model_type):
"""Apply Grad-CAM to PIL image for deep learning models"""
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
# Prepare transform
if model_type in ['resnet', 'fusion', 'fusion_improved']:
transform = transforms.Compose([
transforms.Lambda(lambda img: pad_to_min_size(img, 224)),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
target_size = (224, 224)
else:
transform = transforms.Compose([
transforms.Lambda(lambda img: pad_to_min_size(img, 128)),
transforms.CenterCrop(128),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])
target_size = (128, 128)
input_tensor = transform(pil_img).unsqueeze(0).to(device)
# Get Grad-CAM
from train import GradCAM
if model_type == 'resnet':
target_layer = model.resnet.layer4[-1].conv3
elif model_type in ['fusion', 'fusion_improved']:
# For fusion model, use ResNet's last conv layer
target_layer = model.resnet[7][-1].conv3 # layer4 of ResNet
else:
target_layer = model.conv4 # Last conv layer of custom CNN
grad_cam = GradCAM(model, target_layer)
cam = grad_cam.generate_cam(input_tensor, target_class=1) # Focus on fake class
# Create overlay
heatmap = cv2.applyColorMap(np.uint8(255 * cam), cv2.COLORMAP_JET)
heatmap = cv2.cvtColor(heatmap, cv2.COLOR_BGR2RGB)
# Resize original image
original = cv2.resize(np.array(pil_img), target_size)
# Overlay
overlay = cv2.addWeighted(original, 0.6, heatmap, 0.4, 0)
return overlay
title = "Advanced Deepfake Detection System with Ethical Assessment"
# Create balanced layout using Blocks
with gr.Blocks(title=title) as iface:
gr.Markdown(f"""
# {title}
Upload an image to detect if it's AI-generated and assess its ethical status.
**Current Model:** {MODEL_INFO['model_type'].upper() if MODEL_INFO else 'Heuristic-based'}
""")
with gr.Row():
# Left Column - Inputs
with gr.Column(scale=1):
gr.Markdown("### Input")
input_image = gr.Image(type='pil', label='Upload Image', height=400)
gr.Markdown("### Settings")
ethical_threshold = gr.Slider(
minimum=0.0,
maximum=1.0,
step=0.01,
value=0.5,
label='Ethical Risk Threshold',
info='Lower = more strict classification'
)
show_raw_features = gr.Checkbox(
label='Show raw feature values',
value=False
)
analyze_btn = gr.Button("Analyze Image", variant="primary", size="lg")
gr.Markdown("""
---
**Features:**
- Deep Learning: CNN/ResNet with transfer learning
- Grad-CAM visualization highlights suspicious regions
- Ethical assessment evaluates privacy and misuse risks
- Real-time GPU-accelerated inference
""")
# Right Column - Outputs
with gr.Column(scale=1):
gr.Markdown("### Detection Results")
with gr.Row():
detection_result = gr.Label(num_top_classes=2, label='Classification')
ai_score = gr.Number(label='AI-likelihood Score', precision=4)
heatmap = gr.Image(label='Detection Heatmap', height=400)
gr.Markdown("### Ethical Assessment")
ethical_status = gr.Textbox(label='Status', lines=2)
with gr.Accordion("Full Report", open=False):
ethical_report = gr.Textbox(
label='Detailed Assessment',
lines=30
)
gr.Markdown("""
---
**How it works:** The heatmap overlay shows regions the model considers suspicious for deepfake artifacts.
Ethical classification is based on artifact detectability and human face presence.
*Powered by FHIBE Dataset concepts for face authenticity verification.*
""")
# Connect button to function
analyze_btn.click(
fn=predict_gradio,
inputs=[input_image, ethical_threshold, show_raw_features],
outputs=[detection_result, ai_score, heatmap, ethical_status, ethical_report]
)
if __name__ == '__main__':
iface.launch()
>>>>>>> 65ab9814191b6bb448da441c53a768594e7d1d59