Adanbalf commited on
Commit
ce0c277
·
verified ·
1 Parent(s): c3fead6

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +107 -54
app.py CHANGED
@@ -3,100 +3,153 @@ import gradio as gr
3
  import torch
4
  from PIL import Image
5
  from transformers import AutoProcessor, AutoModelForVision2Seq
6
- import requests
 
7
 
8
- # Configuración
9
  LOCAL_MODEL_ID = "lmms-lab/llava-onevision-1.5-8b-instruct"
10
- API_MODEL_ID = "lmms-lab/llava-onevision-1.5-8b-instruct"
11
- HF_API_URL = f"https://api-inference.huggingface.co/models/{API_MODEL_ID}"
12
  HF_API_KEY = os.getenv("API_KEY")
13
 
14
- # Inicializa modelo local (si hay GPU)
15
  model, processor = None, None
16
  use_local = False
17
 
18
  try:
19
- print("⏳ Intentando cargar modelo local...")
20
- processor = AutoProcessor.from_pretrained(LOCAL_MODEL_ID)
21
  model = AutoModelForVision2Seq.from_pretrained(
22
  LOCAL_MODEL_ID,
 
23
  torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
24
  device_map="auto"
25
  )
26
  use_local = True
27
  print("✅ Modelo local cargado correctamente.")
28
  except Exception as e:
29
- print(f"⚠️ No se pudo cargar el modelo local: {e}")
30
- print("➡️ Se usará la API de Hugging Face para inferencia remota.")
31
 
32
- # Función principal
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  def analyze_food(image, text_prompt=""):
34
  if image is None:
35
- return "Por favor, subí una imagen del plato."
36
-
37
  if not text_prompt.strip():
38
- text_prompt = (
39
- "Analiza esta comida. Describe los alimentos, "
40
- "y estima las calorías, proteínas, carbohidratos y grasas totales."
41
- )
42
 
43
  try:
44
  if use_local:
45
- # Procesamiento local
46
  inputs = processor(text=text_prompt, images=image, return_tensors="pt").to(model.device)
47
- output = model.generate(**inputs, max_new_tokens=300)
48
- answer = processor.decode(output[0], skip_special_tokens=True)
49
- return answer
50
-
51
  else:
52
- # Fallback: usar API de Hugging Face
 
 
53
  headers = {"Authorization": f"Bearer {HF_API_KEY}"}
54
- data = {
55
- "inputs": {"image": image, "text": text_prompt},
56
- "parameters": {"max_new_tokens": 300},
57
- }
58
- response = requests.post(HF_API_URL, headers=headers, json=data)
59
- if response.status_code != 200:
60
- return f" Error remoto ({response.status_code}): {response.text}"
61
- result = response.json()
62
- if isinstance(result, dict) and "error" in result:
63
- return f"⚠️ Error remoto: {result['error']}"
64
- return str(result)
65
 
66
  except Exception as e:
67
- return f"⚠️ Ocurrió un error al procesar la imagen: {e}"
68
 
69
- # Interfaz Gradio
70
  def build_interface():
71
- with gr.Blocks() as demo:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
72
  gr.Markdown(
73
  """
74
- # 🍽️ NasFit Vision AI
75
- Subí una foto de tu comida y NasFit IA estimará su contenido nutricional.
76
- Basado en **LLaVA-OneVision-1.5**, modelo multimodal open source con análisis visual avanzado.
77
- *(El sistema usa GPU local si está disponible, o la API de Hugging Face si no lo está.)*
78
  """
79
  )
80
 
81
  with gr.Row():
82
  with gr.Column(scale=1):
83
- image_input = gr.Image(label="📸 Imagen del plato", type="pil")
84
- text_input = gr.Textbox(
85
- label="💬 Instrucción (opcional)",
86
- placeholder="Ejemplo: Cuántas proteínas tiene este plato?",
87
- )
88
- analyze_btn = gr.Button("🔍 Analizar comida")
89
-
90
  with gr.Column(scale=1):
