Spaces:
Running
Running
| import streamlit as st | |
| import google.generativeai as genai | |
| from PIL import Image | |
| import io | |
| class ForensicVLMOrchestrator: | |
| def __init__(self, mode="api"): | |
| self.mode = mode | |
| # Configura a API do Google Gemini | |
| api_key = st.secrets.get("GOOGLE_API_KEY") | |
| if api_key: | |
| genai.configure(api_key=api_key) | |
| self.model = genai.GenerativeModel('gemini-2.5-flash') | |
| def _determine_typology(self, prob_swin, prob_clip): | |
| if prob_swin > prob_clip: | |
| return "uma imagem totalmente criada por computador (não existe na vida real)" | |
| else: | |
| return "uma fotografia verdadeira onde o rosto foi alterado ou trocado" | |
| def _crop_artifact_zone(self, img_rgb, bbox): | |
| if not bbox: | |
| return img_rgb | |
| h, w = img_rgb.shape[:2] | |
| x1, y1, x2, y2 = bbox | |
| x1, y1 = max(0, x1), max(0, y1) | |
| x2, y2 = min(w, x2), min(h, y2) | |
| crop = img_rgb[y1:y2, x1:x2] | |
| if crop.size == 0: | |
| return img_rgb | |
| return crop | |
| def build_forensic_prompt(self, prob_final, prob_swin, prob_clip, zones_list): | |
| tipo_fraude = self._determine_typology(prob_swin, prob_clip) | |
| prompt = ( | |
| f"Gere um relatório pericial objetivo e acessível a um público leigo.\n" | |
| f"Analise o recorte da imagem.\n\n" | |
| f"--- DADOS DO SISTEMA ---\n" | |
| f"Certeza de fraude: {prob_final * 100:.1f}%\n" | |
| f"Tipo de fraude: {tipo_fraude}\n" | |
| f"Zonas com anomalias detetadas: {zones_list}\n" | |
| f"-----------------------\n\n" | |
| f"REGRAS OBRIGATÓRIAS:\n" | |
| f"1. Escreva exatamente 4 frases num único parágrafo, em Português de Portugal.\n" | |
| f"2. PROIBIDO usar a primeira pessoa (eu, vejo, noto, acho). Use exclusivamente voz impessoal e formal.\n" | |
| f"3. PROIBIDO usar calão ou jargão técnico (IA, algoritmo, síntese, CLIP, Swin, artefactos, píxeis).\n" | |
| f"4. FRASE 1: Identifique as zonas afetadas ({zones_list}) e descreva os defeitos visuais presentes. Utilize conceitos reais de análise de imagem.\n" | |
| f"5. FRASE 2: Indique que estas inconsistências visuais evidenciam tratar-se de {tipo_fraude}.\n" | |
| f"6. FRASE 3: Descreva características globais da imagem que não são condizentes com uma fotografia genuína.\n" | |
| f"7. FRASE 4: Conclua EXATAMENTE com: 'O sistema tem {prob_final * 100:.1f}% de certeza de que esta imagem é falsa.'\n" | |
| ) | |
| return prompt | |
| def generate_justification(self, img_rgb, prob_final, prob_swin, prob_clip, zone_name, bbox): | |
| crop_rgb = self._crop_artifact_zone(img_rgb, bbox) | |
| prompt = self.build_forensic_prompt(prob_final, prob_swin, prob_clip, zone_name) | |
| if not hasattr(self, 'model'): | |
| return "Erro: Google API Key não configurada." | |
| # Preparar imagem (Thumb para otimizar token/latência) | |
| pil_img = Image.fromarray(crop_rgb) | |
| pil_img.thumbnail((512, 512), Image.Resampling.BICUBIC) | |
| try: | |
| # Geração com streaming nativo do SDK do Gemini | |
| response = self.model.generate_content( | |
| [prompt, pil_img], | |
| stream=True, | |
| generation_config={"temperature": 0.1} | |
| ) | |
| def stream_generator(): | |
| for chunk in response: | |
| if chunk.text: | |
| yield chunk.text | |
| return stream_generator() | |
| except Exception as e: | |
| erro_str = str(e) | |
| if "429" in erro_str or "quota" in erro_str.lower(): | |
| return "O sistema está a processar demasiados pedidos em simultâneo (Rate Limit atingido). Por favor, aguarde 30 segundos e tente analisar a imagem novamente." | |
| else: | |
| return f"Falha de comunicação com o motor de inferência LVM. Detalhe técnico: {erro_str}" |