legendarydragontamer's picture
Upload 3 files
92fdad7 verified
Raw
History Blame Contribute Delete
10.7 kB
# ============================================
# 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()