euIaxs22 commited on
Commit
3c4ad98
·
verified ·
1 Parent(s): be0cee3

Update app_seedvr.py

Browse files
Files changed (1) hide show
  1. app_seedvr.py +280 -71
app_seedvr.py CHANGED
@@ -1,81 +1,290 @@
1
- #!/usr/bin/env python3
2
- # -*- coding: utf-8 -*-
3
-
4
  import os
5
- from pathlib import Path
6
- from typing import Optional, Tuple
 
 
 
 
7
  import gradio as gr
 
 
8
 
9
- from services.seedvr import SeedVRRefineService
10
 
11
- APP_HOME = Path(os.environ.get("APP_HOME", "/app"))
12
- OUT_DIR = APP_HOME / "outputs" / "seedvr_refine"
 
 
13
 
14
- svc = SeedVRRefineService()
 
 
 
 
 
 
 
15
 
16
- def setup() -> str:
17
- try:
18
- token = os.environ.get("HF_TOKEN") or os.environ.get("HFTOKEN")
19
- msg = svc.ensure_model(max_workers=int(os.environ.get("MAX_WORKERS", "48")), token=token)
20
- svc.ensure_apex(enable_shim=True)
21
- return f"Setup concluído: {msg} (apex shim ativo)."
22
- except Exception as e:
23
- return f"Setup falhou: {e}"
24
-
25
- def refine_ui(
26
- input_file: gr.File,
27
- upscale: float,
28
- strength: float,
29
- denoise: float,
30
- t_consistency: float,
31
- fps_out: int,
32
- tile: int,
33
- dtype: str
34
- ) -> Tuple[str, str]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
  try:
36
- inp = Path(input_file.name) if hasattr(input_file, "name") else Path(input_file)
37
- out = svc.refine(
38
- input_path=inp,
39
- output_dir=OUT_DIR,
40
- upscale=upscale,
41
- strength=strength,
42
- denoise=denoise,
43
- t_consistency=t_consistency,
44
- fps_out=(None if fps_out <= 0 else fps_out),
45
- tile=(None if tile <= 0 else tile),
46
- dtype=(None if not dtype else dtype)
 
 
 
 
 
 
47
  )
48
- return (f"OK: saída em {out}", out)
49
- except Exception as e:
50
- return (f"Erro: {e}", "")
51
-
52
- with gr.Blocks(title="SeedVR — Refinamento (sem Apex)", theme="Default") as demo:
53
- gr.Markdown("### SeedVR — Refinamento de vídeo/imagem (qualidade, sem Apex)")
54
- with gr.Row():
55
- with gr.Column(scale=2):
56
- input_file = gr.File(label="Vídeo/Imagem de entrada", type="filepath")
57
- upscale = gr.Slider(1.0, 2.0, value=1.0, step=0.1, label="Upscale factor")
58
- strength = gr.Slider(0.0, 1.0, value=0.35, step=0.05, label="Força do refine")
59
- denoise = gr.Slider(0.0, 1.0, value=0.1, step=0.05, label="Denoise")
60
- tcons = gr.Slider(0.0, 1.0, value=0.7, step=0.05, label="Consistência temporal")
61
- fps_out = gr.Number(value=0, precision=0, label="FPS saída (0 = manter)")
62
- tile = gr.Number(value=0, precision=0, label="Tile (0 = off)")
63
- dtype = gr.Dropdown(choices=["", "bfloat16", "float16"], value="", label="Dtype override")
64
- go = gr.Button("Refinar", variant="primary")
65
- with gr.Column(scale=1):
66
- status = gr.Textbox(label="Status")
67
- outdir = gr.Textbox(label="Saída", value=str(OUT_DIR), interactive=False)
68
-
69
- go.click(
70
- fn=refine_ui,
71
- inputs=[input_file, upscale, strength, denoise, tcons, fps_out, tile, dtype],
72
- outputs=[status, outdir]
73
- )
74
-
75
- init = gr.Button("Preparar modelos (setup)")
76
- init.click(fn=setup, outputs=status)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
77
 
