RajatMalviya commited on
Commit
7c29aab
·
verified ·
1 Parent(s): c6b4893

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +59 -249
app.py CHANGED
@@ -1,178 +1,14 @@
1
- import os
2
- import uvicorn
3
  from fastapi import FastAPI, File, UploadFile
4
- from fastapi.middleware.cors import CORSMiddleware
5
  import google.generativeai as genai
6
- from ultralytics import YOLO
7
  from PIL import Image
8
  import io
9
  import json
10
- import re
11
- import pickle
12
- import time
13
- from datetime import datetime
14
-
15
- # Initialize FastAPI app
16
- app = FastAPI(title="Food Nutrition API",
17
- description="API for detecting food items and retrieving nutritional information")
18
-
19
- # Add CORS middleware
20
- app.add_middleware(
21
- CORSMiddleware,
22
- allow_origins=["*"], # Allows all origins, modify for production
23
- allow_credentials=True,
24
- allow_methods=["*"], # Allows all methods
25
- allow_headers=["*"], # Allows all headers
26
- )
27
-
28
- # Load YOLOv8 model
29
- model = YOLO("models/best.pt")
30
-
31
-
32
-
33
- # Set up Google Gemini API - Get from environment variable
34
- GOOGLE_API_KEY = "AIzaSyD_VdzzRsWhM7_SrYTwgi0VT2cZJ6tWETE"
35
- genai.configure(api_key=GOOGLE_API_KEY)
36
- gemini_model = genai.GenerativeModel("gemini-1.5-pro")
37
-
38
- # # Cache configuration
39
- # CACHE_DIR = os.getenv("CACHE_DIR", ".") # Get cache directory from environment or use current directory
40
- # CACHE_FILE = os.path.join(CACHE_DIR, "food_nutrition_cache.pkl")
41
- # CACHE_EXPIRY_DAYS = int(os.getenv("CACHE_EXPIRY_DAYS", "30")) # Cache entries expire after 30 days by default
42
-
43
- CACHE_DIR = os.getenv("CACHE_DIR", "") # Use /tmp for temporary storage
44
- CACHE_FILE = os.path.join(CACHE_DIR, "food_nutrition_cache.pkl")
45
- CACHE_EXPIRY_DAYS = int(os.getenv("CACHE_EXPIRY_DAYS", "30")) # Default to 30 days
46
-
47
- def load_cache():
48
- """Load the nutrition cache from disk."""
49
- if os.path.exists(CACHE_FILE):
50
- try:
51
- with open(CACHE_FILE, 'rb') as f:
52
- return pickle.load(f)
53
- except (pickle.PickleError, EOFError):
54
- print("Cache file corrupted, creating new cache")
55
- return {}
56
-
57
-
58
- def save_cache(cache):
59
- """Save the nutrition cache to disk."""
60
- try:
61
- # Ensure cache directory exists
62
- os.makedirs(os.path.dirname(CACHE_FILE), exist_ok=True)
63
- with open(CACHE_FILE, 'wb') as f:
64
- pickle.dump(cache, f)
65
- except Exception as e:
66
- print(f"Error saving cache: {e}")
67
-
68
-
69
- # Initialize the cache
70
- nutrition_cache = load_cache()
71
-
72
-
73
- def clean_expired_cache_entries():
74
- """Remove expired entries from the cache."""
75
- current_time = time.time()
76
- expiry_seconds = CACHE_EXPIRY_DAYS * 24 * 60 * 60
77
- expired_keys = []
78
-
79
- for food_item, entry in nutrition_cache.items():
80
- if current_time - entry['timestamp'] > expiry_seconds:
81
- expired_keys.append(food_item)
82
-
83
- for key in expired_keys:
84
- del nutrition_cache[key]
85
-
86
- if expired_keys:
87
- save_cache(nutrition_cache)
88
- print(f"Removed {len(expired_keys)} expired cache entries")
89
-
90
-
91
- def get_food_info(food_item):
92
- """Fetch nutritional information with caching."""
93
- # Check if the food item is in cache and not expired
94
- if food_item in nutrition_cache:
95
- print(f"Cache hit for {food_item}")
96
- return nutrition_cache[food_item]['data']
97
-
98
- print(f"Cache miss for {food_item}, fetching from API")
99
- # If not in cache, fetch from Gemini API
100
- prompt = f"""
101
- Provide the nutritional information for "{food_item}" in JSON format with the following structure:
102
-
103
- {{
104
- "food_item": "{food_item}",
105
- "nutritional_info": {{
106
- "calories_kcal": value,
107
- "protein_g": value,
108
- "fiber_g": value,
109
- "vitamins": {{
110
- "Vitamin A_mcg": value,
111
- "Vitamin C_mg": value,
112
- "Vitamin D_mcg": value,
113
- "Vitamin E_mg": value,
114
- "Vitamin K_mcg": value,
115
- "Vitamin B1_mg": value,
116
- "Vitamin B2_mg": value,
117
- "Vitamin B3_mg": value,
118
- "Vitamin B6_mg": value,
119
- "Vitamin B12_mcg": value,
120
- "Folate_mcg": value
121
- }}
122
- }},
123
- "health_benefits": [
124
- "Brief description of health benefit 1",
125
- "Brief description of health benefit 2"
126
- ]
127
- }}
128
-
129
- Ensure the response is **valid JSON** inside triple backticks (```json ... ```).
130
- """
131
 