91
- output_text = gr.Textbox(
92
- label="🧠 Resultado del análisis",
93
- placeholder="Aquí aparecerá la descripción nutricional...",
94
- lines=8
95
- )
96
 
97
- analyze_btn.click(fn=analyze_food, inputs=[image_input, text_input], outputs=output_text)
98
- return demo
99
 
 
100
 
101
  if __name__ == "__main__":
102
  demo = build_interface()
 
3
  import torch
4
  from PIL import Image
5
  from transformers import AutoProcessor, AutoModelForVision2Seq
6
+ import requests, base64, re
7
+ from io import BytesIO
8
 
9
+ # Configuración del modelo
10
  LOCAL_MODEL_ID = "lmms-lab/llava-onevision-1.5-8b-instruct"
11
+ HF_API_URL = f"https://api-inference.huggingface.co/models/{LOCAL_MODEL_ID}"
 
12
  HF_API_KEY = os.getenv("API_KEY")
13
 
 
14
  model, processor = None, None
15
  use_local = False
16
 
17
  try:
18
+ print("⏳ Cargando modelo local...")
19
+ processor = AutoProcessor.from_pretrained(LOCAL_MODEL_ID, trust_remote_code=True)
20
  model = AutoModelForVision2Seq.from_pretrained(
21
  LOCAL_MODEL_ID,
22
+ trust_remote_code=True,
23
  torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
24
  device_map="auto"
25
  )
26
  use_local = True
27
  print("✅ Modelo local cargado correctamente.")
28
  except Exception as e:
29
+ print(f"⚠️ No se pudo cargar localmente: {e}")
30
+ print("➡️ Se usará la API de Hugging Face.")
31
 
32
+ # ---------- Utilidades ----------
33
+ def extract_macros(text):
34
+ def find_value(keyword):
35
+ m = re.search(rf"{keyword}[^0-9]*([0-9]+)", text.lower())
36
+ return int(m.group(1)) if m else 0
37
+ p, c, f = find_value("prote"), find_value("carb"), find_value("gras")
38
+ kcal = p * 4 + c * 4 + f * 9 if any([p, c, f]) else 0
39
+ return {"protein": p, "carbs": c, "fat": f, "kcal": kcal}
40
+
41
+ def build_macro_card(macros):
42
+ if not any(macros.values()):
43
+ return "<div class='card'>⚖️ No se pudieron estimar los macros.</div>"
44
+
45
+ def bar_html(value, color):
46
+ width = min(value, 100)
47
+ return f"""
48
+ <div class='bar-bg'>
49
+ <div class='bar-fill' style='width:{width}%; background:{color};'></div>
50
+ </div>
51
+ """
52
+
53
+ return f"""
54
+ <div class='card'>
55
+ <h2>🍽️ Estimación Nutricional</h2>
56
+ <div class='macro'><span>💪 Proteínas</span><span>{macros['protein']} g</span></div>
57
+ {bar_html(macros['protein'], '#b25eff')}
58
+ <div class='macro'><span>🥔 Carbohidratos</span><span>{macros['carbs']} g</span></div>
59
+ {bar_html(macros['carbs'], '#00f0ff')}
60
+ <div class='macro'><span>🥑 Grasas</span><span>{macros['fat']} g</span></div>
61
+ {bar_html(macros['fat'], '#ff5efb')}
62
+ <div class='macro kcal'><span>🔥 Calorías Totales</span><span>{macros['kcal']} kcal</span></div>
63
+ </div>
64
+ """
65
+
66
+ # ---------- Lógica principal ----------
67
  def analyze_food(image, text_prompt=""):
68
  if image is None:
69
+ return "<div class='card'>Subí una imagen del plato 🍽️</div>"
 
70
  if not text_prompt.strip():
71
+ text_prompt = "Describe esta comida y estima calorías, proteínas, carbohidratos y grasas."
 
 
 
72
 
73
  try:
