""" SpectralDetector V3.6 - Web Interface Gradio app per il rilevamento di immagini AI-generated Cascaded 4-HEAD architecture (A, B, C, D) """ import gradio as gr import cv2 import numpy as np from pathlib import Path import sys import json from datetime import datetime import tempfile import os # Import del detector project_root = Path(__file__).parent sys.path.insert(0, str(project_root)) sys.path.insert(0, str(project_root / 'archive' / 'legacy_src')) from detect_image import SpectralDetectorV36Cascaded # Configurazione MODELS_DIR = "outputs/heads_v36_67features" # Inizializza detector (caricato una volta all'avvio) print("π Caricamento modelli V3.6...") detector = SpectralDetectorV36Cascaded( models_dir=MODELS_DIR, verbose=True ) print("β Modelli V3.6 caricati!") def analyze_image(image): """ Analizza un'immagine caricata dall'utente con V3.6. Args: image: numpy array (H, W, 3) RGB Returns: decision, confidence, details, zone_color """ try: # Converti RGB -> BGR per OpenCV if image is None: return "β Errore", "Nessuna immagine caricata", "", "#ff0000" image_bgr = cv2.cvtColor(image, cv2.COLOR_RGB2BGR) # Salva temporaneamente l'immagine temp_dir = tempfile.gettempdir() temp_path = os.path.join(temp_dir, f"gradio_upload_{datetime.now().strftime('%Y%m%d_%H%M%S')}.jpg") cv2.imwrite(temp_path, image_bgr) try: # Classifica usando V3.6 start_time = datetime.now() result = detector.classify_image(temp_path) proc_time = (datetime.now() - start_time).total_seconds() finally: # Rimuovi il file temporaneo if os.path.exists(temp_path): os.remove(temp_path) # Estrai risultati V3.6 decision = result['final_label'] confidence = result['confidence'] route = result['route_taken'] exit_head = result['exit_head'] probs = result['probabilities'] # Decision display if decision == 'REAL': decision_display = "πΈ IMMAGINE REALE" decision_emoji = "β " zone_color = '#22c55e' # Verde elif 'Unknown' in decision: decision_display = f"π€ {decision}" decision_emoji = "β" zone_color = '#f97316' # Arancione else: decision_display = f"π€ AI: {decision}" decision_emoji = "β οΈ" zone_color = '#ef4444' # Rosso confidence_label = f"Confidenza: {confidence*100:.1f}%" # Dettagli tecnici V3.6 details = f""" ### π Analisi Dettagliata V3.6 **Decisione Finale:** {decision_emoji} **{decision}** **Route:** {route} **Exit HEAD:** {exit_head} **Tempo di Elaborazione:** {proc_time:.2f}s --- #### ProbabilitΓ (POST-CALIBRATION) - **P(REAL):** {probs['p_real']:.4f} ({probs['p_real']*100:.2f}%) - **P(AI):** {probs['p_ai']:.4f} ({probs['p_ai']*100:.2f}%) #### Identificazione Generatore (4-HEAD System) - **P(GPT-IMAGE-1):** {probs['p_gpt_image_1']:.4f} (HEAD D) - **P(SDXL):** {probs['p_sdxl']:.4f} (HEAD B) - **P(ChatGPT):** {probs['p_chatgpt']:.4f} (HEAD C) - **P(Gemini):** {probs['p_gemini']:.4f} (HEAD C) #### Architettura Cascaded - **Threshold Hit:** {result['threshold_hit']} - **Meta-Router Scores:** {result.get('scores', 'N/A')} --- *SpectralDetector V3.6-CASCADED - Build 2025-11-07* """ return decision_display, confidence_label, details, zone_color except Exception as e: import traceback error_trace = traceback.format_exc() error_details = f""" ### β Errore nell'Analisi **Messaggio:** {str(e)} **Traceback:** ``` {error_trace} ``` Si prega di: 1. Verificare che l'immagine sia valida (JPG, PNG) 2. Assicurarsi che l'immagine non sia corrotta 3. Provare con un'altra immagine --- *Se l'errore persiste, contattare il supporto.* """ return "β Errore", "Analisi fallita", error_details, "#ff0000" def create_interface(): """ Crea l'interfaccia Gradio. """ # CSS personalizzato custom_css = """ .gradio-container { font-family: 'Inter', sans-serif; } .decision-box { padding: 20px; border-radius: 10px; text-align: center; font-size: 24px; font-weight: bold; margin: 10px 0; } .confidence-box { padding: 15px; border-radius: 8px; text-align: center; font-size: 18px; margin: 10px 0; } .footer { text-align: center; padding: 20px; color: #6b7280; font-size: 14px; } """ with gr.Blocks(css=custom_css, theme=gr.themes.Soft(), title="SpectralDetector V3.6") as demo: # Header gr.Markdown(""" # π¬ SpectralDetector V3.6 ### Rilevatore di Immagini AI-Generated - Architettura Cascaded 4-HEAD Carica un'immagine per verificare se Γ¨ **reale** o **generata da AI** (ChatGPT, GPT-IMAGE-1, Gemini, SDXL, etc.) **NovitΓ V3.6:** 4 HEAD specializzati (A: REAL/AI, B: SDXL, C: ChatGPT/Gemini, D: GPT-IMAGE-1) con router cascaded + calibrazione temperature """) with gr.Row(): with gr.Column(scale=1): # Input gr.Markdown("### π€ Carica Immagine") image_input = gr.Image( type="numpy", label="Seleziona o trascina un'immagine", height=400 ) analyze_btn = gr.Button( "π Analizza Immagine", variant="primary", size="lg" ) gr.Markdown(""" #### βΉοΈ Formati Supportati - JPG, JPEG, PNG - Qualsiasi risoluzione - Massimo 10 MB #### π‘οΈ Privacy Le immagini non vengono salvate nΓ© condivise. """) with gr.Column(scale=1): # Output gr.Markdown("### π Risultato Analisi") decision_output = gr.Textbox( label="Decisione", interactive=False, elem_classes=["decision-box"] ) confidence_output = gr.Textbox( label="Confidenza", interactive=False, elem_classes=["confidence-box"] ) zone_indicator = gr.Textbox( label="Indicatore Zona", visible=False, interactive=False ) details_output = gr.Markdown(label="Dettagli Tecnici") # Esempi gr.Markdown("### π― Prova con Esempi") # Cerca immagini di esempio example_images = [] example_dir = Path("examples") if example_dir.exists(): for img_path in example_dir.glob("*.jpg"): example_images.append([str(img_path)]) for img_path in example_dir.glob("*.png"): example_images.append([str(img_path)]) if example_images: gr.Examples( examples=example_images[:6], # Max 6 esempi inputs=image_input, label="Clicca su un esempio" ) # Info tecniche with gr.Accordion("π¬ Dettagli Tecnici del Sistema V3.6", open=False): gr.Markdown(""" ### Architettura SpectralDetector V3.6 - Cascaded 4-HEAD System #### π§ Modelli (4-HEAD Architecture) - **HEAD A**: Random Forest (200 trees) - REAL vs AI Gate - **HEAD B**: Random Forest (200 trees) - SDXL Detection - **HEAD C**: Random Forest (200 trees) - ChatGPT vs Gemini - **HEAD D**: Random Forest (200 trees) - GPT-IMAGE-1 Detection - **Meta-Router V3.6**: Cascaded decision with fallback (Opzione 3) - **Calibration**: Temperature scaling per HEAD A/B/C #### π Features - **HEAD A/B/C**: 67 features (FFT, DCT, compression, saturation) - **HEAD D**: 90 features (67 base + 23 global/patch extra) #### π― Performance (Dataset Completo - 1,546 immagini) - **Binary Accuracy**: 94.1% (β₯92% target) β - **Generator ID**: 84.8% (β₯80% target) β - **Unknown Rate**: 5.4% (β€25% target) β - **AI Recall**: 99.9% - **Per-Generator**: ChatGPT 93.9%, Gemini 94.3%, SDXL 73.7%, GPT-IMAGE-1 74.3% - **Processing Time**: ~93ms per immagine #### π Cascaded Router Architecture 1. **HEAD A Gate**: p_real β₯ 0.65 β REAL | p_ai β₯ 0.65 β Cascade | Gray zone β Meta-Router 2. **AI Cascade (DβBβC)**: Early-exit con thresholds ad alta precisione - HEAD D: p_gpt_image_1 β₯ 0.78 β GPT-IMAGE-1 - HEAD B: p_sdxl β₯ 0.72 β SDXL - HEAD C: max(p_cg, p_ge) β₯ 0.68 β ChatGPT/Gemini 3. **Meta-Router Fallback**: Weighted scores + margin guardrail quando incerto #### π Generatori Rilevabili - ChatGPT (DALL-E 3) - GPT-IMAGE-1 (OpenAI) - Google Gemini (Imagen) - Stable Diffusion XL (SDXL) #### π Release - **Versione**: 3.6-CASCADED - **Build**: 2025-11-07 - **Training**: Dataset Completo (1,546 samples, 5 sources) - **Validation**: 70/20/10 split stratified """) # Footer gr.Markdown(""" ---
""") # Event handlers analyze_btn.click( fn=analyze_image, inputs=image_input, outputs=[decision_output, confidence_output, details_output, zone_indicator] ) return demo # Create demo instance at module level (required for HuggingFace Spaces) demo = create_interface() if __name__ == "__main__": demo.launch( server_name="0.0.0.0", # Accessibile da rete server_port=7860, # Porta standard Hugging Face share=False, # Non creare link pubblico (solo per test locale) show_error=True )