| """ |
| Document Forgery Detection β Professional Gradio Dashboard |
| Hugging Face Spaces Deployment |
| """ |
|
|
| import gradio as gr |
| import torch |
| import cv2 |
| import numpy as np |
| from PIL import Image |
| import plotly.graph_objects as go |
| from pathlib import Path |
| import sys |
| import json |
|
|
| |
| |
| |
| sys.path.insert(0, str(Path(__file__).parent)) |
|
|
| from src.models import get_model |
| from src.config import get_config |
| from src.data.preprocessing import DocumentPreprocessor |
| from src.data.augmentation import DatasetAwareAugmentation |
| from src.features.region_extraction import get_mask_refiner, get_region_extractor |
| from src.features.feature_extraction import get_feature_extractor |
| from src.training.classifier import ForgeryClassifier |
|
|
| |
| |
| |
| CLASS_NAMES = {0: "Copy-Move", 1: "Splicing", 2: "Generation"} |
| CLASS_COLORS = { |
| 0: (255, 0, 0), |
| 1: (0, 255, 0), |
| 2: (0, 0, 255), |
| } |
|
|
| |
| |
| |
| class ForgeryDetector: |
| def __init__(self): |
| print("Loading models...") |
|
|
| self.config = get_config("config.yaml") |
| self.device = torch.device("cuda" if torch.cuda.is_available() else "cpu") |
|
|
| self.model = get_model(self.config).to(self.device) |
| checkpoint = torch.load("models/best_doctamper.pth", map_location=self.device) |
| self.model.load_state_dict(checkpoint["model_state_dict"]) |
| self.model.eval() |
|
|
| self.classifier = ForgeryClassifier(self.config) |
| self.classifier.load("models/classifier") |
|
|
| self.preprocessor = DocumentPreprocessor(self.config, "doctamper") |
| self.augmentation = DatasetAwareAugmentation(self.config, "doctamper", is_training=False) |
| self.mask_refiner = get_mask_refiner(self.config) |
| self.region_extractor = get_region_extractor(self.config) |
| self.feature_extractor = get_feature_extractor(self.config, is_text_document=True) |
|
|
| print("β Models loaded") |
|
|
| def detect(self, image): |
| if isinstance(image, Image.Image): |
| image = np.array(image) |
|
|
| if image.ndim == 2: |
| image = cv2.cvtColor(image, cv2.COLOR_GRAY2RGB) |
| elif image.shape[2] == 4: |
| image = cv2.cvtColor(image, cv2.COLOR_RGBA2RGB) |
|
|
| original = image.copy() |
|
|
| preprocessed, _ = self.preprocessor(image, None) |
| augmented = self.augmentation(preprocessed, None) |
| image_tensor = augmented["image"].unsqueeze(0).to(self.device) |
|
|
| with torch.no_grad(): |
| logits, decoder_features = self.model(image_tensor) |
| prob_map = torch.sigmoid(logits).cpu().numpy()[0, 0] |
|
|
| binary = (prob_map > 0.5).astype(np.uint8) |
| refined = self.mask_refiner.refine(binary, original_size=original.shape[:2]) |
| regions = self.region_extractor.extract(refined, prob_map, original) |
|
|
| results = [] |
| for r in regions: |
| features = self.feature_extractor.extract( |
| preprocessed, r["region_mask"], [f.cpu() for f in decoder_features] |
| ) |
|
|
| if features.ndim == 1: |
| features = features.reshape(1, -1) |
|
|
| if features.shape[1] != 526: |
| pad = max(0, 526 - features.shape[1]) |
| features = np.pad(features, ((0, 0), (0, pad)))[:, :526] |
|
|
| pred, conf = self.classifier.predict(features) |
| if conf[0] > 0.6: |
| results.append({ |
| "bounding_box": r["bounding_box"], |
| "forgery_type": CLASS_NAMES[int(pred[0])], |
| "confidence": float(conf[0]), |
| }) |
|
|
| overlay = self._draw_overlay(original, results) |
|
|
| return overlay, { |
| "num_detections": len(results), |
| "detections": results, |
| } |
|
|
| def _draw_overlay(self, image, results): |
| out = image.copy() |
| for r in results: |
| x, y, w, h = r["bounding_box"] |
| fid = [k for k, v in CLASS_NAMES.items() if v == r["forgery_type"]][0] |
| color = CLASS_COLORS[fid] |
|
|
| cv2.rectangle(out, (x, y), (x + w, y + h), color, 2) |
| label = f"{r['forgery_type']} ({r['confidence']*100:.1f}%)" |
| cv2.putText(out, label, (x, y - 6), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.5, color, 2) |
| return out |
|
|
|
|
| detector = ForgeryDetector() |
|
|
| |
| |
| |
| def gauge(value, title): |
| fig = go.Figure(go.Indicator( |
| mode="gauge+number", |
| value=value, |
| title={"text": title}, |
| gauge={"axis": {"range": [0, 100]}, "bar": {"color": "#2563eb"}} |
| )) |
| fig.update_layout(height=240, margin=dict(t=40, b=20)) |
| return fig |
|
|
| |
| |
| |
| def run_detection(file): |
| image = Image.open(file.name) |
| overlay, result = detector.detect(image) |
|
|
| avg_conf = ( |
| sum(d["confidence"] for d in result["detections"]) / max(1, result["num_detections"]) |
| ) * 100 |
|
|
| return ( |
| overlay, |
| result, |
| gauge(75, "Localization Dice (%)"), |
| gauge(92, "Classifier Accuracy (%)"), |
| gauge(avg_conf, "Avg Detection Confidence (%)"), |
| ) |
|
|
| |
| |
| |
| with gr.Blocks(theme=gr.themes.Soft(), title="Document Forgery Detection") as demo: |
|
|
| gr.Markdown("# π Document Forgery Detection System") |
|
|
| with gr.Row(): |
| file_input = gr.File(label="Upload Document (Image/PDF)") |
| detect_btn = gr.Button("Run Detection", variant="primary") |
|
|
| output_img = gr.Image(label="Forgery Localization Result", type="numpy") |
|
|
| with gr.Tabs(): |
| with gr.Tab("π Metrics"): |
| with gr.Row(): |
| dice_plot = gr.Plot() |
| acc_plot = gr.Plot() |
| conf_plot = gr.Plot() |
|
|
| with gr.Tab("π§Ύ Details"): |
| json_out = gr.JSON() |
|
|
| with gr.Tab("π₯ Team"): |
| gr.Markdown(""" |
| **Document Forgery Detection Project** |
| |
| - Krishnanandhaa β Model & Training |
| - Teammate 1 β Feature Engineering |
| - Teammate 2 β Evaluation |
| - Teammate 3 β Deployment |
| |
| *Collaborators are added via Hugging Face Space settings.* |
| """) |
|
|
| detect_btn.click( |
| run_detection, |
| inputs=file_input, |
| outputs=[output_img, json_out, dice_plot, acc_plot, conf_plot] |
| ) |
|
|
| if __name__ == "__main__": |
| demo.launch() |
|
|