CoelinhaJob commited on
Commit
c70ef7a
·
verified ·
1 Parent(s): 81f0222

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +132 -75
app.py CHANGED
@@ -1,92 +1,149 @@
1
- import gradio as gr
2
- import trimesh
3
- from instant_texture import Converter
4
  import os
5
- import uuid
6
- from PIL import Image
7
 
8
- # Inicializa o conversor
9
- converter = Converter()
10
-
11
- def extract_texture(mesh_path: str):
12
- """Tenta extrair a textura base do arquivo GLB gerado."""
 
 
 
13
  try:
14
- # Carrega a cena GLB
15
- scene = trimesh.load(mesh_path)
16
-
17
- # GLBs são carregados como 'Scenes'. Precisamos pegar a primeira malha (geometry) da cena.
18
- # Se a cena estiver vazia, isso vai gerar um erro que o 'except' pega.
19
- if not scene.geometry:
20
- print("Erro: Nenhuma geometria encontrada no GLB gerado.")
21
- return None
22
-
23
- mesh = next(iter(scene.geometry.values()))
24
 
25
- # Verifica se a malha tem material e se o material tem a textura de cor base
26
- if hasattr(mesh.visual, 'material') and hasattr(mesh.visual.material, 'baseColorTexture'):
27
- texture = mesh.visual.material.baseColorTexture
28
- # Verifica se a textura é uma imagem PIL válida antes de retornar
29
- if isinstance(texture, Image.Image):
30
- return texture
31
-
32
- print("Aviso: O modelo GLB foi gerado, mas nenhuma textura baseColorTexture foi encontrada.")
33
- return None
34
 
35
- except Exception as e:
36
- print(f"Erro ao extrair textura: {e}")
37
- return None
38
 
39
- def convert(input_mesh_path: str):
40
- # Validação de entrada
41
- if input_mesh_path is None:
42
- raise gr.Error("Por favor, envie um arquivo .obj.")
43
-
44
- if not input_mesh_path.endswith(".obj"):
45
- raise gr.Error("Apenas arquivos .obj são suportados.")
46
 
47
- # --- MUDANÇA CRÍTICA 1: Arquivos Temporários Únicos ---
48
- # Em um Space, múltiplos usuários podem usar o app ao mesmo tempo.
49
- # Usar sempre "/tmp/output.glb" faria um usuário sobrescrever o arquivo do outro.
50
- # Usamos UUID para criar um nome de arquivo único para cada execução.
51
- unique_filename = str(uuid.uuid4())
52
- output_path = os.path.join("/tmp", f"output_{unique_filename}.glb")
53
 