78
  if __name__ == "__main__":
79
- port = int(os.environ.get("PORT", "7860"))
80
- demo.launch(server_name="0.0.0.0", server_port=port, allowed_paths=[str(OUT_DIR), str(APP_HOME / "ckpt" / "SeedVR2-3B")])
81
-
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import sys
3
+ import uuid
4
+ import json
5
+ from typing import List, Dict, Any, Optional
6
+
7
+ import torch
8
  import gradio as gr
9
+ from PIL import Image
10
+ from omegaconf import OmegaConf, DictConfig
11
 
12
+ # --- 1. CONFIGURAÇÃO E IMPORTS ---
13
 
14
+ # Adiciona o diretório do VINCIE ao Python Path para importação de módulos.
15
+ VINCIE_DIR = os.getenv("VINCIE_DIR", "/app/VINCIE")
16
+ if VINCIE_DIR not in sys.path:
17
+ sys.path.append(VINCIE_DIR)
18
 
19
+ try:
20
+ from generate import VINCIEGenerator
21
+ from common.config import load_config
22
+ from common.seed import shift_seed
23
+ except ImportError:
24
+ print(f"FATAL: Não foi possível importar os módulos do VINCIE. "
25
+ f"Verifique se o repositório está em '{VINCIE_DIR}'.")
26
+ raise
27
 
28
+ # --- 2. INICIALIZAÇÃO DO MODELO (SINGLETON) ---
29
+
30
+ MODEL: Optional[VINCIEGenerator] = None
31
+ DEVICE: Optional[torch.device] = None
32
+
33
+ def setup_model():
34
+ """
35
+ Inicializa e configura o modelo VINCIE em uma única GPU.
36
+ Esta função é chamada uma vez no início da aplicação.
37
+ """
38
+ global MODEL, DEVICE
39
+
40
+ if not torch.cuda.is_available():
41
+ raise RuntimeError("FATAL: Nenhuma GPU compatível com CUDA foi encontrada.")
42
+
43
+ num_gpus = torch.cuda.device_count()
44
+ if num_gpus == 0:
45
+ raise RuntimeError("FATAL: Nenhuma GPU foi detectada pelo PyTorch.")
46
+
47
+ print(f"INFO: Detectadas {num_gpus} GPUs. A aplicação usará 'cuda:0'.")
48
+ DEVICE = torch.device("cuda:0")
49
+ torch.cuda.set_device(DEVICE)
50
+
51
+ config_path = os.path.join(VINCIE_DIR, "configs/generate.yaml")
52
+ print(f"INFO: Carregando e resolvendo configuração de '{config_path}'...")
53
+ config = load_config(config_path, [])
54
+
55
+ print("INFO: Instanciando VINCIEGenerator...")
56
+ model_instance = VINCIEGenerator(config)
57
+
58
+ print("INFO: Executando sequência de inicialização do VINCIE...")
59
+ model_instance.configure_persistence()
60
+ model_instance.configure_models()
61
+ model_instance.configure_diffusion()
62
+
63
+ if not hasattr(model_instance, 'dit'):
64
+ raise RuntimeError("FATAL: Falha ao inicializar o componente DiT do modelo.")
65
+
66
+ # Move todos os componentes para o dispositivo principal.
67
+ model_instance.dit.to(DEVICE)
68
+ model_instance.vae.to(DEVICE)
69
+ model_instance.text_encoder.to(DEVICE)
70
+
71
+ MODEL = model_instance
72
+ print(f"✅ SUCESSO: Modelo VINCIE pronto para uso na GPU {DEVICE}.")
73
+
74
+ # --- 3. LÓGICAS DE INFERÊNCIA ---
75
+
76
+ def _execute_vincie_logic(
77
+ prompt_config: DictConfig,
78
+ steps: int,
79
+ cfg_scale: float,
80
+ seed: int,
81
+ pad_img_placeholder: bool,
82
+ resolution: int
83
+ ) -> Image.Image:
84
+ """
85
+ Função central que executa a pipeline de inferência do VINCIE.
86
+ """
87
+ # Salva o estado original da configuração para restaurá-lo depois.
88
+ original_config_state = {
89
+ "steps": MODEL.config.diffusion.timesteps.sampling.steps,
90
+ "seed": MODEL.config.generation.seed,
91
+ "pad": MODEL.config.generation.pad_img_placehoder,
92
+ "resolution": MODEL.config.generation.resolution,
93
+ }
94
+
95
  try:
