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() |