54
- try:
55
- # Executa a conversão (assumindo que esta biblioteca funciona corretamente)
56
- converter.convert(input_mesh_path, output_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
 
58
- # Verifica se o arquivo de saída foi realmente criado
59
- if not os.path.exists(output_path):
60
- raise gr.Error("Falha na conversão: O arquivo de saída não foi gerado.")
 
 
 
 
 
 
 
 
61
 
62
- # Extrai a textura do novo arquivo GLB
63
- texture = extract_texture(output_path)
 
 
64
 
65
- if texture is None:
66
- # Se não conseguiu extrair a textura, retorna o modelo, mas avisa no log
67
- print("Retornando modelo sem imagem de textura separada.")
68
- return output_path, None
 
 
 
69
 
70
- return output_path, texture
 
 
 
 
 
 
 
71
 
 
72
  except Exception as e:
73
- # Captura erros da biblioteca instant_texture e mostra pro usuário
74
- raise gr.Error(f"Erro durante o processo de conversão: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
- # Configuração da Interface Gradio
77
- demo = gr.Interface(
78
- fn=convert,
79
- title="Instant Texture",
80
- description="Convert a vertex-colored mesh (.obj) to a uv-mapped, textured mesh (.glb).",
81
- inputs=gr.Model3D(label="Vertex-colored mesh (.obj)"),
82
- outputs=[
83
- gr.Model3D(label="Output uv-mapped, textured mesh (.glb)"),
84
- gr.Image(label="Output texture", type="pil"), # Força o tipo PIL para garantir compatibilidade
85
- ],
86
- # Verifique se o caminho do exemplo está correto no seu repositório
87
- # examples=[["examples/chair.obj"]],
88
- cache_examples=False, # Desativado temporariamente para evitar erros se o exemplo não existir
89
- )
90
 
 
 
 
91
  if __name__ == "__main__":
92
- demo.queue().launch()
 
 
 
 
 
 
 
 
 
 
 
1
  import os
2
+ import sys
3
+ import logging
4
 
5
+ # -----------------------------------------------------------------------------
6
+ # SEÇÃO 1: COMPATIBILIDADE DE SISTEMA (Polyfill para Python 3.13+)
7
+ # -----------------------------------------------------------------------------
8
+ # O Python 3.13 removeu o módulo 'audioop'. O Gradio e Pydub dependem dele.
9
+ # Este bloco tenta importar o substituto 'audioop-lts' e injetá-lo como 'audioop'.
10
+ try:
11
+ import audioop
12
+ except ImportError:
13
  try:
14
+ import audioop_lts as audioop
15
+ sys.modules["audioop"] = audioop
16
+ print("Módulo 'audioop-lts' carregado com sucesso para compatibilidade Python 3.13.")
17
+ except ImportError:
18
+ print("AVISO CRÍTICO: 'audioop' não encontrado. Recursos de áudio falharão em Python 3.13+.")
 
 
 
 
 
19
 
20
+ # -----------------------------------------------------------------------------
21
+ # SEÇÃO 2: IMPORTAÇÕES E CONFIGURAÇÃO DE AMBIENTE
22
+ # -----------------------------------------------------------------------------
23
+ import gradio as gr
24
+ import spaces # Biblioteca essencial para gerenciamento de GPU em HF Spaces
25
+ import torch
26
+ from diffusers import StableDiffusionControlNetPipeline, ControlNetModel, UniPCMultistepScheduler
27
+ from PIL import Image
 
28
 
29
+ # Configuração de Logs para diagnóstico facilitado
30
+ logging.basicConfig(level=logging.INFO)
31
+ logger = logging.getLogger(__name__)
32
 
33
+ logger.info(f"Python Version: {sys.version}")
34
+ logger.info(f"Gradio Version: {gr.__version__}")
35
+ logger.info(f"Pydantic Version: {sys.modules.get('pydantic', 'Not Loaded')}")
36
+ logger.info(f"CUDA Available: {torch.cuda.is_available()}")
 
 
 
37
 
38
+ # -----------------------------------------------------------------------------
39
+ # SEÇÃO 3: CARREGAMENTO DO MODELO (Cache Global)
40
+ # -----------------------------------------------------------------------------
41
+ # Carregar modelos fora da função de inferência para evitar recarregamento a cada requisição.
42
+ # Utiliza variáveis de ambiente ou IDs padrão.
 
43
 
44
+ MODEL_ID = "runwayml/stable-diffusion-v1-5"
45
+ CONTROLNET_ID = "lllyasviel/sd-controlnet-canny"
46
+
47
+ try:
48
+ if torch.cuda.is_available():
49
+ logger.info("Inicializando modelos na GPU...")
50
+ controlnet = ControlNetModel.from_pretrained(CONTROLNET_ID, torch_dtype=torch.float16)
51
+ pipe = StableDiffusionControlNetPipeline.from_pretrained(
52
+ MODEL_ID, controlnet=controlnet, torch_dtype=torch.float16
53
+ )
54
+ pipe.scheduler = UniPCMultistepScheduler.from_config(pipe.scheduler.config)
55
+ pipe.to("cuda")
56
+ pipe.enable_model_cpu_offload() # Otimização para Spaces com VRAM limitada
57
+ else:
58
+ logger.warning("GPU não detectada. O modelo rodará em CPU (extremamente lento).")
59
+ controlnet = ControlNetModel.from_pretrained(CONTROLNET_ID)
60
+ pipe = StableDiffusionControlNetPipeline.from_pretrained(MODEL_ID, controlnet=controlnet)
61
+ except Exception as e:
62
+ logger.error(f"Erro fatal ao carregar modelos: {e}")
63
+ # Não interrompemos o script aqui para permitir que a UI carregue e mostre o erro, se necessário
64
+
65
+ # -----------------------------------------------------------------------------
66
+ # SEÇÃO 4: LÓGICA DE INFERÊNCIA COM DECORADOR ZERO-GPU
67
+ # -----------------------------------------------------------------------------
68
 
69
+ @spaces.GPU(duration=120) # Aloca a GPU para esta função por até 120 segundos
70
+ def process_image(input_image, prompt, negative_prompt, num_steps, guidance_scale):
71
+ """
72
+ Função principal de geração. O decorador @spaces.GPU gerencia a fila e a alocação
73
+ de hardware automaticamente nos Hugging Face Spaces.
74
+ """
75
+ if input_image is None:
76
+ raise gr.Error("Por favor, forneça uma imagem de entrada.")
77
+
78
+ if not torch.cuda.is_available():
79
+ raise gr.Error("GPU não disponível neste ambiente. Impossível gerar imagem.")
80
 
81
+ try:
82
+ # Pre-processamento Canny (Exemplo para ControlNet Canny)
83
+ import cv2
84
+ import numpy as np
85
 
86
+ image = np.array(input_image)
87
+ low_threshold = 100
88
+ high_threshold = 200
89
+ image = cv2.Canny(image, low_threshold, high_threshold)
90
+ image = image[:, :, None]
91
+ image = np.concatenate([image, image, image], axis=2)
92
+ canny_image = Image.fromarray(image)
93
 
94
+ # Inferência
95
+ output = pipe(
96
+ prompt,
97
+ image=canny_image,
98
+ negative_prompt=negative_prompt,
99
+ num_inference_steps=int(num_steps),
100
+ guidance_scale=guidance_scale
101
+ ).images
102
 
103
+ return output
104
  except Exception as e:
105
+ logger.error(f"Erro durante a inferência: {e}")
106
+ raise gr.Error(f"Falha na geração: {str(e)}")
107
+
108
+ # -----------------------------------------------------------------------------
109
+ # SEÇÃO 5: CONSTRUÇÃO DA INTERFACE (Gradio Blocks)
110
+ # -----------------------------------------------------------------------------
111
+
112
+ with gr.Blocks(title="ControlNet Stable Diffusion", theme=gr.themes.Base()) as demo:
113
+ gr.Markdown("# 🎨 Stable Diffusion com ControlNet (Canny)")
114
+ gr.Markdown("Gere variações artísticas baseadas na estrutura de uma imagem de entrada.")
115
+
116
+ with gr.Row():
117
+ with gr.Column():
118
+ input_img = gr.Image(label="Imagem de Referência", type="pil", sources=["upload", "webcam"])
119
+ prompt_txt = gr.Textbox(label="Prompt Positivo", value="cyberpunk city, neon lights, highly detailed", lines=2)
120
+ neg_prompt_txt = gr.Textbox(label="Prompt Negativo", value="low quality, blurred, ugly, disfigured", lines=2)
121
+
122
+ with gr.Accordion("Configurações Avançadas", open=False):
123
+ steps_slider = gr.Slider(label="Passos de Inferência", minimum=10, maximum=50, value=25, step=1)
124
+ cfg_slider = gr.Slider(label="Guidance Scale", minimum=1.0, maximum=20.0, value=7.5, step=0.5)
125
+
126
+ generate_btn = gr.Button("Gerar Imagem", variant="primary", size="lg")
127
+
128
+ with gr.Column():
129
+ output_img = gr.Image(label="Resultado Gerado", interactive=False)
130
 
131
+ # Conexão de Eventos
132
+ generate_btn.click(
133
+ fn=process_image,
134
+ inputs=[input_img, prompt_txt, neg_prompt_txt, steps_slider, cfg_slider],
135
+ outputs=output_img
136
+ )
 
 
 
 
 
 
 
 
137
 
138
+ # -----------------------------------------------------------------------------
139
+ # SEÇÃO 6: LANÇAMENTO DO SERVIDOR
140
+ # -----------------------------------------------------------------------------
141
  if __name__ == "__main__":
142
+ # share=False é CRUCIAL. share=True tenta criar túneis SSH que falham em Spaces e
143
+ # geram o erro "ValueError: When localhost is not accessible".
144
+ demo.launch(
145
+ server_name="0.0.0.0",
146
+ server_port=7860,
147
+ share=False,
148
+ allowed_paths=["."]
149
+ )