tdurzynski commited on
Commit
f7bbdac
·
verified ·
1 Parent(s): 2a1c9dd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +27 -9
app.py CHANGED
@@ -12,9 +12,10 @@ import openai
12
  # Initialize YOLOv8 for multi-label food detection
13
  model = YOLO("yolov8n.pt") # Nano model for speed, fine-tune on food data later
14
 
15
- # Agent Functions (registered with AutoGen)
16
  def recognize_foods(image):
17
  start = time.time()
 
18
  # Check if image is valid (not None or empty)
19
  if image is None or image.size == 0:
20
  print("Warning: Invalid or empty image detected.")
@@ -23,6 +24,8 @@ def recognize_foods(image):
23
  # Convert to RGB and resize to 640x640
24
  try:
25
  pil_image = Image.fromarray(image).convert('RGB').resize((640, 640))
 
 
26
  except Exception as e:
27
  print(f"Error processing image: {str(e)}")
28
  return [] # Return empty list on preprocessing failure
@@ -34,10 +37,11 @@ def recognize_foods(image):
34
  for result in results:
35
  for cls in result.boxes.cls:
36
  label = model.names[int(cls)]
37
- if "food" in label.lower() or label in ["broccoli", "carrot", "green bean", "chicken", "turkey", "pasta", "rice", "tomato", "potato", "bread", "curry"]: # Expanded list
38
  conf = result.boxes.conf[result.boxes.cls == cls].item()
39
  foods.append((label, conf))
40
  detected = True
 
41
  if not detected:
42
  print("Warning: No food items detected in the image. Check YOLOv8 model or image quality.")
43
  print(f"Recognition took {time.time() - start:.2f}s: Found foods {foods}")
@@ -45,6 +49,7 @@ def recognize_foods(image):
45
 
46
  def estimate_sizes(image, foods):
47
  start = time.time()
 
48
  if not foods:
49
  print("Warning: No foods to estimate sizes for.")
50
  return {}
@@ -52,6 +57,7 @@ def estimate_sizes(image, foods):
52
  # Resize to match YOLO output for consistency
53
  try:
54
  img_cv = cv2.cvtColor(image, cv2.COLOR_RGB2BGR).resize((640, 640))
 
55
  except Exception as e:
56
  print(f"Error resizing image for size estimation: {str(e)}")
57
  return {}
@@ -70,6 +76,7 @@ def estimate_sizes(image, foods):
70
  # Simple heuristic: scale box area to grams (tune this based on data)
71
  grams = min(500, int((box_area / (640 * 640)) * 500)) # Cap at 500g
72
  sizes[label] = grams
 
73
 
74
  # Fallback: even split if no boxes found
75
  if not sizes:
@@ -77,12 +84,14 @@ def estimate_sizes(image, foods):
77
  area = total_area / len(foods) # Even split for now
78
  grams = min(500, int(area / (640 * 640) * 100)) # 100g per ~640k pixels, capped at 500g
79
  sizes[food] = grams
 
80
 
81
  print(f"Size estimation took {time.time() - start:.2f}s: Estimated sizes {sizes}")
82
  return sizes
83
 
84
  def fetch_nutrition(foods_with_sizes, nutritionix_key):
85
  start = time.time()
 
86
  if not nutritionix_key:
87
  print("Warning: No Nutritionix API key provided.")
88
  return "Please provide a Nutritionix API key for nutrition data."
@@ -98,12 +107,13 @@ def fetch_nutrition(foods_with_sizes, nutritionix_key):
98
  }
99
  # Build query from foods and sizes
100
  query = "\n".join([f"{size}g {food}" for food, size in foods_with_sizes.items()])
 
101
  body = {"query": query}
102
 
103
  try:
104
  response = requests.post(url, headers=headers, json=body, timeout=10)
105
  if response.status_code != 200:
106
- print(f"Nutritionix API error: {response.text}")
107
  return f"Nutritionix API error: {response.text}"
108
 
109
  data = response.json().get("foods", [])
@@ -127,6 +137,7 @@ def fetch_nutrition(foods_with_sizes, nutritionix_key):
127
 
128
  def get_nutrition_advice(nutrition_data, openai_key):
129
  start = time.time()
 
130
  if not openai_key:
131
  print("Warning: No OpenAI API key provided—skipping advice.")
132
  return "No OpenAI key provided—skipping advice."
