fenix-grounding / app.py
Cristobal299's picture
Rename grounding_app.py to app.py
dd30e5a verified
Raw
History Blame Contribute Delete
3.04 kB
# app.py — Fénix Grounding (ShowUI-2B en ZeroGPU)
# Recibe una captura + una descripción de qué clicar, y devuelve las
# coordenadas normalizadas 0-1 del elemento. El agente lo llama SOLO
# cuando necesita acertar un clic fino sobre un icono/botón.
import spaces
import ast
import torch
import gradio as gr
from transformers import Qwen2VLForConditionalGeneration, AutoProcessor
from qwen_vl_utils import process_vision_info
MODEL_ID = "showlab/ShowUI-2B"
_SYSTEM = (
"Based on the screenshot of the page, I give a text description and you give its "
"corresponding location. The coordinate represents a clickable location [x, y] "
"for an element, which is a relative coordinate on the screenshot, scaled from 0 to 1."
)
min_pixels = 256 * 28 * 28
max_pixels = 1344 * 28 * 28
# Se carga en CPU al arrancar; ZeroGPU asigna la GPU solo dentro de @spaces.GPU
model = Qwen2VLForConditionalGeneration.from_pretrained(
MODEL_ID, torch_dtype=torch.bfloat16
)
processor = AutoProcessor.from_pretrained(
"Qwen/Qwen2-VL-2B-Instruct", min_pixels=min_pixels, max_pixels=max_pixels
)
@spaces.GPU(duration=60)
def localizar(imagen, consulta):
"""imagen: ruta de la captura. consulta: qué elemento clicar (mejor en inglés)."""
if imagen is None or not (consulta or "").strip():
return "ERROR: falta imagen o consulta"
model.to("cuda")
messages = [{
"role": "user",
"content": [
{"type": "text", "text": _SYSTEM},
{"type": "image", "image": imagen,
"min_pixels": min_pixels, "max_pixels": max_pixels},
{"type": "text", "text": consulta},
],
}]
text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
image_inputs, video_inputs = process_vision_info(messages)
inputs = processor(text=[text], images=image_inputs, videos=video_inputs,
padding=True, return_tensors="pt").to("cuda")
generated = model.generate(**inputs, max_new_tokens=128)
trimmed = [out[len(inp):] for inp, out in zip(inputs.input_ids, generated)]
out_text = processor.batch_decode(
trimmed, skip_special_tokens=True, clean_up_tokenization_spaces=False
)[0]
try:
coords = ast.literal_eval(out_text)
return f"{float(coords[0]):.4f},{float(coords[1]):.4f}"
except Exception:
return f"ERROR: {out_text}"
with gr.Blocks(title="Fénix Grounding") as demo:
gr.Markdown("## 🎯 Fénix Grounding — ShowUI-2B\n"
"Sube una captura y describe el elemento; devuelve x,y (0-1).")
with gr.Row():
img = gr.Image(label="Captura", type="filepath")
with gr.Column():
q = gr.Textbox(label="Qué clicar (mejor en inglés)",
placeholder="Notepad icon in the taskbar")
btn = gr.Button("Localizar", variant="primary")
out = gr.Textbox(label="Coordenadas x,y (0-1)")
btn.click(localizar, [img, q], out, api_name="localizar")
demo.launch()