# ============================================ # app.py — TruthLens Deepfake Detector # HuggingFace Spaces — Works on CPU + ZeroGPU # ============================================ import gradio as gr import torch import torch.nn as nn import torch.nn.functional as F from torchvision import transforms, models from PIL import Image, ImageFilter import numpy as np # Auto-detect ZeroGPU vs CPU try: import spaces HAS_ZEROGPU = True print("ZeroGPU detected — GPU mode enabled") except ImportError: HAS_ZEROGPU = False print("No ZeroGPU — running on CPU") DEVICE = "cpu" # Will switch to cuda inside @spaces.GPU # ============================================ # Model: EfficientNet-B0 (lightweight, fast on CPU too) # Using B0 instead of B4 for speed on free tier # Swap to B4 + your ImageCLEF weights when on PRO # ============================================ class DeepfakeDetector(nn.Module): def __init__(self): super().__init__() self.backbone = models.efficientnet_b0(weights="IMAGENET1K_V1") n = self.backbone.classifier[1].in_features self.backbone.classifier = nn.Sequential( nn.Dropout(0.3), nn.Linear(n, 256), nn.ReLU(), nn.Dropout(0.2), nn.Linear(256, 1), ) def forward(self, x): return self.backbone(x) print("Loading model...") model = DeepfakeDetector() model.eval() print(f"Model loaded ({sum(p.numel() for p in model.parameters()):,} params)") # ============================================ # Preprocessing # ============================================ preprocess = transforms.Compose([ transforms.Resize((224, 224)), transforms.ToTensor(), transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]), ]) # ============================================ # Analysis Functions # ============================================ def frequency_analysis(img: Image.Image): """FFT-based frequency domain analysis.""" gray = np.array(img.convert("L"), dtype=np.float32) f = np.fft.fftshift(np.fft.fft2(gray)) magnitude = np.log1p(np.abs(f)) # Normalize for display mag_norm = (magnitude - magnitude.min()) / (magnitude.max() - magnitude.min() + 1e-8) freq_img = Image.fromarray((mag_norm * 255).astype(np.uint8)) # Compute anomaly metrics h, w = magnitude.shape center = magnitude[h // 4:3 * h // 4, w // 4:3 * w // 4] outer = np.concatenate([ magnitude[:h // 4].flatten(), magnitude[3 * h // 4:].flatten(), magnitude[:, :w // 4].flatten(), magnitude[:, 3 * w // 4:].flatten(), ]) ratio = float(np.mean(outer) / (np.mean(center) + 1e-8)) spectral_std = float(np.std(magnitude)) return freq_img, ratio, spectral_std def edge_analysis(img: Image.Image): """Edge density analysis — AI images often have smoother edges.""" gray = np.array(img.convert("L"), dtype=np.float32) # Sobel-like edge detection gx = np.abs(gray[:, 1:] - gray[:, :-1]) gy = np.abs(gray[1:, :] - gray[:-1, :]) edge_density = float((np.mean(gx) + np.mean(gy)) / 2) return edge_density def noise_analysis(img: Image.Image): """ Noise residual analysis. Real photos have sensor noise; AI images have different patterns. """ arr = np.array(img.convert("RGB"), dtype=np.float32) # Simple denoising: median filter denoised = np.array(img.filter(ImageFilter.MedianFilter(3)), dtype=np.float32) noise = arr - denoised noise_level = float(np.std(noise)) # Noise uniformity (AI tends to have more uniform noise) h, w = noise.shape[:2] quadrants = [ noise[:h // 2, :w // 2], noise[:h // 2, w // 2:], noise[h // 2:, :w // 2], noise[h // 2:, w // 2:], ] q_stds = [float(np.std(q)) for q in quadrants] uniformity = 1.0 - (max(q_stds) - min(q_stds)) / (max(q_stds) + 1e-8) return noise_level, uniformity # ============================================ # Main Detection Function # ============================================ def _detect(image_array): """Core detection logic — called with or without GPU.""" if image_array is None: return "⚠️ Please upload an image to analyze.", None img = Image.fromarray(image_array).convert("RGB") # 1. CNN spatial analysis tensor = preprocess(img).unsqueeze(0) if torch.cuda.is_available(): tensor = tensor.cuda() model.cuda() with torch.no_grad(): logit = model(tensor) cnn_score = torch.sigmoid(logit).item() if torch.cuda.is_available(): model.cpu() # 2. Frequency analysis freq_img, freq_ratio, spectral_std = frequency_analysis(img) # 3. Edge analysis edge_density = edge_analysis(img) # 4. Noise analysis noise_level, noise_uniformity = noise_analysis(img) # ---- Multi-signal fusion ---- # Heuristic scoring (replace with trained fusion when you have labels) signals = { "cnn": cnn_score, "freq_anomaly": min(max((freq_ratio - 0.15) * 3, 0), 1), "edge_smooth": min(max(1.0 - (edge_density / 30), 0), 1), "noise_uniform": noise_uniformity, } # Weighted ensemble weights = {"cnn": 0.50, "freq_anomaly": 0.20, "edge_smooth": 0.15, "noise_uniform": 0.15} final_score = sum(signals[k] * weights[k] for k in weights) final_score = min(max(final_score, 0), 1) fake_pct = final_score * 100 real_pct = (1 - final_score) * 100 if final_score > 0.65: verdict = f"⚠️ LIKELY AI-GENERATED" emoji = "🔴" elif final_score > 0.40: verdict = f"⚡ INCONCLUSIVE" emoji = "🟡" else: verdict = f"✅ LIKELY AUTHENTIC" emoji = "🟢" analysis = f"""## {emoji} {verdict} **Composite Score: {fake_pct:.1f}% fake / {real_pct:.1f}% real** --- ### Signal Breakdown | Signal | Score | Weight | Contribution | |--------|-------|--------|-------------| | 🧠 CNN Spatial | {signals['cnn']:.3f} | 50% | {signals['cnn'] * weights['cnn']:.3f} | | 📊 Frequency Anomaly | {signals['freq_anomaly']:.3f} | 20% | {signals['freq_anomaly'] * weights['freq_anomaly']:.3f} | | ✏️ Edge Smoothness | {signals['edge_smooth']:.3f} | 15% | {signals['edge_smooth'] * weights['edge_smooth']:.3f} | | 🔬 Noise Uniformity | {signals['noise_uniform']:.3f} | 15% | {signals['noise_uniform'] * weights['noise_uniform']:.3f} | ### Detailed Metrics | Metric | Value | Interpretation | |--------|-------|---------------| | Frequency Ratio | {freq_ratio:.4f} | {'Unusual' if freq_ratio > 0.25 else 'Normal'} | | Spectral Std | {spectral_std:.2f} | {'Low variance (suspicious)' if spectral_std < 1.5 else 'Natural'} | | Edge Density | {edge_density:.2f} | {'Smooth (AI-like)' if edge_density < 15 else 'Textured (photo-like)'} | | Noise Level | {noise_level:.2f} | {'Low (suspicious)' if noise_level < 3 else 'Natural sensor noise'} | | Noise Uniformity | {noise_uniformity:.3f} | {'Uniform (AI-like)' if noise_uniformity > 0.85 else 'Varied (natural)'} | --- *TruthLens v1.0 — Multi-signal analysis (CNN + frequency + edge + noise)* *[API Access →](https://rapidapi.com) · Built by [AgenticEdge.in](https://agenticedge.in)* """ return analysis, freq_img # ---- GPU/CPU wrapper ---- if HAS_ZEROGPU: @spaces.GPU(duration=20) def detect_deepfake(image): return _detect(image) else: def detect_deepfake(image): return _detect(image) # ============================================ # Gradio UI # ============================================ CUSTOM_CSS = """ .gradio-container { max-width: 920px !important; margin: auto; } .gr-button-primary { background: linear-gradient(135deg, #7c3aed, #2563eb) !important; font-size: 1.1em !important; } footer { display: none !important; } """ with gr.Blocks( title="🔍 TruthLens — Deepfake Detector", theme=gr.themes.Soft(primary_hue="violet", neutral_hue="slate"), css=CUSTOM_CSS, ) as demo: gr.Markdown(""" # 🔍 TruthLens — Is This Image Real or AI-Generated? Multi-signal deepfake detection combining **deep learning** (EfficientNet CNN), **frequency domain analysis** (FFT), **edge density**, and **noise pattern** analysis. Upload any image to get an instant analysis. **Free.** No signup needed. > 🚀 **Developer?** Use the API tab below to integrate into your app — > or get a production API key at [RapidAPI →](https://rapidapi.com) """) with gr.Row(): with gr.Column(scale=1): input_image = gr.Image( label="Upload an image to analyze", type="numpy", height=380, ) with gr.Row(): detect_btn = gr.Button( "🔍 Analyze Image", variant="primary", size="lg", scale=2, ) clear_btn = gr.ClearButton( [input_image], value="Clear", size="lg", scale=1, ) with gr.Column(scale=1): result_text = gr.Markdown( label="Analysis Result", value="*Upload an image and click Analyze to begin.*", ) freq_image = gr.Image( label="📊 Frequency Spectrum (FFT)", height=220, type="pil", ) detect_btn.click( fn=detect_deepfake, inputs=[input_image], outputs=[result_text, freq_image], ) with gr.Accordion("ℹ️ How it works", open=False): gr.Markdown(""" **4 signals are analyzed and fused:** 1. **CNN Spatial Analysis** — EfficientNet trained to spot pixel-level artifacts that human eyes miss (blending boundaries, texture inconsistencies) 2. **Frequency Domain (FFT)** — AI-generated images leave signatures in the frequency spectrum: grid artifacts, unusual symmetry, sharp cutoffs 3. **Edge Density** — Real photos have complex, varied edges from natural scenes. AI images tend to be smoother with less micro-texture 4. **Noise Pattern Analysis** — Camera sensors produce characteristic noise. AI generators produce different (often more uniform) noise patterns Each signal is weighted and combined into a composite score. """) gr.Markdown(""" --- **Built by** [@AnubhavBharadwaaj](https://github.com/AnubhavBharadwaaj) | Powered by ImageCLEF research | [AgenticEdge.in](https://agenticedge.in) | [GitHub](https://github.com/AnubhavBharadwaaj) """) demo.launch()