@@ -140,6 +151,7 @@ def get_nutrition_advice(nutrition_data, openai_key):
140
  [f"- {food}: {data['calories']} cal, {data['protein']}g protein, {data['fat']}g fat, {data['carbs']}g carbs"
141
  for food, data in nutrition_data.items()]
142
  )
 
143
  response = openai.Completion.create(
144
  model="text-davinci-003",
145
  prompt=prompt,
@@ -157,31 +169,31 @@ def get_nutrition_advice(nutrition_data, openai_key):
157
  # AutoGen Agent Definitions
158
  food_recognizer = AssistantAgent(
159
  name="FoodRecognizer",
160
- system_message="Identify all food items in the image and return a list of (label, probability) pairs. Parse the message for the image data and call recognize_foods with it.",
161
  function_map={"recognize_foods": recognize_foods}
162
  )
163
 
164
  size_estimator = AssistantAgent(
165
  name="SizeEstimator",
166
- system_message="Estimate portion sizes in grams for each recognized food based on the image. Parse the previous message for the list of foods and call estimate_sizes with the image and foods.",
167
  function_map={"estimate_sizes": estimate_sizes}
168
  )
169
 
170
  nutrition_fetcher = AssistantAgent(
171
  name="NutritionFetcher",
172
- system_message="Fetch nutritional data from the Nutritionix API using the user's key. Parse the previous message for the foods and sizes dictionary and the initial message for the Nutritionix key, then call fetch_nutrition.",
173
  function_map={"fetch_nutrition": fetch_nutrition}
174
  )
175
 
176
  advice_agent = AssistantAgent(
177
  name="NutritionAdvisor",
178
- system_message="Provide basic nutrition advice based on the food data using the user's OpenAI key. Parse the previous message for the nutrition data and the initial message for the OpenAI key, then call get_nutrition_advice.",
179
  function_map={"get_nutrition_advice": get_nutrition_advice}
180
  )
181
 
182
  orchestrator = AssistantAgent(
183
  name="Orchestrator",
184
- system_message="Coordinate the workflow, format the output, and return the final result as text. Start by asking FoodRecognizer to process the image, then SizeEstimator, then NutritionFetcher, then NutritionAdvisor (if OpenAI key provided), and finally format the results into 'Food Analysis:\\n- food1 (size1g, prob1% confidence): calories1 cal, protein1g protein, fat1g fat, carbs1g carbs\\n...' for each food, followed by '\\nNutrition Advice:\\n' and the advice if available.",
185
  function_map={}
186
  )
187
 
@@ -207,11 +219,14 @@ manager = GroupChatManager(groupchat=group_chat)
207
  # Orchestrator Logic (via AutoGen chat)
208
  def orchestrate_workflow(image, nutritionix_key, openai_key=None):
209
  start = time.time()
 
 
210
 
211
  # Initiate chat with Orchestrator, passing image and keys as message
212
  message = f"Process this image: {image} with Nutritionix key: {nutritionix_key}"
213
  if openai_key:
214
  message += f" and OpenAI key: {openai_key}"
 
215
  response = manager.initiate_chat(
216
  orchestrator,
217
  message=message,
@@ -223,11 +238,14 @@ def orchestrate_workflow(image, nutritionix_key, openai_key=None):
223
  # Get the last message from chat history
224
  last_message = response.chat_history[-1]
225
  result = last_message.get("content", "No output from agents.")
 
226
  else:
227
  result = "No output from agents."
 
228
 
229
  if isinstance(result, dict):
230
  result = result.get("text", "No text output from agents.")
 
231
 
232
  # Split result into nutrition and advice if OpenAI key was provided
233
  if openai_key and isinstance(result, str) and "\nNutrition Advice:\n" in result:
@@ -238,7 +256,7 @@ def orchestrate_workflow(image, nutritionix_key, openai_key=None):
238
  nutrition = result if result != "No output from agents." else "No nutrition data."
239
  advice = "No advice available (OpenAI key required)."
240
 
241
- print(f"Total time: {time.time() - start:.2f}s")
242
  return nutrition, advice
243
 
244
  # Gradio Interface
 
12
  # Initialize YOLOv8 for multi-label food detection
13
  model = YOLO("yolov8n.pt") # Nano model for speed, fine-tune on food data later
14
 
15
+ # Agent Functions (registered with AutoGen, enhanced debugging)
16
  def recognize_foods(image):
17
  start = time.time()
18
+ print(f"Recognize_foods called with image shape: {image.shape if image is not None else 'None'}")
19
  # Check if image is valid (not None or empty)
20
  if image is None or image.size == 0:
21
  print("Warning: Invalid or empty image detected.")
 
24
  # Convert to RGB and resize to 640x640
25
  try:
26
  pil_image = Image.fromarray(image).convert('RGB').resize((640, 640))
27
+ img_np = np.array(pil_image)
28
+ print(f"Image converted to RGB, shape: {img_np.shape}, min RGB: {img_np.min()}, max RGB: {img_np.max()}")
29
  except Exception as e:
30
  print(f"Error processing image: {str(e)}")
31
  return [] # Return empty list on preprocessing failure
 
37
  for result in results:
38
  for cls in result.boxes.cls:
39
  label = model.names[int(cls)]
40
+ if "food" in label.lower() or label in ["waffle fry", "lettuce", "cucumber", "tomato", "broccoli", "carrot", "green bean", "chicken", "turkey", "pasta", "rice", "potato", "bread", "curry"]: # Expanded list
41
  conf = result.boxes.conf[result.boxes.cls == cls].item()
42
  foods.append((label, conf))
43
  detected = True
44
+ print(f"Detected: {label} with confidence {conf:.2f}, box: {result.boxes.xyxy[result.boxes.cls == cls]}")
45
  if not detected:
46
  print("Warning: No food items detected in the image. Check YOLOv8 model or image quality.")
47
  print(f"Recognition took {time.time() - start:.2f}s: Found foods {foods}")
 
49
 
50
  def estimate_sizes(image, foods):
51
  start = time.time()
52
+ print(f"Estimate_sizes called with foods: {foods}")
53
  if not foods:
54
  print("Warning: No foods to estimate sizes for.")
55
  return {}
 
57
  # Resize to match YOLO output for consistency
58
  try:
59
  img_cv = cv2.cvtColor(image, cv2.COLOR_RGB2BGR).resize((640, 640))
60
+ print(f"Image resized to shape: {img_cv.shape}")
61
  except Exception as e:
62
  print(f"Error resizing image for size estimation: {str(e)}")
63
  return {}
 
76
  # Simple heuristic: scale box area to grams (tune this based on data)
77
  grams = min(500, int((box_area / (640 * 640)) * 500)) # Cap at 500g
78
  sizes[label] = grams
79
+ print(f"Estimated size for {label}: {grams}g (via bounding box)")
80
 
81
  # Fallback: even split if no boxes found
82
  if not sizes:
 
84
  area = total_area / len(foods) # Even split for now
85
  grams = min(500, int(area / (640 * 640) * 100)) # 100g per ~640k pixels, capped at 500g
86
  sizes[food] = grams
87
+ print(f"Estimated size for {food}: {grams}g (via fallback)")
88
 
89
  print(f"Size estimation took {time.time() - start:.2f}s: Estimated sizes {sizes}")
90
  return sizes
91
 
92
  def fetch_nutrition(foods_with_sizes, nutritionix_key):
93
  start = time.time()
94
+ print(f"Fetch_nutrition called with foods_with_sizes: {foods_with_sizes}, key: {nutritionix_key[:5]}... (partial)")
95
  if not nutritionix_key:
96
  print("Warning: No Nutritionix API key provided.")
97
  return "Please provide a Nutritionix API key for nutrition data."
 
107
  }
108
  # Build query from foods and sizes
109
  query = "\n".join([f"{size}g {food}" for food, size in foods_with_sizes.items()])
110
+ print(f"Nutritionix query: {query}")
111
  body = {"query": query}
112
 
113
  try:
114
  response = requests.post(url, headers=headers, json=body, timeout=10)
115
  if response.status_code != 200:
116
+ print(f"Nutritionix API error (status {response.status_code}): {response.text}")
117
  return f"Nutritionix API error: {response.text}"
118
 
119
  data = response.json().get("foods", [])
 
137
 
138
  def get_nutrition_advice(nutrition_data, openai_key):
139
  start = time.time()
140
+ print(f"Get_nutrition_advice called with nutrition_data: {nutrition_data}, key: {openai_key[:5]}... (partial)")
141
  if not openai_key:
142
  print("Warning: No OpenAI API key provided—skipping advice.")
143
  return "No OpenAI key provided—skipping advice."
 
151
  [f"- {food}: {data['calories']} cal, {data['protein']}g protein, {data['fat']}g fat, {data['carbs']}g carbs"
152
  for food, data in nutrition_data.items()]
153
  )
