Spaces:
Sleeping
Sleeping
from datasets import load_dataset | |
import gradio as gr | |
import numpy as np | |
import pandas as pd | |
import matplotlib.pyplot as plt | |
import torch | |
from transformers import AutoImageProcessor, AutoModelForImageClassification | |
from PIL import Image | |
from difflib import get_close_matches | |
from typing import Optional, Dict, Any | |
import json | |
import io | |
import os | |
# Configuration (unchanged) | |
INSULIN_TYPES = { | |
"Rapid-Acting": {"onset": 0.25, "duration": 4, "peak_time": 1.0}, | |
"Long-Acting": {"onset": 2, "duration": 24, "peak_time": 8}, | |
} | |
DEFAULT_BASAL_RATES = { | |
"00:00-06:00": 0.8, | |
"06:00-12:00": 1.0, | |
"12:00-18:00": 0.9, | |
"18:00-24:00": 0.7 | |
} | |
GI_RANGES = { | |
"low": (0, 55), | |
"medium": (56, 69), | |
"high": (70, 100) | |
} | |
# Utility Functions (mostly unchanged) | |
def estimate_gi_timing(gi_value: Optional[int]) -> tuple[float, float]: | |
if gi_value is None: | |
return 1.0, 2.5 | |
if gi_value <= 55: | |
return 1.0, 3.0 | |
elif 56 <= gi_value <= 69: | |
return 0.75, 2.0 | |
else: | |
return 0.5, 1.5 | |
def load_food_data(): | |
try: | |
ds = load_dataset("Anupam007/diabetic-food-analyzer") | |
food_data = pd.DataFrame(ds['train']) | |
food_data.columns = [col.lower().strip() for col in food_data.columns] | |
food_data['food_name'] = food_data['food_name'].str.lower().str.strip() | |
return food_data | |
except Exception as e: | |
print(f"Error loading food data: {e}") | |
return pd.DataFrame() | |
try: | |
processor = AutoImageProcessor.from_pretrained("rajistics/finetuned-indian-food") | |
model = AutoModelForImageClassification.from_pretrained("rajistics/finetuned-indian-food") | |
model_loaded = True | |
except Exception as e: | |
print(f"Model Load Error: {e}") | |
model_loaded = False | |
processor = None | |
model = None | |
def classify_food(image): | |
if not model_loaded or image is None: | |
return "unknown" | |
try: | |
inputs = processor(images=image, return_tensors="pt") | |
with torch.no_grad(): | |
outputs = model(**inputs) | |
predicted_idx = torch.argmax(outputs.logits, dim=-1).item() | |
food_name = model.config.id2label.get(predicted_idx, "unknown").lower() | |
return food_name | |
except Exception as e: | |
print(f"Classify food error: {e}") | |
return "unknown" | |
def get_food_nutrition(food_name: str, food_data: pd.DataFrame, weight_grams: Optional[float] = None) -> tuple[Optional[Dict[str, Any]], str]: | |
food_name_lower = food_name.lower().strip() | |
matches = get_close_matches(food_name_lower, food_data['food_name'].tolist(), n=1, cutoff=0.6) | |
correction_note = "" | |
if matches: | |
corrected_name = matches[0] | |
if corrected_name != food_name_lower: | |
correction_note = f"Note: '{food_name}' corrected to '{corrected_name}'" | |
matched_row = food_data[food_data['food_name'] == corrected_name].iloc[0] | |
base_carbs = float(matched_row.get('unit_serving_carb_g', matched_row.get('carb_g', 0.0))) | |
serving_size = matched_row.get('servings_unit', 'unknown') | |
gi_value = matched_row.get('glycemic_index', None) | |
if pd.isna(gi_value): | |
gi_value = None | |
else: | |
try: | |
gi_value = int(float(gi_value)) | |
except (ValueError, TypeError): | |
gi_value = None | |
if weight_grams and serving_size != 'unknown': | |
try: | |
serving_weight = float(matched_row.get('serving_weight_grams', 100)) | |
portion_size = weight_grams / serving_weight | |
except (ValueError, TypeError): | |
portion_size = 1.0 | |
else: | |
portion_size = 1.0 | |
adjusted_carbs = base_carbs * portion_size | |
nutrition_info = { | |
'matched_food': matched_row['food_name'], | |
'category': matched_row.get('primarysource', 'unknown'), | |
'subcategory': 'unknown', | |
'base_carbs': base_carbs, | |
'adjusted_carbs': adjusted_carbs, | |
'serving_size': f"1 {serving_size}", | |
'portion_multiplier': portion_size, | |
'notes': 'none', | |
'glycemic_index': gi_value | |
} | |
return nutrition_info, correction_note | |
return None, f"No close match found for '{food_name}'" | |
def determine_gi_level(gi_value: Optional[int]) -> str: | |
if gi_value is None: | |
return "Unknown" | |
for level, (lower, upper) in GI_RANGES.items(): | |
if lower <= gi_value <= upper: | |
return level.capitalize() | |
return "Unknown" | |
def get_basal_rate(current_time_hour: float, basal_rates: Dict[str, float]) -> float: | |
for interval, rate in basal_rates.items(): | |
try: | |
start, end = [int(x.split(':')[0]) for x in interval.split('-')] | |
if start <= current_time_hour < end or (start <= current_time_hour and end == 24): | |
return rate | |
except Exception as e: | |
print(f"Invalid basal interval {interval}: {e}") | |
return 0.8 | |
def insulin_activity(t: float, insulin_type: str, bolus_dose: float, bolus_duration: float = 0) -> float: | |
insulin_data = INSULIN_TYPES.get(insulin_type, INSULIN_TYPES["Rapid-Acting"]) | |
peak_time = insulin_data['peak_time'] | |
duration = insulin_data['duration'] | |
if bolus_duration > 0: | |
if 0 <= t <= bolus_duration: | |
return bolus_dose / bolus_duration | |
return 0 | |
if t < 0: | |
return 0 | |
elif t < peak_time: | |
return bolus_dose * (t / peak_time) * np.exp(1 - t/peak_time) | |
elif t < duration: | |
return bolus_dose * np.exp((peak_time - t) / (duration - peak_time)) | |
return 0 | |
def calculate_active_insulin(insulin_history: list, current_time: float) -> float: | |
return sum(insulin_activity(current_time - dose_time, insulin_type, dose_amount, bolus_duration) | |
for dose_time, dose_amount, insulin_type, bolus_duration in insulin_history) | |
def calculate_insulin_needs(carbs: float, glucose_current: float, glucose_target: float, | |
tdd: float, weight: float, insulin_type: str = "Rapid-Acting", | |
override_correction_dose: Optional[float] = None) -> Dict[str, Any]: | |
if tdd <= 0 or weight <= 0: | |
return {'error': 'TDD and weight must be positive'} | |
insulin_data = INSULIN_TYPES.get(insulin_type, INSULIN_TYPES["Rapid-Acting"]) | |
icr = 400 / tdd | |
isf = 1700 / tdd | |
correction_dose = (glucose_current - glucose_target) / isf if override_correction_dose is None else override_correction_dose | |
carb_dose = carbs / icr | |
total_bolus = max(0, carb_dose + correction_dose) | |
basal_dose = weight * 0.5 | |
return { | |
'icr': round(icr, 2), | |
'isf': round(isf, 2), | |
'correction_dose': round(correction_dose, 2), | |
'carb_dose': round(carb_dose, 2), | |
'total_bolus': round(total_bolus, 2), | |
'basal_dose': round(basal_dose, 2), | |
'insulin_type': insulin_type, | |
'insulin_onset': insulin_data['onset'], | |
'insulin_duration': insulin_data['duration'], | |
'peak_time': insulin_data['peak_time'] | |
} | |
def create_detailed_report(nutrition_info: Dict[str, Any], insulin_info: Dict[str, Any], | |
current_basal_rate: float, correction_note: str) -> tuple[str, str, str]: | |
gi_level = determine_gi_level(nutrition_info.get('glycemic_index')) | |
peak_time, duration = estimate_gi_timing(nutrition_info.get('glycemic_index')) | |
glucose_meal_details = f""" | |
GLUCOSE & MEAL DETAILS: | |
- Detected Food: {nutrition_info['matched_food']} | |
- Category: {nutrition_info['category']} | |
- Glycemic Index: {nutrition_info.get('glycemic_index', 'N/A')} ({gi_level}) | |
- Peak Glucose Time: {peak_time} hours | |
- Glucose Effect Duration: {duration} hours | |
- Serving Size: {nutrition_info['serving_size']} | |
- Carbs per Serving: {nutrition_info['base_carbs']}g | |
- Portion Multiplier: {nutrition_info['portion_multiplier']}x | |
- Total Carbs: {nutrition_info['adjusted_carbs']}g | |
{correction_note} | |
""" | |
insulin_details = f""" | |
INSULIN DETAILS: | |
- ICR: 1:{insulin_info['icr']} | |
- ISF: 1:{insulin_info['isf']} | |
- Insulin Type: {insulin_info['insulin_type']} | |
- Onset: {insulin_info['insulin_onset']}h | |
- Duration: {insulin_info['insulin_duration']}h | |
- Peak: {insulin_info['peak_time']}h | |
- Correction Dose: {insulin_info['correction_dose']} units | |
- Carb Dose: {insulin_info['carb_dose']} units | |
- Total Bolus: {insulin_info['total_bolus']} units | |
""" | |
basal_details = f""" | |
BASAL SETTINGS: | |
- Basal Dose: {insulin_info['basal_dose']} units/day | |
- Current Basal Rate: {current_basal_rate} units/h | |
""" | |
return glucose_meal_details, insulin_details, basal_details | |
# Modified Main Dashboard | |
def diabetes_dashboard(initial_glucose, food_image, food_name_input, weight_grams, | |
insulin_type, override_correction_dose, extended_bolus_duration, | |
weight, tdd, target_glucose, basal_rates_input, | |
stress_level, sleep_hours, exercise_duration, exercise_intensity, time_hours): | |
food_data = load_food_data() | |
if food_data.empty: | |
return "Error loading food data", None, None, None, None | |
if food_name_input and food_name_input.strip(): | |
food_name = food_name_input.strip() | |
else: | |
food_name = classify_food(food_image) | |
nutrition_info, correction_note = get_food_nutrition(food_name, food_data, weight_grams) | |
if not nutrition_info: | |
return correction_note, None, None, None, None | |
try: | |
basal_rates = json.loads(basal_rates_input) | |
except: | |
basal_rates = DEFAULT_BASAL_RATES | |
insulin_info = calculate_insulin_needs( | |
nutrition_info['adjusted_carbs'], initial_glucose, target_glucose, | |
tdd, weight, insulin_type, override_correction_dose | |
) | |
if 'error' in insulin_info: | |
return insulin_info['error'], None, None, None, None | |
current_basal_rate = get_basal_rate(12, basal_rates) | |
glucose_meal_details, insulin_details, basal_details = create_detailed_report(nutrition_info, insulin_info, current_basal_rate, correction_note) | |
hours = list(range(time_hours)) | |
glucose_levels = [] | |
current_glucose = initial_glucose | |
insulin_history = [(0, insulin_info['total_bolus'], insulin_type, extended_bolus_duration)] | |
for t in hours: | |
carb_effect = nutrition_info['adjusted_carbs'] * 0.1 * np.exp(-(t - 1.5) ** 2 / 2) | |
insulin_effect = calculate_active_insulin(insulin_history, t) | |
basal_effect = get_basal_rate(t, basal_rates) | |
stress_effect = stress_level * 2 | |
sleep_effect = abs(8 - sleep_hours) * 5 | |
exercise_effect = (exercise_duration / 60) * exercise_intensity * 2 | |
current_glucose += carb_effect - insulin_effect - basal_effect + stress_effect + sleep_effect - exercise_effect | |
glucose_levels.append(max(70, min(400, current_glucose))) | |
fig, ax = plt.subplots(figsize=(10, 5)) | |
ax.plot(hours, glucose_levels, 'b-', label='Predicted Glucose') | |
ax.axhline(y=target_glucose, color='g', linestyle='--', label='Target') | |
ax.fill_between(hours, 70, 180, alpha=0.1, color='g', label='Target Range') | |
ax.set_xlabel('Hours') | |
ax.set_ylabel('Glucose (mg/dL)') | |
ax.legend() | |
ax.grid(True) | |
return glucose_meal_details, insulin_details, basal_details, insulin_info['total_bolus'], fig | |
# Gradio Interface (unchanged) | |
with gr.Blocks(title="Type 1 Diabetes Management Dashboard") as app: | |
gr.Markdown("# Type 1 Diabetes Management Dashboard") | |
with gr.Tab("Glucose & Meal"): | |
initial_glucose = gr.Number(label="Current Glucose (mg/dL)", value=120) | |
target_glucose = gr.Number(label="Target Glucose (mg/dL)", value=100) | |
food_name_input = gr.Textbox(label="Food Name (optional)", placeholder="Enter food name manually") | |
weight_grams = gr.Number(label="Weight (grams, optional)", value=None) | |
food_image = gr.Image(label="Food Image (optional)", type="pil") | |
glucose_meal_output = gr.Textbox(label="Glucose & Meal Details", lines=10) | |
with gr.Tab("Insulin"): | |
insulin_type = gr.Dropdown(list(INSULIN_TYPES.keys()), label="Insulin Type", value="Rapid-Acting") | |
override_correction_dose = gr.Number(label="Override Correction Dose (units)", value=None) | |
extended_bolus_duration = gr.Number(label="Extended Bolus Duration (h)", value=0) | |
weight = gr.Number(label="Weight (kg)", value=70) | |
tdd = gr.Number(label="Total Daily Dose (units)", value=40) | |
insulin_output = gr.Textbox(label="Insulin Details", lines=10) | |
bolus_output = gr.Number(label="Bolus Dose (units)") | |
with gr.Tab("Basal Settings"): | |
basal_rates_input = gr.Textbox(label="Basal Rates (JSON)", value=json.dumps(DEFAULT_BASAL_RATES), lines=2) | |
basal_output = gr.Textbox(label="Basal Settings", lines=4) | |
with gr.Tab("Other Factors"): | |
stress_level = gr.Slider(1, 10, step=1, label="Stress Level", value=1) | |
sleep_hours = gr.Number(label="Sleep Hours", value=7) | |
exercise_duration = gr.Number(label="Exercise Duration (min)", value=0) | |
exercise_intensity = gr.Slider(1, 10, step=1, label="Exercise Intensity", value=1) | |
time_hours = gr.Slider(1, 24, step=1, label="Prediction Time (h)", value=6) | |
plot_output = gr.Plot(label="Glucose Prediction") | |
calculate_btn = gr.Button("Calculate") | |
calculate_btn.click( | |
diabetes_dashboard, | |
inputs=[ | |
initial_glucose, food_image, food_name_input, weight_grams, | |
insulin_type, override_correction_dose, extended_bolus_duration, | |
weight, tdd, target_glucose, basal_rates_input, | |
stress_level, sleep_hours, exercise_duration, exercise_intensity, time_hours | |
], | |
outputs=[glucose_meal_output, insulin_output, basal_output, bolus_output, plot_output] | |
) | |
if __name__ == "__main__": | |
app.launch() |