132
- try:
133
- response = gemini_model.generate_content(prompt)
134
- result = response.text if response else "No data found"
135
-
136
- # Store in cache with timestamp
137
- nutrition_cache[food_item] = {
138
- 'data': result,
139
- 'timestamp': time.time()
140
- }
141
-
142
- # Save updated cache
143
- save_cache(nutrition_cache)
144
-
145
- return result
146
- except Exception as e:
147
- print(f"Error fetching from Gemini API: {e}")
148
- return json.dumps({"error": f"Failed to retrieve data: {str(e)}"})
149
-
150
-
151
- def parse_nutrition_response(response_dict):
152
- """Parses the raw response containing JSON-like data embedded in a string format."""
153
- parsed_data = {}
154
-
155
- for food, raw_json in response_dict.items():
156
- # Extract JSON part from the response using regex
157
- json_match = re.search(r"```json\n(.*?)\n```", raw_json, re.DOTALL)
158
-
159
- if json_match:
160
- json_data = json_match.group(1) # Extract JSON content
161
- try:
162
- parsed_data[food] = json.loads(json_data) # Convert JSON string to dictionary
163
- except json.JSONDecodeError:
164
- parsed_data[food] = {"error": "Invalid JSON format"}
165
- else:
166
- parsed_data[food] = {"error": "JSON not found in response"}
167
 
168
- return parsed_data
169
-
170
-
171
- def detect_food(image: Image.Image):
172
- """Detect food items in an image using YOLOv8."""
173
- results = model(image)
174
- detected_foods = {model.names[int(box.cls)] for result in results for box in result.boxes}
175
- return list(detected_foods)
176
 
177
  @app.get("/")
178
  def read_root():
@@ -183,92 +19,66 @@ async def root_head():
183
  return {} # Empty response for HEAD requests
184
 
185
  @app.post("/analyze")
186
- async def analyze_image(file: UploadFile = File(...)):
187
- """API endpoint to analyze food from an image and return nutritional info."""
188
- # Periodically clean expired cache entries
189
- clean_expired_cache_entries()
190
-
191
- try:
192
- # Process the image
193
- contents = await file.read()
194
- image = Image.open(io.BytesIO(contents))
195
- detected_foods = detect_food(image)
196
-
197
- if not detected_foods:
198
- return {"message": "No food detected"}
199
-
200
- # Get nutrition info (from cache if available)
201
- raw_nutrition_info = {food: get_food_info(food) for food in detected_foods}
202
- parsed_nutrition_info = parse_nutrition_response(raw_nutrition_info)
203
-
204
- # Add cache statistics to the response
205
- cache_stats = {
206
- "cache_size": len(nutrition_cache),
207
- "cache_hits": sum(1 for food in detected_foods if food in nutrition_cache),
208
- "cache_misses": sum(1 for food in detected_foods if food not in nutrition_cache),
209
- "last_cache_update": datetime.fromtimestamp(
210
- max([entry['timestamp'] for entry in nutrition_cache.values()])
211
- if nutrition_cache else time.time()
212
- ).isoformat()
 
 
 
 
 
 
 
 
 
 
 
 
 
213
  }
214
-
215
- return {
216
- "detected_foods": detected_foods,
217
- "nutrition_info": parsed_nutrition_info,
218
- "cache_stats": cache_stats
219
- }
220
- except Exception as e:
221
- return {"error": f"An error occurred: {str(e)}"}
222
-
223
-
224
- # Add endpoints to manage the cache
225
- @app.get("/cache/stats")
226
- async def get_cache_stats():
227
- """Get statistics about the nutrition cache."""
228
- if not nutrition_cache:
229
- return {"message": "Cache is empty"}
230
-
231
- return {
232
- "cache_size": len(nutrition_cache),
233
- "foods_cached": list(nutrition_cache.keys()),
234
- "oldest_entry": datetime.fromtimestamp(
235
- min([entry['timestamp'] for entry in nutrition_cache.values()])
236
- ).isoformat(),
237
- "newest_entry": datetime.fromtimestamp(
238
- max([entry['timestamp'] for entry in nutrition_cache.values()])
239
- ).isoformat()
240
  }
 
