""" AI Image Detector — New Approach (Fine-Tuned Model) ==================================================== Uses YOUR fine-tuned ViT model as the primary detector, backed by the 2 best pre-trained models + noise forensics. Engines: 1. FFT — frequency-domain artifact detection (visual) 2. ELA — compression tampering map (visual) 3. Noise — noise pattern forensics (visual + scoring) 4. ViT-FT — YOUR fine-tuned model (primary detector) 5. SigLIP — Ateeqq/ai-vs-human-image-detector (backup) 6. SMOGY — Smogy/SMOGY-Ai-images-detector (backup) """ import io import json import functools import numpy as np import matplotlib matplotlib.use("Agg") import matplotlib.pyplot as plt from PIL import Image, ImageChops, ImageOps, ImageFilter from transformers import pipeline import gradio as gr from fastapi import FastAPI, File, UploadFile from fastapi.responses import JSONResponse # ───────────────────────────────────────────── # CONFIGURATION — Update after fine-tuning # ───────────────────────────────────────────── # ⬇️ CHANGE THIS to your fine-tuned model ID after running the notebook FINETUNED_MODEL = "mohamed9679/ai-image-detector-v1" # Weights for the ensemble (fine-tuned model is the primary detector) WEIGHTS = { "finetuned": 0.85, # Your model — 85% of the vote "siglip": 0.00, # Disabled — fine-tuned model is strong enough "smogy": 0.00, # Disabled — fine-tuned model is strong enough "noise": 0.15, # Physics-based forensics (catches non-ML artifacts) } # ───────────────────────────────────────────── # Model loading # ───────────────────────────────────────────── @functools.lru_cache(maxsize=1) def load_finetuned_pipeline(): return pipeline("image-classification", model=FINETUNED_MODEL) @functools.lru_cache(maxsize=1) def load_siglip_pipeline(): return pipeline("image-classification", model="Ateeqq/ai-vs-human-image-detector") @functools.lru_cache(maxsize=1) def load_smogy_pipeline(): return pipeline("image-classification", model="Smogy/SMOGY-Ai-images-detector") # ───────────────────────────────────────────── # Pre-processing # ───────────────────────────────────────────── def prepare_image(pil_image: Image.Image): img = pil_image.convert("RGB") data = list(img.getdata()) clean_img = Image.new(img.mode, img.size) clean_img.putdata(data) grayscale_array = np.array(clean_img.convert("L")) buffer = io.BytesIO() clean_img.save(buffer, format="JPEG", quality=90) buffer.seek(0) ela_jpeg_img = Image.open(buffer).convert("RGB") return grayscale_array, ela_jpeg_img, clean_img # ───────────────────────────────────────────── # Test-Time Augmentation # ───────────────────────────────────────────── def _generate_views(image: Image.Image) -> list: w, h = image.size views = [image] # Horizontal flip views.append(ImageOps.mirror(image)) # Center crop 80% cw, ch = int(w * 0.8), int(h * 0.8) left, top = (w - cw) // 2, (h - ch) // 2 views.append(image.crop((left, top, left + cw, top + ch)).resize((w, h), Image.LANCZOS)) return views def _run_with_tta(model_fn, image: Image.Image) -> float: views = _generate_views(image) scores = [model_fn(view) for view in views] return sum(scores) / len(scores) # ───────────────────────────────────────────── # Visual analysis engines # ───────────────────────────────────────────── def fig_to_pil(fig): buf = io.BytesIO() fig.savefig(buf, format="png", bbox_inches="tight", dpi=120) buf.seek(0) img = Image.open(buf).copy() buf.close() plt.close(fig) return img def run_fft(grayscale_array): f = np.fft.fft2(grayscale_array) fshift = np.fft.fftshift(f) magnitude = 20 * np.log(np.abs(fshift) + 1e-8) fig, ax = plt.subplots(figsize=(4, 4)) ax.imshow(magnitude, cmap="gray") ax.axis("off") ax.set_title("FFT Magnitude Spectrum", fontsize=10) plt.tight_layout() return fig_to_pil(fig) def run_ela(original, jpeg): diff = ImageChops.difference(original, jpeg) return Image.eval(diff, lambda x: min(255, x * 15.0)) # ───────────────────────────────────────────── # Noise Pattern Forensic Analysis # ───────────────────────────────────────────── def run_noise_analysis(image: Image.Image) -> tuple: arr = np.array(image).astype(np.float64) denoised = np.array(image.filter(ImageFilter.MedianFilter(size=3))).astype(np.float64) noise = arr - denoised # Feature 1: Noise variance noise_var = np.var(noise) var_score = 1.0 - min(1.0, noise_var / 50.0) # Feature 2: Spatial correlation noise_gray = np.mean(noise, axis=2) h, w = noise_gray.shape if h > 2 and w > 2: horiz = np.corrcoef(noise_gray[:, :-1].flatten(), noise_gray[:, 1:].flatten())[0, 1] vert = np.corrcoef(noise_gray[:-1, :].flatten(), noise_gray[1:, :].flatten())[0, 1] spatial_corr = (abs(horiz) + abs(vert)) / 2.0 else: spatial_corr = 0.0 corr_score = min(1.0, spatial_corr / 0.4) # Feature 3: Channel consistency r, g, b = noise[:,:,0].flatten(), noise[:,:,1].flatten(), noise[:,:,2].flatten() rg = abs(np.corrcoef(r, g)[0,1]) if len(r) > 10 else 0.0 rb = abs(np.corrcoef(r, b)[0,1]) if len(r) > 10 else 0.0 chan_score = min(1.0, max(0.0, ((rg + rb) / 2 - 0.3) / 0.5)) # Feature 4: Noise entropy noise_u8 = np.clip((noise_gray * 10) + 128, 0, 255).astype(np.uint8) hist, _ = np.histogram(noise_u8, bins=256, range=(0, 256)) hist = hist / hist.sum() hist = hist[hist > 0] entropy = -np.sum(hist * np.log2(hist)) entropy_score = 1.0 - min(1.0, entropy / 6.0) # Combined score score = var_score * 0.25 + corr_score * 0.30 + chan_score * 0.25 + entropy_score * 0.20 score = max(0.0, min(1.0, score)) # Visualization noise_vis = np.clip(np.abs(noise) * 8.0, 0, 255).astype(np.uint8) noise_img = Image.fromarray(noise_vis) return score, noise_img # ───────────────────────────────────────────── # Score extraction # ───────────────────────────────────────────── _FAKE = frozenset({"artificial","fake","ai","ai generated","ai_generated","deepfake","generated","computer","synthetic"}) _REAL = frozenset({"human","real","realism","authentic","nature","photo","not_ai_generated","not ai generated"}) def _extract_fake_score(results): for r in results: l = r["label"].lower().strip() if l in _FAKE: return float(r["score"]) if l in _REAL: return float(1.0 - r["score"]) if results: top = results[0] l = top["label"].lower().strip() if any(k in l for k in ("fake","ai","deep","artifi","generat","synth")): return float(top["score"]) if any(k in l for k in ("real","human","authen","photo","nature")): return float(1.0 - top["score"]) return float(top["score"]) return 0.5 def run_finetuned(image): return _extract_fake_score(load_finetuned_pipeline()(image)) def run_siglip(image): return _extract_fake_score(load_siglip_pipeline()(image)) def run_smogy(image): return _extract_fake_score(load_smogy_pipeline()(image)) # ───────────────────────────────────────────── # Weighted ensemble # ───────────────────────────────────────────── def _weighted_ensemble(scores: dict) -> tuple: weighted_sum = sum(scores[k] * WEIGHTS[k] for k in scores) total_weight = sum(WEIGHTS[k] for k in scores) avg = weighted_sum / total_weight # Count votes fake_votes = sum(1 for s in scores.values() if s > 0.5) real_votes = len(scores) - fake_votes if avg > 0.5: verdict = "FAKE" confidence = round(avg * 100, 2) else: verdict = "REAL" confidence = round((1.0 - avg) * 100, 2) agreement = f"{fake_votes} fake / {real_votes} real" return verdict, confidence, agreement # ───────────────────────────────────────────── # Core analysis # ───────────────────────────────────────────── def run_full_analysis(pil_image: Image.Image) -> dict: grayscale_array, ela_jpeg_img, rgb_img = prepare_image(pil_image) # Run models with TTA scores = { "finetuned": _run_with_tta(run_finetuned, rgb_img), "siglip": _run_with_tta(run_siglip, rgb_img), "smogy": _run_with_tta(run_smogy, rgb_img), } # Run noise forensics noise_score, noise_img = run_noise_analysis(rgb_img) scores["noise"] = noise_score # Ensemble verdict verdict, confidence, agreement = _weighted_ensemble(scores) return { "verdict": verdict, "confidence": confidence, "agreement": agreement, "scores": {k: round(v * 100, 2) for k, v in scores.items()}, "_fft_img": run_fft(grayscale_array), "_ela_img": run_ela(rgb_img, ela_jpeg_img), "_noise_img": noise_img, } # ───────────────────────────────────────────── # Gradio UI # ───────────────────────────────────────────── def analyze_image(pil_image): if pil_image is None: empty = "
Upload an image to begin.
" return empty, None, None, None, 0.0, 0.0, 0.0, 0.0, "{}" result = run_full_analysis(pil_image) v, c, a = result["verdict"], result["confidence"], result["agreement"] if v == "FAKE": color, icon = "#ff4b4b", "🤖" else: color, icon = "#00c44f", "✅" html = f"""{c:.1f}% certainty · {a}
Powered by a fine-tuned Vision Transformer with 99.4% accuracy
✨ FINE-TUNED MODEL · 4 ENGINES · NOISE FORENSICSUpload an image to begin analysis.
" return empty, None, None, None, 0.0, 0.0, 0.0, 0.0, "{}" result = run_full_analysis(pil_image) v, c, a = result["verdict"], result["confidence"], result["agreement"] if v == "FAKE": color, bg, icon = "#ef4444", "rgba(239,68,68,0.12)", "🤖" else: color, bg, icon = "#22c55e", "rgba(34,197,94,0.12)", "✅" html = f"""{c:.1f}% certainty
Engine votes: {a}