96
+ OmegaConf.set_readonly(MODEL.config, False)
97
+
98
+ # 1. Aplica configurações dinâmicas da UI.
99
+ MODEL.config.diffusion.timesteps.sampling.steps = int(steps)
100
+ MODEL.configure_diffusion() # Recria o sampler com os novos passos.
101
+
102
+ current_seed = seed if seed != -1 else torch.randint(0, 2**32 - 1, (1,)).item()
103
+ MODEL.config.generation.seed = shift_seed(current_seed, 0)
104
+ MODEL.config.generation.pad_img_placehoder = pad_img_placeholder
105
+ MODEL.config.generation.resolution = int(resolution)
106
+
107
+ # Log detalhado dos argumentos que serão enviados para a pipeline.
108
+ _log_pipeline_args(prompt_config, steps, cfg_scale, MODEL.config.generation.seed, resolution, pad_img_placeholder)
109
+
110
+ # 2. Prepara as entradas para o modelo.
111
+ text_pos, condition, noise, _, _ = MODEL.prepare_input(
112
+ prompt=prompt_config, repeat_idx=0, device=DEVICE
113
  )
114
+
115
+ # 3. Executa a inferência.
116
+ with torch.no_grad():
117
+ samples = MODEL.inference(
118
+ noises=[noise],
119
+ conditions=[condition],
120
+ texts_pos=[text_pos],
121
+ texts_neg=[MODEL.config.generation.negative_prompt],
122
+ cfg_scale=cfg_scale
123
+ )
124
+
125
+ if not samples:
126
+ raise RuntimeError("A inferência do modelo não produziu resultados.")
127
+
128
+ # 4. Processa a saída para formato de imagem.
129
+ output_tensor = samples[0][:, -1, :, :]
130
+ output_image_np = output_tensor.clip(-1, 1).add(1).div(2).mul(255).byte().permute(1, 2, 0).cpu().numpy()
131
+ return Image.fromarray(output_image_np)
132
+
133
+ finally:
134
+ # 5. Restaura a configuração original para garantir consistência entre chamadas.
135
+ OmegaConf.set_readonly(MODEL.config, False)
136
+ for key, value in original_config_state.items():
137
+ if key == "steps":
138
+ MODEL.config.diffusion.timesteps.sampling.steps = value
139
+ else:
140
+ OmegaConf.update(MODEL.config.generation, key, value, merge=True)
141
+ OmegaConf.set_readonly(MODEL.config, True)
142
+ MODEL.configure_diffusion() # Restaura o sampler padrão.
143
+
144
+ def run_single_turn_inference(
145
+ input_image: str, prompt: str, aspect_ratio: str, resolution: int, steps: int, cfg_scale: float, seed: int
146
+ ) -> Image.Image:
147
+ """Handler para a aba 'Edição Simples'."""
148
+ if not all([input_image, prompt]):
149
+ raise gr.Error("É necessário fornecer uma imagem de entrada e um prompt.")
150
+
151
+ prompt_config = OmegaConf.create({
152
+ "index": 0, "img_paths": [input_image], "context": [prompt], "aspect_ratio": aspect_ratio
153
+ })
154
+
155
+ return _execute_vincie_logic(prompt_config, steps, cfg_scale, seed, pad_img_placeholder=True, resolution=resolution)
156
+
157
+ def run_multi_turn_inference(
158
+ input_image: str, prompts_text: str, steps: int, cfg_scale: float, seed: int, progress=gr.Progress()
159
+ ) -> List[Image.Image]:
160
+ """Handler para a aba 'Edição em Múltiplos Turnos'."""
161
+ if not all([input_image, prompts_text]):
162
+ raise gr.Error("É necessário fornecer uma imagem de entrada e pelo menos um prompt.")
163
+
164
+ prompts = [p.strip() for p in prompts_text.splitlines() if p.strip()]
165
+ if not prompts:
166
+ raise gr.Error("Nenhum prompt válido fornecido.")
167
+
168
+ output_images_with_paths = []
169
+
170
+ for i, prompt in enumerate(progress.tqdm(prompts, desc="Processando turnos")):
171
+ image_paths = [input_image] + [path for _, path in output_images_with_paths]
172
+ context_prompts = prompts[:i+1]
173
+
174
+ prompt_config = OmegaConf.create({
175
+ "index": i, "img_paths": image_paths, "context": context_prompts, "aspect_ratio": "keep_ratio"
176
+ })
177
+
178
+ turn_seed = seed if seed == -1 else seed + i
179
+ result_image = _execute_vincie_logic(prompt_config, steps, cfg_scale, turn_seed, pad_img_placeholder=True, resolution=512)
180
+
181
+ temp_path = os.path.join("/tmp", f"{uuid.uuid4()}.png")
182
+ result_image.save(temp_path)
183
+ output_images_with_paths.append((result_image, temp_path))
184
+
185
+ return [img for img, _ in output_images_with_paths]
186
+
187
+ def run_multi_concept_inference(prompt: str, *images: str) -> Image.Image:
188
+ """Handler para a aba 'Composição de Conceitos'."""
189
+ image_paths = [img for img in images if img is not None]
190
+ if not image_paths or not prompt.strip():
191
+ raise gr.Error("É necessário um prompt e pelo menos uma imagem de entrada.")
192
+
193
+ # Constrói a lista de prompts: N-1 placeholders + 1 prompt de composição.
194
+ prompts_list = [f"<IMG{i}>: " for i in range(1, len(image_paths))]
195
+ prompts_list.append(prompt)
196
+
197
+ prompt_config = OmegaConf.create({
198
+ "index": 0, "img_paths": image_paths, "context": prompts_list, "aspect_ratio": "1:1"
199
+ })
200
+
201
+ # Usa parâmetros fixos para esta funcionalidade, conforme documentação do VINCIE.
202
+ return _execute_vincie_logic(prompt_config, steps=50, cfg_scale=7.5, seed=1, pad_img_placeholder=False, resolution=512)
203
+
204
+ def _log_pipeline_args(prompt_config, steps, cfg_scale, final_seed, resolution, pad_placeholder):
205
+ """Função auxiliar para imprimir os argumentos exatos enviados à pipeline do VINCIE."""
206
+ log_data = {
207
+ "--- INÍCIO DOS ARGUMENTOS DA PIPELINE VINCIE ---": "",
208
+ "1. Configuração do Prompt": OmegaConf.to_container(prompt_config, resolve=True),
209
+ "2. Parâmetros de Difusão": {
210
+ "steps": int(steps),
211
+ "cfg_scale": float(cfg_scale),
212
+ },
213
+ "3. Parâmetros de Geração": {
214
+ "seed_final": int(final_seed),
215
+ "resolution": int(resolution),
216
+ "pad_img_placeholder": bool(pad_placeholder),
217
+ },
218
+ "--- FIM DOS ARGUMENTOS ---": ""
219
+ }
220
+ print(json.dumps(log_data, indent=2, ensure_ascii=False))
221
+
222
+ # --- 4. CONSTRUÇÃO DA INTERFACE GRADIO ---
223
+
224
+ def create_ui():
225
+ """Cria e retorna a interface Gradio completa com todas as abas e controles."""
226
+ with gr.Blocks(theme=gr.themes.Soft(primary_hue="blue"), title="VINCIE Playground") as demo:
227
+ gr.Markdown("# 🖼️ **VINCIE Playground**\nExplore as diferentes capacidades do modelo VINCIE.")
228
+
229
+ # Controles avançados compartilhados pelas abas 1 e 2
230
+ with gr.Accordion("Opções Avançadas (para Abas 1 e 2)", open=False):
231
+ steps_input = gr.Slider(label="Passos de Inferência", minimum=10, maximum=100, step=1, value=50)
232
+ cfg_scale_input = gr.Slider(label="Escala de Orientação (CFG)", minimum=1.0, maximum=15.0, step=0.5, value=7.5)
233
+ seed_input = gr.Number(label="Semente (Seed)", value=-1, precision=0, info="Use -1 para aleatório.")
234
+
235
+ with gr.Tabs():
236
+ # Aba 1: Edição Simples
237
+ with gr.TabItem("Edição Simples"):
238
+ with gr.Row(equal_height=False):
239
+ with gr.Column(scale=1):
240
+ single_turn_img_in = gr.Image(type="filepath", label="Imagem de Entrada")
241
+ single_turn_prompt = gr.Textbox(lines=2, label="Prompt de Edição")
242
+ with gr.Accordion("Opções de Imagem", open=True):
243
+ aspect_ratio_input = gr.Dropdown(label="Aspect Ratio", choices=["keep_ratio", "1:1", "16:9", "9:16", "4:3", "3:4"], value="keep_ratio")
244
+ resolution_input = gr.Slider(label="Resolução (lado menor)", minimum=256, maximum=1024, step=64, value=512)
245
+ single_turn_button = gr.Button("Gerar", variant="primary")
246
+ with gr.Column(scale=1):
247
+ single_turn_img_out = gr.Image(label="Resultado", interactive=False)
248
+ gr.Examples([["/app/VINCIE/assets/woman_pineapple.png", "Adicione uma coroa na cabeça da mulher."]], [single_turn_img_in, single_turn_prompt])
249
+
250
+ # Aba 2: Edição em Múltiplos Turnos
251
+ with gr.TabItem("Edição em Múltiplos Turnos"):
252
+ with gr.Row():
253
+ with gr.Column(scale=1):
254
+ multi_turn_img_in = gr.Image(type="filepath", label="Imagem de Entrada Inicial")
255
+ multi_turn_prompts = gr.Textbox(lines=5, label="Prompts (um por linha)", placeholder="Turno 1: faça isso\nTurno 2: agora mude aquilo...")
256
+ multi_turn_button = gr.Button("Gerar Sequência", variant="primary")
257
+ with gr.Column(scale=2):
258
+ multi_turn_gallery_out = gr.Gallery(label="Resultados dos Turnos", columns=3, height="auto")
259
+
260
+ # Aba 3: Composição de Conceitos
261
+ with gr.TabItem("Composição de Conceitos"):
262
+ 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>`).")
263
+ with gr.Row():
264
+ concept_inputs = [gr.Image(type="filepath", label=f"Imagem {i} (<IMG{i}>)") for i in range(6)]
265
+ 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>:")
266
+ concept_button = gr.Button("Compor Imagem", variant="primary")
267
+ concept_img_out = gr.Image(label="Resultado da Composição", interactive=False)
268
+
269
+ # Conecta os botões às suas respectivas funções de backend
270
+ 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])
271
+ 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])
272
+ concept_button.click(fn=run_multi_concept_inference, inputs=[concept_prompt] + concept_inputs, outputs=[concept_img_out])
273
+
274
+ return demo
275
+
276
+ # --- 5. PONTO DE ENTRADA DA APLICAÇÃO ---
277
 
278
  if __name__ == "__main__":
279
+ setup_model()
280
+ ui = create_ui()
281
+
282
+ server_name = os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0")
283
+ server_port = int(os.environ.get("GRADIO_SERVER_PORT", 7860))
284
+ enable_queue = os.environ.get("GRADIO_ENABLE_QUEUE", "True").lower() == "true"
285
+
286
+ print(f"INFO: Lançando a interface Gradio em http://{server_name}:{server_port}")
287
+ if enable_queue:
288
+ ui.queue()
289
+
290
+ ui.launch(server_name=server_name, server_port=server_port)