dbilli's picture
Update app.py
e47c9c3 verified
Raw
History Blame Contribute Delete
11.1 kB
"""
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("""
---
<div class="footer">
<p><strong>SpectralDetector V3.6-CASCADED</strong> - Build 2025-11-07</p>
<p>Developed by Denis Billi | 4-HEAD Cascaded Architecture with Meta-Router Fallback</p>
<p>⚠️ Questo strumento è da considerarsi di supporto. Per decisioni critiche, consultare un esperto.</p>
</div>
""")
# 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
)