154
+ print(f"OpenAI prompt: {prompt}")
155
  response = openai.Completion.create(
156
  model="text-davinci-003",
157
  prompt=prompt,
 
169
  # AutoGen Agent Definitions
170
  food_recognizer = AssistantAgent(
171
  name="FoodRecognizer",
172
+ system_message="Identify all food items in the image and return a list of (label, probability) pairs. Parse the message for the image data (e.g., 'Process this image: <numpy_array>') and call recognize_foods with it.",
173
  function_map={"recognize_foods": recognize_foods}
174
  )
175
 
176
  size_estimator = AssistantAgent(
177
  name="SizeEstimator",
178
+ system_message="Estimate portion sizes in grams for each recognized food based on the image. Parse the previous message for the list of foods (e.g., '[(\"food1\", 0.85), ...]') and call estimate_sizes with the image and foods from the message history.",
179
  function_map={"estimate_sizes": estimate_sizes}
180
  )
181
 
182
  nutrition_fetcher = AssistantAgent(
183
  name="NutritionFetcher",
184
+ system_message="Fetch nutritional data from the Nutritionix API using the user's key. Parse the previous message for the foods and sizes dictionary (e.g., {'food1': 150, ...}) and the initial message for the Nutritionix key (e.g., 'with Nutritionix key: <key>'), then call fetch_nutrition.",
185
  function_map={"fetch_nutrition": fetch_nutrition}
186
  )
187
 
188
  advice_agent = AssistantAgent(
189
  name="NutritionAdvisor",
190
+ system_message="Provide basic nutrition advice based on the food data using the user's OpenAI key. Parse the previous message for the nutrition data (e.g., {'food1': {'calories': 200, ...}}) and the initial message for the OpenAI key (e.g., 'with OpenAI key: <key>'), then call get_nutrition_advice.",
191
  function_map={"get_nutrition_advice": get_nutrition_advice}
192
  )
193
 
194
  orchestrator = AssistantAgent(
195
  name="Orchestrator",
196
+ system_message="Coordinate the workflow, format the output, and return the final result as text. Parse the initial message for the image, Nutritionix key, and OpenAI key. Start by asking FoodRecognizer to process the image, then SizeEstimator, then NutritionFetcher, then NutritionAdvisor (if OpenAI key provided), and finally format the results into 'Food Analysis:\\n- food1 (size1g, prob1% confidence): calories1 cal, protein1g protein, fat1g fat, carbs1g carbs\\n...' for each food, followed by '\\nNutrition Advice:\\n' and the advice if available.",
197
  function_map={}
198
  )
199
 
 
219
  # Orchestrator Logic (via AutoGen chat)
220
  def orchestrate_workflow(image, nutritionix_key, openai_key=None):
221
  start = time.time()
222
+ print(f"Orchestrate_workflow called with image shape: {image.shape if image is not None else 'None'}, "
223
+ f"Nutritionix key: {nutritionix_key[:5]}..., OpenAI key: {openai_key[:5]}... (partial)")
224
 
225
  # Initiate chat with Orchestrator, passing image and keys as message
226
  message = f"Process this image: {image} with Nutritionix key: {nutritionix_key}"
227
  if openai_key:
228
  message += f" and OpenAI key: {openai_key}"
229
+ print(f"Starting chat with message: {message[:100]}...") # Truncate for readability
230
  response = manager.initiate_chat(
231
  orchestrator,
232
  message=message,
 
238
  # Get the last message from chat history
239
  last_message = response.chat_history[-1]
240
  result = last_message.get("content", "No output from agents.")
241
+ print(f"Chat history last message: {result}")
242
  else:
243
  result = "No output from agents."
244
+ print("Warning: No chat history in response.")
245
 
246
  if isinstance(result, dict):
247
  result = result.get("text", "No text output from agents.")
248
+ print(f"Result is dict, extracted text: {result}")
249
 
250
  # Split result into nutrition and advice if OpenAI key was provided
251
  if openai_key and isinstance(result, str) and "\nNutrition Advice:\n" in result:
 
256
  nutrition = result if result != "No output from agents." else "No nutrition data."
257
  advice = "No advice available (OpenAI key required)."
258
 
259
+ print(f"Total time: {time.time() - start:.2f}s, Nutrition: {nutrition[:50]}..., Advice: {advice[:50]}...")
260
  return nutrition, advice
261
 
262
  # Gradio Interface