241
 
 
 
 
 
 
242
 
243
- @app.delete("/cache/clear")
244
- async def clear_cache():
245
- """Clear the nutrition cache."""
246
- global nutrition_cache
247
- nutrition_cache = {}
248
- save_cache(nutrition_cache)
249
- return {"message": "Cache cleared successfully"}
250
-
251
-
252
- @app.delete("/cache/item/{food_item}")
253
- async def delete_cache_item(food_item: str):
254
- """Delete a specific food item from the cache."""
255
- if food_item in nutrition_cache:
256
- del nutrition_cache[food_item]
257
- save_cache(nutrition_cache)
258
- return {"message": f"Removed {food_item} from cache"}
259
- return {"message": f"{food_item} not found in cache"}
260
-
261
 
262
- # Add health check endpoint for production deployments
263
- @app.get("/health")
264
- async def health_check():
265
- """Health check endpoint for monitoring."""
266
- return {
267
- "status": "healthy",
268
- "cache_size": len(nutrition_cache),
269
- "yolo_model_loaded": model is not None,
270
- "gemini_api_configured": GOOGLE_API_KEY != ""
271
- }
272
 
273
  import os
274
 
 
 
 
1
  from fastapi import FastAPI, File, UploadFile
 
2
  import google.generativeai as genai
 
3
  from PIL import Image
4
  import io
5
  import json
6
+ from google.colab import userdata
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7
 
8
+ app = FastAPI()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
+ # Set your API key
11
+ genai.configure(api_key=userdata.get('GOOGLE_API_KEY'))
 
 
 
 
 
 
12
 
13
  @app.get("/")
14
  def read_root():
 
19
  return {} # Empty response for HEAD requests
20
 
21
  @app.post("/analyze")
22
+ async def analyze_food_image(file: UploadFile = File(...)):
23
+ """Analyzes food items in the image using Gemini 1.5 Flash"""
24
+ image_bytes = await file.read()
25
+ img = Image.open(io.BytesIO(image_bytes))
26
+
27
+ model = genai.GenerativeModel("gemini-1.5-flash") # Best for real-time image processing
28
+
29
+ # Updated prompt for structured JSON response
30
+ prompt = """
31
+ Identify all food items in the given image and determine their approximate quantity. Then, provide nutritional information
32
+ in valid JSON format following this structure:
33
+
34
+ ```json
35
+ {
36
+ "detected_food_items": [
37
+ {
38
+ "food_item": "Detected food name",
39
+ "quantity": "Approximate quantity (e.g., 1 bowl, 2 slices, half a chapati)",
40
+ "nutritional_info": {
41
+ "calories_kcal": value,
42
+ "protein_g": value,
43
+ "fiber_g": value,
44
+ "vitamins": {
45
+ "Vitamin A_mcg": value,
46
+ "Vitamin C_mg": value,
47
+ "Vitamin D_mcg": value,
48
+ "Vitamin E_mg": value,
49
+ "Vitamin K_mcg": value,
50
+ "Vitamin B1_mg": value,
51
+ "Vitamin B2_mg": value,
52
+ "Vitamin B3_mg": value,
53
+ "Vitamin B6_mg": value,
54
+ "Vitamin B12_mcg": value,
55
+ "Folate_mcg": value
56
+ }
57
+ },
58
+ "health_benefits": [
59
+ "Brief description of health benefit 1",
60
+ "Brief description of health benefit 2"
61
+ ]
62
  }
63
+ ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
64
  }
65
+ ```
66
 
67
+ - **Ensure the response is valid JSON** inside triple backticks (```json ... ```).
68
+ - **Include accurate nutritional values based on the given quantity.**
69
+ - **If exact values are unavailable, provide estimated values.**
70
+ - **Ensure proper formatting and completeness of data.**
71
+ """
72
 
73
+ # Get response from API
74
+ response = model.generate_content([prompt, img])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
75
 
76
+ # Extract JSON response
77
+ try:
78
+ json_response = response.text.strip("```json").strip("```") # Remove markdown formatting
79
+ return json.loads(json_response) # Convert to Python dictionary
80
+ except json.JSONDecodeError:
81
+ return {"error": "Invalid response format from AI model"}
 
 
 
 
82
 
83
  import os
84