File size: 5,402 Bytes
21e657e
 
 
 
 
 
 
 
 
 
 
1014901
c3fead6
21e657e
 
a56b134
 
c3fead6
a56b134
21e657e
a56b134
 
 
 
 
21e657e
 
a56b134
 
 
 
 
 
21e657e
 
a56b134
 
21e657e
a56b134
 
ce0c277
a56b134
ce0c277
 
 
 
 
 
 
a56b134
ce0c277
a56b134
ce0c277
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
c113217
a56b134
 
1014901
a56b134
 
 
ce0c277
 
 
 
1014901
c3fead6
a56b134
ec6c728
a56b134
21e657e
a56b134
c3fead6
 
ce0c277
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
a56b134
 
 
 
 
1014901
c3fead6
 
ce0c277
a56b134
ce0c277
c3fead6
ce0c277
1014901
ce0c277
1014901
ce0c277
1014901
a56b134
1014901
c3fead6
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
# ============================================
# 💜 NasFit Vision AI — Dark Neon Edition
# ============================================

# 🚧 Asegúrate de tener este contenido en tu requirements.txt:
# transformers>=4.43.0
# torch
# accelerate
# gradio
# Pillow

import gradio as gr
import torch
from transformers import AutoModelForVision2Seq
from transformers import LlavaOneVisionProcessor  # 👈 clase correcta
from PIL import Image
import re

# ============================================
# 🔮 CARGA DEL MODELO LOCAL
# ============================================

MODEL_ID = "lmms-lab/llava-onevision-1.5-8b-instruct"

print("⏳ Cargando modelo local con trust_remote_code=True...")

processor = LlavaOneVisionProcessor.from_pretrained(MODEL_ID, trust_remote_code=True)
model = AutoModelForVision2Seq.from_pretrained(
    MODEL_ID,
    trust_remote_code=True,
    torch_dtype=torch.float16 if torch.cuda.is_available() else torch.float32,
    device_map="auto"
)

print("✅ Modelo LLaVA-OneVision cargado correctamente.")

# ============================================
# 🧠 FUNCIONES DE ANÁLISIS NUTRICIONAL
# ============================================

def extract_macros(text):
    """Extrae proteínas, carbohidratos y grasas del texto generado."""
    def find_value(keyword):
        m = re.search(rf"{keyword}[^0-9]*([0-9]+)", text.lower())
        return int(m.group(1)) if m else 0
    p, c, f = find_value("prote"), find_value("carb"), find_value("gras")
    kcal = p * 4 + c * 4 + f * 9 if any([p, c, f]) else 0
    return {"protein": p, "carbs": c, "fat": f, "kcal": kcal}


def build_macro_card(macros):
    """Genera el HTML visual con barras de progreso tipo dashboard."""
    if not any(macros.values()):
        return "<div class='card'>⚖️ No se pudieron estimar los macros.</div>"

    def bar_html(value, color):
        width = min(value, 100)
        return f"""
        <div class='bar-bg'>
            <div class='bar-fill' style='width:{width}%; background:{color};'></div>
        </div>
        """

    return f"""
    <div class='card'>
      <h2>🍽️ Estimación Nutricional</h2>
      <div class='macro'><span>💪 Proteínas</span><span>{macros['protein']} g</span></div>
      {bar_html(macros['protein'], '#b25eff')}
      <div class='macro'><span>🥔 Carbohidratos</span><span>{macros['carbs']} g</span></div>
      {bar_html(macros['carbs'], '#00f0ff')}
      <div class='macro'><span>🥑 Grasas</span><span>{macros['fat']} g</span></div>
      {bar_html(macros['fat'], '#ff5efb')}
      <div class='macro kcal'><span>🔥 Calorías Totales</span><span>{macros['kcal']} kcal</span></div>
    </div>
    """


def analyze_food(image, text_prompt="Describe esta comida y estima sus calorías, proteínas, carbohidratos y grasas."):
    """Procesa la imagen localmente con el modelo y devuelve descripción + macros."""
    try:
        inputs = processor(text=text_prompt, images=image, return_tensors="pt").to(model.device)
        out = model.generate(**inputs, max_new_tokens=400)
        answer = processor.decode(out[0], skip_special_tokens=True)

        macros = extract_macros(answer)
        card = build_macro_card(macros)
        return f"<div class='desc'>{answer}</div>{card}"

    except Exception as e:
        return f"<div class='card error'>⚠️ Error: {e}</div>"

# ============================================
# 💅 INTERFAZ DE USUARIO (Purple Glassmorphism)
# ============================================

def build_interface():
    with gr.Blocks(css="""
/* --- DARK NEON THEME --- */
body {
  background: radial-gradient(circle at 20% 20%, #0d001f, #000);
  color: #fff;
  font-family: 'Inter', sans-serif;
}
.gradio-container {background: transparent !important;}
.card {
  backdrop-filter: blur(12px);
  background: rgba(30, 0, 60, 0.3);
  border: 1px solid rgba(200, 100, 255, 0.2);
  border-radius: 16px;
  padding: 1.2em;
  margin-top: 1em;
  box-shadow: 0 0 25px rgba(180, 0, 255, 0.15);
}
h1,h2 {color:#c18fff;}
.bar-bg {
  width:100%; height:8px; border-radius:6px;
  background:rgba(255,255,255,0.1); margin:4px 0 12px 0;
  overflow:hidden;
}
.bar-fill {height:100%; border-radius:6px; transition:width 1s ease;}
.macro {display:flex; justify-content:space-between; font-size:0.95em;}
.kcal {font-weight:600; color:#ffb3ff;}
.desc {
  background:rgba(255,255,255,0.05);
  padding:1em; border-radius:10px; line-height:1.5em;
  box-shadow:inset 0 0 20px rgba(180,0,255,0.1);
}
button {
  background:linear-gradient(90deg,#b25eff,#00f0ff);
  color:#fff; border:none; border-radius:12px;
  font-weight:600; transition:opacity .2s;
}
button:hover {opacity:0.8;}
""") as demo:

        gr.Markdown("""
        <h1>💜 NasFit Vision AI</h1>
        <p>Analiza tus comidas con IA y obtené tu ficha nutricional instantánea.</p>
        """)

        with gr.Row():
            with gr.Column(scale=1):
                img = gr.Image(label="📸 Imagen del plato", type="pil")
                txt = gr.Textbox(label="💬 Instrucción (opcional)", placeholder="Ej: ¿Cuántas calorías tiene este plato?")
                btn = gr.Button("🔍 Analizar", variant="primary")
            with gr.Column(scale=1):
                out = gr.HTML(label="🧠 Resultado")

        btn.click(analyze_food, [img, txt], out)

    return demo


if __name__ == "__main__":
    demo = build_interface()
    demo.launch()