74
  if use_local:
 
75
  inputs = processor(text=text_prompt, images=image, return_tensors="pt").to(model.device)
76
+ out = model.generate(**inputs, max_new_tokens=400)
77
+ answer = processor.decode(out[0], skip_special_tokens=True)
 
 
78
  else:
79
+ buffered = BytesIO()
80
+ image.save(buffered, format="JPEG")
81
+ img_b64 = base64.b64encode(buffered.getvalue()).decode("utf-8")
82
  headers = {"Authorization": f"Bearer {HF_API_KEY}"}
83
+ data = {"inputs": {"text": text_prompt, "image": f"data:image/jpeg;base64,{img_b64}"}}
84
+ r = requests.post(HF_API_URL, headers=headers, json=data)
85
+ answer = str(r.json())
86
+
87
+ macros = extract_macros(answer)
88
+ card = build_macro_card(macros)
89
+ return f"<div class='desc'>{answer}</div>{card}"
 
 
 
 
90
 
91
  except Exception as e:
92
+ return f"<div class='card error'>⚠️ Error: {e}</div>"
93
 
94
+ # ---------- Interfaz ----------
95
  def build_interface():
96
+ with gr.Blocks(css="""
97
+ /* --- DARK NEON THEME --- */
98
+ body {
99
+ background: radial-gradient(circle at 20% 20%, #0d001f, #000);
100
+ color: #fff;
101
+ font-family: 'Inter', sans-serif;
102
+ }
103
+ .gradio-container {background: transparent !important;}
104
+ .card {
105
+ backdrop-filter: blur(12px);
106
+ background: rgba(30, 0, 60, 0.3);
107
+ border: 1px solid rgba(200, 100, 255, 0.2);
108
+ border-radius: 16px;
109
+ padding: 1.2em;
110
+ margin-top: 1em;
111
+ box-shadow: 0 0 25px rgba(180, 0, 255, 0.15);
112
+ }
113
+ h1,h2 {color:#c18fff;}
114
+ .bar-bg {
115
+ width:100%; height:8px; border-radius:6px;
116
+ background:rgba(255,255,255,0.1); margin:4px 0 12px 0;
117
+ overflow:hidden;
118
+ }
119
+ .bar-fill {height:100%; border-radius:6px; transition:width 1s ease;}
120
+ .macro {display:flex; justify-content:space-between; font-size:0.95em;}
121
+ .kcal {font-weight:600; color:#ffb3ff;}
122
+ .desc {
123
+ background:rgba(255,255,255,0.05);
124
+ padding:1em; border-radius:10px; line-height:1.5em;
125
+ box-shadow:inset 0 0 20px rgba(180,0,255,0.1);
126
+ }
127
+ button {
128
+ background:linear-gradient(90deg,#b25eff,#00f0ff);
129
+ color:#fff; border:none; border-radius:12px;
130
+ font-weight:600; transition:opacity .2s;
131
+ }
132
+ button:hover {opacity:0.8;}
133
+ """) as demo:
134
  gr.Markdown(
135
  """
136
+ <h1>💜 NasFit Vision AI</h1>
137
+ <p>Analiza tus comidas con IA y obtené tu ficha nutricional instantánea.</p>
 
 
138
  """
139
  )
140
 
141
  with gr.Row():
142
  with gr.Column(scale=1):
143
+ img = gr.Image(label="📸 Imagen del plato", type="pil")
144
+ txt = gr.Textbox(label="💬 Instrucción (opcional)",
145
+ placeholder="Ej: ¿Cuántas calorías tiene este plato?")
146
+ btn = gr.Button("🔍 Analizar", variant="primary")
 
 
 
147
  with gr.Column(scale=1):
148
+ out = gr.HTML(label="🧠 Resultado")
 
 
 
 
149
 
150
+ btn.click(analyze_food, [img, txt], out)
 
151
 
152
+ return demo
153
 
154
  if __name__ == "__main__":
155
  demo = build_interface()