Spaces:
Paused
Paused
fix(deploy): enforce memory-safe spatial-FFT deepfake detector in containers to prevent 512MB OOM crash
a5c0306 Download app/deepfake/classifier.py from rishik1111/faceid: direct link, hf CLI and curl.
- Browser
- Download file 7.44 kB
-
https://huggingface.co/spaces/rishik1111/faceid/resolve/main/app/deepfake/classifier.py
- Command line
-
hf download hf://spaces/rishik1111/faceid/app/deepfake/classifier.py
-
curl -L -o classifier.py https://huggingface.co/spaces/rishik1111/faceid/resolve/main/app/deepfake/classifier.py
7.44 kB
| import os | |
| import logging | |
| from pathlib import Path | |
| from typing import Union, Dict, Any, Optional | |
| import numpy as np | |
| from PIL import Image | |
| logger = logging.getLogger(__name__) | |
| DEFAULT_MODEL_ID = "prithivMLmods/Deep-Fake-Detector-v2-Model" | |
| class DeepfakeClassifier: | |
| """ | |
| Vision Transformer (ViT) based deepfake and synthetic face detector. | |
| Evaluates candidate media to alert investigators to manipulated, swapped, | |
| or synthetically generated facial content. | |
| """ | |
| def __init__(self, model_name: str = DEFAULT_MODEL_ID): | |
| self.model_name = model_name | |
| self._processor = None | |
| self._model = None | |
| self._load_attempted = False | |
| self._available = False | |
| def _lazy_load(self): | |
| if self._load_attempted: | |
| return | |
| self._load_attempted = True | |
| # Memory conservation safeguard for cloud free tiers (e.g. Render 512MB RAM) | |
| enable_vit = os.getenv("ENABLE_VIT_MODEL", "").lower() | |
| if enable_vit == "false": | |
| logger.info("ViT model disabled via ENABLE_VIT_MODEL=false. Using lightweight spatial-FFT detector.") | |
| self._available = False | |
| return | |
| elif enable_vit != "true": | |
| try: | |
| import psutil | |
| total_mb = psutil.virtual_memory().total / (1024 * 1024) | |
| if total_mb < 1200: | |
| logger.info("Host memory is %.0f MB (< 1200 MB). Using lightweight spatial-FFT detector to protect container stability.", total_mb) | |
| self._available = False | |
| return | |
| except Exception: | |
| pass | |
| try: | |
| from transformers import AutoImageProcessor, AutoModelForImageClassification | |
| import torch | |
| logger.info("Initializing ViT deepfake detector: %s", self.model_name) | |
| self._processor = AutoImageProcessor.from_pretrained( | |
| self.model_name | |
| ) | |
| self._model = AutoModelForImageClassification.from_pretrained( | |
| self.model_name | |
| ) | |
| self._model.eval() | |
| self._available = True | |
| logger.info("ViT Deepfake detector loaded successfully.") | |
| except Exception as exc: | |
| logger.warning( | |
| "Could not load HuggingFace ViT weights (%s): %s. " | |
| "Engaging resilient heuristic synthetic media analysis.", | |
| self.model_name, exc | |
| ) | |
| self._available = False | |
| def _heuristic_analysis(self, image: Image.Image) -> Dict[str, Any]: | |
| """ | |
| Fallback spatial-frequency analysis when offline or weights unavailable. | |
| Checks for spectral artifacts and boundary anomalies characteristic of synthetic generation. | |
| """ | |
| try: | |
| gray = image.convert("L").resize((256, 256)) | |
| arr = np.asarray(gray, dtype=np.float32) | |
| # 2D FFT spectral high-frequency decay | |
| f = np.fft.fft2(arr) | |
| fshift = np.fft.fftshift(f) | |
| magnitude = 20 * np.log(np.abs(fshift) + 1e-5) | |
| h, w = magnitude.shape | |
| center_h, center_w = h // 2, w // 2 | |
| # High frequency energy ratio | |
| radius = 30 | |
| y, x = np.ogrid[:h, :w] | |
| dist_from_center = np.sqrt((x - center_w)**2 + (y - center_h)**2) | |
| high_freq_mask = dist_from_center > radius | |
| high_freq_energy = float(np.mean(magnitude[high_freq_mask])) | |
| total_energy = float(np.mean(magnitude)) | |
| ratio = (high_freq_energy / (total_energy + 1e-5)) if total_energy > 0 else 0.5 | |
| # Typical synthetic GAN/diffusion faces have muted or irregular high-frequency spectra | |
| synthetic_score = float(np.clip(1.0 - (ratio * 1.2), 0.05, 0.95)) | |
| is_synthetic = synthetic_score > 0.65 | |
| return { | |
| "label": "Synthetic / Deepfake" if is_synthetic else "Authentic / Real", | |
| "risk_score": round(synthetic_score, 4), | |
| "confidence": round(abs(synthetic_score - 0.5) * 2, 4), | |
| "is_synthetic": is_synthetic, | |
| "model": "Spatial-FFT-Heuristic (Offline Fallback)", | |
| "status": "ANALYZED", | |
| } | |
| except Exception as exc: | |
| return { | |
| "label": "Undetermined", | |
| "risk_score": 0.5, | |
| "confidence": 0.0, | |
| "is_synthetic": False, | |
| "model": "None", | |
| "status": f"SKIPPED ({exc})", | |
| } | |
| def analyze(self, image_input: Union[Path, str, Image.Image, np.ndarray]) -> Dict[str, Any]: | |
| """ | |
| Analyze an input image for deepfake or synthetic facial generation signals. | |
| """ | |
| if isinstance(image_input, (str, Path)): | |
| img_path = Path(image_input) | |
| if not img_path.exists(): | |
| return { | |
| "label": "Image Not Found", | |
| "risk_score": 0.0, | |
| "confidence": 0.0, | |
| "is_synthetic": False, | |
| "model": "None", | |
| "status": "ERROR", | |
| } | |
| image = Image.open(img_path).convert("RGB") | |
| elif isinstance(image_input, Image.Image): | |
| image = image_input.convert("RGB") | |
| elif isinstance(image_input, np.ndarray): | |
| image = Image.fromarray(image_input).convert("RGB") | |
| else: | |
| return { | |
| "label": "Invalid Format", | |
| "risk_score": 0.0, | |
| "confidence": 0.0, | |
| "is_synthetic": False, | |
| "model": "None", | |
| "status": "ERROR", | |
| } | |
| self._lazy_load() | |
| if not self._available or self._model is None or self._processor is None: | |
| return self._heuristic_analysis(image) | |
| try: | |
| import torch | |
| inputs = self._processor(images=image, return_tensors="pt") | |
| with torch.no_grad(): | |
| outputs = self._model(**inputs) | |
| logits = outputs.logits | |
| probs = torch.nn.functional.softmax(logits, dim=-1).squeeze().tolist() | |
| id2label = self._model.config.id2label or {0: "Real", 1: "Fake"} | |
| # Determine fake/synthetic index | |
| fake_idx = 1 | |
| for idx, label_str in id2label.items(): | |
| if any(w in label_str.lower() for w in ["fake", "synth", "deepfake", "manipulated"]): | |
| fake_idx = int(idx) | |
| break | |
| risk_score = float(probs[fake_idx]) if isinstance(probs, list) and len(probs) > fake_idx else float(probs) | |
| pred_idx = int(np.argmax(probs)) if isinstance(probs, list) else (1 if probs >= 0.5 else 0) | |
| pred_label = id2label.get(pred_idx, "Unknown") | |
| is_synthetic = risk_score >= 0.50 | |
| return { | |
| "label": pred_label, | |
| "risk_score": round(risk_score, 4), | |
| "confidence": round(float(np.max(probs)), 4) if isinstance(probs, list) else round(risk_score, 4), | |
| "is_synthetic": is_synthetic, | |
| "model": self.model_name, | |
| "status": "ANALYZED", | |
| } | |
| except Exception as exc: | |
| logger.warning("ViT inference failed: %s. Reverting to heuristic analysis.", exc) | |
| return self._heuristic_analysis(image) | |