Add application file
Browse files- .dockerignore +29 -0
- __init__.py +2 -0
- app - Copy (2).py +606 -0
- app - Copy.py +626 -0
- app_v0.py +351 -0
- best_ai_detector.pth +3 -0
- best_ai_detector_new.pth +3 -0
- saveFullModel.py +183 -0
- static/styles.css +184 -0
- templates/index.html +175 -0
- test.ipynb +192 -0
.dockerignore
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
__pycache__/
|
| 2 |
+
*.pyc
|
| 3 |
+
*.pyo
|
| 4 |
+
*.pyd
|
| 5 |
+
.pytest_cache/
|
| 6 |
+
.mypy_cache/
|
| 7 |
+
.ruff_cache/
|
| 8 |
+
.venv/
|
| 9 |
+
venv/
|
| 10 |
+
.git/
|
| 11 |
+
.cursor/
|
| 12 |
+
terminals/
|
| 13 |
+
agent-transcripts/
|
| 14 |
+
*.ipynb
|
| 15 |
+
*.png
|
| 16 |
+
*.jpg
|
| 17 |
+
*.jpeg
|
| 18 |
+
*.gif
|
| 19 |
+
*.webp
|
| 20 |
+
*.mp4
|
| 21 |
+
*.mov
|
| 22 |
+
*.avi
|
| 23 |
+
*.mkv
|
| 24 |
+
*.zip
|
| 25 |
+
*.tar
|
| 26 |
+
*.tar.gz
|
| 27 |
+
*.7z
|
| 28 |
+
.DS_Store
|
| 29 |
+
|
__init__.py
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""AI vs Real detector app package."""
|
| 2 |
+
|
app - Copy (2).py
ADDED
|
@@ -0,0 +1,606 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
from torchvision import transforms, models
|
| 6 |
+
from PIL import Image
|
| 7 |
+
import gradio as gr
|
| 8 |
+
import numpy as np
|
| 9 |
+
import pandas as pd
|
| 10 |
+
import requests
|
| 11 |
+
from io import BytesIO
|
| 12 |
+
import warnings
|
| 13 |
+
warnings.filterwarnings('ignore')
|
| 14 |
+
|
| 15 |
+
# Global variables
|
| 16 |
+
detector = None
|
| 17 |
+
current_model_path = None
|
| 18 |
+
|
| 19 |
+
class AIDetectorModel(nn.Module):
|
| 20 |
+
def __init__(self, num_classes=2, dropout_prob=0.3, load_pretrained=True):
|
| 21 |
+
super(AIDetectorModel, self).__init__()
|
| 22 |
+
|
| 23 |
+
# Smart loading: only download RegNet if needed
|
| 24 |
+
if load_pretrained:
|
| 25 |
+
print("π₯ Loading RegNet with pre-trained weights (319MB download)...")
|
| 26 |
+
self.backbone = models.regnet_y_16gf(weights=models.RegNet_Y_16GF_Weights.IMAGENET1K_SWAG_E2E_V1)
|
| 27 |
+
else:
|
| 28 |
+
print("π Creating RegNet architecture without pre-trained weights...")
|
| 29 |
+
self.backbone = models.regnet_y_16gf(weights=None)
|
| 30 |
+
|
| 31 |
+
# Freeze most layers (only relevant for pre-trained)
|
| 32 |
+
for param in self.backbone.parameters():
|
| 33 |
+
param.requires_grad = False
|
| 34 |
+
|
| 35 |
+
# Unfreeze last block
|
| 36 |
+
if hasattr(self.backbone, 'trunk_output') and hasattr(self.backbone.trunk_output, 'block4'):
|
| 37 |
+
self.backbone.trunk_output.block4.requires_grad_(True)
|
| 38 |
+
|
| 39 |
+
# Replace average pooling with max pooling
|
| 40 |
+
self.backbone.avgpool = nn.AdaptiveMaxPool2d(output_size=(1, 1))
|
| 41 |
+
|
| 42 |
+
# Get feature dimension
|
| 43 |
+
num_ftrs = self.backbone.fc.in_features
|
| 44 |
+
|
| 45 |
+
# Replace classifier with custom layers
|
| 46 |
+
self.backbone.fc = nn.Sequential(
|
| 47 |
+
nn.Linear(num_ftrs, 2048),
|
| 48 |
+
nn.SiLU(),
|
| 49 |
+
nn.Dropout(dropout_prob),
|
| 50 |
+
nn.Linear(2048, 1024),
|
| 51 |
+
nn.SiLU(),
|
| 52 |
+
nn.Dropout(dropout_prob),
|
| 53 |
+
nn.Linear(1024, 512),
|
| 54 |
+
nn.SiLU(),
|
| 55 |
+
nn.Dropout(dropout_prob),
|
| 56 |
+
nn.Linear(512, num_classes)
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
def forward(self, x):
|
| 60 |
+
return self.backbone(x)
|
| 61 |
+
|
| 62 |
+
def analyze_checkpoint(checkpoint_path):
|
| 63 |
+
"""Analyze checkpoint to determine if backbone weights are included"""
|
| 64 |
+
try:
|
| 65 |
+
print(f"π Analyzing checkpoint: {checkpoint_path}")
|
| 66 |
+
checkpoint = torch.load(checkpoint_path, map_location='cpu')
|
| 67 |
+
|
| 68 |
+
# Extract state dict
|
| 69 |
+
if isinstance(checkpoint, dict) and 'model_state_dict' in checkpoint:
|
| 70 |
+
state_dict = checkpoint['model_state_dict']
|
| 71 |
+
print("π¦ Found structured checkpoint with model_state_dict")
|
| 72 |
+
else:
|
| 73 |
+
state_dict = checkpoint
|
| 74 |
+
print("π¦ Found direct state_dict checkpoint")
|
| 75 |
+
|
| 76 |
+
# Analyze parameters
|
| 77 |
+
all_keys = list(state_dict.keys())
|
| 78 |
+
backbone_params = [k for k in all_keys if k.startswith('backbone.')]
|
| 79 |
+
backbone_conv_params = [k for k in backbone_params if 'conv' in k]
|
| 80 |
+
backbone_block_params = [k for k in backbone_params if 'block' in k]
|
| 81 |
+
classifier_params = [k for k in all_keys if 'fc' in k]
|
| 82 |
+
|
| 83 |
+
total_params = len(all_keys)
|
| 84 |
+
|
| 85 |
+
print(f"π Parameter analysis:")
|
| 86 |
+
print(f" β’ Total parameters: {total_params}")
|
| 87 |
+
print(f" β’ Backbone parameters: {len(backbone_params)}")
|
| 88 |
+
print(f" β’ Backbone conv layers: {len(backbone_conv_params)}")
|
| 89 |
+
print(f" β’ Backbone blocks: {len(backbone_block_params)}")
|
| 90 |
+
print(f" β’ Classifier parameters: {len(classifier_params)}")
|
| 91 |
+
|
| 92 |
+
# Determine if full backbone is included
|
| 93 |
+
# RegNet has many backbone parameters, so if we have 100+ backbone params, it's likely complete
|
| 94 |
+
has_full_backbone = len(backbone_params) > 100 and len(backbone_conv_params) > 10
|
| 95 |
+
|
| 96 |
+
if has_full_backbone:
|
| 97 |
+
print("β
COMPLETE MODEL DETECTED - backbone weights included!")
|
| 98 |
+
print("π Will skip RegNet download for faster loading")
|
| 99 |
+
else:
|
| 100 |
+
print("β οΈ Incomplete model detected - backbone weights missing")
|
| 101 |
+
print("π₯ Will download RegNet pre-trained weights")
|
| 102 |
+
|
| 103 |
+
return has_full_backbone, checkpoint
|
| 104 |
+
|
| 105 |
+
except Exception as e:
|
| 106 |
+
print(f"β Error analyzing checkpoint: {e}")
|
| 107 |
+
print("π₯ Will fallback to downloading RegNet weights")
|
| 108 |
+
return False, None
|
| 109 |
+
|
| 110 |
+
class AIImageDetector:
|
| 111 |
+
def __init__(self, model_path, device=None):
|
| 112 |
+
"""Initialize AI Image Detector with smart loading"""
|
| 113 |
+
self.device = device if device else ('cuda' if torch.cuda.is_available() else 'cpu')
|
| 114 |
+
print(f"π Initializing AI Image Detector on {self.device}")
|
| 115 |
+
|
| 116 |
+
# Analyze checkpoint to determine loading strategy
|
| 117 |
+
has_backbone, checkpoint = analyze_checkpoint(model_path)
|
| 118 |
+
|
| 119 |
+
# Load model with optimized strategy
|
| 120 |
+
self.model = self._load_model(model_path, has_backbone, checkpoint)
|
| 121 |
+
self.model.eval()
|
| 122 |
+
|
| 123 |
+
# Define image preprocessing transforms
|
| 124 |
+
self.transform = transforms.Compose([
|
| 125 |
+
transforms.Resize(224, interpolation=transforms.InterpolationMode.BICUBIC),
|
| 126 |
+
transforms.CenterCrop(224),
|
| 127 |
+
transforms.ToTensor(),
|
| 128 |
+
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
|
| 129 |
+
])
|
| 130 |
+
|
| 131 |
+
print("β
Model loaded and ready for inference!")
|
| 132 |
+
|
| 133 |
+
def _load_model(self, model_path, has_backbone, checkpoint):
|
| 134 |
+
"""Load model with optimized backbone loading"""
|
| 135 |
+
try:
|
| 136 |
+
# Load checkpoint if not already loaded
|
| 137 |
+
if checkpoint is None:
|
| 138 |
+
checkpoint = torch.load(model_path, map_location=self.device)
|
| 139 |
+
|
| 140 |
+
# Determine whether to load pre-trained weights
|
| 141 |
+
load_pretrained = not has_backbone
|
| 142 |
+
|
| 143 |
+
# Create model
|
| 144 |
+
if isinstance(checkpoint, dict) and 'model_state_dict' in checkpoint:
|
| 145 |
+
# Structured checkpoint
|
| 146 |
+
model = AIDetectorModel(
|
| 147 |
+
num_classes=checkpoint.get('num_classes', 2),
|
| 148 |
+
dropout_prob=checkpoint.get('dropout_prob', 0.3),
|
| 149 |
+
load_pretrained=load_pretrained
|
| 150 |
+
)
|
| 151 |
+
model.load_state_dict(checkpoint['model_state_dict'])
|
| 152 |
+
|
| 153 |
+
# Show additional info if available
|
| 154 |
+
if 'epoch' in checkpoint:
|
| 155 |
+
print(f"π Loaded model from epoch {checkpoint['epoch']}")
|
| 156 |
+
if 'val_loss' in checkpoint:
|
| 157 |
+
print(f"π Validation loss: {checkpoint['val_loss']:.4f}")
|
| 158 |
+
|
| 159 |
+
else:
|
| 160 |
+
# Direct state dict
|
| 161 |
+
model = AIDetectorModel(load_pretrained=load_pretrained)
|
| 162 |
+
model.load_state_dict(checkpoint)
|
| 163 |
+
|
| 164 |
+
return model.to(self.device)
|
| 165 |
+
|
| 166 |
+
except Exception as e:
|
| 167 |
+
print(f"β Error loading model: {e}")
|
| 168 |
+
print("π Attempting fallback loading...")
|
| 169 |
+
|
| 170 |
+
# Fallback: try with pre-trained weights
|
| 171 |
+
try:
|
| 172 |
+
model = AIDetectorModel(load_pretrained=True)
|
| 173 |
+
if isinstance(checkpoint, dict) and 'model_state_dict' in checkpoint:
|
| 174 |
+
model.load_state_dict(checkpoint['model_state_dict'])
|
| 175 |
+
else:
|
| 176 |
+
model.load_state_dict(checkpoint)
|
| 177 |
+
return model.to(self.device)
|
| 178 |
+
except Exception as fallback_error:
|
| 179 |
+
print(f"β Fallback loading also failed: {fallback_error}")
|
| 180 |
+
raise e
|
| 181 |
+
|
| 182 |
+
def preprocess_image(self, image):
|
| 183 |
+
"""Preprocess image for model input"""
|
| 184 |
+
try:
|
| 185 |
+
# Ensure RGB format
|
| 186 |
+
if image.mode != 'RGB':
|
| 187 |
+
image = image.convert('RGB')
|
| 188 |
+
|
| 189 |
+
# Apply transforms and add batch dimension
|
| 190 |
+
tensor = self.transform(image).unsqueeze(0)
|
| 191 |
+
return tensor.to(self.device)
|
| 192 |
+
|
| 193 |
+
except Exception as e:
|
| 194 |
+
print(f"β Error preprocessing image: {e}")
|
| 195 |
+
raise e
|
| 196 |
+
|
| 197 |
+
def predict(self, image):
|
| 198 |
+
"""
|
| 199 |
+
Predict if image is real or AI-generated
|
| 200 |
+
|
| 201 |
+
Returns:
|
| 202 |
+
tuple: (prediction, confidence, probabilities)
|
| 203 |
+
"""
|
| 204 |
+
try:
|
| 205 |
+
# Preprocess image
|
| 206 |
+
input_tensor = self.preprocess_image(image)
|
| 207 |
+
|
| 208 |
+
# Run inference
|
| 209 |
+
with torch.no_grad():
|
| 210 |
+
outputs = self.model(input_tensor)
|
| 211 |
+
probabilities = F.softmax(outputs, dim=1)
|
| 212 |
+
confidence, predicted = torch.max(probabilities, 1)
|
| 213 |
+
|
| 214 |
+
# Convert to numpy for easier handling
|
| 215 |
+
probs = probabilities.cpu().numpy()[0]
|
| 216 |
+
pred_class = predicted.cpu().item()
|
| 217 |
+
conf_score = confidence.cpu().item()
|
| 218 |
+
|
| 219 |
+
# Map to class names
|
| 220 |
+
class_names = ['REAL', 'FAKE']
|
| 221 |
+
prediction = class_names[pred_class]
|
| 222 |
+
|
| 223 |
+
return prediction, conf_score, probs
|
| 224 |
+
|
| 225 |
+
except Exception as e:
|
| 226 |
+
print(f"β Error during prediction: {e}")
|
| 227 |
+
raise e
|
| 228 |
+
|
| 229 |
+
def predict_from_url(self, url):
|
| 230 |
+
"""Download image from URL and make prediction"""
|
| 231 |
+
try:
|
| 232 |
+
# Download image with timeout
|
| 233 |
+
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'}
|
| 234 |
+
response = requests.get(url, timeout=15, headers=headers)
|
| 235 |
+
response.raise_for_status()
|
| 236 |
+
|
| 237 |
+
# Load image
|
| 238 |
+
image = Image.open(BytesIO(response.content))
|
| 239 |
+
|
| 240 |
+
# Make prediction
|
| 241 |
+
prediction, confidence, probabilities = self.predict(image)
|
| 242 |
+
|
| 243 |
+
return prediction, confidence, probabilities, image
|
| 244 |
+
|
| 245 |
+
except requests.exceptions.RequestException as e:
|
| 246 |
+
print(f"β Error downloading image: {e}")
|
| 247 |
+
raise Exception(f"Failed to download image from URL: {str(e)}")
|
| 248 |
+
except Exception as e:
|
| 249 |
+
print(f"β Error processing image from URL: {e}")
|
| 250 |
+
raise e
|
| 251 |
+
|
| 252 |
+
def get_available_models():
|
| 253 |
+
"""Get list of available model files"""
|
| 254 |
+
# Define possible model files
|
| 255 |
+
model_options = [
|
| 256 |
+
'best_ai_detector.pth',
|
| 257 |
+
'best_ai_detector_new.pth'
|
| 258 |
+
]
|
| 259 |
+
|
| 260 |
+
# Check which files actually exist
|
| 261 |
+
available_models = []
|
| 262 |
+
for model in model_options:
|
| 263 |
+
if os.path.exists(model):
|
| 264 |
+
available_models.append(model)
|
| 265 |
+
|
| 266 |
+
# If no models found, return the default options anyway
|
| 267 |
+
if not available_models:
|
| 268 |
+
available_models = model_options
|
| 269 |
+
print("β οΈ No model files found in current directory, showing default options")
|
| 270 |
+
else:
|
| 271 |
+
print(f"β
Found {len(available_models)} model file(s): {available_models}")
|
| 272 |
+
|
| 273 |
+
return available_models
|
| 274 |
+
|
| 275 |
+
def load_detector(model_path=None):
|
| 276 |
+
"""Load the detector model with specified path"""
|
| 277 |
+
global detector, current_model_path
|
| 278 |
+
|
| 279 |
+
try:
|
| 280 |
+
# Use specified path or default
|
| 281 |
+
if model_path is None:
|
| 282 |
+
model_path = 'best_ai_detector.pth'
|
| 283 |
+
|
| 284 |
+
# Check if the same model is already loaded
|
| 285 |
+
if detector is not None and current_model_path == model_path:
|
| 286 |
+
return f"β
Model '{model_path}' is already loaded and ready!"
|
| 287 |
+
|
| 288 |
+
# Check if file exists
|
| 289 |
+
if not os.path.exists(model_path):
|
| 290 |
+
available_models = get_available_models()
|
| 291 |
+
error_msg = f"β Model file '{model_path}' not found!\n\n"
|
| 292 |
+
error_msg += f"Available models in current directory:\n"
|
| 293 |
+
for model in available_models:
|
| 294 |
+
exists = "β
" if os.path.exists(model) else "β"
|
| 295 |
+
error_msg += f" {exists} {model}\n"
|
| 296 |
+
error_msg += f"\nPlease ensure your trained model file is uploaded to the Space."
|
| 297 |
+
return error_msg
|
| 298 |
+
|
| 299 |
+
print(f"π― Loading model: {model_path}")
|
| 300 |
+
|
| 301 |
+
# Initialize detector
|
| 302 |
+
detector = AIImageDetector(model_path)
|
| 303 |
+
current_model_path = model_path
|
| 304 |
+
|
| 305 |
+
return f"β
Model '{model_path}' loaded successfully!\nπ Ready for image analysis."
|
| 306 |
+
|
| 307 |
+
except Exception as e:
|
| 308 |
+
error_msg = f"β Error loading model '{model_path}': {str(e)}\n\n"
|
| 309 |
+
error_msg += "This might be due to:\n"
|
| 310 |
+
error_msg += "β’ Incompatible model file\n"
|
| 311 |
+
error_msg += "β’ Corrupted checkpoint\n"
|
| 312 |
+
error_msg += "β’ Missing dependencies\n"
|
| 313 |
+
return error_msg
|
| 314 |
+
|
| 315 |
+
def predict_image(image):
|
| 316 |
+
"""Handle image upload and prediction"""
|
| 317 |
+
global detector
|
| 318 |
+
|
| 319 |
+
# Check if detector is loaded
|
| 320 |
+
if detector is None:
|
| 321 |
+
return "β Please load a model first using the dropdown and 'Load Model' button!", None, None, None
|
| 322 |
+
|
| 323 |
+
if image is None:
|
| 324 |
+
return "β Please upload an image first!", None, None, None
|
| 325 |
+
|
| 326 |
+
try:
|
| 327 |
+
# Make prediction
|
| 328 |
+
prediction, confidence, probabilities = detector.predict(image)
|
| 329 |
+
|
| 330 |
+
# Format detailed results
|
| 331 |
+
result_text = f"π **Prediction: {prediction}**\n\n"
|
| 332 |
+
result_text += f"π **Confidence: {confidence:.1%}**\n\n"
|
| 333 |
+
result_text += f"π **Detailed Probabilities:**\n"
|
| 334 |
+
result_text += f"β’ π’ REAL (Human-made): {probabilities[0]:.1%}\n"
|
| 335 |
+
result_text += f"β’ π΄ FAKE (AI-generated): {probabilities[1]:.1%}\n\n"
|
| 336 |
+
|
| 337 |
+
# Add interpretation
|
| 338 |
+
if confidence > 0.8:
|
| 339 |
+
result_text += f"πͺ **High confidence prediction**"
|
| 340 |
+
elif confidence > 0.6:
|
| 341 |
+
result_text += f"π€ **Moderate confidence prediction**"
|
| 342 |
+
else:
|
| 343 |
+
result_text += f"β οΈ **Low confidence - uncertain prediction**"
|
| 344 |
+
|
| 345 |
+
# Create chart data
|
| 346 |
+
prob_df = pd.DataFrame({
|
| 347 |
+
'Category': ['REAL (Human)', 'FAKE (AI)'],
|
| 348 |
+
'Probability': [float(probabilities[0]), float(probabilities[1])]
|
| 349 |
+
})
|
| 350 |
+
|
| 351 |
+
# Quick status
|
| 352 |
+
color = "π’" if prediction == "REAL" else "π΄"
|
| 353 |
+
status = f"{color} {prediction} - {confidence:.1%} confidence"
|
| 354 |
+
|
| 355 |
+
return result_text, prob_df, prediction, status
|
| 356 |
+
|
| 357 |
+
except Exception as e:
|
| 358 |
+
error_msg = f"β Prediction failed: {str(e)}"
|
| 359 |
+
print(error_msg)
|
| 360 |
+
return error_msg, None, None, None
|
| 361 |
+
|
| 362 |
+
def predict_from_url(url):
|
| 363 |
+
"""Handle URL input and prediction"""
|
| 364 |
+
global detector
|
| 365 |
+
|
| 366 |
+
# Check if detector is loaded
|
| 367 |
+
if detector is None:
|
| 368 |
+
return "β Please load a model first using the dropdown and 'Load Model' button!", None, None, None, None
|
| 369 |
+
|
| 370 |
+
if not url or not url.strip():
|
| 371 |
+
return "β Please enter a valid image URL!", None, None, None, None
|
| 372 |
+
|
| 373 |
+
try:
|
| 374 |
+
# Download and predict
|
| 375 |
+
prediction, confidence, probabilities, image = detector.predict_from_url(url.strip())
|
| 376 |
+
|
| 377 |
+
# Format results (same as upload)
|
| 378 |
+
result_text = f"π **Prediction: {prediction}**\n\n"
|
| 379 |
+
result_text += f"π **Confidence: {confidence:.1%}**\n\n"
|
| 380 |
+
result_text += f"π **Detailed Probabilities:**\n"
|
| 381 |
+
result_text += f"β’ π’ REAL (Human-made): {probabilities[0]:.1%}\n"
|
| 382 |
+
result_text += f"β’ π΄ FAKE (AI-generated): {probabilities[1]:.1%}\n\n"
|
| 383 |
+
|
| 384 |
+
# Add interpretation
|
| 385 |
+
if confidence > 0.8:
|
| 386 |
+
result_text += f"πͺ **High confidence prediction**"
|
| 387 |
+
elif confidence > 0.6:
|
| 388 |
+
result_text += f"π€ **Moderate confidence prediction**"
|
| 389 |
+
else:
|
| 390 |
+
result_text += f"β οΈ **Low confidence - uncertain prediction**"
|
| 391 |
+
|
| 392 |
+
# Create chart data
|
| 393 |
+
prob_df = pd.DataFrame({
|
| 394 |
+
'Category': ['REAL (Human)', 'FAKE (AI)'],
|
| 395 |
+
'Probability': [float(probabilities[0]), float(probabilities[1])]
|
| 396 |
+
})
|
| 397 |
+
|
| 398 |
+
# Quick status
|
| 399 |
+
color = "π’" if prediction == "REAL" else "π΄"
|
| 400 |
+
status = f"{color} {prediction} - {confidence:.1%} confidence"
|
| 401 |
+
|
| 402 |
+
return result_text, prob_df, prediction, status, image
|
| 403 |
+
|
| 404 |
+
except Exception as e:
|
| 405 |
+
error_msg = f"β URL processing failed: {str(e)}"
|
| 406 |
+
print(error_msg)
|
| 407 |
+
return error_msg, None, None, None, None
|
| 408 |
+
|
| 409 |
+
def create_interface():
|
| 410 |
+
"""Create the Gradio web interface"""
|
| 411 |
+
|
| 412 |
+
# Simplified CSS for better compatibility
|
| 413 |
+
css = """
|
| 414 |
+
.gradio-container {
|
| 415 |
+
background-color: #1a1a1a !important;
|
| 416 |
+
color: white !important;
|
| 417 |
+
}
|
| 418 |
+
|
| 419 |
+
.main-header {
|
| 420 |
+
text-align: center;
|
| 421 |
+
background: linear-gradient(135deg, #eb001b 0%, #ff5f00 100%);
|
| 422 |
+
color: white !important;
|
| 423 |
+
padding: 30px;
|
| 424 |
+
border-radius: 15px;
|
| 425 |
+
margin-bottom: 25px;
|
| 426 |
+
}
|
| 427 |
+
|
| 428 |
+
.model-section {
|
| 429 |
+
background: #2a2a2a !important;
|
| 430 |
+
padding: 25px;
|
| 431 |
+
border-radius: 15px;
|
| 432 |
+
margin-bottom: 25px;
|
| 433 |
+
border: 2px solid #ff5f00;
|
| 434 |
+
}
|
| 435 |
+
"""
|
| 436 |
+
|
| 437 |
+
with gr.Blocks(css=css, title="AI Image Detector") as demo:
|
| 438 |
+
# Main header
|
| 439 |
+
gr.HTML("""
|
| 440 |
+
<div class="main-header">
|
| 441 |
+
<h1>π€ AI Image Detector</h1>
|
| 442 |
+
<p>Upload an image or provide a URL to detect if it's REAL (human-made) or FAKE (AI-generated)</p>
|
| 443 |
+
</div>
|
| 444 |
+
""")
|
| 445 |
+
|
| 446 |
+
# Model selection section
|
| 447 |
+
with gr.Group():
|
| 448 |
+
gr.HTML("""
|
| 449 |
+
<div class="model-section">
|
| 450 |
+
<h3>π§ Model Selection & Loading</h3>
|
| 451 |
+
</div>
|
| 452 |
+
""")
|
| 453 |
+
|
| 454 |
+
with gr.Row():
|
| 455 |
+
with gr.Column(scale=2):
|
| 456 |
+
model_dropdown = gr.Dropdown(
|
| 457 |
+
choices=get_available_models(),
|
| 458 |
+
value='best_ai_detector.pth',
|
| 459 |
+
label="π― Select Model",
|
| 460 |
+
info="Choose which trained model to load"
|
| 461 |
+
)
|
| 462 |
+
with gr.Column(scale=1):
|
| 463 |
+
load_btn = gr.Button("π Load Model", variant="secondary")
|
| 464 |
+
|
| 465 |
+
model_status = gr.Textbox(
|
| 466 |
+
label="π Model Status",
|
| 467 |
+
value="Select a model and click 'Load Model' to initialize the AI detector",
|
| 468 |
+
interactive=False,
|
| 469 |
+
lines=2
|
| 470 |
+
)
|
| 471 |
+
|
| 472 |
+
with gr.Tabs():
|
| 473 |
+
# Image upload tab
|
| 474 |
+
with gr.TabItem("π Upload Image"):
|
| 475 |
+
with gr.Row():
|
| 476 |
+
with gr.Column(scale=1):
|
| 477 |
+
input_image = gr.Image(
|
| 478 |
+
label="π· Upload Image for Analysis",
|
| 479 |
+
type="pil",
|
| 480 |
+
height=400
|
| 481 |
+
)
|
| 482 |
+
predict_btn = gr.Button(
|
| 483 |
+
"π Analyze Image",
|
| 484 |
+
variant="primary"
|
| 485 |
+
)
|
| 486 |
+
|
| 487 |
+
with gr.Column(scale=1):
|
| 488 |
+
prediction_status = gr.Textbox(
|
| 489 |
+
label="π― Quick Result",
|
| 490 |
+
interactive=False,
|
| 491 |
+
lines=1
|
| 492 |
+
)
|
| 493 |
+
|
| 494 |
+
prediction_output = gr.Markdown(
|
| 495 |
+
label="π Detailed Analysis",
|
| 496 |
+
value="Load a model and upload an image to get AI detection results"
|
| 497 |
+
)
|
| 498 |
+
|
| 499 |
+
probability_chart = gr.BarPlot(
|
| 500 |
+
label="π Probability Distribution",
|
| 501 |
+
x="Category",
|
| 502 |
+
y="Probability",
|
| 503 |
+
width=400,
|
| 504 |
+
height=250
|
| 505 |
+
)
|
| 506 |
+
|
| 507 |
+
# URL input tab
|
| 508 |
+
with gr.TabItem("π Image URL"):
|
| 509 |
+
with gr.Row():
|
| 510 |
+
with gr.Column(scale=1):
|
| 511 |
+
url_input = gr.Textbox(
|
| 512 |
+
label="π Image URL",
|
| 513 |
+
placeholder="https://example.com/image.jpg",
|
| 514 |
+
lines=2
|
| 515 |
+
)
|
| 516 |
+
url_predict_btn = gr.Button(
|
| 517 |
+
"π Analyze from URL",
|
| 518 |
+
variant="primary"
|
| 519 |
+
)
|
| 520 |
+
|
| 521 |
+
downloaded_image = gr.Image(
|
| 522 |
+
label="π₯ Downloaded Image",
|
| 523 |
+
type="pil",
|
| 524 |
+
height=300
|
| 525 |
+
)
|
| 526 |
+
|
| 527 |
+
with gr.Column(scale=1):
|
| 528 |
+
url_prediction_status = gr.Textbox(
|
| 529 |
+
label="π― Quick Result",
|
| 530 |
+
interactive=False,
|
| 531 |
+
lines=1
|
| 532 |
+
)
|
| 533 |
+
|
| 534 |
+
url_prediction_output = gr.Markdown(
|
| 535 |
+
label="π Detailed Analysis",
|
| 536 |
+
value="Load a model and enter an image URL to get AI detection results"
|
| 537 |
+
)
|
| 538 |
+
|
| 539 |
+
url_probability_chart = gr.BarPlot(
|
| 540 |
+
label="π Probability Distribution",
|
| 541 |
+
x="Category",
|
| 542 |
+
y="Probability",
|
| 543 |
+
width=400,
|
| 544 |
+
height=250
|
| 545 |
+
)
|
| 546 |
+
|
| 547 |
+
# Information section
|
| 548 |
+
with gr.Accordion("βΉοΈ About This AI Detector", open=False):
|
| 549 |
+
gr.Markdown("""
|
| 550 |
+
### π― What This Model Does
|
| 551 |
+
|
| 552 |
+
This AI detector analyzes images to determine if they are:
|
| 553 |
+
- **π’ REAL**: Created by humans (photographs, traditional digital art, etc.)
|
| 554 |
+
- **π΄ FAKE**: Generated by AI systems (DALL-E, Midjourney, Stable Diffusion, etc.)
|
| 555 |
+
|
| 556 |
+
### π Understanding the Results
|
| 557 |
+
|
| 558 |
+
- **Prediction**: The model's best guess (REAL or FAKE)
|
| 559 |
+
- **Confidence**: How certain the model is (higher = more confident)
|
| 560 |
+
- **Probabilities**: Breakdown showing likelihood for each category
|
| 561 |
+
""")
|
| 562 |
+
|
| 563 |
+
# Connect event handlers
|
| 564 |
+
load_btn.click(
|
| 565 |
+
fn=load_detector,
|
| 566 |
+
inputs=[model_dropdown],
|
| 567 |
+
outputs=model_status
|
| 568 |
+
)
|
| 569 |
+
|
| 570 |
+
predict_btn.click(
|
| 571 |
+
fn=predict_image,
|
| 572 |
+
inputs=[input_image],
|
| 573 |
+
outputs=[prediction_output, probability_chart, gr.State(), prediction_status]
|
| 574 |
+
)
|
| 575 |
+
|
| 576 |
+
url_predict_btn.click(
|
| 577 |
+
fn=predict_from_url,
|
| 578 |
+
inputs=[url_input],
|
| 579 |
+
outputs=[url_prediction_output, url_probability_chart, gr.State(), url_prediction_status, downloaded_image]
|
| 580 |
+
)
|
| 581 |
+
|
| 582 |
+
return demo
|
| 583 |
+
|
| 584 |
+
if __name__ == "__main__":
|
| 585 |
+
print("π Starting AI Image Detector with Model Selection...")
|
| 586 |
+
print("π Available models can be selected from dropdown")
|
| 587 |
+
|
| 588 |
+
# Check available models at startup
|
| 589 |
+
available_models = get_available_models()
|
| 590 |
+
print(f"π― Available models: {available_models}")
|
| 591 |
+
|
| 592 |
+
# Create interface
|
| 593 |
+
demo = create_interface()
|
| 594 |
+
|
| 595 |
+
# Don't auto-load model - let user choose
|
| 596 |
+
print("π‘ Select a model from the dropdown and click 'Load Model' to begin")
|
| 597 |
+
|
| 598 |
+
# Launch interface
|
| 599 |
+
print("π Launching interface...")
|
| 600 |
+
demo.launch(
|
| 601 |
+
server_name="0.0.0.0",
|
| 602 |
+
server_port=7860,
|
| 603 |
+
share=True,
|
| 604 |
+
show_error=True,
|
| 605 |
+
debug=False
|
| 606 |
+
)
|
app - Copy.py
ADDED
|
@@ -0,0 +1,626 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
from torchvision import transforms, models
|
| 6 |
+
from PIL import Image
|
| 7 |
+
import gradio as gr
|
| 8 |
+
import numpy as np
|
| 9 |
+
import pandas as pd
|
| 10 |
+
import requests
|
| 11 |
+
from io import BytesIO
|
| 12 |
+
import warnings
|
| 13 |
+
warnings.filterwarnings('ignore')
|
| 14 |
+
|
| 15 |
+
# Global variables
|
| 16 |
+
detector = None
|
| 17 |
+
|
| 18 |
+
class AIDetectorModel(nn.Module):
|
| 19 |
+
def __init__(self, num_classes=2, dropout_prob=0.3, load_pretrained=True):
|
| 20 |
+
super(AIDetectorModel, self).__init__()
|
| 21 |
+
|
| 22 |
+
# Smart loading: only download RegNet if needed
|
| 23 |
+
if load_pretrained:
|
| 24 |
+
print("π₯ Loading RegNet with pre-trained weights (319MB download)...")
|
| 25 |
+
self.backbone = models.regnet_y_16gf(weights=models.RegNet_Y_16GF_Weights.IMAGENET1K_SWAG_E2E_V1)
|
| 26 |
+
else:
|
| 27 |
+
print("π Creating RegNet architecture without pre-trained weights...")
|
| 28 |
+
self.backbone = models.regnet_y_16gf(weights=None)
|
| 29 |
+
|
| 30 |
+
# Freeze most layers (only relevant for pre-trained)
|
| 31 |
+
for param in self.backbone.parameters():
|
| 32 |
+
param.requires_grad = False
|
| 33 |
+
|
| 34 |
+
# Unfreeze last block
|
| 35 |
+
if hasattr(self.backbone, 'trunk_output') and hasattr(self.backbone.trunk_output, 'block4'):
|
| 36 |
+
self.backbone.trunk_output.block4.requires_grad_(True)
|
| 37 |
+
|
| 38 |
+
# Replace average pooling with max pooling
|
| 39 |
+
self.backbone.avgpool = nn.AdaptiveMaxPool2d(output_size=(1, 1))
|
| 40 |
+
|
| 41 |
+
# Get feature dimension
|
| 42 |
+
num_ftrs = self.backbone.fc.in_features
|
| 43 |
+
|
| 44 |
+
# Replace classifier with custom layers
|
| 45 |
+
self.backbone.fc = nn.Sequential(
|
| 46 |
+
nn.Linear(num_ftrs, 2048),
|
| 47 |
+
nn.SiLU(),
|
| 48 |
+
nn.Dropout(dropout_prob),
|
| 49 |
+
nn.Linear(2048, 1024),
|
| 50 |
+
nn.SiLU(),
|
| 51 |
+
nn.Dropout(dropout_prob),
|
| 52 |
+
nn.Linear(1024, 512),
|
| 53 |
+
nn.SiLU(),
|
| 54 |
+
nn.Dropout(dropout_prob),
|
| 55 |
+
nn.Linear(512, num_classes)
|
| 56 |
+
)
|
| 57 |
+
|
| 58 |
+
def forward(self, x):
|
| 59 |
+
return self.backbone(x)
|
| 60 |
+
|
| 61 |
+
def analyze_checkpoint(checkpoint_path):
|
| 62 |
+
"""Analyze checkpoint to determine if backbone weights are included"""
|
| 63 |
+
try:
|
| 64 |
+
print(f"π Analyzing checkpoint: {checkpoint_path}")
|
| 65 |
+
checkpoint = torch.load(checkpoint_path, map_location='cpu')
|
| 66 |
+
|
| 67 |
+
# Extract state dict
|
| 68 |
+
if isinstance(checkpoint, dict) and 'model_state_dict' in checkpoint:
|
| 69 |
+
state_dict = checkpoint['model_state_dict']
|
| 70 |
+
print("π¦ Found structured checkpoint with model_state_dict")
|
| 71 |
+
else:
|
| 72 |
+
state_dict = checkpoint
|
| 73 |
+
print("π¦ Found direct state_dict checkpoint")
|
| 74 |
+
|
| 75 |
+
# Analyze parameters
|
| 76 |
+
all_keys = list(state_dict.keys())
|
| 77 |
+
backbone_params = [k for k in all_keys if k.startswith('backbone.')]
|
| 78 |
+
backbone_conv_params = [k for k in backbone_params if 'conv' in k]
|
| 79 |
+
backbone_block_params = [k for k in backbone_params if 'block' in k]
|
| 80 |
+
classifier_params = [k for k in all_keys if 'fc' in k]
|
| 81 |
+
|
| 82 |
+
total_params = len(all_keys)
|
| 83 |
+
|
| 84 |
+
print(f"π Parameter analysis:")
|
| 85 |
+
print(f" β’ Total parameters: {total_params}")
|
| 86 |
+
print(f" β’ Backbone parameters: {len(backbone_params)}")
|
| 87 |
+
print(f" β’ Backbone conv layers: {len(backbone_conv_params)}")
|
| 88 |
+
print(f" β’ Backbone blocks: {len(backbone_block_params)}")
|
| 89 |
+
print(f" β’ Classifier parameters: {len(classifier_params)}")
|
| 90 |
+
|
| 91 |
+
# Determine if full backbone is included
|
| 92 |
+
# RegNet has many backbone parameters, so if we have 100+ backbone params, it's likely complete
|
| 93 |
+
has_full_backbone = len(backbone_params) > 100 and len(backbone_conv_params) > 10
|
| 94 |
+
|
| 95 |
+
if has_full_backbone:
|
| 96 |
+
print("β
COMPLETE MODEL DETECTED - backbone weights included!")
|
| 97 |
+
print("π Will skip RegNet download for faster loading")
|
| 98 |
+
else:
|
| 99 |
+
print("β οΈ Incomplete model detected - backbone weights missing")
|
| 100 |
+
print("π₯ Will download RegNet pre-trained weights")
|
| 101 |
+
|
| 102 |
+
return has_full_backbone, checkpoint
|
| 103 |
+
|
| 104 |
+
except Exception as e:
|
| 105 |
+
print(f"β Error analyzing checkpoint: {e}")
|
| 106 |
+
print("π₯ Will fallback to downloading RegNet weights")
|
| 107 |
+
return False, None
|
| 108 |
+
|
| 109 |
+
class AIImageDetector:
|
| 110 |
+
def __init__(self, model_path, device=None):
|
| 111 |
+
"""Initialize AI Image Detector with smart loading"""
|
| 112 |
+
self.device = device if device else ('cuda' if torch.cuda.is_available() else 'cpu')
|
| 113 |
+
print(f"π Initializing AI Image Detector on {self.device}")
|
| 114 |
+
|
| 115 |
+
# Analyze checkpoint to determine loading strategy
|
| 116 |
+
has_backbone, checkpoint = analyze_checkpoint(model_path)
|
| 117 |
+
|
| 118 |
+
# Load model with optimized strategy
|
| 119 |
+
self.model = self._load_model(model_path, has_backbone, checkpoint)
|
| 120 |
+
self.model.eval()
|
| 121 |
+
|
| 122 |
+
# Define image preprocessing transforms
|
| 123 |
+
self.transform = transforms.Compose([
|
| 124 |
+
transforms.Resize(224, interpolation=transforms.InterpolationMode.BICUBIC),
|
| 125 |
+
transforms.CenterCrop(224),
|
| 126 |
+
transforms.ToTensor(),
|
| 127 |
+
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
|
| 128 |
+
])
|
| 129 |
+
|
| 130 |
+
print("β
Model loaded and ready for inference!")
|
| 131 |
+
|
| 132 |
+
def _load_model(self, model_path, has_backbone, checkpoint):
|
| 133 |
+
"""Load model with optimized backbone loading"""
|
| 134 |
+
try:
|
| 135 |
+
# Load checkpoint if not already loaded
|
| 136 |
+
if checkpoint is None:
|
| 137 |
+
checkpoint = torch.load(model_path, map_location=self.device)
|
| 138 |
+
|
| 139 |
+
# Determine whether to load pre-trained weights
|
| 140 |
+
load_pretrained = not has_backbone
|
| 141 |
+
|
| 142 |
+
# Create model
|
| 143 |
+
if isinstance(checkpoint, dict) and 'model_state_dict' in checkpoint:
|
| 144 |
+
# Structured checkpoint
|
| 145 |
+
model = AIDetectorModel(
|
| 146 |
+
num_classes=checkpoint.get('num_classes', 2),
|
| 147 |
+
dropout_prob=checkpoint.get('dropout_prob', 0.3),
|
| 148 |
+
load_pretrained=load_pretrained
|
| 149 |
+
)
|
| 150 |
+
model.load_state_dict(checkpoint['model_state_dict'])
|
| 151 |
+
|
| 152 |
+
# Show additional info if available
|
| 153 |
+
if 'epoch' in checkpoint:
|
| 154 |
+
print(f"π Loaded model from epoch {checkpoint['epoch']}")
|
| 155 |
+
if 'val_loss' in checkpoint:
|
| 156 |
+
print(f"π Validation loss: {checkpoint['val_loss']:.4f}")
|
| 157 |
+
|
| 158 |
+
else:
|
| 159 |
+
# Direct state dict
|
| 160 |
+
model = AIDetectorModel(load_pretrained=load_pretrained)
|
| 161 |
+
model.load_state_dict(checkpoint)
|
| 162 |
+
|
| 163 |
+
return model.to(self.device)
|
| 164 |
+
|
| 165 |
+
except Exception as e:
|
| 166 |
+
print(f"β Error loading model: {e}")
|
| 167 |
+
print("π Attempting fallback loading...")
|
| 168 |
+
|
| 169 |
+
# Fallback: try with pre-trained weights
|
| 170 |
+
try:
|
| 171 |
+
model = AIDetectorModel(load_pretrained=True)
|
| 172 |
+
if isinstance(checkpoint, dict) and 'model_state_dict' in checkpoint:
|
| 173 |
+
model.load_state_dict(checkpoint['model_state_dict'])
|
| 174 |
+
else:
|
| 175 |
+
model.load_state_dict(checkpoint)
|
| 176 |
+
return model.to(self.device)
|
| 177 |
+
except Exception as fallback_error:
|
| 178 |
+
print(f"β Fallback loading also failed: {fallback_error}")
|
| 179 |
+
raise e
|
| 180 |
+
|
| 181 |
+
def preprocess_image(self, image):
|
| 182 |
+
"""Preprocess image for model input"""
|
| 183 |
+
try:
|
| 184 |
+
# Ensure RGB format
|
| 185 |
+
if image.mode != 'RGB':
|
| 186 |
+
image = image.convert('RGB')
|
| 187 |
+
|
| 188 |
+
# Apply transforms and add batch dimension
|
| 189 |
+
tensor = self.transform(image).unsqueeze(0)
|
| 190 |
+
return tensor.to(self.device)
|
| 191 |
+
|
| 192 |
+
except Exception as e:
|
| 193 |
+
print(f"β Error preprocessing image: {e}")
|
| 194 |
+
raise e
|
| 195 |
+
|
| 196 |
+
def predict(self, image):
|
| 197 |
+
"""
|
| 198 |
+
Predict if image is real or AI-generated
|
| 199 |
+
|
| 200 |
+
Returns:
|
| 201 |
+
tuple: (prediction, confidence, probabilities)
|
| 202 |
+
"""
|
| 203 |
+
try:
|
| 204 |
+
# Preprocess image
|
| 205 |
+
input_tensor = self.preprocess_image(image)
|
| 206 |
+
|
| 207 |
+
# Run inference
|
| 208 |
+
with torch.no_grad():
|
| 209 |
+
outputs = self.model(input_tensor)
|
| 210 |
+
probabilities = F.softmax(outputs, dim=1)
|
| 211 |
+
confidence, predicted = torch.max(probabilities, 1)
|
| 212 |
+
|
| 213 |
+
# Convert to numpy for easier handling
|
| 214 |
+
probs = probabilities.cpu().numpy()[0]
|
| 215 |
+
pred_class = predicted.cpu().item()
|
| 216 |
+
conf_score = confidence.cpu().item()
|
| 217 |
+
|
| 218 |
+
# Map to class names
|
| 219 |
+
class_names = ['REAL', 'FAKE']
|
| 220 |
+
prediction = class_names[pred_class]
|
| 221 |
+
|
| 222 |
+
return prediction, conf_score, probs
|
| 223 |
+
|
| 224 |
+
except Exception as e:
|
| 225 |
+
print(f"β Error during prediction: {e}")
|
| 226 |
+
raise e
|
| 227 |
+
|
| 228 |
+
def predict_from_url(self, url):
|
| 229 |
+
"""Download image from URL and make prediction"""
|
| 230 |
+
try:
|
| 231 |
+
# Download image with timeout
|
| 232 |
+
headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'}
|
| 233 |
+
response = requests.get(url, timeout=15, headers=headers)
|
| 234 |
+
response.raise_for_status()
|
| 235 |
+
|
| 236 |
+
# Load image
|
| 237 |
+
image = Image.open(BytesIO(response.content))
|
| 238 |
+
|
| 239 |
+
# Make prediction
|
| 240 |
+
prediction, confidence, probabilities = self.predict(image)
|
| 241 |
+
|
| 242 |
+
return prediction, confidence, probabilities, image
|
| 243 |
+
|
| 244 |
+
except requests.exceptions.RequestException as e:
|
| 245 |
+
print(f"β Error downloading image: {e}")
|
| 246 |
+
raise Exception(f"Failed to download image from URL: {str(e)}")
|
| 247 |
+
except Exception as e:
|
| 248 |
+
print(f"β Error processing image from URL: {e}")
|
| 249 |
+
raise e
|
| 250 |
+
|
| 251 |
+
def load_detector(model_path=None):
|
| 252 |
+
"""Load the detector model with automatic path detection"""
|
| 253 |
+
global detector
|
| 254 |
+
|
| 255 |
+
if detector is None:
|
| 256 |
+
try:
|
| 257 |
+
# Define possible model file locations
|
| 258 |
+
possible_paths = [
|
| 259 |
+
'best_ai_detector.pth',
|
| 260 |
+
'ai_detector_complete.pth',
|
| 261 |
+
'./models/best_ai_detector.pth',
|
| 262 |
+
'./models/ai_detector_complete.pth',
|
| 263 |
+
'./checkpoints/best_ai_detector.pth',
|
| 264 |
+
'./checkpoints/ai_detector_complete.pth'
|
| 265 |
+
]
|
| 266 |
+
|
| 267 |
+
# Use specified path or find available file
|
| 268 |
+
if model_path and os.path.exists(model_path):
|
| 269 |
+
target_path = model_path
|
| 270 |
+
print(f"π― Using specified model: {target_path}")
|
| 271 |
+
else:
|
| 272 |
+
target_path = None
|
| 273 |
+
print(f"π Searching for model files...")
|
| 274 |
+
|
| 275 |
+
for path in possible_paths:
|
| 276 |
+
if os.path.exists(path):
|
| 277 |
+
target_path = path
|
| 278 |
+
print(f"β
Found model file: {path}")
|
| 279 |
+
break
|
| 280 |
+
|
| 281 |
+
if target_path is None:
|
| 282 |
+
# List available files for debugging
|
| 283 |
+
current_files = [f for f in os.listdir('.') if f.endswith('.pth')]
|
| 284 |
+
error_msg = f"β No model file found!\n\n"
|
| 285 |
+
error_msg += f"Searched locations:\n"
|
| 286 |
+
for path in possible_paths:
|
| 287 |
+
error_msg += f" β’ {path}\n"
|
| 288 |
+
error_msg += f"\nAvailable .pth files in current directory:\n"
|
| 289 |
+
for file in current_files:
|
| 290 |
+
error_msg += f" β’ {file}\n"
|
| 291 |
+
error_msg += f"\nPlease ensure your trained model file is uploaded to the Space."
|
| 292 |
+
return error_msg
|
| 293 |
+
|
| 294 |
+
# Initialize detector
|
| 295 |
+
detector = AIImageDetector(target_path)
|
| 296 |
+
return f"β
Model loaded successfully from {target_path}!"
|
| 297 |
+
|
| 298 |
+
except Exception as e:
|
| 299 |
+
error_msg = f"β Error loading model: {str(e)}\n\n"
|
| 300 |
+
error_msg += "This might be due to:\n"
|
| 301 |
+
error_msg += "β’ Incompatible model file\n"
|
| 302 |
+
error_msg += "β’ Corrupted checkpoint\n"
|
| 303 |
+
error_msg += "β’ Missing dependencies\n"
|
| 304 |
+
return error_msg
|
| 305 |
+
|
| 306 |
+
return "β
Model already loaded and ready!"
|
| 307 |
+
|
| 308 |
+
def predict_image(image):
|
| 309 |
+
"""Handle image upload and prediction"""
|
| 310 |
+
global detector
|
| 311 |
+
|
| 312 |
+
# Ensure detector is loaded
|
| 313 |
+
if detector is None:
|
| 314 |
+
load_result = load_detector()
|
| 315 |
+
if "β" in load_result:
|
| 316 |
+
return load_result, None, None, None
|
| 317 |
+
|
| 318 |
+
if image is None:
|
| 319 |
+
return "β Please upload an image first!", None, None, None
|
| 320 |
+
|
| 321 |
+
try:
|
| 322 |
+
# Make prediction
|
| 323 |
+
prediction, confidence, probabilities = detector.predict(image)
|
| 324 |
+
|
| 325 |
+
# Format detailed results
|
| 326 |
+
result_text = f"π **Prediction: {prediction}**\n\n"
|
| 327 |
+
result_text += f"π **Confidence: {confidence:.1%}**\n\n"
|
| 328 |
+
result_text += f"π **Detailed Probabilities:**\n"
|
| 329 |
+
result_text += f"β’ π’ REAL (Human-made): {probabilities[0]:.1%}\n"
|
| 330 |
+
result_text += f"β’ π΄ FAKE (AI-generated): {probabilities[1]:.1%}\n\n"
|
| 331 |
+
|
| 332 |
+
# Add interpretation
|
| 333 |
+
if confidence > 0.8:
|
| 334 |
+
result_text += f"πͺ **High confidence prediction**"
|
| 335 |
+
elif confidence > 0.6:
|
| 336 |
+
result_text += f"π€ **Moderate confidence prediction**"
|
| 337 |
+
else:
|
| 338 |
+
result_text += f"β οΈ **Low confidence - uncertain prediction**"
|
| 339 |
+
|
| 340 |
+
# Create chart data
|
| 341 |
+
prob_df = pd.DataFrame({
|
| 342 |
+
'Category': ['REAL (Human)', 'FAKE (AI)'],
|
| 343 |
+
'Probability': [float(probabilities[0]), float(probabilities[1])]
|
| 344 |
+
})
|
| 345 |
+
|
| 346 |
+
# Quick status
|
| 347 |
+
color = "π’" if prediction == "REAL" else "π΄"
|
| 348 |
+
status = f"{color} {prediction} - {confidence:.1%} confidence"
|
| 349 |
+
|
| 350 |
+
return result_text, prob_df, prediction, status
|
| 351 |
+
|
| 352 |
+
except Exception as e:
|
| 353 |
+
error_msg = f"β Prediction failed: {str(e)}"
|
| 354 |
+
print(error_msg)
|
| 355 |
+
return error_msg, None, None, None
|
| 356 |
+
|
| 357 |
+
def predict_from_url(url):
|
| 358 |
+
"""Handle URL input and prediction"""
|
| 359 |
+
global detector
|
| 360 |
+
|
| 361 |
+
# Ensure detector is loaded
|
| 362 |
+
if detector is None:
|
| 363 |
+
load_result = load_detector()
|
| 364 |
+
if "β" in load_result:
|
| 365 |
+
return load_result, None, None, None, None
|
| 366 |
+
|
| 367 |
+
if not url or not url.strip():
|
| 368 |
+
return "β Please enter a valid image URL!", None, None, None, None
|
| 369 |
+
|
| 370 |
+
try:
|
| 371 |
+
# Download and predict
|
| 372 |
+
prediction, confidence, probabilities, image = detector.predict_from_url(url.strip())
|
| 373 |
+
|
| 374 |
+
# Format results (same as upload)
|
| 375 |
+
result_text = f"π **Prediction: {prediction}**\n\n"
|
| 376 |
+
result_text += f"π **Confidence: {confidence:.1%}**\n\n"
|
| 377 |
+
result_text += f"π **Detailed Probabilities:**\n"
|
| 378 |
+
result_text += f"β’ π’ REAL (Human-made): {probabilities[0]:.1%}\n"
|
| 379 |
+
result_text += f"β’ π΄ FAKE (AI-generated): {probabilities[1]:.1%}\n\n"
|
| 380 |
+
|
| 381 |
+
# Add interpretation
|
| 382 |
+
if confidence > 0.8:
|
| 383 |
+
result_text += f"πͺ **High confidence prediction**"
|
| 384 |
+
elif confidence > 0.6:
|
| 385 |
+
result_text += f"π€ **Moderate confidence prediction**"
|
| 386 |
+
else:
|
| 387 |
+
result_text += f"β οΈ **Low confidence - uncertain prediction**"
|
| 388 |
+
|
| 389 |
+
# Create chart data
|
| 390 |
+
prob_df = pd.DataFrame({
|
| 391 |
+
'Category': ['REAL (Human)', 'FAKE (AI)'],
|
| 392 |
+
'Probability': [float(probabilities[0]), float(probabilities[1])]
|
| 393 |
+
})
|
| 394 |
+
|
| 395 |
+
# Quick status
|
| 396 |
+
color = "π’" if prediction == "REAL" else "π΄"
|
| 397 |
+
status = f"{color} {prediction} - {confidence:.1%} confidence"
|
| 398 |
+
|
| 399 |
+
return result_text, prob_df, prediction, status, image
|
| 400 |
+
|
| 401 |
+
except Exception as e:
|
| 402 |
+
error_msg = f"β URL processing failed: {str(e)}"
|
| 403 |
+
print(error_msg)
|
| 404 |
+
return error_msg, None, None, None, None
|
| 405 |
+
|
| 406 |
+
def create_interface():
|
| 407 |
+
"""Create the Gradio web interface"""
|
| 408 |
+
|
| 409 |
+
# Custom CSS for better styling
|
| 410 |
+
css = """
|
| 411 |
+
.main-header {
|
| 412 |
+
text-align: center;
|
| 413 |
+
background: linear-gradient(90deg, #667eea 0%, #764ba2 100%);
|
| 414 |
+
color: white;
|
| 415 |
+
padding: 20px;
|
| 416 |
+
border-radius: 10px;
|
| 417 |
+
margin-bottom: 20px;
|
| 418 |
+
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
|
| 419 |
+
}
|
| 420 |
+
.status-box {
|
| 421 |
+
font-size: 18px;
|
| 422 |
+
font-weight: bold;
|
| 423 |
+
text-align: center;
|
| 424 |
+
}
|
| 425 |
+
"""
|
| 426 |
+
|
| 427 |
+
with gr.Blocks(css=css, title="AI Image Detector", theme=gr.themes.Soft()) as demo:
|
| 428 |
+
# Main header
|
| 429 |
+
gr.HTML("""
|
| 430 |
+
<div class="main-header">
|
| 431 |
+
<h1>π€ AI Image Detector</h1>
|
| 432 |
+
<p>Upload an image or provide a URL to detect if it's REAL (human-made) or FAKE (AI-generated)</p>
|
| 433 |
+
<p><small>Powered by RegNetY-16GF with custom classification layers</small></p>
|
| 434 |
+
</div>
|
| 435 |
+
""")
|
| 436 |
+
|
| 437 |
+
# Model loading section
|
| 438 |
+
with gr.Row():
|
| 439 |
+
with gr.Column(scale=3):
|
| 440 |
+
model_status = gr.Textbox(
|
| 441 |
+
label="π§ Model Status",
|
| 442 |
+
value="Click 'Load Model' to initialize the AI detector",
|
| 443 |
+
interactive=False,
|
| 444 |
+
lines=2
|
| 445 |
+
)
|
| 446 |
+
with gr.Column(scale=1):
|
| 447 |
+
load_btn = gr.Button("π Load Model", variant="secondary", size="lg")
|
| 448 |
+
|
| 449 |
+
# Main functionality tabs
|
| 450 |
+
with gr.Tabs():
|
| 451 |
+
# Image upload tab
|
| 452 |
+
with gr.TabItem("π Upload Image"):
|
| 453 |
+
with gr.Row():
|
| 454 |
+
with gr.Column(scale=1):
|
| 455 |
+
input_image = gr.Image(
|
| 456 |
+
label="π· Upload Image for Analysis",
|
| 457 |
+
type="pil",
|
| 458 |
+
height=400
|
| 459 |
+
)
|
| 460 |
+
predict_btn = gr.Button(
|
| 461 |
+
"π Analyze Image",
|
| 462 |
+
variant="primary",
|
| 463 |
+
size="lg"
|
| 464 |
+
)
|
| 465 |
+
|
| 466 |
+
with gr.Column(scale=1):
|
| 467 |
+
prediction_status = gr.Textbox(
|
| 468 |
+
label="π― Quick Result",
|
| 469 |
+
interactive=False,
|
| 470 |
+
lines=1,
|
| 471 |
+
elem_classes=["status-box"]
|
| 472 |
+
)
|
| 473 |
+
|
| 474 |
+
prediction_output = gr.Markdown(
|
| 475 |
+
label="π Detailed Analysis",
|
| 476 |
+
value="Upload an image and click 'Analyze Image' to get AI detection results"
|
| 477 |
+
)
|
| 478 |
+
|
| 479 |
+
probability_chart = gr.BarPlot(
|
| 480 |
+
label="π Probability Distribution",
|
| 481 |
+
x="Category",
|
| 482 |
+
y="Probability",
|
| 483 |
+
width=400,
|
| 484 |
+
height=250,
|
| 485 |
+
color="Category"
|
| 486 |
+
)
|
| 487 |
+
|
| 488 |
+
# URL input tab
|
| 489 |
+
with gr.TabItem("π Image URL"):
|
| 490 |
+
with gr.Row():
|
| 491 |
+
with gr.Column(scale=1):
|
| 492 |
+
url_input = gr.Textbox(
|
| 493 |
+
label="π Image URL",
|
| 494 |
+
placeholder="https://example.com/image.jpg",
|
| 495 |
+
lines=2
|
| 496 |
+
)
|
| 497 |
+
url_predict_btn = gr.Button(
|
| 498 |
+
"π Analyze from URL",
|
| 499 |
+
variant="primary",
|
| 500 |
+
size="lg"
|
| 501 |
+
)
|
| 502 |
+
|
| 503 |
+
downloaded_image = gr.Image(
|
| 504 |
+
label="π₯ Downloaded Image",
|
| 505 |
+
type="pil",
|
| 506 |
+
height=300
|
| 507 |
+
)
|
| 508 |
+
|
| 509 |
+
with gr.Column(scale=1):
|
| 510 |
+
url_prediction_status = gr.Textbox(
|
| 511 |
+
label="π― Quick Result",
|
| 512 |
+
interactive=False,
|
| 513 |
+
lines=1,
|
| 514 |
+
elem_classes=["status-box"]
|
| 515 |
+
)
|
| 516 |
+
|
| 517 |
+
url_prediction_output = gr.Markdown(
|
| 518 |
+
label="π Detailed Analysis",
|
| 519 |
+
value="Enter an image URL and click 'Analyze from URL' to get AI detection results"
|
| 520 |
+
)
|
| 521 |
+
|
| 522 |
+
url_probability_chart = gr.BarPlot(
|
| 523 |
+
label="π Probability Distribution",
|
| 524 |
+
x="Category",
|
| 525 |
+
y="Probability",
|
| 526 |
+
width=400,
|
| 527 |
+
height=250,
|
| 528 |
+
color="Category"
|
| 529 |
+
)
|
| 530 |
+
|
| 531 |
+
# Information and help section
|
| 532 |
+
with gr.Accordion("βΉοΈ About This AI Detector", open=False):
|
| 533 |
+
gr.Markdown("""
|
| 534 |
+
### π― What This Model Does
|
| 535 |
+
|
| 536 |
+
This AI detector analyzes images to determine if they are:
|
| 537 |
+
- **π’ REAL**: Created by humans (photographs, traditional digital art, etc.)
|
| 538 |
+
- **π΄ FAKE**: Generated by AI systems (DALL-E, Midjourney, Stable Diffusion, etc.)
|
| 539 |
+
|
| 540 |
+
### π Understanding the Results
|
| 541 |
+
|
| 542 |
+
- **Prediction**: The model's best guess (REAL or FAKE)
|
| 543 |
+
- **Confidence**: How certain the model is (higher = more confident)
|
| 544 |
+
- **Probabilities**: Breakdown showing likelihood for each category
|
| 545 |
+
|
| 546 |
+
### π§ Model Architecture
|
| 547 |
+
|
| 548 |
+
- **Backbone**: RegNetY-16GF (efficient computer vision model)
|
| 549 |
+
- **Input Size**: 224Γ224 pixels
|
| 550 |
+
- **Classes**: Binary classification (REAL vs FAKE)
|
| 551 |
+
- **Training**: Fine-tuned on diverse real and AI-generated images
|
| 552 |
+
|
| 553 |
+
### β οΈ Important Limitations
|
| 554 |
+
|
| 555 |
+
- This is a research tool and may not be 100% accurate
|
| 556 |
+
- Performance varies depending on image type and AI generation method
|
| 557 |
+
- Always use human judgment alongside model predictions
|
| 558 |
+
- New AI generation techniques may not be detected accurately
|
| 559 |
+
|
| 560 |
+
### π‘ Tips for Best Results
|
| 561 |
+
|
| 562 |
+
- Use clear, high-quality images
|
| 563 |
+
- Avoid heavily compressed or low-resolution images
|
| 564 |
+
- Be aware that the model may struggle with:
|
| 565 |
+
- Very recent AI generation techniques
|
| 566 |
+
- Heavily edited or filtered images
|
| 567 |
+
- Images with mixed real/AI content
|
| 568 |
+
""")
|
| 569 |
+
|
| 570 |
+
# Connect event handlers
|
| 571 |
+
load_btn.click(
|
| 572 |
+
fn=load_detector,
|
| 573 |
+
outputs=model_status
|
| 574 |
+
)
|
| 575 |
+
|
| 576 |
+
predict_btn.click(
|
| 577 |
+
fn=predict_image,
|
| 578 |
+
inputs=[input_image],
|
| 579 |
+
outputs=[prediction_output, probability_chart, gr.State(), prediction_status]
|
| 580 |
+
)
|
| 581 |
+
|
| 582 |
+
url_predict_btn.click(
|
| 583 |
+
fn=predict_from_url,
|
| 584 |
+
inputs=[url_input],
|
| 585 |
+
outputs=[url_prediction_output, url_probability_chart, gr.State(), url_prediction_status, downloaded_image]
|
| 586 |
+
)
|
| 587 |
+
|
| 588 |
+
# Optional: Add example images if you have them
|
| 589 |
+
# gr.Examples(
|
| 590 |
+
# examples=[
|
| 591 |
+
# ["path/to/example_real.jpg"],
|
| 592 |
+
# ["path/to/example_fake.jpg"],
|
| 593 |
+
# ],
|
| 594 |
+
# inputs=input_image,
|
| 595 |
+
# label="π· Try These Examples"
|
| 596 |
+
# )
|
| 597 |
+
|
| 598 |
+
return demo
|
| 599 |
+
|
| 600 |
+
if __name__ == "__main__":
|
| 601 |
+
# Initialize
|
| 602 |
+
detector = AIImageDetector('best_ai_detector.pth')
|
| 603 |
+
print("π Starting Optimized AI Image Detector...")
|
| 604 |
+
print("π This version will automatically detect if RegNet download is needed")
|
| 605 |
+
|
| 606 |
+
# Create interface
|
| 607 |
+
demo = create_interface()
|
| 608 |
+
|
| 609 |
+
# Try to auto-load model at startup (optional)
|
| 610 |
+
try:
|
| 611 |
+
print("π Attempting to auto-load model...")
|
| 612 |
+
load_result = load_detector()
|
| 613 |
+
print(f"π§ Startup result: {load_result}")
|
| 614 |
+
except Exception as e:
|
| 615 |
+
print(f"β οΈ Model not auto-loaded: {e}")
|
| 616 |
+
print("π‘ Use the 'Load Model' button in the interface")
|
| 617 |
+
|
| 618 |
+
# Launch interface optimized for Hugging Face Spaces
|
| 619 |
+
print("π Launching interface...")
|
| 620 |
+
demo.launch(
|
| 621 |
+
server_name="0.0.0.0",
|
| 622 |
+
server_port=7860,
|
| 623 |
+
share=True, # Don't use share=True in Hugging Face Spaces
|
| 624 |
+
show_error=True,
|
| 625 |
+
debug=False,ssr_mode=False
|
| 626 |
+
)
|
app_v0.py
ADDED
|
@@ -0,0 +1,351 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
from torchvision import transforms, models
|
| 6 |
+
from PIL import Image
|
| 7 |
+
import gradio as gr
|
| 8 |
+
import numpy as np
|
| 9 |
+
import pandas as pd
|
| 10 |
+
import warnings
|
| 11 |
+
import requests
|
| 12 |
+
from io import BytesIO
|
| 13 |
+
|
| 14 |
+
warnings.filterwarnings('ignore')
|
| 15 |
+
|
| 16 |
+
# =============================================================================
|
| 17 |
+
# MODEL FUNCTIONS
|
| 18 |
+
# =============================================================================
|
| 19 |
+
|
| 20 |
+
class AIDetectorModel(nn.Module):
|
| 21 |
+
def __init__(self, num_classes=2, dropout_prob=0.3):
|
| 22 |
+
super(AIDetectorModel, self).__init__()
|
| 23 |
+
|
| 24 |
+
self.backbone = models.regnet_y_16gf(weights=None)
|
| 25 |
+
self.backbone.avgpool = nn.AdaptiveMaxPool2d(output_size=(1, 1))
|
| 26 |
+
|
| 27 |
+
num_ftrs = self.backbone.fc.in_features
|
| 28 |
+
self.backbone.fc = nn.Sequential(
|
| 29 |
+
nn.Linear(num_ftrs, 2048),
|
| 30 |
+
nn.SiLU(),
|
| 31 |
+
nn.Dropout(dropout_prob),
|
| 32 |
+
nn.Linear(2048, 1024),
|
| 33 |
+
nn.SiLU(),
|
| 34 |
+
nn.Dropout(dropout_prob),
|
| 35 |
+
nn.Linear(1024, 512),
|
| 36 |
+
nn.SiLU(),
|
| 37 |
+
nn.Dropout(dropout_prob),
|
| 38 |
+
nn.Linear(512, num_classes)
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
def forward(self, x):
|
| 42 |
+
return self.backbone(x)
|
| 43 |
+
|
| 44 |
+
class AIDetector:
|
| 45 |
+
def __init__(self):
|
| 46 |
+
self.device = 'cuda' if torch.cuda.is_available() else 'cpu'
|
| 47 |
+
self.model = None
|
| 48 |
+
self.model_loaded = False
|
| 49 |
+
|
| 50 |
+
self.transform = transforms.Compose([
|
| 51 |
+
transforms.Resize(224, interpolation=transforms.InterpolationMode.BICUBIC),
|
| 52 |
+
transforms.CenterCrop(224),
|
| 53 |
+
transforms.ToTensor(),
|
| 54 |
+
transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
|
| 55 |
+
])
|
| 56 |
+
|
| 57 |
+
def load_model(self, model_path):
|
| 58 |
+
try:
|
| 59 |
+
if not os.path.exists(model_path):
|
| 60 |
+
return f"Error: Model file '{model_path}' not found"
|
| 61 |
+
|
| 62 |
+
checkpoint = torch.load(model_path, map_location='cpu')
|
| 63 |
+
|
| 64 |
+
if isinstance(checkpoint, dict) and 'model_state_dict' in checkpoint:
|
| 65 |
+
self.model = AIDetectorModel(
|
| 66 |
+
num_classes=checkpoint.get('num_classes', 2),
|
| 67 |
+
dropout_prob=checkpoint.get('dropout_prob', 0.3)
|
| 68 |
+
)
|
| 69 |
+
self.model.load_state_dict(checkpoint['model_state_dict'])
|
| 70 |
+
else:
|
| 71 |
+
self.model = AIDetectorModel()
|
| 72 |
+
self.model.load_state_dict(checkpoint)
|
| 73 |
+
|
| 74 |
+
self.model.to(self.device)
|
| 75 |
+
self.model.eval()
|
| 76 |
+
self.model_loaded = True
|
| 77 |
+
|
| 78 |
+
file_size = os.path.getsize(model_path) / (1024*1024)
|
| 79 |
+
return f"β
Model '{model_path}' loaded successfully! ({file_size:.1f}MB)"
|
| 80 |
+
|
| 81 |
+
except Exception as e:
|
| 82 |
+
self.model_loaded = False
|
| 83 |
+
return f"β Failed to load model: {str(e)}"
|
| 84 |
+
|
| 85 |
+
def predict(self, image):
|
| 86 |
+
if not self.model_loaded:
|
| 87 |
+
raise RuntimeError("No model loaded")
|
| 88 |
+
|
| 89 |
+
if image.mode != 'RGB':
|
| 90 |
+
image = image.convert('RGB')
|
| 91 |
+
|
| 92 |
+
input_tensor = self.transform(image).unsqueeze(0).to(self.device)
|
| 93 |
+
|
| 94 |
+
with torch.no_grad():
|
| 95 |
+
outputs = self.model(input_tensor)
|
| 96 |
+
probabilities = F.softmax(outputs, dim=1)
|
| 97 |
+
confidence, predicted = torch.max(probabilities, 1)
|
| 98 |
+
|
| 99 |
+
probs = probabilities.cpu().numpy()[0]
|
| 100 |
+
pred_class = predicted.cpu().item()
|
| 101 |
+
conf_score = confidence.cpu().item()
|
| 102 |
+
|
| 103 |
+
class_names = ['REAL', 'FAKE']
|
| 104 |
+
prediction = class_names[pred_class]
|
| 105 |
+
|
| 106 |
+
return prediction, conf_score, probs
|
| 107 |
+
|
| 108 |
+
# =============================================================================
|
| 109 |
+
# GLOBAL VARIABLES AND UTILITY FUNCTIONS
|
| 110 |
+
# =============================================================================
|
| 111 |
+
|
| 112 |
+
detector = AIDetector()
|
| 113 |
+
|
| 114 |
+
def get_model_files():
|
| 115 |
+
"""Get available .pth model files in current directory"""
|
| 116 |
+
try:
|
| 117 |
+
files = [f for f in os.listdir('.') if f.endswith('.pth')]
|
| 118 |
+
if not files:
|
| 119 |
+
files = ["No .pth files found"]
|
| 120 |
+
return files
|
| 121 |
+
except:
|
| 122 |
+
return ["Error reading directory"]
|
| 123 |
+
|
| 124 |
+
def refresh_models():
|
| 125 |
+
"""Refresh the model list"""
|
| 126 |
+
return gr.Dropdown(choices=get_model_files())
|
| 127 |
+
|
| 128 |
+
# =============================================================================
|
| 129 |
+
# UI HANDLER FUNCTIONS
|
| 130 |
+
# =============================================================================
|
| 131 |
+
|
| 132 |
+
def load_model_handler(model_name):
|
| 133 |
+
if not model_name or model_name in ["No .pth files found", "Error reading directory"]:
|
| 134 |
+
return "Please select a valid model file"
|
| 135 |
+
|
| 136 |
+
return detector.load_model(model_name)
|
| 137 |
+
|
| 138 |
+
def predict_handler(image):
|
| 139 |
+
if not detector.model_loaded:
|
| 140 |
+
return "β No model loaded. Please load a model first.", None, "No Model", "0%"
|
| 141 |
+
|
| 142 |
+
if image is None:
|
| 143 |
+
return "β Please upload an image", None, "No Image", "0%"
|
| 144 |
+
|
| 145 |
+
try:
|
| 146 |
+
prediction, confidence, probabilities = detector.predict(image)
|
| 147 |
+
|
| 148 |
+
result_text = f"""
|
| 149 |
+
## Analysis Results
|
| 150 |
+
|
| 151 |
+
**Prediction:** {prediction}
|
| 152 |
+
**Confidence:** {confidence:.1%}
|
| 153 |
+
|
| 154 |
+
**Detailed Probabilities:**
|
| 155 |
+
- REAL (Human-made): {probabilities[0]:.1%}
|
| 156 |
+
- FAKE (AI-generated): {probabilities[1]:.1%}
|
| 157 |
+
|
| 158 |
+
**Confidence Level:** {"High" if confidence > 0.8 else "Moderate" if confidence > 0.6 else "Low"}
|
| 159 |
+
"""
|
| 160 |
+
|
| 161 |
+
chart_data = pd.DataFrame({
|
| 162 |
+
'Category': ['REAL', 'FAKE'],
|
| 163 |
+
'Probability': [probabilities[0], probabilities[1]]
|
| 164 |
+
})
|
| 165 |
+
|
| 166 |
+
return result_text, chart_data, prediction, f"{confidence:.1%}"
|
| 167 |
+
|
| 168 |
+
except Exception as e:
|
| 169 |
+
return f"β Prediction failed: {str(e)}", None, "Error", "0%"
|
| 170 |
+
|
| 171 |
+
def url_handler(url):
|
| 172 |
+
if not url or not url.strip():
|
| 173 |
+
return "β Please enter a URL", None, "No URL", "0%", None
|
| 174 |
+
|
| 175 |
+
try:
|
| 176 |
+
url = url.strip()
|
| 177 |
+
if not url.startswith(('http://', 'https://')):
|
| 178 |
+
url = 'https://' + url
|
| 179 |
+
|
| 180 |
+
response = requests.get(url, timeout=10, headers={'User-Agent': 'Mozilla/5.0'})
|
| 181 |
+
response.raise_for_status()
|
| 182 |
+
|
| 183 |
+
image = Image.open(BytesIO(response.content))
|
| 184 |
+
result_text, chart_data, prediction, confidence = predict_handler(image)
|
| 185 |
+
|
| 186 |
+
return result_text, chart_data, prediction, confidence, image
|
| 187 |
+
|
| 188 |
+
except Exception as e:
|
| 189 |
+
return f"β Failed to load image from URL: {str(e)}", None, "Error", "0%", None
|
| 190 |
+
|
| 191 |
+
def clear_handler():
|
| 192 |
+
return None, "Upload an image to analyze", None, "No Image", "0%"
|
| 193 |
+
|
| 194 |
+
def clear_url_handler():
|
| 195 |
+
return "", "Enter an image URL to analyze", None, "No URL", "0%", None
|
| 196 |
+
|
| 197 |
+
# =============================================================================
|
| 198 |
+
# GRADIO INTERFACE
|
| 199 |
+
# =============================================================================
|
| 200 |
+
|
| 201 |
+
def create_interface():
|
| 202 |
+
# Simple theme without custom CSS
|
| 203 |
+
theme = theme = gr.themes.Soft(
|
| 204 |
+
primary_hue="orange",
|
| 205 |
+
secondary_hue="gray"
|
| 206 |
+
).set(
|
| 207 |
+
body_background_fill="*neutral_950",
|
| 208 |
+
block_background_fill="*neutral_900",
|
| 209 |
+
button_primary_background_fill="*orange_500",
|
| 210 |
+
button_primary_background_fill_hover="*orange_600"
|
| 211 |
+
)
|
| 212 |
+
|
| 213 |
+
with gr.Blocks(theme=theme, title="AI Image Detector") as demo:
|
| 214 |
+
|
| 215 |
+
gr.Markdown("# AI Image Detector")
|
| 216 |
+
gr.Markdown("Upload an image or provide a URL to detect if it's AI-generated or real.")
|
| 217 |
+
|
| 218 |
+
# Model Loading Section
|
| 219 |
+
with gr.Row():
|
| 220 |
+
with gr.Column(scale=3):
|
| 221 |
+
model_dropdown = gr.Dropdown(
|
| 222 |
+
label="Select Model File",
|
| 223 |
+
choices=get_model_files(),
|
| 224 |
+
value=None,
|
| 225 |
+
interactive=True
|
| 226 |
+
)
|
| 227 |
+
with gr.Column(scale=1):
|
| 228 |
+
load_btn = gr.Button("Load Model", variant="primary")
|
| 229 |
+
refresh_btn = gr.Button("Refresh List", variant="secondary")
|
| 230 |
+
|
| 231 |
+
model_status = gr.Textbox(
|
| 232 |
+
label="Model Status",
|
| 233 |
+
value="No model loaded",
|
| 234 |
+
interactive=False,
|
| 235 |
+
lines=2
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
# Main Interface
|
| 239 |
+
with gr.Tabs():
|
| 240 |
+
# Upload Tab
|
| 241 |
+
with gr.TabItem("Upload Image"):
|
| 242 |
+
with gr.Row():
|
| 243 |
+
with gr.Column():
|
| 244 |
+
upload_image = gr.Image(
|
| 245 |
+
label="Upload Image",
|
| 246 |
+
type="pil"
|
| 247 |
+
)
|
| 248 |
+
with gr.Row():
|
| 249 |
+
analyze_btn = gr.Button("Analyze Image", variant="primary")
|
| 250 |
+
clear_btn = gr.Button("Clear")
|
| 251 |
+
|
| 252 |
+
with gr.Column():
|
| 253 |
+
status_row = gr.Row()
|
| 254 |
+
with status_row:
|
| 255 |
+
result_status = gr.Textbox(label="Result", interactive=False)
|
| 256 |
+
confidence_status = gr.Textbox(label="Confidence", interactive=False)
|
| 257 |
+
|
| 258 |
+
results_text = gr.Markdown("Upload an image to analyze")
|
| 259 |
+
results_chart = gr.BarPlot(
|
| 260 |
+
x="Category",
|
| 261 |
+
y="Probability",
|
| 262 |
+
title="Prediction Probabilities",
|
| 263 |
+
height=300
|
| 264 |
+
)
|
| 265 |
+
|
| 266 |
+
# URL Tab
|
| 267 |
+
with gr.TabItem("Analyze URL"):
|
| 268 |
+
with gr.Row():
|
| 269 |
+
with gr.Column():
|
| 270 |
+
url_input = gr.Textbox(
|
| 271 |
+
label="Image URL",
|
| 272 |
+
placeholder="https://example.com/image.jpg"
|
| 273 |
+
)
|
| 274 |
+
with gr.Row():
|
| 275 |
+
url_btn = gr.Button("Load & Analyze", variant="primary")
|
| 276 |
+
url_clear_btn = gr.Button("Clear")
|
| 277 |
+
|
| 278 |
+
url_image = gr.Image(label="Loaded Image", type="pil")
|
| 279 |
+
|
| 280 |
+
with gr.Column():
|
| 281 |
+
url_status_row = gr.Row()
|
| 282 |
+
with url_status_row:
|
| 283 |
+
url_result_status = gr.Textbox(label="Result", interactive=False)
|
| 284 |
+
url_confidence_status = gr.Textbox(label="Confidence", interactive=False)
|
| 285 |
+
|
| 286 |
+
url_results_text = gr.Markdown("Enter an image URL to analyze")
|
| 287 |
+
url_results_chart = gr.BarPlot(
|
| 288 |
+
x="Category",
|
| 289 |
+
y="Probability",
|
| 290 |
+
title="URL Prediction Probabilities",
|
| 291 |
+
height=300
|
| 292 |
+
)
|
| 293 |
+
|
| 294 |
+
# Event Handlers
|
| 295 |
+
load_btn.click(
|
| 296 |
+
fn=load_model_handler,
|
| 297 |
+
inputs=[model_dropdown],
|
| 298 |
+
outputs=[model_status]
|
| 299 |
+
)
|
| 300 |
+
|
| 301 |
+
refresh_btn.click(
|
| 302 |
+
fn=refresh_models,
|
| 303 |
+
outputs=[model_dropdown]
|
| 304 |
+
)
|
| 305 |
+
|
| 306 |
+
analyze_btn.click(
|
| 307 |
+
fn=predict_handler,
|
| 308 |
+
inputs=[upload_image],
|
| 309 |
+
outputs=[results_text, results_chart, result_status, confidence_status]
|
| 310 |
+
)
|
| 311 |
+
|
| 312 |
+
url_btn.click(
|
| 313 |
+
fn=url_handler,
|
| 314 |
+
inputs=[url_input],
|
| 315 |
+
outputs=[url_results_text, url_results_chart, url_result_status, url_confidence_status, url_image]
|
| 316 |
+
)
|
| 317 |
+
|
| 318 |
+
clear_btn.click(
|
| 319 |
+
fn=clear_handler,
|
| 320 |
+
outputs=[upload_image, results_text, results_chart, result_status, confidence_status]
|
| 321 |
+
)
|
| 322 |
+
|
| 323 |
+
url_clear_btn.click(
|
| 324 |
+
fn=clear_url_handler,
|
| 325 |
+
outputs=[url_input, url_results_text, url_results_chart, url_result_status, url_confidence_status, url_image]
|
| 326 |
+
)
|
| 327 |
+
|
| 328 |
+
return demo
|
| 329 |
+
|
| 330 |
+
# =============================================================================
|
| 331 |
+
# MAIN FUNCTION
|
| 332 |
+
# =============================================================================
|
| 333 |
+
|
| 334 |
+
def main():
|
| 335 |
+
print("AI Image Detector Starting...")
|
| 336 |
+
print(f"PyTorch: {torch.__version__}")
|
| 337 |
+
print(f"CUDA Available: {torch.cuda.is_available()}")
|
| 338 |
+
print(f"Device: {'GPU' if torch.cuda.is_available() else 'CPU'}")
|
| 339 |
+
|
| 340 |
+
models = get_model_files()
|
| 341 |
+
print(f"Found models: {models}")
|
| 342 |
+
|
| 343 |
+
demo = create_interface()
|
| 344 |
+
demo.launch(
|
| 345 |
+
server_name="0.0.0.0",
|
| 346 |
+
server_port=7860,
|
| 347 |
+
share=False
|
| 348 |
+
)
|
| 349 |
+
|
| 350 |
+
if __name__ == "__main__":
|
| 351 |
+
main()
|
best_ai_detector.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f1cbd0d1efdf3c8f84622b519ff204e286d8ccfb90ffe106ec96b5efbbb34311
|
| 3 |
+
size 358207028
|
best_ai_detector_new.pth
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:59044857b458fd807706133bf2d4cb2ac7089c10dc30c6f24ffaf3476a7611fa
|
| 3 |
+
size 358210596
|
saveFullModel.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
from torchvision import models
|
| 4 |
+
import os
|
| 5 |
+
|
| 6 |
+
class AIDetectorModel(nn.Module):
|
| 7 |
+
def __init__(self, num_classes=2, dropout_prob=0.3, load_pretrained=True):
|
| 8 |
+
super(AIDetectorModel, self).__init__()
|
| 9 |
+
|
| 10 |
+
if load_pretrained:
|
| 11 |
+
print("π₯ Loading RegNet with pre-trained weights...")
|
| 12 |
+
self.backbone = models.regnet_y_16gf(weights=models.RegNet_Y_16GF_Weights.IMAGENET1K_SWAG_E2E_V1)
|
| 13 |
+
else:
|
| 14 |
+
self.backbone = models.regnet_y_16gf(weights=None)
|
| 15 |
+
|
| 16 |
+
# Freeze most layers
|
| 17 |
+
for param in self.backbone.parameters():
|
| 18 |
+
param.requires_grad = False
|
| 19 |
+
|
| 20 |
+
# Unfreeze last block
|
| 21 |
+
if hasattr(self.backbone, 'trunk_output') and hasattr(self.backbone.trunk_output, 'block4'):
|
| 22 |
+
self.backbone.trunk_output.block4.requires_grad_(True)
|
| 23 |
+
|
| 24 |
+
# Replace average pooling with max pooling
|
| 25 |
+
self.backbone.avgpool = nn.AdaptiveMaxPool2d(output_size=(1, 1))
|
| 26 |
+
|
| 27 |
+
# Get feature dimension
|
| 28 |
+
num_ftrs = self.backbone.fc.in_features
|
| 29 |
+
|
| 30 |
+
# Replace classifier
|
| 31 |
+
self.backbone.fc = nn.Sequential(
|
| 32 |
+
nn.Linear(num_ftrs, 2048),
|
| 33 |
+
nn.SiLU(),
|
| 34 |
+
nn.Dropout(dropout_prob),
|
| 35 |
+
nn.Linear(2048, 1024),
|
| 36 |
+
nn.SiLU(),
|
| 37 |
+
nn.Dropout(dropout_prob),
|
| 38 |
+
nn.Linear(1024, 512),
|
| 39 |
+
nn.SiLU(),
|
| 40 |
+
nn.Dropout(dropout_prob),
|
| 41 |
+
nn.Linear(512, num_classes)
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
def forward(self, x):
|
| 45 |
+
return self.backbone(x)
|
| 46 |
+
|
| 47 |
+
def verify_complete_backbone(model):
|
| 48 |
+
"""Verify that model has complete RegNet backbone - CORRECTED"""
|
| 49 |
+
state_dict = model.state_dict()
|
| 50 |
+
all_keys = list(state_dict.keys())
|
| 51 |
+
|
| 52 |
+
# Check for actual RegNet components based on real structure
|
| 53 |
+
stem_keys = [k for k in all_keys if 'backbone.stem' in k]
|
| 54 |
+
block_keys = [k for k in all_keys if 'backbone.trunk_output.block' in k]
|
| 55 |
+
proj_keys = [k for k in all_keys if 'backbone.trunk_output.block' in k and 'proj' in k]
|
| 56 |
+
fc_keys = [k for k in all_keys if 'backbone.fc' in k]
|
| 57 |
+
|
| 58 |
+
print(f"π Backbone verification:")
|
| 59 |
+
print(f" β’ Total keys: {len(all_keys)}")
|
| 60 |
+
print(f" β’ Stem keys: {len(stem_keys)}")
|
| 61 |
+
print(f" β’ Block keys: {len(block_keys)}")
|
| 62 |
+
print(f" β’ Projection keys: {len(proj_keys)}")
|
| 63 |
+
print(f" β’ FC keys: {len(fc_keys)}")
|
| 64 |
+
|
| 65 |
+
# CORRECTED: RegNet Y 16GF has: ~420 block params, ~24 proj layers, 6 stem params
|
| 66 |
+
is_complete = (
|
| 67 |
+
len(block_keys) > 400 and # Should have ~420 block parameters
|
| 68 |
+
len(stem_keys) > 5 and # Should have 6 stem parameters
|
| 69 |
+
len(proj_keys) > 20 # Should have ~24 projection parameters
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
if is_complete:
|
| 73 |
+
print("β
Complete backbone verified!")
|
| 74 |
+
print(f"β
Found {len(block_keys)} blocks, {len(stem_keys)} stem, {len(proj_keys)} proj layers")
|
| 75 |
+
else:
|
| 76 |
+
print("β Incomplete backbone detected!")
|
| 77 |
+
print(f"Expected: >400 blocks, >5 stem, >20 proj")
|
| 78 |
+
print(f"Found: {len(block_keys)} blocks, {len(stem_keys)} stem, {len(proj_keys)} proj")
|
| 79 |
+
|
| 80 |
+
return is_complete
|
| 81 |
+
|
| 82 |
+
def create_truly_complete_model(trained_checkpoint_path, output_path):
|
| 83 |
+
"""Create a truly complete model with full RegNet weights"""
|
| 84 |
+
|
| 85 |
+
print("π Creating TRULY complete offline model...")
|
| 86 |
+
print("=" * 60)
|
| 87 |
+
|
| 88 |
+
# Step 1: Create fresh model WITH pretrained weights
|
| 89 |
+
print("π₯ Step 1: Loading fresh RegNet with ImageNet weights...")
|
| 90 |
+
fresh_model = AIDetectorModel(load_pretrained=True)
|
| 91 |
+
|
| 92 |
+
# Step 2: Verify fresh model is complete (FIXED VALIDATION)
|
| 93 |
+
print("\nπ Step 2: Verifying fresh model completeness...")
|
| 94 |
+
if not verify_complete_backbone(fresh_model):
|
| 95 |
+
raise RuntimeError("β Fresh model is not complete!")
|
| 96 |
+
|
| 97 |
+
# Step 3: Load your trained weights
|
| 98 |
+
print(f"\nπ Step 3: Loading your trained weights from {trained_checkpoint_path}...")
|
| 99 |
+
trained_checkpoint = torch.load(trained_checkpoint_path, map_location='cpu')
|
| 100 |
+
|
| 101 |
+
if isinstance(trained_checkpoint, dict) and 'model_state_dict' in trained_checkpoint:
|
| 102 |
+
trained_state = trained_checkpoint['model_state_dict']
|
| 103 |
+
extra_info = {
|
| 104 |
+
'num_classes': trained_checkpoint.get('num_classes', 2),
|
| 105 |
+
'dropout_prob': trained_checkpoint.get('dropout_prob', 0.3),
|
| 106 |
+
'epoch': trained_checkpoint.get('epoch', 0),
|
| 107 |
+
'val_loss': trained_checkpoint.get('val_loss', 0.0)
|
| 108 |
+
}
|
| 109 |
+
else:
|
| 110 |
+
trained_state = trained_checkpoint
|
| 111 |
+
extra_info = {'num_classes': 2, 'dropout_prob': 0.3}
|
| 112 |
+
|
| 113 |
+
# Step 4: Merge weights intelligently
|
| 114 |
+
print("\nπ Step 4: Merging backbone + trained classifier...")
|
| 115 |
+
fresh_state = fresh_model.state_dict()
|
| 116 |
+
|
| 117 |
+
# Keep ALL backbone weights from fresh model (includes RegNet pretrained)
|
| 118 |
+
# Replace ONLY classifier weights from trained model
|
| 119 |
+
merged_state = fresh_state.copy()
|
| 120 |
+
|
| 121 |
+
classifier_keys = [k for k in trained_state.keys() if 'backbone.fc' in k]
|
| 122 |
+
print(f" β’ Replacing {len(classifier_keys)} classifier parameters")
|
| 123 |
+
|
| 124 |
+
for key in classifier_keys:
|
| 125 |
+
if key in trained_state:
|
| 126 |
+
merged_state[key] = trained_state[key]
|
| 127 |
+
print(f" β
Updated: {key}")
|
| 128 |
+
|
| 129 |
+
# Step 5: Create final model and verify
|
| 130 |
+
print("\nβ
Step 5: Creating final complete model...")
|
| 131 |
+
final_model = AIDetectorModel(load_pretrained=False) # Empty architecture
|
| 132 |
+
final_model.load_state_dict(merged_state) # Load merged weights
|
| 133 |
+
|
| 134 |
+
# Final verification (should definitely pass now)
|
| 135 |
+
print("\nπ Final verification:")
|
| 136 |
+
if not verify_complete_backbone(final_model):
|
| 137 |
+
raise RuntimeError("β Final model verification failed!")
|
| 138 |
+
|
| 139 |
+
# Step 6: Save complete model
|
| 140 |
+
print(f"\nπΎ Step 6: Saving complete model to {output_path}...")
|
| 141 |
+
complete_checkpoint = {
|
| 142 |
+
'model_state_dict': merged_state,
|
| 143 |
+
'complete_model': True,
|
| 144 |
+
'backbone_included': True,
|
| 145 |
+
'offline_ready': True,
|
| 146 |
+
**extra_info
|
| 147 |
+
}
|
| 148 |
+
|
| 149 |
+
torch.save(complete_checkpoint, output_path)
|
| 150 |
+
|
| 151 |
+
file_size = os.path.getsize(output_path) / (1024*1024)
|
| 152 |
+
print(f"β
SUCCESS! Complete model saved:")
|
| 153 |
+
print(f" β’ File: {output_path}")
|
| 154 |
+
print(f" β’ Size: {file_size:.1f}MB")
|
| 155 |
+
print(f" β’ Ready for offline use!")
|
| 156 |
+
|
| 157 |
+
return output_path
|
| 158 |
+
|
| 159 |
+
if __name__ == "__main__":
|
| 160 |
+
# Input and output paths
|
| 161 |
+
input_model = "2_block_best_ai_detector.pth" # Your trained model
|
| 162 |
+
output_model = "truly_complete_ai_detector_new.pth" # New complete model
|
| 163 |
+
|
| 164 |
+
if not os.path.exists(input_model):
|
| 165 |
+
print(f"β Trained model not found: {input_model}")
|
| 166 |
+
# Show available files
|
| 167 |
+
pth_files = [f for f in os.listdir('.') if f.endswith('.pth')]
|
| 168 |
+
if pth_files:
|
| 169 |
+
print("Available .pth files:")
|
| 170 |
+
for f in pth_files:
|
| 171 |
+
size = os.path.getsize(f) / (1024*1024)
|
| 172 |
+
print(f" β’ {f} ({size:.1f}MB)")
|
| 173 |
+
else:
|
| 174 |
+
print("No .pth files found in current directory")
|
| 175 |
+
else:
|
| 176 |
+
try:
|
| 177 |
+
result_path = create_truly_complete_model(input_model, output_model)
|
| 178 |
+
print("\n" + "="*60)
|
| 179 |
+
print(f"π SUCCESS! Use this file in your offline app:")
|
| 180 |
+
print(f"π {result_path}")
|
| 181 |
+
print("="*60)
|
| 182 |
+
except Exception as e:
|
| 183 |
+
print(f"β Failed to create complete model: {e}")
|
static/styles.css
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
:root{
|
| 2 |
+
--bg:#0b0f14;
|
| 3 |
+
--card:#121824;
|
| 4 |
+
--card2:#0f1520;
|
| 5 |
+
--text:#e8eef7;
|
| 6 |
+
--muted:#a9b6c7;
|
| 7 |
+
--accent:#ff5f00;
|
| 8 |
+
--accent2:#eb001b;
|
| 9 |
+
--border:rgba(255,255,255,.08);
|
| 10 |
+
}
|
| 11 |
+
*{box-sizing:border-box}
|
| 12 |
+
body{
|
| 13 |
+
margin:0;
|
| 14 |
+
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, "Apple Color Emoji", "Segoe UI Emoji";
|
| 15 |
+
background: radial-gradient(1200px 600px at 20% 0%, rgba(255,95,0,.25), transparent 50%),
|
| 16 |
+
radial-gradient(1000px 500px at 90% 10%, rgba(235,0,27,.18), transparent 55%),
|
| 17 |
+
var(--bg);
|
| 18 |
+
color:var(--text);
|
| 19 |
+
}
|
| 20 |
+
.container{max-width:1100px;margin:0 auto;padding:28px 18px 60px}
|
| 21 |
+
.header{
|
| 22 |
+
background: linear-gradient(135deg, rgba(235,0,27,.95), rgba(255,95,0,.95));
|
| 23 |
+
border-radius:16px;
|
| 24 |
+
padding:22px 22px;
|
| 25 |
+
border:1px solid rgba(255,255,255,.12);
|
| 26 |
+
}
|
| 27 |
+
h1{margin:0 0 6px 0;font-size:28px}
|
| 28 |
+
.sub{margin:0;color:rgba(255,255,255,.92)}
|
| 29 |
+
.grid{display:grid;grid-template-columns:repeat(3,1fr);gap:14px;margin-top:14px}
|
| 30 |
+
.card{
|
| 31 |
+
background: linear-gradient(180deg, rgba(18,24,36,.95), rgba(15,21,32,.95));
|
| 32 |
+
border:1px solid var(--border);
|
| 33 |
+
border-radius:16px;
|
| 34 |
+
padding:16px;
|
| 35 |
+
box-shadow: 0 10px 30px rgba(0,0,0,.25);
|
| 36 |
+
}
|
| 37 |
+
.card h2{margin:0 0 12px 0;font-size:16px;color:var(--text)}
|
| 38 |
+
.row{display:flex;gap:12px;align-items:flex-end;flex-wrap:wrap}
|
| 39 |
+
.field{display:flex;flex-direction:column;gap:6px;width:100%}
|
| 40 |
+
.field span{font-size:12px;color:var(--muted)}
|
| 41 |
+
select,input[type="url"],input[type="number"],input[type="file"]{
|
| 42 |
+
width:100%;
|
| 43 |
+
padding:10px 10px;
|
| 44 |
+
border-radius:10px;
|
| 45 |
+
border:1px solid var(--border);
|
| 46 |
+
background:#0b111b;
|
| 47 |
+
color:var(--text);
|
| 48 |
+
outline:none;
|
| 49 |
+
}
|
| 50 |
+
.btn{
|
| 51 |
+
appearance:none;
|
| 52 |
+
border:1px solid rgba(255,255,255,.14);
|
| 53 |
+
background: linear-gradient(135deg, var(--accent2), var(--accent));
|
| 54 |
+
color:white;
|
| 55 |
+
border-radius:12px;
|
| 56 |
+
padding:10px 14px;
|
| 57 |
+
font-weight:700;
|
| 58 |
+
cursor:pointer;
|
| 59 |
+
width:100%;
|
| 60 |
+
}
|
| 61 |
+
.btn.secondary{
|
| 62 |
+
background: transparent;
|
| 63 |
+
border:1px solid rgba(255,255,255,.18);
|
| 64 |
+
}
|
| 65 |
+
.btn:hover{filter:brightness(1.03)}
|
| 66 |
+
.status{margin-top:12px;padding:10px;border:1px dashed rgba(255,255,255,.16);border-radius:12px}
|
| 67 |
+
.label{font-size:12px;color:var(--muted);margin-bottom:6px}
|
| 68 |
+
.mono{font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;}
|
| 69 |
+
.result{margin-top:12px}
|
| 70 |
+
.pill{
|
| 71 |
+
display:inline-block;
|
| 72 |
+
padding:8px 10px;
|
| 73 |
+
border-radius:999px;
|
| 74 |
+
border:1px solid rgba(255,255,255,.14);
|
| 75 |
+
background: rgba(255,255,255,.06);
|
| 76 |
+
font-weight:700;
|
| 77 |
+
}
|
| 78 |
+
.hint{margin-top:8px;font-size:12px;color:var(--muted)}
|
| 79 |
+
.media-row{margin-top:10px}
|
| 80 |
+
.preview{
|
| 81 |
+
width:100%;
|
| 82 |
+
height:auto;
|
| 83 |
+
border-radius:14px;
|
| 84 |
+
border:1px solid rgba(255,255,255,.10);
|
| 85 |
+
background: rgba(0,0,0,.18);
|
| 86 |
+
}
|
| 87 |
+
.kv{
|
| 88 |
+
margin-top:10px;
|
| 89 |
+
display:grid;
|
| 90 |
+
grid-template-columns:1fr 1fr;
|
| 91 |
+
gap:8px 10px;
|
| 92 |
+
}
|
| 93 |
+
.kv .k{
|
| 94 |
+
display:block;
|
| 95 |
+
font-size:12px;
|
| 96 |
+
color:var(--muted);
|
| 97 |
+
margin-bottom:4px;
|
| 98 |
+
}
|
| 99 |
+
.kv .v{
|
| 100 |
+
display:block;
|
| 101 |
+
padding:8px 10px;
|
| 102 |
+
border-radius:12px;
|
| 103 |
+
border:1px solid rgba(255,255,255,.10);
|
| 104 |
+
background: rgba(0,0,0,.18);
|
| 105 |
+
}
|
| 106 |
+
.frame-grid{
|
| 107 |
+
margin-top:12px;
|
| 108 |
+
display:grid;
|
| 109 |
+
grid-template-columns: repeat(2, 1fr);
|
| 110 |
+
gap:10px;
|
| 111 |
+
}
|
| 112 |
+
.frame-card{
|
| 113 |
+
border:1px solid rgba(255,255,255,.10);
|
| 114 |
+
background: rgba(0,0,0,.12);
|
| 115 |
+
border-radius:14px;
|
| 116 |
+
overflow:hidden;
|
| 117 |
+
}
|
| 118 |
+
.frame{
|
| 119 |
+
width:100%;
|
| 120 |
+
height:auto;
|
| 121 |
+
display:block;
|
| 122 |
+
}
|
| 123 |
+
.frame-meta{
|
| 124 |
+
padding:10px;
|
| 125 |
+
color:var(--muted);
|
| 126 |
+
display:flex;
|
| 127 |
+
flex-direction:column;
|
| 128 |
+
gap:6px;
|
| 129 |
+
}
|
| 130 |
+
.history{
|
| 131 |
+
margin-top:10px;
|
| 132 |
+
display:flex;
|
| 133 |
+
flex-direction:column;
|
| 134 |
+
gap:10px;
|
| 135 |
+
}
|
| 136 |
+
.history-item{
|
| 137 |
+
border:1px solid rgba(255,255,255,.10);
|
| 138 |
+
background: rgba(0,0,0,.14);
|
| 139 |
+
border-radius:14px;
|
| 140 |
+
padding:10px;
|
| 141 |
+
}
|
| 142 |
+
.history-top{
|
| 143 |
+
display:flex;
|
| 144 |
+
justify-content:space-between;
|
| 145 |
+
gap:10px;
|
| 146 |
+
color:var(--muted);
|
| 147 |
+
font-size:12px;
|
| 148 |
+
}
|
| 149 |
+
.history-mid{
|
| 150 |
+
margin-top:6px;
|
| 151 |
+
display:flex;
|
| 152 |
+
flex-direction:column;
|
| 153 |
+
gap:4px;
|
| 154 |
+
}
|
| 155 |
+
.history-bottom{
|
| 156 |
+
margin-top:8px;
|
| 157 |
+
font-weight:700;
|
| 158 |
+
color:var(--text);
|
| 159 |
+
}
|
| 160 |
+
.error{
|
| 161 |
+
padding:10px;
|
| 162 |
+
border-radius:12px;
|
| 163 |
+
border:1px solid rgba(255,95,0,.35);
|
| 164 |
+
background: rgba(255,95,0,.12);
|
| 165 |
+
color: #ffd7bf;
|
| 166 |
+
}
|
| 167 |
+
.md{
|
| 168 |
+
margin-top:10px;
|
| 169 |
+
white-space:pre-wrap;
|
| 170 |
+
padding:10px;
|
| 171 |
+
border-radius:12px;
|
| 172 |
+
border:1px solid rgba(255,255,255,.10);
|
| 173 |
+
background: rgba(0,0,0,.18);
|
| 174 |
+
color: var(--text);
|
| 175 |
+
overflow:auto;
|
| 176 |
+
}
|
| 177 |
+
.footer{margin-top:18px}
|
| 178 |
+
@media (max-width: 980px){
|
| 179 |
+
.grid{grid-template-columns:1fr}
|
| 180 |
+
.btn{width:100%}
|
| 181 |
+
.frame-grid{grid-template-columns:1fr}
|
| 182 |
+
.kv{grid-template-columns:1fr}
|
| 183 |
+
}
|
| 184 |
+
|
templates/index.html
ADDED
|
@@ -0,0 +1,175 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8" />
|
| 5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
| 6 |
+
<title>AI Image Detector</title>
|
| 7 |
+
<link rel="stylesheet" href="/static/styles.css" />
|
| 8 |
+
</head>
|
| 9 |
+
<body>
|
| 10 |
+
<div class="container">
|
| 11 |
+
<header class="header">
|
| 12 |
+
<div>
|
| 13 |
+
<h1>AI Image Detector</h1>
|
| 14 |
+
<p class="sub">Detect whether an image looks REAL (human-made) or FAKE (AI-generated). Also supports quick video sampling.</p>
|
| 15 |
+
</div>
|
| 16 |
+
</header>
|
| 17 |
+
|
| 18 |
+
<section class="card">
|
| 19 |
+
<h2>Model</h2>
|
| 20 |
+
<form class="row" method="post" action="/load_model">
|
| 21 |
+
<label class="field">
|
| 22 |
+
<span>Model file</span>
|
| 23 |
+
<select name="model_path">
|
| 24 |
+
{% for m in available_models %}
|
| 25 |
+
<option value="{{ m }}" {% if current_model_path == m %}selected{% endif %}>{{ m }}</option>
|
| 26 |
+
{% endfor %}
|
| 27 |
+
</select>
|
| 28 |
+
</label>
|
| 29 |
+
<button type="submit" class="btn secondary">Load model</button>
|
| 30 |
+
</form>
|
| 31 |
+
<div class="status">
|
| 32 |
+
<div class="label">Status</div>
|
| 33 |
+
<div class="mono">{{ model_status }}</div>
|
| 34 |
+
</div>
|
| 35 |
+
</section>
|
| 36 |
+
|
| 37 |
+
<section class="card">
|
| 38 |
+
<h2>History (last 10)</h2>
|
| 39 |
+
{% if history and history|length > 0 %}
|
| 40 |
+
<div class="history">
|
| 41 |
+
{% for h in history %}
|
| 42 |
+
<div class="history-item">
|
| 43 |
+
<div class="history-top">
|
| 44 |
+
<div class="mono">{{ h.ts }}</div>
|
| 45 |
+
<div class="mono">{{ h.type }}</div>
|
| 46 |
+
</div>
|
| 47 |
+
<div class="history-mid">
|
| 48 |
+
<div class="hint">Model: <span class="mono">{{ h.model }}</span></div>
|
| 49 |
+
<div class="hint">Input: <span class="mono">{{ h.source }}</span></div>
|
| 50 |
+
</div>
|
| 51 |
+
<div class="history-bottom">
|
| 52 |
+
<div class="mono">{{ h.prediction }}{% if h.confidence is not none %} Β· {{ (h.confidence*100) | round(1) }}%{% endif %}</div>
|
| 53 |
+
</div>
|
| 54 |
+
</div>
|
| 55 |
+
{% endfor %}
|
| 56 |
+
</div>
|
| 57 |
+
{% else %}
|
| 58 |
+
<div class="hint">No checks yet.</div>
|
| 59 |
+
{% endif %}
|
| 60 |
+
</section>
|
| 61 |
+
|
| 62 |
+
<section class="grid">
|
| 63 |
+
<div class="card">
|
| 64 |
+
<h2>Upload image</h2>
|
| 65 |
+
<form method="post" action="/analyze_image_upload" enctype="multipart/form-data">
|
| 66 |
+
<label class="field">
|
| 67 |
+
<span>Image file</span>
|
| 68 |
+
<input type="file" name="file" accept="image/*" required />
|
| 69 |
+
</label>
|
| 70 |
+
<button type="submit" class="btn">Analyze image</button>
|
| 71 |
+
</form>
|
| 72 |
+
|
| 73 |
+
{% if image_result %}
|
| 74 |
+
<div class="result">
|
| 75 |
+
{% if image_result.error %}
|
| 76 |
+
<div class="error">Error: {{ image_result.error }}</div>
|
| 77 |
+
{% else %}
|
| 78 |
+
<div class="pill">{{ image_result.quick }}</div>
|
| 79 |
+
<div class="media-row">
|
| 80 |
+
<img class="preview" src="{{ image_result.image_data_uri }}" alt="Uploaded image preview" />
|
| 81 |
+
</div>
|
| 82 |
+
<div class="kv">
|
| 83 |
+
<div><span class="k">Prediction</span><span class="v mono">{{ image_result.prediction }}</span></div>
|
| 84 |
+
<div><span class="k">Confidence</span><span class="v mono">{{ (image_result.confidence*100) | round(1) }}%</span></div>
|
| 85 |
+
<div><span class="k">p_real</span><span class="v mono">{{ (image_result.probabilities[0]*100) | round(1) }}%</span></div>
|
| 86 |
+
<div><span class="k">p_fake</span><span class="v mono">{{ (image_result.probabilities[1]*100) | round(1) }}%</span></div>
|
| 87 |
+
</div>
|
| 88 |
+
{% endif %}
|
| 89 |
+
</div>
|
| 90 |
+
{% endif %}
|
| 91 |
+
</div>
|
| 92 |
+
|
| 93 |
+
<div class="card">
|
| 94 |
+
<h2>Image URL</h2>
|
| 95 |
+
<form method="post" action="/analyze_image_url">
|
| 96 |
+
<label class="field">
|
| 97 |
+
<span>Image URL</span>
|
| 98 |
+
<input type="url" name="url" placeholder="https://example.com/image.jpg" required />
|
| 99 |
+
</label>
|
| 100 |
+
<button type="submit" class="btn">Analyze URL</button>
|
| 101 |
+
</form>
|
| 102 |
+
|
| 103 |
+
{% if url_result %}
|
| 104 |
+
<div class="result">
|
| 105 |
+
{% if url_result.error %}
|
| 106 |
+
<div class="error">Error: {{ url_result.error }}</div>
|
| 107 |
+
{% else %}
|
| 108 |
+
<div class="pill">{{ url_result.quick }}</div>
|
| 109 |
+
<div class="hint">Source: <span class="mono">{{ url_result.url }}</span></div>
|
| 110 |
+
<div class="media-row">
|
| 111 |
+
<img class="preview" src="{{ url_result.image_data_uri }}" alt="URL image preview" />
|
| 112 |
+
</div>
|
| 113 |
+
<div class="kv">
|
| 114 |
+
<div><span class="k">Prediction</span><span class="v mono">{{ url_result.prediction }}</span></div>
|
| 115 |
+
<div><span class="k">Confidence</span><span class="v mono">{{ (url_result.confidence*100) | round(1) }}%</span></div>
|
| 116 |
+
<div><span class="k">p_real</span><span class="v mono">{{ (url_result.probabilities[0]*100) | round(1) }}%</span></div>
|
| 117 |
+
<div><span class="k">p_fake</span><span class="v mono">{{ (url_result.probabilities[1]*100) | round(1) }}%</span></div>
|
| 118 |
+
</div>
|
| 119 |
+
{% endif %}
|
| 120 |
+
</div>
|
| 121 |
+
{% endif %}
|
| 122 |
+
</div>
|
| 123 |
+
|
| 124 |
+
<div class="card">
|
| 125 |
+
<h2>Video analyzer (random frames)</h2>
|
| 126 |
+
<form method="post" action="/analyze_video_url">
|
| 127 |
+
<label class="field">
|
| 128 |
+
<span>Video URL</span>
|
| 129 |
+
<input type="url" name="video_url" placeholder="https://example.com/video.mp4" required />
|
| 130 |
+
</label>
|
| 131 |
+
<label class="field">
|
| 132 |
+
<span>Frames to sample</span>
|
| 133 |
+
<input type="number" name="num_frames" min="1" max="50" value="5" />
|
| 134 |
+
</label>
|
| 135 |
+
<button type="submit" class="btn">Analyze video</button>
|
| 136 |
+
<div class="hint">Downloads the video, samples random frames, and aggregates results into a report.</div>
|
| 137 |
+
</form>
|
| 138 |
+
|
| 139 |
+
{% if video_result %}
|
| 140 |
+
<div class="result">
|
| 141 |
+
{% if video_result.error %}
|
| 142 |
+
<div class="error">Error: {{ video_result.error }}</div>
|
| 143 |
+
{% else %}
|
| 144 |
+
<div class="pill">Overall: {{ video_result.overall }} (avg p_fake {{ (video_result.avg_p_fake*100) | round(1) }}%)</div>
|
| 145 |
+
<div class="hint">Source: <span class="mono">{{ video_result.video_url }}</span></div>
|
| 146 |
+
{% if video_result.is_youtube %}
|
| 147 |
+
<div class="hint">Detected YouTube URL (downloaded via <span class="mono">yt-dlp</span>).</div>
|
| 148 |
+
{% endif %}
|
| 149 |
+
<div class="frame-grid">
|
| 150 |
+
{% for f in video_result.frames %}
|
| 151 |
+
<div class="frame-card">
|
| 152 |
+
<img class="frame" src="{{ f.image_data_uri }}" alt="Frame {{ f.frame_index }}" />
|
| 153 |
+
<div class="frame-meta">
|
| 154 |
+
<div class="mono">frame {{ f.frame_index }}</div>
|
| 155 |
+
<div class="mono">{{ f.prediction }} Β· conf {{ (f.confidence*100) | round(1) }}%</div>
|
| 156 |
+
<div class="mono">p_fake {{ (f.p_fake*100) | round(1) }}%</div>
|
| 157 |
+
</div>
|
| 158 |
+
</div>
|
| 159 |
+
{% endfor %}
|
| 160 |
+
</div>
|
| 161 |
+
{% endif %}
|
| 162 |
+
</div>
|
| 163 |
+
{% endif %}
|
| 164 |
+
</div>
|
| 165 |
+
</section>
|
| 166 |
+
|
| 167 |
+
<footer class="footer">
|
| 168 |
+
<div class="hint">
|
| 169 |
+
Tip: on Hugging Face Spaces, run with <span class="mono">uvicorn ai_vs_real.app:app --host 0.0.0.0 --port 7860</span>.
|
| 170 |
+
</div>
|
| 171 |
+
</footer>
|
| 172 |
+
</div>
|
| 173 |
+
</body>
|
| 174 |
+
</html>
|
| 175 |
+
|
test.ipynb
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cells": [
|
| 3 |
+
{
|
| 4 |
+
"cell_type": "code",
|
| 5 |
+
"execution_count": 1,
|
| 6 |
+
"id": "ab0628fc",
|
| 7 |
+
"metadata": {},
|
| 8 |
+
"outputs": [
|
| 9 |
+
{
|
| 10 |
+
"name": "stdout",
|
| 11 |
+
"output_type": "stream",
|
| 12 |
+
"text": [
|
| 13 |
+
"π Exploring RegNet Y 16GF structure...\n",
|
| 14 |
+
"π Total parameters: 428\n",
|
| 15 |
+
"\n",
|
| 16 |
+
"ποΈ RegNet Structure Analysis:\n",
|
| 17 |
+
"\n",
|
| 18 |
+
"π stem.0: 1 parameters\n",
|
| 19 |
+
" β’ stem.0.weight\n",
|
| 20 |
+
"\n",
|
| 21 |
+
"π stem.1: 5 parameters\n",
|
| 22 |
+
" β’ stem.1.weight\n",
|
| 23 |
+
" β’ stem.1.bias\n",
|
| 24 |
+
" β’ stem.1.running_mean\n",
|
| 25 |
+
" β’ stem.1.running_var\n",
|
| 26 |
+
" β’ stem.1.num_batches_tracked\n",
|
| 27 |
+
"\n",
|
| 28 |
+
"π trunk_output.block1: 50 parameters\n",
|
| 29 |
+
" β’ trunk_output.block1.block1-0.proj.0.weight\n",
|
| 30 |
+
" β’ trunk_output.block1.block1-0.proj.1.weight\n",
|
| 31 |
+
" β’ trunk_output.block1.block1-0.proj.1.bias\n",
|
| 32 |
+
" β’ trunk_output.block1.block1-0.proj.1.running_mean\n",
|
| 33 |
+
" β’ trunk_output.block1.block1-0.proj.1.running_var\n",
|
| 34 |
+
" ... and 45 more\n",
|
| 35 |
+
"\n",
|
| 36 |
+
"π trunk_output.block2: 94 parameters\n",
|
| 37 |
+
" β’ trunk_output.block2.block2-0.proj.0.weight\n",
|
| 38 |
+
" β’ trunk_output.block2.block2-0.proj.1.weight\n",
|
| 39 |
+
" β’ trunk_output.block2.block2-0.proj.1.bias\n",
|
| 40 |
+
" β’ trunk_output.block2.block2-0.proj.1.running_mean\n",
|
| 41 |
+
" β’ trunk_output.block2.block2-0.proj.1.running_var\n",
|
| 42 |
+
" ... and 89 more\n",
|
| 43 |
+
"\n",
|
| 44 |
+
"π trunk_output.block3: 248 parameters\n",
|
| 45 |
+
" β’ trunk_output.block3.block3-0.proj.0.weight\n",
|
| 46 |
+
" β’ trunk_output.block3.block3-0.proj.1.weight\n",
|
| 47 |
+
" β’ trunk_output.block3.block3-0.proj.1.bias\n",
|
| 48 |
+
" β’ trunk_output.block3.block3-0.proj.1.running_mean\n",
|
| 49 |
+
" β’ trunk_output.block3.block3-0.proj.1.running_var\n",
|
| 50 |
+
" ... and 243 more\n",
|
| 51 |
+
"\n",
|
| 52 |
+
"π trunk_output.block4: 28 parameters\n",
|
| 53 |
+
" β’ trunk_output.block4.block4-0.proj.0.weight\n",
|
| 54 |
+
" β’ trunk_output.block4.block4-0.proj.1.weight\n",
|
| 55 |
+
" β’ trunk_output.block4.block4-0.proj.1.bias\n",
|
| 56 |
+
" β’ trunk_output.block4.block4-0.proj.1.running_mean\n",
|
| 57 |
+
" β’ trunk_output.block4.block4-0.proj.1.running_var\n",
|
| 58 |
+
" ... and 23 more\n",
|
| 59 |
+
"\n",
|
| 60 |
+
"π fc.weight: 1 parameters\n",
|
| 61 |
+
" β’ fc.weight\n",
|
| 62 |
+
"\n",
|
| 63 |
+
"π fc.bias: 1 parameters\n",
|
| 64 |
+
" β’ fc.bias\n",
|
| 65 |
+
"\n",
|
| 66 |
+
"============================================================\n",
|
| 67 |
+
"π Searching for conv layers specifically:\n",
|
| 68 |
+
"Found 0 conv parameters:\n",
|
| 69 |
+
"\n",
|
| 70 |
+
"============================================================\n",
|
| 71 |
+
"π Looking for block structure:\n",
|
| 72 |
+
"Found 420 block parameters:\n",
|
| 73 |
+
" β’ trunk_output.block1.block1-0.proj.0.weight\n",
|
| 74 |
+
" β’ trunk_output.block1.block1-0.proj.1.weight\n",
|
| 75 |
+
" β’ trunk_output.block1.block1-0.proj.1.bias\n",
|
| 76 |
+
" β’ trunk_output.block1.block1-0.proj.1.running_mean\n",
|
| 77 |
+
" β’ trunk_output.block1.block1-0.proj.1.running_var\n",
|
| 78 |
+
" β’ trunk_output.block1.block1-0.proj.1.num_batches_tracked\n",
|
| 79 |
+
" β’ trunk_output.block1.block1-0.f.a.0.weight\n",
|
| 80 |
+
" β’ trunk_output.block1.block1-0.f.a.1.weight\n",
|
| 81 |
+
" β’ trunk_output.block1.block1-0.f.a.1.bias\n",
|
| 82 |
+
" β’ trunk_output.block1.block1-0.f.a.1.running_mean\n",
|
| 83 |
+
" ... and 410 more\n",
|
| 84 |
+
"\n",
|
| 85 |
+
"============================================================\n",
|
| 86 |
+
"π‘ ANALYSIS COMPLETE\n",
|
| 87 |
+
"Use the output above to understand the correct parameter structure\n",
|
| 88 |
+
"and update the validation logic accordingly.\n"
|
| 89 |
+
]
|
| 90 |
+
}
|
| 91 |
+
],
|
| 92 |
+
"source": [
|
| 93 |
+
"import torch\n",
|
| 94 |
+
"from torchvision import models\n",
|
| 95 |
+
"\n",
|
| 96 |
+
"def explore_regnet_structure():\n",
|
| 97 |
+
" \"\"\"Explore the actual RegNet structure to understand parameter names\"\"\"\n",
|
| 98 |
+
" print(\"π Exploring RegNet Y 16GF structure...\")\n",
|
| 99 |
+
" \n",
|
| 100 |
+
" # Load RegNet with pretrained weights\n",
|
| 101 |
+
" model = models.regnet_y_16gf(weights=models.RegNet_Y_16GF_Weights.IMAGENET1K_SWAG_E2E_V1)\n",
|
| 102 |
+
" \n",
|
| 103 |
+
" # Get all parameter names\n",
|
| 104 |
+
" param_names = list(model.state_dict().keys())\n",
|
| 105 |
+
" \n",
|
| 106 |
+
" print(f\"π Total parameters: {len(param_names)}\")\n",
|
| 107 |
+
" print(\"\\nποΈ RegNet Structure Analysis:\")\n",
|
| 108 |
+
" \n",
|
| 109 |
+
" # Group parameters by component\n",
|
| 110 |
+
" components = {}\n",
|
| 111 |
+
" for name in param_names:\n",
|
| 112 |
+
" parts = name.split('.')\n",
|
| 113 |
+
" if len(parts) >= 2:\n",
|
| 114 |
+
" component = f\"{parts[0]}.{parts[1]}\"\n",
|
| 115 |
+
" if component not in components:\n",
|
| 116 |
+
" components[component] = []\n",
|
| 117 |
+
" components[component].append(name)\n",
|
| 118 |
+
" \n",
|
| 119 |
+
" # Display structure\n",
|
| 120 |
+
" for component, params in components.items():\n",
|
| 121 |
+
" print(f\"\\nπ {component}: {len(params)} parameters\")\n",
|
| 122 |
+
" # Show first few parameter names as examples\n",
|
| 123 |
+
" for i, param in enumerate(params[:5]):\n",
|
| 124 |
+
" print(f\" β’ {param}\")\n",
|
| 125 |
+
" if len(params) > 5:\n",
|
| 126 |
+
" print(f\" ... and {len(params) - 5} more\")\n",
|
| 127 |
+
" \n",
|
| 128 |
+
" print(\"\\n\" + \"=\"*60)\n",
|
| 129 |
+
" print(\"π Searching for conv layers specifically:\")\n",
|
| 130 |
+
" conv_params = [name for name in param_names if 'conv' in name.lower()]\n",
|
| 131 |
+
" print(f\"Found {len(conv_params)} conv parameters:\")\n",
|
| 132 |
+
" for param in conv_params[:10]: # Show first 10\n",
|
| 133 |
+
" print(f\" β’ {param}\")\n",
|
| 134 |
+
" if len(conv_params) > 10:\n",
|
| 135 |
+
" print(f\" ... and {len(conv_params) - 10} more\")\n",
|
| 136 |
+
" \n",
|
| 137 |
+
" print(\"\\n\" + \"=\"*60)\n",
|
| 138 |
+
" print(\"π Looking for block structure:\")\n",
|
| 139 |
+
" block_params = [name for name in param_names if 'block' in name.lower()]\n",
|
| 140 |
+
" print(f\"Found {len(block_params)} block parameters:\")\n",
|
| 141 |
+
" for param in block_params[:10]:\n",
|
| 142 |
+
" print(f\" β’ {param}\")\n",
|
| 143 |
+
" if len(block_params) > 10:\n",
|
| 144 |
+
" print(f\" ... and {len(block_params) - 10} more\")\n",
|
| 145 |
+
" \n",
|
| 146 |
+
" return param_names, model\n",
|
| 147 |
+
"\n",
|
| 148 |
+
"if __name__ == \"__main__\":\n",
|
| 149 |
+
" try:\n",
|
| 150 |
+
" param_names, model = explore_regnet_structure()\n",
|
| 151 |
+
" \n",
|
| 152 |
+
" print(\"\\n\" + \"=\"*60)\n",
|
| 153 |
+
" print(\"π‘ ANALYSIS COMPLETE\")\n",
|
| 154 |
+
" print(\"Use the output above to understand the correct parameter structure\")\n",
|
| 155 |
+
" print(\"and update the validation logic accordingly.\")\n",
|
| 156 |
+
" \n",
|
| 157 |
+
" except Exception as e:\n",
|
| 158 |
+
" print(f\"β Error exploring RegNet: {e}\")\n",
|
| 159 |
+
" print(\"Make sure you have internet access to download RegNet weights\")"
|
| 160 |
+
]
|
| 161 |
+
},
|
| 162 |
+
{
|
| 163 |
+
"cell_type": "code",
|
| 164 |
+
"execution_count": null,
|
| 165 |
+
"id": "d6a4fd46",
|
| 166 |
+
"metadata": {},
|
| 167 |
+
"outputs": [],
|
| 168 |
+
"source": []
|
| 169 |
+
}
|
| 170 |
+
],
|
| 171 |
+
"metadata": {
|
| 172 |
+
"kernelspec": {
|
| 173 |
+
"display_name": ".venv",
|
| 174 |
+
"language": "python",
|
| 175 |
+
"name": "python3"
|
| 176 |
+
},
|
| 177 |
+
"language_info": {
|
| 178 |
+
"codemirror_mode": {
|
| 179 |
+
"name": "ipython",
|
| 180 |
+
"version": 3
|
| 181 |
+
},
|
| 182 |
+
"file_extension": ".py",
|
| 183 |
+
"mimetype": "text/x-python",
|
| 184 |
+
"name": "python",
|
| 185 |
+
"nbconvert_exporter": "python",
|
| 186 |
+
"pygments_lexer": "ipython3",
|
| 187 |
+
"version": "3.11.0"
|
| 188 |
+
}
|
| 189 |
+
},
|
| 190 |
+
"nbformat": 4,
|
| 191 |
+
"nbformat_minor": 5
|
| 192 |
+
}
|