DHEIVER commited on
Commit
a91cbb3
·
verified ·
1 Parent(s): 15646ab

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +193 -150
app.py CHANGED
@@ -1,80 +1,116 @@
1
  import gradio as gr
2
  import torch
3
- from transformers import pipeline, AutoProcessor, AutoModelForVision2Seq
4
- from PIL import Image, ImageOps
5
  import numpy as np
6
  import os
7
- from huggingface_hub import snapshot_download
8
  import logging
 
 
 
9
  from pathlib import Path
10
  import tempfile
11
- import requests
12
- from io import BytesIO
13
 
14
- # Configure logging
15
- logging.basicConfig(level=logging.INFO)
16
  logger = logging.getLogger(__name__)
17
 
18
- class ImageHandler:
19
- """Handle image processing and conversion"""
 
20
  @staticmethod
21
- def convert_to_rgb(image_path):
22
- """Convert image to RGB format supporting multiple formats"""
23
  try:
24
- # If image is a URL, download it first
25
- if isinstance(image_path, str) and (image_path.startswith('http://') or image_path.startswith('https://')):
26
- response = requests.get(image_path)
27
- image_data = BytesIO(response.content)
28
- image = Image.open(image_data)
29
- else:
30
- image = Image.open(image_path)
31
-
32
- # Convert RGBA to RGB if needed
33
- if image.mode == 'RGBA':
34
- background = Image.new('RGB', image.size, (255, 255, 255))
35
- background.paste(image, mask=image.split()[3])
36
- image = background
37
- # Convert any other mode to RGB
38
- elif image.mode != 'RGB':
39
- image = image.convert('RGB')
 
 
 
 
 
 
40
 
41
- return image
42
-
43
  except Exception as e:
44
- logger.error(f"Error converting image: {str(e)}")
45
- raise ValueError(f"Não foi possível processar a imagem. Erro: {str(e)}")
46
-
47
  @staticmethod
48
- def process_image(image):
49
- """Process image from various input types"""
50
  try:
51
- if isinstance(image, np.ndarray):
52
- return Image.fromarray(image)
53
- elif isinstance(image, Image.Image):
54
- return image
55
- elif isinstance(image, (str, Path)):
56
- return ImageHandler.convert_to_rgb(image)
57
- else:
58
- raise ValueError("Formato de imagem não suportado")
59
-
60
- except Exception as e:
61
- logger.error(f"Error processing image: {str(e)}")
62
- raise ValueError(f"Erro no processamento da imagem: {str(e)}")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
 
64
  class NutritionalAnalyzer:
65
  def __init__(self):
66
  self.device = "cuda" if torch.cuda.is_available() else "cpu"
67
  self.models = {}
68
  self.processors = {}
69
- self.image_handler = ImageHandler()
70
 
71
- def initialize_model(self, model_name):
72
- """Initialize a specific model with error handling and caching"""
73
  try:
74
  if model_name not in self.models:
75
- logger.info(f"Initializing {model_name}...")
76
 
