Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -1,45 +1,27 @@
|
|
1 |
-
import os
|
2 |
import gradio as gr
|
3 |
-
from fastapi import FastAPI, UploadFile, File
|
4 |
-
import io
|
5 |
-
from PIL import Image
|
6 |
-
import json
|
7 |
-
import pandas as pd
|
8 |
import numpy as np
|
|
|
9 |
import matplotlib.pyplot as plt
|
10 |
import torch
|
11 |
from transformers import AutoImageProcessor, AutoModelForImageClassification
|
|
|
12 |
from difflib import get_close_matches
|
13 |
-
from
|
14 |
-
import
|
15 |
-
|
16 |
-
|
17 |
-
try:
|
18 |
-
from nltk.stem import PorterStemmer # Or LancasterStemmer
|
19 |
-
stemmer = PorterStemmer()
|
20 |
-
nltk.data.find("corpora/wordnet")
|
21 |
-
from nltk.stem import WordNetLemmatizer
|
22 |
-
lemmatizer = WordNetLemmatizer()
|
23 |
-
except LookupError:
|
24 |
-
import nltk
|
25 |
-
nltk.download('wordnet')
|
26 |
-
nltk.download('omw-1.4')
|
27 |
-
from nltk.stem import WordNetLemmatizer
|
28 |
-
|
29 |
-
lemmatizer = WordNetLemmatizer()
|
30 |
-
|
31 |
-
from nltk.stem import PorterStemmer # Or LancasterStemmer
|
32 |
-
stemmer = PorterStemmer()
|
33 |
-
|
34 |
-
|
35 |
|
36 |
# -------------------------------------------------
|
37 |
# Configuration
|
38 |
# -------------------------------------------------
|
|
|
|
|
39 |
INSULIN_TYPES = {
|
40 |
-
"Rapid-Acting": {"onset": 0.25, "duration": 4, "peak_time": 1.0},
|
41 |
"Long-Acting": {"onset": 2, "duration": 24, "peak_time": 8},
|
42 |
}
|
|
|
|
|
43 |
DEFAULT_BASAL_RATES = {
|
44 |
"00:00-06:00": 0.8,
|
45 |
"06:00-12:00": 1.0,
|
@@ -50,6 +32,7 @@ DEFAULT_BASAL_RATES = {
|
|
50 |
# -------------------------------------------------
|
51 |
# Load Food Data from Hugging Face Dataset
|
52 |
# -------------------------------------------------
|
|
|
53 |
def load_food_data(dataset_name="Anupam007/Diabetic"):
|
54 |
try:
|
55 |
dataset = load_dataset(dataset_name)
|
@@ -61,6 +44,13 @@ def load_food_data(dataset_name="Anupam007/Diabetic"):
|
|
61 |
# Remove unnamed columns
|
62 |
food_data = food_data.loc[:, ~food_data.columns.str.contains('^unnamed')]
|
63 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
64 |
# Normalize food_name column to lowercase: Crucial for matching
|
65 |
if 'food_name' in food_data.columns:
|
66 |
food_data['food_name'] = food_data['food_name'].str.lower()
|
@@ -76,6 +66,8 @@ def load_food_data(dataset_name="Anupam007/Diabetic"):
|
|
76 |
'serving_amount': [29],
|
77 |
'serving_unit': ['g'],
|
78 |
'carbohydrate_grams': [15],
|
|
|
|
|
79 |
'notes': ['default']
|
80 |
})
|
81 |
|
@@ -91,11 +83,13 @@ def load_food_data(dataset_name="Anupam007/Diabetic"):
|
|
91 |
food_data = pd.DataFrame({
|
92 |
'food_category': ['starch'],
|
93 |
'food_subcategory': ['bread'],
|
94 |
-
'food_name': ['white bread'],
|
95 |
'serving_description': ['servingsize'],
|
96 |
'serving_amount': [29],
|
97 |
'serving_unit': ['g'],
|
98 |
'carbohydrate_grams': [15],
|
|
|
|
|
99 |
'notes': ['default']
|
100 |
})
|
101 |
return food_data
|
@@ -103,13 +97,14 @@ def load_food_data(dataset_name="Anupam007/Diabetic"):
|
|
103 |
# -------------------------------------------------
|
104 |
# Load Food Classification Model
|
105 |
# -------------------------------------------------
|
106 |
-
|
107 |
try:
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
|
|
|
|
113 |
model_loaded = True #Flag for error handling in other defs
|
114 |
except Exception as e:
|
115 |
print(f"Model Load Error: {e}") # include e in print statement
|
@@ -118,16 +113,8 @@ except Exception as e:
|
|
118 |
model = None
|
119 |
|
120 |
def classify_food(image):
|
121 |
-
|
122 |
-
|
123 |
-
print(f"Processed image keys: {inputs.keys()}") # Print the keys of the inputs dictionary
|
124 |
-
if 'pixel_values' in inputs:
|
125 |
-
print(f"Pixel values shape: {inputs['pixel_values'].shape}")
|
126 |
-
print(f"Pixel values type: {inputs['pixel_values'].dtype}")
|
127 |
-
print(f"First few pixel values: {inputs['pixel_values'][0, :5]}") # Print a small slice
|
128 |
-
else:
|
129 |
-
print("Pixel values not found in inputs!")
|
130 |
-
|
131 |
try:
|
132 |
if not model_loaded:
|
133 |
print("Model not loaded, returning 'Unknown'")
|
@@ -158,214 +145,73 @@ def classify_food(image):
|
|
158 |
return "Unknown" # If an exception arises make sure to create a default case
|
159 |
|
160 |
# -------------------------------------------------
|
161 |
-
#
|
162 |
# -------------------------------------------------
|
163 |
|
164 |
-
def
|
165 |
-
"""
|
166 |
-
|
167 |
-
|
168 |
-
def stem_food_name(food_name):
|
169 |
-
"""Stem the food name to its root form."""
|
170 |
-
return stemmer.stem(food_name)
|
171 |
-
|
172 |
-
def get_food_nutrition(food_name: str, food_data, portion_size: float = 1.0) -> dict:
|
173 |
-
"""
|
174 |
-
Retrieves nutrition information for a given food name from a local database,
|
175 |
-
with enhanced matching including stemming, lemmatization, and synonym lookup.
|
176 |
-
"""
|
177 |
-
food_name_lower = food_name.lower()
|
178 |
-
food_names = food_data['food_name'].str.lower().tolist()
|
179 |
-
|
180 |
-
print(f"Searching for: {food_name_lower}")
|
181 |
-
|
182 |
-
# 1. Exact Match
|
183 |
-
if food_name_lower in food_names:
|
184 |
-
print("Exact match found.")
|
185 |
-
return _extract_nutrition_data(food_data, food_name_lower, portion_size)
|
186 |
-
|
187 |
-
# 2. Close Matches
|
188 |
-
close_matches = get_close_matches(food_name_lower, food_names, n=1, cutoff=0.6)
|
189 |
-
if close_matches:
|
190 |
-
print(f"Close match found: {close_matches[0]}")
|
191 |
-
return _extract_nutrition_data(food_data, close_matches[0], portion_size)
|
192 |
-
|
193 |
-
# 3. Stemming and Lemmatization
|
194 |
-
stemmed_name = stem_food_name(food_name_lower)
|
195 |
-
lemmatized_name = lemmatize_food_name(food_name_lower)
|
196 |
-
|
197 |
-
stemmed_matches = get_close_matches(stemmed_name, [stem_food_name(name) for name in food_names], n=1, cutoff=0.6)
|
198 |
-
lemmatized_matches = get_close_matches(lemmatized_name, [lemmatize_food_name(name) for name in food_names], n=1, cutoff=0.6)
|
199 |
-
|
200 |
-
if stemmed_matches:
|
201 |
-
original_name = food_data['food_name'].iloc[[food_names.index(stemmed_matches[0])]].values[0]
|
202 |
-
print(f"Stemmed match found: {stemmed_matches[0]} -> {original_name}")
|
203 |
-
return _extract_nutrition_data(food_data, original_name, portion_size)
|
204 |
-
|
205 |
-
if lemmatized_matches:
|
206 |
-
original_name = food_data['food_name'].iloc[[food_names.index(lemmatized_matches[0])]].values[0]
|
207 |
-
print(f"Lemmatized match found: {lemmatized_matches[0]} -> {original_name}")
|
208 |
-
return _extract_nutrition_data(food_data, original_name, portion_size)
|
209 |
-
|
210 |
-
# 4. Synonym Lookup (if 'synonyms' column exists)
|
211 |
-
if 'synonyms' in food_data.columns:
|
212 |
-
for index, row in food_data.iterrows():
|
213 |
-
synonyms = row.get('synonyms', '')
|
214 |
-
if synonyms:
|
215 |
-
synonym_list = [s.strip().lower() for s in synonyms.split(',')]
|
216 |
-
if food_name_lower in synonym_list:
|
217 |
-
print(f"Synonym match found: {food_name_lower} in synonyms for {row['food_name']}")
|
218 |
-
return _extract_nutrition_data(food_data, row['food_name'].lower(), portion_size)
|
219 |
-
|
220 |
-
print(f"No nutrition information found for {food_name} using any matching method.")
|
221 |
-
return None
|
222 |
-
|
223 |
-
def _extract_nutrition_data(food_data: pd.DataFrame, matched_food_name: str, portion_size: float) -> dict:
|
224 |
-
"""Extracts nutrition data from a matched food item."""
|
225 |
-
matched_row = food_data[food_data['food_name'] == matched_food_name].iloc[0]
|
226 |
-
|
227 |
-
carb_col = 'carbohydrate_grams'
|
228 |
-
amount_col = 'serving_amount'
|
229 |
-
unit_col = 'serving_unit'
|
230 |
-
fiber_col = 'fiber_grams'
|
231 |
-
sugar_col = 'sugar_grams' # Add Sugar
|
232 |
-
|
233 |
-
base_carbs = matched_row.get(carb_col, 0.0)
|
234 |
-
base_fiber = matched_row.get(fiber_col, 0.0) # Get Fiber
|
235 |
-
base_sugar = matched_row.get(sugar_col, 0.0) # Get Sugar
|
236 |
-
|
237 |
try:
|
238 |
-
|
239 |
-
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
266 |
-
|
267 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
268 |
|
269 |
-
# -------------------------------------------------
|
270 |
-
# Insulin and Glucose Calculations
|
271 |
-
# -------------------------------------------------
|
272 |
-
def get_basal_rate(current_time_hour, basal_rates):
|
273 |
-
"""Gets the appropriate basal rate for a given time of day."""
|
274 |
-
for interval, rate in basal_rates.items():
|
275 |
-
try: # add a try and except to handle values in intervals that do not have the format "start-end"
|
276 |
-
parts = interval.split(":")[0].split("-")
|
277 |
-
if len(parts) == 2: # Check if there are two parts (start and end)
|
278 |
-
start_hour, end_hour = map(int, parts)
|
279 |
-
if start_hour <= current_time_hour < end_hour or (start_hour <= current_time_hour and end_hour == 24):
|
280 |
-
return rate
|
281 |
-
except Exception as e: # include exception in exception handling
|
282 |
-
print(f"Warning: Invalid interval format: {interval}. Skipping. Error: {e}") #Inform user of formatting issues
|
283 |
-
|
284 |
-
return 0 # Default if no matching interval
|
285 |
-
|
286 |
-
def insulin_activity(t, insulin_type, bolus_dose, bolus_duration=0):
|
287 |
-
"""Models insulin activity over time."""
|
288 |
-
insulin_data = INSULIN_TYPES.get(insulin_type)
|
289 |
-
if not insulin_data:
|
290 |
-
return 0 # Or raise an error
|
291 |
-
|
292 |
-
# Simple exponential decay model (replace with a more sophisticated model)
|
293 |
-
peak_time = insulin_data['peak_time'] # Time in hours at which insulin activity is at max level
|
294 |
-
duration = insulin_data['duration'] # Total time for which insulin stays in effect
|
295 |
-
if t < peak_time:
|
296 |
-
activity = (bolus_dose * t / peak_time) * np.exp(1- t/peak_time) # rising activity
|
297 |
-
elif t < duration:
|
298 |
-
activity = bolus_dose * np.exp((peak_time - t) / (duration - peak_time)) # decaying activity
|
299 |
-
else:
|
300 |
-
activity = 0
|
301 |
-
|
302 |
-
if bolus_duration > 0: # Extended Bolus
|
303 |
-
if 0 <= t <= bolus_duration:
|
304 |
-
# Linear release of insulin over bolus_duration
|
305 |
-
effective_dose = bolus_dose / bolus_duration
|
306 |
-
duration = INSULIN_TYPES.get(insulin_type)['duration']
|
307 |
-
if t < duration:
|
308 |
-
activity = effective_dose
|
309 |
-
else:
|
310 |
-
activity = 0
|
311 |
-
else:
|
312 |
-
activity = 0
|
313 |
-
|
314 |
-
return activity
|
315 |
-
|
316 |
-
def calculate_active_insulin(insulin_history, current_time):
|
317 |
-
"""Calculates remaining active insulin from previous doses."""
|
318 |
-
active_insulin = 0
|
319 |
-
for dose_time, dose_amount, insulin_type, bolus_duration in insulin_history:
|
320 |
-
elapsed_time = current_time - dose_time
|
321 |
-
remaining_activity = insulin_activity(elapsed_time, insulin_type, dose_amount, bolus_duration)
|
322 |
-
active_insulin += remaining_activity
|
323 |
-
return active_insulin
|
324 |
-
|
325 |
-
def calculate_insulin_needs(carbs, glucose_current, glucose_target, tdd, weight, insulin_type="Rapid-Acting", override_correction_dose = None):
|
326 |
-
"""Calculate insulin needs for Type 1 diabetes"""
|
327 |
-
if tdd <= 0:
|
328 |
-
return {
|
329 |
-
'error': 'Total Daily Dose (TDD) must be greater than 0'
|
330 |
-
}
|
331 |
-
insulin_data = INSULIN_TYPES.get(insulin_type)
|
332 |
-
if not insulin_data:
|
333 |
-
return {
|
334 |
-
'error': "Invalid insulin type. Choose from" + ", ".join(INSULIN_TYPES.keys())
|
335 |
-
}
|
336 |
-
|
337 |
-
# Refined calculations
|
338 |
-
icr = (450 if weight <= 45 else 500) / tdd
|
339 |
-
isf = 1700 / tdd
|
340 |
-
|
341 |
-
# Calculate correction dose
|
342 |
-
glucose_difference = glucose_current - glucose_target
|
343 |
-
correction_dose = glucose_difference / isf
|
344 |
-
|
345 |
-
if override_correction_dose is not None: # Check for None
|
346 |
-
correction_dose = override_correction_dose
|
347 |
-
|
348 |
-
# Calculate carb dose
|
349 |
-
carb_dose = carbs / icr
|
350 |
-
|
351 |
-
# Calculate total bolus
|
352 |
-
total_bolus = max(0, carb_dose + correction_dose)
|
353 |
-
|
354 |
-
# Calculate basal
|
355 |
-
basal_dose = weight * 0.5
|
356 |
-
|
357 |
-
return {
|
358 |
-
'icr': round(icr, 2),
|
359 |
-
'isf': round(isf, 2),
|
360 |
-
'correction_dose': round(correction_dose, 2),
|
361 |
-
'carb_dose': round(carb_dose, 2),
|
362 |
-
'total_bolus': round(total_bolus, 2),
|
363 |
-
'basal_dose': round(basal_dose, 2),
|
364 |
-
'insulin_type': insulin_type,
|
365 |
-
'insulin_onset': insulin_data['onset'],
|
366 |
-
'insulin_duration': insulin_data['duration'],
|
367 |
-
'peak_time': insulin_data['peak_time'],
|
368 |
-
}
|
369 |
|
370 |
def create_detailed_report(nutrition_info, insulin_info, current_basal_rate):
|
371 |
"""Create a detailed report of carbs and insulin calculations"""
|
@@ -380,12 +226,12 @@ def create_detailed_report(nutrition_info, insulin_info, current_basal_rate):
|
|
380 |
------------------------
|
381 |
Standard Serving Size: {nutrition_info['serving_size']}
|
382 |
Carbs per Serving: {nutrition_info['base_carbs']}g
|
|
|
383 |
Fiber per Serving: {nutrition_info['base_fiber']}g
|
|
|
384 |
Sugar per Serving: {nutrition_info['base_sugar']}g
|
|
|
385 |
Portion Multiplier: {nutrition_info['portion_multiplier']}x
|
386 |
-
Total Carbs: {nutrition_info['adjusted_carbs']}g
|
387 |
-
Total Fiber: {nutrition_info['adjusted_fiber']}g
|
388 |
-
Total Sugar: {nutrition_info['adjusted_sugar']}g
|
389 |
Notes: {nutrition_info['notes']}
|
390 |
"""
|
391 |
|
@@ -425,17 +271,22 @@ def diabetes_dashboard(initial_glucose, food_image, stress_level, sleep_hours, t
|
|
425 |
food_name = classify_food(food_image) # This line is now inside the function
|
426 |
print(f"Classified food name: {food_name}") # Debugging: What is classified as? # Corrected indentation
|
427 |
nutrition_info = get_food_nutrition(food_name, food_data, portion_size) # Changed to pass in data
|
428 |
-
|
429 |
if not nutrition_info:
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
439 |
|
440 |
# 2. Insulin Calculations
|
441 |
try:
|
@@ -455,7 +306,7 @@ def diabetes_dashboard(initial_glucose, food_image, stress_level, sleep_hours, t
|
|
455 |
)
|
456 |
|
457 |
if 'error' in insulin_info:
|
458 |
-
return insulin_info['error'], None, None, None, None
|
459 |
|
460 |
# 3. Create detailed reports
|
461 |
current_time_for_basal = 12 #Arbitrary number to pull from Basal Rates Dict
|
@@ -516,13 +367,11 @@ def diabetes_dashboard(initial_glucose, food_image, stress_level, sleep_hours, t
|
|
516 |
insulin_details,
|
517 |
insulin_info['basal_dose'],
|
518 |
insulin_info['total_bolus'],
|
519 |
-
fig
|
520 |
-
nutrition_info['base_fiber'], # Base Fiber Output
|
521 |
-
nutrition_info['base_sugar'] # Base Sugar Output
|
522 |
)
|
523 |
|
524 |
except Exception as e:
|
525 |
-
return f"Error: {str(e)}", None, None, None, None
|
526 |
|
527 |
# -------------------------------------------------
|
528 |
# Gradio Interface Setup
|
@@ -570,8 +419,6 @@ with gr.Blocks() as app: # using Blocks API to manually design the layout
|
|
570 |
basal_dose_output = gr.Number(label="Basal Insulin Dose (units/day)")
|
571 |
bolus_dose_output = gr.Number(label="Bolus Insulin Dose (units)")
|
572 |
glucose_plot_output = gr.Plot(label="Glucose Prediction")
|
573 |
-
fiber_output = gr.Number(label="Fiber (g)")
|
574 |
-
sugar_output = gr.Number(label="Sugar (g)")
|
575 |
|
576 |
calculate_button.click(
|
577 |
fn=diabetes_dashboard,
|
@@ -597,9 +444,7 @@ with gr.Blocks() as app: # using Blocks API to manually design the layout
|
|
597 |
insulin_details_output,
|
598 |
basal_dose_output,
|
599 |
bolus_dose_output,
|
600 |
-
glucose_plot_output
|
601 |
-
fiber_output,
|
602 |
-
sugar_output
|
603 |
]
|
604 |
)
|
605 |
|
|
|
|
|
1 |
import gradio as gr
|
|
|
|
|
|
|
|
|
|
|
2 |
import numpy as np
|
3 |
+
import pandas as pd
|
4 |
import matplotlib.pyplot as plt
|
5 |
import torch
|
6 |
from transformers import AutoImageProcessor, AutoModelForImageClassification
|
7 |
+
from PIL import Image
|
8 |
from difflib import get_close_matches
|
9 |
+
from typing import Optional, Dict, Any
|
10 |
+
import json
|
11 |
+
import io
|
12 |
+
from datasets import load_dataset # Import the datasets library
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
13 |
|
14 |
# -------------------------------------------------
|
15 |
# Configuration
|
16 |
# -------------------------------------------------
|
17 |
+
|
18 |
+
# Define insulin types and their durations and peak times
|
19 |
INSULIN_TYPES = {
|
20 |
+
"Rapid-Acting": {"onset": 0.25, "duration": 4, "peak_time": 1.0}, # Onset in hours, duration in hours, peak time in hours
|
21 |
"Long-Acting": {"onset": 2, "duration": 24, "peak_time": 8},
|
22 |
}
|
23 |
+
|
24 |
+
#Define basal rates
|
25 |
DEFAULT_BASAL_RATES = {
|
26 |
"00:00-06:00": 0.8,
|
27 |
"06:00-12:00": 1.0,
|
|
|
32 |
# -------------------------------------------------
|
33 |
# Load Food Data from Hugging Face Dataset
|
34 |
# -------------------------------------------------
|
35 |
+
|
36 |
def load_food_data(dataset_name="Anupam007/Diabetic"):
|
37 |
try:
|
38 |
dataset = load_dataset(dataset_name)
|
|
|
44 |
# Remove unnamed columns
|
45 |
food_data = food_data.loc[:, ~food_data.columns.str.contains('^unnamed')]
|
46 |
|
47 |
+
# Check if "fiber_grams" and "sugar_grams" are not in the dataset
|
48 |
+
if "fiber_grams" not in food_data.columns:
|
49 |
+
food_data['fiber_grams'] = None
|
50 |
+
if "sugar_grams" not in food_data.columns:
|
51 |
+
food_data['sugar_grams'] = None
|
52 |
+
|
53 |
+
|
54 |
# Normalize food_name column to lowercase: Crucial for matching
|
55 |
if 'food_name' in food_data.columns:
|
56 |
food_data['food_name'] = food_data['food_name'].str.lower()
|
|
|
66 |
'serving_amount': [29],
|
67 |
'serving_unit': ['g'],
|
68 |
'carbohydrate_grams': [15],
|
69 |
+
'fiber_grams': [0], #added column
|
70 |
+
'sugar_grams': [0], #added column
|
71 |
'notes': ['default']
|
72 |
})
|
73 |
|
|
|
83 |
food_data = pd.DataFrame({
|
84 |
'food_category': ['starch'],
|
85 |
'food_subcategory': ['bread'],
|
86 |
+
'food_name': ['white bread'],
|
87 |
'serving_description': ['servingsize'],
|
88 |
'serving_amount': [29],
|
89 |
'serving_unit': ['g'],
|
90 |
'carbohydrate_grams': [15],
|
91 |
+
'fiber_grams': [0], #added column
|
92 |
+
'sugar_grams': [0], #added column
|
93 |
'notes': ['default']
|
94 |
})
|
95 |
return food_data
|
|
|
97 |
# -------------------------------------------------
|
98 |
# Load Food Classification Model
|
99 |
# -------------------------------------------------
|
|
|
100 |
try:
|
101 |
+
processor = AutoImageProcessor.from_pretrained("google/vit-base-patch16-224")
|
102 |
+
model = AutoModelForImageClassification.from_pretrained(
|
103 |
+
"google/vit-base-patch16-224",
|
104 |
+
torch_dtype=torch.float16, # Use float16 for efficiency
|
105 |
+
device_map="cpu", # Run on CPU
|
106 |
+
low_cpu_mem_usage=True # Optimize memory usage
|
107 |
+
)
|
108 |
model_loaded = True #Flag for error handling in other defs
|
109 |
except Exception as e:
|
110 |
print(f"Model Load Error: {e}") # include e in print statement
|
|
|
113 |
model = None
|
114 |
|
115 |
def classify_food(image):
|
116 |
+
"""Classify food image using the pre-trained model"""
|
117 |
+
print("classify_food function called") # Check if this function is even called
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
118 |
try:
|
119 |
if not model_loaded:
|
120 |
print("Model not loaded, returning 'Unknown'")
|
|
|
145 |
return "Unknown" # If an exception arises make sure to create a default case
|
146 |
|
147 |
# -------------------------------------------------
|
148 |
+
# USDA API Integration - REMOVED for local HF Spaces deployment
|
149 |
# -------------------------------------------------
|
150 |
|
151 |
+
def get_food_nutrition(food_name: str, food_data, portion_size: float = 1.0) -> Optional[Dict[str, Any]]:
|
152 |
+
"""Get carbohydrate content for the given food, and also sugar and fiber"""
|
153 |
+
print("get_food_nutrition function called") # Ensure the function is called
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
154 |
try:
|
155 |
+
# First try the local CSV database
|
156 |
+
food_name_lower = food_name.lower() # Ensure input is also lowercase
|
157 |
+
food_names = food_data['food_name'].str.lower().tolist()
|
158 |
+
|
159 |
+
print(f"Searching for: {food_name_lower}")
|
160 |
+
matches = get_close_matches(food_name_lower, food_names, n=1, cutoff=0.5)
|
161 |
+
|
162 |
+
print(f"Matches found: {matches}")
|
163 |
+
|
164 |
+
if matches:
|
165 |
+
matched_row = food_data[food_data['food_name'].str.lower() == matches[0]]
|
166 |
+
|
167 |
+
if not matched_row.empty:
|
168 |
+
row = matched_row.iloc[0]
|
169 |
+
|
170 |
+
print(f"Matched row from CSV: {row}")
|
171 |
+
|
172 |
+
carb_col = 'carbohydrate_grams'
|
173 |
+
amount_col = 'serving_amount'
|
174 |
+
unit_col = 'serving_unit'
|
175 |
+
fiber_col = 'fiber_grams' # Added Fiber column
|
176 |
+
sugar_col = 'sugar_grams' # Added Sugar column
|
177 |
+
|
178 |
+
base_carbs = row.get(carb_col, 0.0) if pd.notna(row.get(carb_col, None)) else 0.0
|
179 |
+
base_fiber = row.get(fiber_col, 0.0) if pd.notna(row.get(fiber_col, None)) else 0.0
|
180 |
+
base_sugar = row.get(sugar_col, 0.0) if pd.notna(row.get(sugar_col, None)) else 0.0
|
181 |
+
|
182 |
+
|
183 |
+
if amount_col not in row or unit_col not in row or pd.isna(row[amount_col]) or pd.isna(row[unit_col]):
|
184 |
+
serving_size = "Unknown"
|
185 |
+
print(f"Warning: '{amount_col}' or '{unit_col}' is missing in CSV")
|
186 |
+
else:
|
187 |
+
serving_size = f"{row[amount_col]} {row[unit_col]}"
|
188 |
+
|
189 |
+
adjusted_carbs = base_carbs * portion_size
|
190 |
+
adjusted_fiber = base_fiber * portion_size
|
191 |
+
adjusted_sugar = base_sugar * portion_size
|
192 |
+
|
193 |
+
return {
|
194 |
+
'matched_food': row['food_name'],
|
195 |
+
'category': row['food_category'] if 'food_category' in row and not pd.isna(row['food_category']) else 'Unknown',
|
196 |
+
'subcategory': row['food_subcategory'] if 'food_subcategory' in row and not pd.isna(row['food_subcategory']) else 'Unknown',
|
197 |
+
'base_carbs': base_carbs,
|
198 |
+
'adjusted_carbs': adjusted_carbs,
|
199 |
+
'base_fiber': base_fiber,
|
200 |
+
'adjusted_fiber': adjusted_fiber,
|
201 |
+
'base_sugar': base_sugar,
|
202 |
+
'adjusted_sugar': adjusted_sugar,
|
203 |
+
'serving_size': serving_size,
|
204 |
+
'portion_multiplier': portion_size,
|
205 |
+
'notes': row['notes'] if 'notes' in row and not pd.isna(row['notes']) else ''
|
206 |
+
}
|
207 |
+
|
208 |
+
print(f"No match found in CSV for {food_name}")
|
209 |
+
print(f"No nutrition information found for {food_name} in the local database.")
|
210 |
+
return None
|
211 |
+
except Exception as e:
|
212 |
+
print(f"Error in get_food_nutrition: {e}")
|
213 |
+
return None
|
214 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
215 |
|
216 |
def create_detailed_report(nutrition_info, insulin_info, current_basal_rate):
|
217 |
"""Create a detailed report of carbs and insulin calculations"""
|
|
|
226 |
------------------------
|
227 |
Standard Serving Size: {nutrition_info['serving_size']}
|
228 |
Carbs per Serving: {nutrition_info['base_carbs']}g
|
229 |
+
Adjusted Carbs: {nutrition_info['adjusted_carbs']}g
|
230 |
Fiber per Serving: {nutrition_info['base_fiber']}g
|
231 |
+
Adjusted Fiber: {nutrition_info['adjusted_fiber']}g
|
232 |
Sugar per Serving: {nutrition_info['base_sugar']}g
|
233 |
+
Adjusted Sugar: {nutrition_info['adjusted_sugar']}g
|
234 |
Portion Multiplier: {nutrition_info['portion_multiplier']}x
|
|
|
|
|
|
|
235 |
Notes: {nutrition_info['notes']}
|
236 |
"""
|
237 |
|
|
|
271 |
food_name = classify_food(food_image) # This line is now inside the function
|
272 |
print(f"Classified food name: {food_name}") # Debugging: What is classified as? # Corrected indentation
|
273 |
nutrition_info = get_food_nutrition(food_name, food_data, portion_size) # Changed to pass in data
|
|
|
274 |
if not nutrition_info:
|
275 |
+
# Try with generic categories if specific food not found
|
276 |
+
generic_terms = food_name.split()
|
277 |
+
for term in generic_terms:
|
278 |
+
nutrition_info = get_food_nutrition(term, food_data, portion_size) # Changed to pass in data
|
279 |
+
if nutrition_info:
|
280 |
+
break
|
281 |
+
|
282 |
+
if not nutrition_info:
|
283 |
+
return (
|
284 |
+
f"Could not find nutrition information for: {food_name} in the local database", # Removed USDA ref
|
285 |
+
"No insulin calculations available",
|
286 |
+
None,
|
287 |
+
None,
|
288 |
+
None
|
289 |
+
)
|
290 |
|
291 |
# 2. Insulin Calculations
|
292 |
try:
|
|
|
306 |
)
|
307 |
|
308 |
if 'error' in insulin_info:
|
309 |
+
return insulin_info['error'], None, None, None, None
|
310 |
|
311 |
# 3. Create detailed reports
|
312 |
current_time_for_basal = 12 #Arbitrary number to pull from Basal Rates Dict
|
|
|
367 |
insulin_details,
|
368 |
insulin_info['basal_dose'],
|
369 |
insulin_info['total_bolus'],
|
370 |
+
fig
|
|
|
|
|
371 |
)
|
372 |
|
373 |
except Exception as e:
|
374 |
+
return f"Error: {str(e)}", None, None, None, None
|
375 |
|
376 |
# -------------------------------------------------
|
377 |
# Gradio Interface Setup
|
|
|
419 |
basal_dose_output = gr.Number(label="Basal Insulin Dose (units/day)")
|
420 |
bolus_dose_output = gr.Number(label="Bolus Insulin Dose (units)")
|
421 |
glucose_plot_output = gr.Plot(label="Glucose Prediction")
|
|
|
|
|
422 |
|
423 |
calculate_button.click(
|
424 |
fn=diabetes_dashboard,
|
|
|
444 |
insulin_details_output,
|
445 |
basal_dose_output,
|
446 |
bolus_dose_output,
|
447 |
+
glucose_plot_output
|
|
|
|
|
448 |
]
|
449 |
)
|
450 |
|