Aduc-sdr-2_5s / app_seedvr.py
euIaxs22's picture
Update app_seedvr.py
3c4ad98 verified
raw
history blame
13.6 kB
import os
import sys
import uuid
import json
from typing import List, Dict, Any, Optional
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 Python Path para importação de módulos.
VINCIE_DIR = os.getenv("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:
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
# --- 2. INICIALIZAÇÃO DO MODELO (SINGLETON) ---
MODEL: Optional[VINCIEGenerator] = None
DEVICE: Optional[torch.device] = None
def setup_model():
"""
Inicializa e configura o modelo VINCIE em uma única GPU.
Esta função é chamada uma vez no início da aplicação.
"""
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)
print("INFO: Executando sequência de inicialização do VINCIE...")
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 que executa a pipeline de inferência do VINCIE.
"""
# 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 da UI.
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)
# Log detalhado dos argumentos que serão enviados para a pipeline.
_log_pipeline_args(prompt_config, steps, cfg_scale, MODEL.config.generation.seed, resolution, pad_img_placeholder)
# 2. Prepara as entradas para o modelo.
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)
for key, value in original_config_state.items():
if key == "steps":
MODEL.config.diffusion.timesteps.sampling.steps = value
else:
OmegaConf.update(MODEL.config.generation, key, value, merge=True)
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.")
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.")
output_images_with_paths = []
for i, prompt in enumerate(progress.tqdm(prompts, desc="Processando turnos")):
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.")
# Constrói a lista de prompts: N-1 placeholders + 1 prompt de composição.
prompts_list = [f"<IMG{i}>: " for i in range(1, len(image_paths))]
prompts_list.append(prompt)
prompt_config = OmegaConf.create({
"index": 0, "img_paths": image_paths, "context": prompts_list, "aspect_ratio": "1:1"
})
# Usa parâmetros fixos para esta funcionalidade, conforme documentação do VINCIE.
return _execute_vincie_logic(prompt_config, steps=50, cfg_scale=7.5, seed=1, pad_img_placeholder=False, resolution=512)
def _log_pipeline_args(prompt_config, steps, cfg_scale, final_seed, resolution, pad_placeholder):
"""Função auxiliar para imprimir os argumentos exatos enviados à pipeline do VINCIE."""
log_data = {
"--- INÍCIO DOS ARGUMENTOS DA PIPELINE VINCIE ---": "",
"1. Configuração do Prompt": OmegaConf.to_container(prompt_config, resolve=True),
"2. Parâmetros de Difusão": {
"steps": int(steps),
"cfg_scale": float(cfg_scale),
},
"3. Parâmetros de Geração": {
"seed_final": int(final_seed),
"resolution": int(resolution),
"pad_img_placeholder": bool(pad_placeholder),
},
"--- FIM DOS ARGUMENTOS ---": ""
}
print(json.dumps(log_data, indent=2, ensure_ascii=False))
# --- 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.")
# Controles avançados compartilhados pelas abas 1 e 2
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(equal_height=False):
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)
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 Inicial")
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=2):
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 (`<IMG0>` a `<IMG5>`) e escreva um prompt que as combine para gerar uma nova imagem (`<IMG6>`).")
with gr.Row():
concept_inputs = [gr.Image(type="filepath", label=f"Imagem {i} (<IMG{i}>)") for i in range(6)]
concept_prompt = gr.Textbox(lines=4, label="Prompt de Composição Final", value="Baseado em <IMG0> e <IMG1>, um retrato do homem de <IMG0> usando o chapéu de <IMG1>. Saída <IMG6>:")
concept_button = gr.Button("Compor Imagem", variant="primary")
concept_img_out = gr.Image(label="Resultado da Composição", interactive=False)
# 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)