import os import sys import uuid from typing import List, Dict, Any, Optional, Tuple import torch import gradio as gr from PIL import Image from omegaconf import OmegaConf, DictConfig # --- 1. CONFIGURAÇÃO E IMPORTS --- # Adiciona o diretório do VINCIE ao path do Python para permitir a importação de seus módulos. VINCIE_DIR = "/app/VINCIE" if VINCIE_DIR not in sys.path: sys.path.append(VINCIE_DIR) try: from generate import VINCIEGenerator from common.config import load_config from common.seed import shift_seed except ImportError as e: print(f"FATAL: Não foi possível importar os módulos do VINCIE. " f"Verifique se o repositório está em '{VINCIE_DIR}'.") raise e # --- 2. INICIALIZAÇÃO DO MODELO (SINGLETON) --- # Variáveis globais para armazenar a instância única do modelo e o dispositivo. MODEL: Optional[VINCIEGenerator] = None DEVICE: Optional[torch.device] = None def setup_model(): """ Inicializa e configura o modelo VINCIE. Esta função é chamada uma vez no início da aplicação para carregar o modelo na GPU. """ global MODEL, DEVICE if not torch.cuda.is_available(): raise RuntimeError("FATAL: Nenhuma GPU compatível com CUDA foi encontrada.") num_gpus = torch.cuda.device_count() if num_gpus == 0: raise RuntimeError("FATAL: Nenhuma GPU foi detectada pelo PyTorch.") print(f"INFO: Detectadas {num_gpus} GPUs. A aplicação usará 'cuda:0'.") DEVICE = torch.device("cuda:0") torch.cuda.set_device(DEVICE) config_path = os.path.join(VINCIE_DIR, "configs/generate.yaml") print(f"INFO: Carregando e resolvendo configuração de '{config_path}'...") config = load_config(config_path, []) print("INFO: Instanciando VINCIEGenerator...") model_instance = VINCIEGenerator(config) # Executa a sequência de inicialização necessária do VINCIE print("INFO: Configurando persistência, modelos e difusão...") model_instance.configure_persistence() model_instance.configure_models() model_instance.configure_diffusion() if not hasattr(model_instance, 'dit'): raise RuntimeError("FATAL: Falha ao inicializar o componente DiT do modelo.") # Move todos os componentes para o dispositivo principal model_instance.dit.to(DEVICE) model_instance.vae.to(DEVICE) model_instance.text_encoder.to(DEVICE) MODEL = model_instance print(f"✅ SUCESSO: Modelo VINCIE pronto para uso na GPU {DEVICE}.") # --- 3. LÓGICAS DE INFERÊNCIA --- def _execute_vincie_logic( prompt_config: DictConfig, steps: int, cfg_scale: float, seed: int, pad_img_placeholder: bool, resolution: int ) -> Image.Image: """ Função central e reutilizável que executa a inferência do VINCIE. Args: prompt_config (DictConfig): Configuração do prompt para o modelo. steps (int): Número de passos de difusão. cfg_scale (float): Escala de orientação (Classifier-Free Guidance). seed (int): Semente para reprodutibilidade. pad_img_placeholder (bool): Se deve formatar o prompt com placeholders . resolution (int): Resolução do lado menor da imagem para processamento. Returns: Image.Image: A imagem gerada. """ # Salva o estado original da configuração para restaurá-lo depois original_config_state = { "steps": MODEL.config.diffusion.timesteps.sampling.steps, "seed": MODEL.config.generation.seed, "pad": MODEL.config.generation.pad_img_placehoder, "resolution": MODEL.config.generation.resolution, } try: OmegaConf.set_readonly(MODEL.config, False) # 1. Aplica configurações dinâmicas MODEL.config.diffusion.timesteps.sampling.steps = int(steps) MODEL.configure_diffusion() # Recria o sampler com os novos passos current_seed = seed if seed != -1 else torch.randint(0, 2**32 - 1, (1,)).item() MODEL.config.generation.seed = shift_seed(current_seed, 0) MODEL.config.generation.pad_img_placehoder = pad_img_placeholder MODEL.config.generation.resolution = int(resolution) # 2. Prepara as entradas text_pos, condition, noise, _, _ = MODEL.prepare_input( prompt=prompt_config, repeat_idx=0, device=DEVICE ) # 3. Executa a inferência with torch.no_grad(): samples = MODEL.inference( noises=[noise], conditions=[condition], texts_pos=[text_pos], texts_neg=[MODEL.config.generation.negative_prompt], cfg_scale=cfg_scale ) if not samples: raise RuntimeError("A inferência do modelo não produziu resultados.") # 4. Processa a saída para formato de imagem output_tensor = samples[0][:, -1, :, :] output_image_np = output_tensor.clip(-1, 1).add(1).div(2).mul(255).byte().permute(1, 2, 0).cpu().numpy() return Image.fromarray(output_image_np) finally: # 5. Restaura a configuração original para garantir consistência entre chamadas OmegaConf.set_readonly(MODEL.config, False) MODEL.config.diffusion.timesteps.sampling.steps = original_config_state["steps"] MODEL.config.generation.seed = original_config_state["seed"] MODEL.config.generation.pad_img_placehoder = original_config_state["pad"] MODEL.config.generation.resolution = original_config_state["resolution"] OmegaConf.set_readonly(MODEL.config, True) MODEL.configure_diffusion() # Restaura o sampler padrão def run_single_turn_inference( input_image: str, prompt: str, aspect_ratio: str, resolution: int, steps: int, cfg_scale: float, seed: int ) -> Image.Image: """Handler para a aba 'Edição Simples'.""" if not all([input_image, prompt]): raise gr.Error("É necessário fornecer uma imagem de entrada e um prompt.") _print_params("Edição Simples", prompt=prompt, aspect_ratio=aspect_ratio, resolution=resolution, steps=steps, cfg_scale=cfg_scale, seed=seed) prompt_config = OmegaConf.create({ "index": 0, "img_paths": [input_image], "context": [prompt], "aspect_ratio": aspect_ratio }) return _execute_vincie_logic(prompt_config, steps, cfg_scale, seed, pad_img_placeholder=True, resolution=resolution) def run_multi_turn_inference( input_image: str, prompts_text: str, steps: int, cfg_scale: float, seed: int, progress=gr.Progress() ) -> List[Image.Image]: """Handler para a aba 'Edição em Múltiplos Turnos'.""" if not all([input_image, prompts_text]): raise gr.Error("É necessário fornecer uma imagem de entrada e pelo menos um prompt.") prompts = [p.strip() for p in prompts_text.splitlines() if p.strip()] if not prompts: raise gr.Error("Nenhum prompt válido fornecido.") _print_params("Edição em Múltiplos Turnos", prompts=prompts, steps=steps, cfg_scale=cfg_scale, seed=seed) output_images_with_paths = [] for i, prompt in enumerate(progress.tqdm(prompts, desc="Processando turnos")): print(f"--- Turno {i+1}/{len(prompts)}: {prompt} ---") image_paths = [input_image] + [path for _, path in output_images_with_paths] context_prompts = prompts[:i+1] prompt_config = OmegaConf.create({ "index": i, "img_paths": image_paths, "context": context_prompts, "aspect_ratio": "keep_ratio" }) turn_seed = seed if seed == -1 else seed + i result_image = _execute_vincie_logic(prompt_config, steps, cfg_scale, turn_seed, pad_img_placeholder=True, resolution=512) temp_path = os.path.join("/tmp", f"{uuid.uuid4()}.png") result_image.save(temp_path) output_images_with_paths.append((result_image, temp_path)) return [img for img, _ in output_images_with_paths] def run_multi_concept_inference(prompt: str, *images: str) -> Image.Image: """Handler para a aba 'Composição de Conceitos'.""" image_paths = [img for img in images if img is not None] if not image_paths or not prompt.strip(): raise gr.Error("É necessário um prompt e pelo menos uma imagem de entrada.") _print_params("Composição de Conceitos", prompt=prompt, num_images=len(image_paths)) prefix_prompts = [f": " for i in range(len(image_paths) - 1)] all_prompts = prefix_prompts + [prompt] prompt_config = OmegaConf.create({ "index": 0, "img_paths": image_paths, "context": all_prompts, "aspect_ratio": "1:1" }) return _execute_vincie_logic(prompt_config, steps=50, cfg_scale=7.5, seed=1, pad_img_placeholder=False, resolution=512) def _print_params(mode: str, **kwargs: Any): """Função auxiliar para logging formatado das requisições.""" log_message = f"\n{'='*50}\nINFO: Nova requisição - Modo: {mode}\n" for key, value in kwargs.items(): log_message += f" - {key.replace('_', ' ').title()}: {value}\n" log_message += f"{'='*50}\n" print(log_message) # --- 4. CONSTRUÇÃO DA INTERFACE GRadio --- def create_ui(): """Cria e retorna a interface Gradio completa com todas as abas e controles.""" with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), title="VINCIE Playground") as demo: gr.Markdown("# 🖼️ **VINCIE Playground**\nExplore as diferentes capacidades do modelo VINCIE.") with gr.Accordion("Opções Avançadas (para Abas 1 e 2)", open=False): steps_input = gr.Slider(label="Passos de Inferência", minimum=10, maximum=100, step=1, value=50) cfg_scale_input = gr.Slider(label="Escala de Orientação (CFG)", minimum=1.0, maximum=15.0, step=0.5, value=7.5) seed_input = gr.Number(label="Semente (Seed)", value=-1, precision=0, info="Use -1 para aleatório.") with gr.Tabs(): # Aba 1: Edição Simples with gr.TabItem("Edição Simples"): with gr.Row(): with gr.Column(scale=1): single_turn_img_in = gr.Image(type="filepath", label="Imagem de Entrada") single_turn_prompt = gr.Textbox(lines=2, label="Prompt de Edição") with gr.Accordion("Opções de Imagem", open=True): aspect_ratio_input = gr.Dropdown(label="Aspect Ratio", choices=["keep_ratio", "1:1", "16:9", "9:16", "4:3", "3:4"], value="keep_ratio") resolution_input = gr.Slider(label="Resolução (lado menor)", minimum=256, maximum=1024, step=64, value=512) single_turn_button = gr.Button("Gerar", variant="primary") with gr.Column(scale=1): single_turn_img_out = gr.Image(label="Resultado", interactive=False, height=512) gr.Examples([["/app/VINCIE/assets/woman_pineapple.png", "Adicione uma coroa na cabeça da mulher."]], [single_turn_img_in, single_turn_prompt]) # Aba 2: Edição em Múltiplos Turnos with gr.TabItem("Edição em Múltiplos Turnos"): with gr.Row(): with gr.Column(scale=1): multi_turn_img_in = gr.Image(type="filepath", label="Imagem de Entrada") multi_turn_prompts = gr.Textbox(lines=5, label="Prompts (um por linha)", placeholder="Turno 1: faça isso\nTurno 2: agora mude aquilo...") multi_turn_button = gr.Button("Gerar Sequência", variant="primary") with gr.Column(scale=1): multi_turn_gallery_out = gr.Gallery(label="Resultados dos Turnos", columns=3, height="auto") # Aba 3: Composição de Conceitos with gr.TabItem("Composição de Conceitos"): gr.Markdown("Faça o upload de até 6 imagens (`` a ``) e escreva um prompt que as combine para gerar uma nova imagem (``).") with gr.Row(): concept_inputs = [gr.Image(type="filepath", label=f"Imagem {i} ()") for i in range(6)] concept_prompt = gr.Textbox(lines=4, label="Prompt de Composição Final", value="Baseado em , e , crie um retrato de uma família com o pai de , a mãe de e o cachorro de em um parque. Saída :") concept_button = gr.Button("Compor Imagem", variant="primary") concept_img_out = gr.Image(label="Resultado da Composição", interactive=False, height=512) gr.Examples( [[ "Baseado em , e , crie um retrato de uma família com o pai de , a mãe de e o cachorro de em um parque. Saída :", "/app/VINCIE/assets/father.png", "/app/VINCIE/assets/mother.png", "/app/VINCIE/assets/dog1.png" ]], [concept_prompt] + concept_inputs ) # Conecta os botões às suas respectivas funções de backend single_turn_button.click(fn=run_single_turn_inference, inputs=[single_turn_img_in, single_turn_prompt, aspect_ratio_input, resolution_input, steps_input, cfg_scale_input, seed_input], outputs=[single_turn_img_out]) multi_turn_button.click(fn=run_multi_turn_inference, inputs=[multi_turn_img_in, multi_turn_prompts, steps_input, cfg_scale_input, seed_input], outputs=[multi_turn_gallery_out]) concept_button.click(fn=run_multi_concept_inference, inputs=[concept_prompt] + concept_inputs, outputs=[concept_img_out]) return demo # --- 5. PONTO DE ENTRADA DA APLICAÇÃO --- if __name__ == "__main__": setup_model() ui = create_ui() server_name = os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0") server_port = int(os.environ.get("GRADIO_SERVER_PORT", 7860)) enable_queue = os.environ.get("GRADIO_ENABLE_QUEUE", "True").lower() == "true" print(f"INFO: Lançando a interface Gradio em http://{server_name}:{server_port}") if enable_queue: ui.queue() ui.launch(server_name=server_name, server_port=server_port)