77
- # Model-specific configurations
78
  model_configs = {
79
  "llava": {
80
  "repo": "llava-hf/llava-1.5-7b-hf",
@@ -90,124 +126,132 @@ class NutritionalAnalyzer:
90
  if not config:
91
  raise ValueError(f"Modelo não suportado: {model_name}")
92
 
93
- # Ensure cache directory exists
94
  os.makedirs(config["local_cache"], exist_ok=True)
95
 
96
- # Download model if not cached
97
- if not os.path.exists(os.path.join(config["local_cache"], "model.safetensors")):
98
- snapshot_download(
99
- repo_id=config["repo"],
100
- local_dir=config["local_cache"],
101
- ignore_patterns=["*.md", "*.txt"]
102
  )
 
 
 
 
 
 
 
 
 
 
 
103
 
104
- # Load processor and model
105
- self.processors[model_name] = AutoProcessor.from_pretrained(
106
- config["local_cache"],
107
- local_files_only=True
108
- )
109
-
110
- self.models[model_name] = AutoModelForVision2Seq.from_pretrained(
111
- config["local_cache"],
112
- torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
113
- device_map="auto",
114
- local_files_only=True
115
- )
116
-
117
- logger.info(f"{model_name} initialized successfully")
118
  return True
119
 
120
  return True
121
 
122
  except Exception as e:
123
- logger.error(f"Error initializing {model_name}: {str(e)}")
124
  return False
125
 
126
- def generate_nutritional_prompt(self, user_question):
127
- """Generate a comprehensive nutritional analysis prompt"""
128
- return f"""Como nutricionista especializado, analise esta refeição detalhadamente:
129
-
130
- 1. Composição do Prato:
131
- - Ingredientes principais
132
- - Proporções aproximadas
133
- - Método de preparo aparente
134
-
135
- 2. Análise Nutricional:
136
- - Estimativa calórica
137
- - Macronutrientes (proteínas, carboidratos, gorduras)
138
- - Principais micronutrientes
139
-
140
- 3. Recomendações:
141
- - Sugestões para versão mais saudável
142
- - Porção recomendada
143
- - Adequação para dietas específicas
144
-
145
- Pergunta específica do usuário: {user_question}
146
-
147
- Por favor, forneça uma análise detalhada em português."""
148
-
149
- def analyze_image(self, image, question, model_choice):
150
- """Analyze image with nutritional focus"""
151
  try:
152
  if image is None:
153
- return "Por favor, faça upload de uma imagem para análise."
154
 
155
- # Convert model choice to internal name
156
  model_name = model_choice.lower().replace("-", "")
157
 
158
- # Initialize model if needed
159
- if not self.initialize_model(model_name):
160
- return "Erro: Não foi possível inicializar o modelo. Por favor, tente novamente."
161
 
162
- # Process image with enhanced error handling
163
  try:
164
- processed_image = self.image_handler.process_image(image)
165
- except ValueError as e:
166
- return str(e)
 
 
 
 
 
 
 
167
  except Exception as e:
168
- return f"Erro no processamento da imagem: {str(e)}"
169
-
170
- # Generate and process prompt
 
171
  nutritional_prompt = self.generate_nutritional_prompt(question)
172
 
173
- # Process input
174
  try:
175
- inputs = self.processors[model_name](
 
176
  images=processed_image,
177
  text=nutritional_prompt,
178
  return_tensors="pt"
179
- ).to(self.device)
 
180
  except Exception as e:
181
- return f"Erro no processamento do modelo: {str(e)}"
182
 
183
- # Generate response with enhanced parameters
184
  try:
185
  with torch.no_grad():
186
- outputs = self.models[model_name].generate(
 
187
  **inputs,
188
  max_new_tokens=300,
189
  num_beams=5,
 
190
  temperature=0.7,
191
  top_p=0.9,
192
  repetition_penalty=1.2,
193
- length_penalty=1.0
 
194
  )
195
 
196
- # Decode and format response
197
  response = self.processors[model_name].decode(outputs[0], skip_special_tokens=True)
198
- formatted_response = self.format_response(response)
199
-
200
- return formatted_response
201
 
202
  except Exception as e:
203
  return f"Erro na geração da análise: {str(e)}"
204
 
205
  except Exception as e:
206
- logger.error(f"Analysis error: {str(e)}")
207
- return f"Erro na análise: {str(e)}\nPor favor, tente novamente ou escolha outro modelo."
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
208
 
209
  def format_response(self, response):
210
- """Format the response for better readability"""
211
  sections = [
212
  "Composição do Prato",
213
  "Análise Nutricional",
@@ -215,20 +259,15 @@ Por favor, forneça uma análise detalhada em português."""
215
  ]
216
 
217
  formatted = "# 📊 Análise Nutricional\n\n"
218
-
219
- # Split response into paragraphs
220
- paragraphs = response.split("\n")
221
-
222
  current_section = ""
223
- for paragraph in paragraphs:
224
- # Check if paragraph starts a new section
225
  for section in sections:
226
  if section.lower() in paragraph.lower():
227
  current_section = f"\n## {section}\n"
228
  formatted += current_section
229
  break
230
 
231
- # Add paragraph to current section
232
  if paragraph.strip() and current_section:
233
  formatted += f"- {paragraph.strip()}\n"
234
  elif paragraph.strip():
@@ -236,20 +275,20 @@ Por favor, forneça uma análise detalhada em português."""
236
 
237
  return formatted
238
 
239
- # Create interface
240
  def create_interface():
 
241
  analyzer = NutritionalAnalyzer()
242
 
243
  with gr.Blocks(theme=gr.themes.Soft()) as iface:
244
  gr.Markdown("""
245
  # 🥗 Análise Nutricional Inteligente
246
- Faça upload da foto do seu prato para receber uma análise nutricional detalhada.
247
  """)
248
 
249
  with gr.Row():
250
  with gr.Column(scale=2):
251
  image_input = gr.Image(
252
- type="pil",
253
  label="📸 Foto do Prato",
254
  height=400
255
  )
@@ -263,33 +302,31 @@ def create_interface():
263
  model_choice = gr.Radio(
264
  choices=["LLaVA", "GIT"],
265
  value="LLaVA",
266
- label="🤖 Escolha o Modelo de Análise"
267
  )
268
 
269
  analyze_btn = gr.Button(
270
  "🔍 Analisar Prato",
271
- variant="primary",
272
- scale=1
273
  )
274
 
275
  with gr.Column(scale=3):
276
  output = gr.Markdown(label="Resultado da Análise")
277
 
278
- # Add examples and tips
279
- with gr.Accordion("💡 Dicas de Uso", open=False):
280
  gr.Markdown("""
281
- ### Sugestões de Perguntas:
282
- - Qual o valor nutricional aproximado deste prato?
283
- - Como tornar esta refeição mais equilibrada?
284
- - Este prato é adequado para dieta low-carb?
285
- - Quais nutrientes importantes estão presentes?
 
286
 
287
- ### Dicas para Melhores Resultados:
288
- 1. Tire a foto com boa iluminação
289
- 2. Capture todos os elementos do prato
290
  3. Evite ângulos muito inclinados
291
- 4. Seja específico em suas perguntas
292
- 5. Formatos de imagem suportados: JPG, PNG, WEBP
293
  """)
294
 
295
  analyze_btn.click(
@@ -302,4 +339,10 @@ def create_interface():
302
 
303
  if __name__ == "__main__":
304
  iface = create_interface()
305
- iface.launch()
 
 
 
 
 
 
 
1
  import gradio as gr
2
  import torch
3
+ from transformers import AutoProcessor, AutoModelForVision2Seq
4
+ from PIL import Image
5
  import numpy as np
6
  import os
 
7
  import logging
8
+ import cv2
9
+ import shutil
10
+ import subprocess
11
  from pathlib import Path
12
  import tempfile
 
 
13
 
14
+ # Configuração de logging
15
+ logging.basicConfig(level=logging.DEBUG)
16
  logger = logging.getLogger(__name__)
17
 
18
+ class ForceImageProcessor:
19
+ """Processador agressivo de imagens com múltiplos fallbacks"""
20
+
21
  @staticmethod
22
+ def force_convert_image(input_path):
23
+ """Converte imagem usando múltiplos métodos até funcionar"""
24
  try:
25
+ # Cria diretório temporário
26
+ with tempfile.TemporaryDirectory() as temp_dir:
27
+ temp_path = Path(temp_dir) / "converted_image.jpg"
28
+
29
+ # Tenta diferentes métodos de conversão
30
+ methods = [
31
+ ForceImageProcessor._try_pillow,
32
+ ForceImageProcessor._try_opencv,
33
+ ForceImageProcessor._try_imagemagick,
34
+ ForceImageProcessor._try_ffmpeg
35
+ ]
36
+
37
+ for method in methods:
38
+ try:
39
+ result = method(input_path, temp_path)
40
+ if result:
41
+ return Image.open(temp_path)
42
+ except Exception as e:
43
+ logger.debug(f"Método falhou: {str(e)}")
44
+ continue
45
+
46
+ raise ValueError("Todos os métodos de conversão falharam")
47
 
 
 
48
  except Exception as e:
49
+ logger.error(f"Erro na conversão: {str(e)}")
50
+ raise
51
+
52
  @staticmethod
53
+ def _try_pillow(input_path, output_path):
54
+ """Tenta converter usando Pillow"""
55
  try:
56
+ img = Image.open(input_path)
57
+ img = img.convert('RGB')
58
+ img.save(output_path, 'JPEG')
59
+ return True
60
+ except:
61
+ return False
62
+
63
+ @staticmethod
64
+ def _try_opencv(input_path, output_path):
65
+ """Tenta converter usando OpenCV"""
66
+ try:
67
+ img = cv2.imread(str(input_path))
68
+ if img is None:
69
+ return False
70
+ cv2.imwrite(str(output_path), img)
71
+ return True
72
+ except:
73
+ return False
74
+
75
+ @staticmethod
76
+ def _try_imagemagick(input_path, output_path):
77
+ """Tenta converter usando ImageMagick"""
78
+ try:
79
+ result = subprocess.run(
80
+ ['convert', str(input_path), str(output_path)],
81
+ capture_output=True,
82
+ text=True
83
+ )
84
+ return result.returncode == 0
85
+ except:
86
+ return False
87
+
88
+ @staticmethod
89
+ def _try_ffmpeg(input_path, output_path):
90
+ """Tenta converter usando FFmpeg"""
91
+ try:
92
+ result = subprocess.run(
93
+ ['ffmpeg', '-i', str(input_path), '-y', str(output_path)],
94
+ capture_output=True,
95
+ text=True
96
+ )
97
+ return result.returncode == 0
98
+ except:
99
+ return False
100
 
101
  class NutritionalAnalyzer:
102
  def __init__(self):
103
  self.device = "cuda" if torch.cuda.is_available() else "cpu"
104
  self.models = {}
105
  self.processors = {}
106
+ self.image_processor = ForceImageProcessor()
107
 
108
+ async def initialize_model(self, model_name):
109
+ """Inicializa modelo com tratamento de erros melhorado"""
110
  try:
111
  if model_name not in self.models:
112
+ logger.info(f"Inicializando {model_name}...")
113
 
 
114
  model_configs = {
115
  "llava": {
116
  "repo": "llava-hf/llava-1.5-7b-hf",
 
126
  if not config:
127
  raise ValueError(f"Modelo não suportado: {model_name}")
128
 
129
+ # Garante que o diretório de cache existe
130
  os.makedirs(config["local_cache"], exist_ok=True)
131
 
132
+ # Carrega processador e modelo
133
+ try:
134
+ self.processors[model_name] = await gr.asyncio.asyncio.to_thread(
135
+ AutoProcessor.from_pretrained,
136
+ config["repo"],
137
+ trust_remote_code=True
138
  )
139
+
140
+ self.models[model_name] = await gr.asyncio.asyncio.to_thread(
141
+ AutoModelForVision2Seq.from_pretrained,
142
+ config["repo"],
143
+ torch_dtype=torch.float16 if self.device == "cuda" else torch.float32,
144
+ device_map="auto",
145
+ trust_remote_code=True
146
+ )
147
+ except Exception as e:
148
+ logger.error(f"Erro ao carregar modelo: {str(e)}")
149
+ raise
150
 
151
+ logger.info(f"{model_name} inicializado com sucesso")
 
 
 
 
 
 
 
 
 
 
 
 
 
152
  return True
153
 
154
  return True
155
 
156
  except Exception as e:
157
+ logger.error(f"Erro na inicialização do {model_name}: {str(e)}")
158
  return False
159
 
160
+ async def analyze_image(self, image, question, model_choice):
161
+ """Analisa imagem com foco nutricional"""
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  try:
163
  if image is None:
164
+ return "Por favor, envie uma imagem para análise."
165
 
166
+ # Converte escolha do modelo
167
  model_name = model_choice.lower().replace("-", "")
168
 
169
+ # Inicializa modelo
170
+ if not await self.initialize_model(model_name):
171
+ return "Erro: Falha ao inicializar o modelo. Tente novamente."
172
 
173
+ # Processa imagem com conversão forçada
174
  try:
175
+ with tempfile.NamedTemporaryFile(delete=False, suffix='.jpg') as temp_file:
176
+ if isinstance(image, np.ndarray):
177
+ cv2.imwrite(temp_file.name, cv2.cvtColor(image, cv2.COLOR_RGB2BGR))
178
+ else:
179
+ shutil.copy2(image, temp_file.name)
180
+
181
+ processed_image = await gr.asyncio.asyncio.to_thread(
182
+ self.image_processor.force_convert_image,
183
+ temp_file.name
184
+ )
185
  except Exception as e:
186
+ logger.error(f"Erro no processamento da imagem: {str(e)}")
187
+ return f"Erro ao processar imagem: {str(e)}"
188
+
189
+ # Gera prompt
190
  nutritional_prompt = self.generate_nutritional_prompt(question)
191
 
192
+ # Processa input
193
  try:
194
+ inputs = await gr.asyncio.asyncio.to_thread(
195
+ self.processors[model_name],
196
  images=processed_image,
197
  text=nutritional_prompt,
198
  return_tensors="pt"
199
+ )
200
+ inputs = {k: v.to(self.device) for k, v in inputs.items()}
201
  except Exception as e:
202
+ return f"Erro no processamento: {str(e)}"
203
 
204
+ # Gera resposta
205
  try:
206
  with torch.no_grad():
207
+ outputs = await gr.asyncio.asyncio.to_thread(
208
+ self.models[model_name].generate,
209
  **inputs,
210
  max_new_tokens=300,
211
  num_beams=5,
212
+ do_sample=True, # Habilitado para usar temperature e top_p
213
  temperature=0.7,
214
  top_p=0.9,
215
  repetition_penalty=1.2,
216
+ no_repeat_ngram_size=3, # Evita repetições de frases
217
+ early_stopping=True # Para quando a geração estiver completa
218
  )
219
 
 
220
  response = self.processors[model_name].decode(outputs[0], skip_special_tokens=True)
221
+ return self.format_response(response)
 
 
222
 
223
  except Exception as e:
224
  return f"Erro na geração da análise: {str(e)}"
225
 
226
  except Exception as e:
227
+ logger.error(f"Erro na análise: {str(e)}")
228
+ return f"Erro: {str(e)}\nPor favor, tente novamente."
229
+
230
+ def generate_nutritional_prompt(self, question):
231
+ """Gera prompt para análise nutricional"""
232
+ return f"""Como nutricionista especializado, analise esta refeição detalhadamente:
233
+
234
+ 1. Composição do Prato:
235
+ - Ingredientes principais
236
+ - Proporções aproximadas
237
+ - Método de preparo aparente
238
+
239
+ 2. Análise Nutricional:
240
+ - Estimativa calórica
241
+ - Macronutrientes (proteínas, carboidratos, gorduras)
242
+ - Principais micronutrientes
243
+
244
+ 3. Recomendações:
245
+ - Sugestões para versão mais saudável
246
+ - Porção recomendada
247
+ - Adequação para dietas específicas
248
+
249
+ Pergunta específica do usuário: {question}
250
+
251
+ Por favor, forneça uma análise detalhada em português."""
252
 
253
  def format_response(self, response):
254
+ """Formata a resposta para melhor legibilidade"""
255
  sections = [
256
  "Composição do Prato",
257
  "Análise Nutricional",
 
259
  ]
260
 
261
  formatted = "# 📊 Análise Nutricional\n\n"
 
 
 
 
262
  current_section = ""
263
+
264
+ for paragraph in response.split("\n"):
265
  for section in sections:
266
  if section.lower() in paragraph.lower():
267
  current_section = f"\n## {section}\n"
268
  formatted += current_section
269
  break
270
 
 
271
  if paragraph.strip() and current_section:
272
  formatted += f"- {paragraph.strip()}\n"
273
  elif paragraph.strip():
 
275
 
276
  return formatted
277
 
 
278
  def create_interface():
279
+ """Cria interface Gradio"""
280
  analyzer = NutritionalAnalyzer()
281
 
282
  with gr.Blocks(theme=gr.themes.Soft()) as iface:
283
  gr.Markdown("""
284
  # 🥗 Análise Nutricional Inteligente
285
+ Upload da foto do seu prato para análise nutricional detalhada.
286
  """)
287
 
288
  with gr.Row():
289
  with gr.Column(scale=2):
290
  image_input = gr.Image(
291
+ type="filepath", # Mudado para filepath para melhor compatibilidade
292
  label="📸 Foto do Prato",
293
  height=400
294
  )
 
302
  model_choice = gr.Radio(
303
  choices=["LLaVA", "GIT"],
304
  value="LLaVA",
305
+ label="🤖 Escolha o Modelo"
306
  )
307
 
308
  analyze_btn = gr.Button(
309
  "🔍 Analisar Prato",
310
+ variant="primary"
 
311
  )
312
 
313
  with gr.Column(scale=3):
314
  output = gr.Markdown(label="Resultado da Análise")
315
 
316
+ with gr.Accordion("💡 Dicas", open=False):
 
317
  gr.Markdown("""
318
+ ### Formatos Suportados:
319
+ - JPG/JPEG
320
+ - PNG
321
+ - WEBP
322
+ - AVIF
323
+ - Outros formatos de imagem comuns
324
 
325
+ ### Para Melhores Resultados:
326
+ 1. Boa iluminação na foto
327
+ 2. Capture todo o prato
328
  3. Evite ângulos muito inclinados
329
+ 4. Perguntas específicas ajudam
 
330
  """)
331
 
332
  analyze_btn.click(
 
339
 
340
  if __name__ == "__main__":
341
  iface = create_interface()
342
+ iface.launch(
343
+ share=False,
344
+ debug=True,
345
+ server_name="0.0.0.0",
346
+ server_port=7860,
347
+ show_error=True
348
+ )