| import numpy as np |
| import pandas as pd |
| import time |
| import gradio as gr |
| import torch |
| import matplotlib.pyplot as plt |
| from PIL import Image |
| import cv2 |
| import pygame |
| from sklearn.preprocessing import StandardScaler |
| from sklearn.model_selection import train_test_split |
| import os |
|
|
| |
| pygame_initialized = False |
|
|
| try: |
| |
| pygame.mixer.init() |
| pygame_initialized = True |
| print("Pygame mixer initialized successfully.") |
| except Exception as e: |
| print(f"Error initializing pygame: {e}. Sound will be disabled.") |
|
|
| |
| print("CUDA available:", torch.cuda.is_available()) |
| if torch.cuda.is_available(): |
| device = torch.device("cuda:0") |
| print("Using GPU:", torch.cuda.get_device_name(0)) |
| else: |
| device = torch.device("cpu") |
| print("Using CPU") |
|
|
| |
| |
| def generate_simulated_data(n_samples=1000): |
| |
| |
| |
| np.random.seed(42) |
|
|
| |
| pressure = np.random.normal(60, 15, n_samples).clip(20, 100) |
|
|
| |
| touch_area = np.random.normal(150, 30, n_samples).clip(80, 220) |
|
|
| |
| perfusion = np.random.normal(0.7, 0.15, n_samples).clip(0.3, 1.0) |
|
|
| |
| opacity = np.random.normal(0.5, 0.1, n_samples).clip(0.2, 0.8) |
|
|
| |
| |
| base_glucose = 100 + (pressure - 60) * 0.5 - (touch_area - 150) * 0.1 + (perfusion - 0.7) * 40 - (opacity - 0.5) * 30 |
| glucose = base_glucose + np.random.normal(0, 15, n_samples) |
| glucose = glucose.clip(40, 400) |
|
|
| |
| df = pd.DataFrame({ |
| 'pressure': pressure, |
| 'touch_area': touch_area, |
| 'perfusion': perfusion, |
| 'opacity': opacity, |
| 'glucose': glucose |
| }) |
|
|
| return df |
|
|
| |
| class GlucosePredictor(torch.nn.Module): |
| def __init__(self): |
| super(GlucosePredictor, self).__init__() |
| self.model = torch.nn.Sequential( |
| torch.nn.Linear(4, 16), |
| torch.nn.ReLU(), |
| torch.nn.Linear(16, 32), |
| torch.nn.ReLU(), |
| torch.nn.Linear(32, 16), |
| torch.nn.ReLU(), |
| torch.nn.Linear(16, 1) |
| ) |
|
|
| def forward(self, x): |
| return self.model(x) |
|
|
| |
| def train_model(df): |
| |
| X = df[['pressure', 'touch_area', 'perfusion', 'opacity']].values |
| y = df['glucose'].values.reshape(-1, 1) |
|
|
| |
| scaler_X = StandardScaler() |
| scaler_y = StandardScaler() |
|
|
| X_scaled = scaler_X.fit_transform(X) |
| y_scaled = scaler_y.fit_transform(y) |
|
|
| |
| X_train, X_test, y_train, y_test = train_test_split(X_scaled, y_scaled, test_size=0.2, random_state=42) |
|
|
| |
| X_train_tensor = torch.FloatTensor(X_train).to(device) |
| y_train_tensor = torch.FloatTensor(y_train).to(device) |
| X_test_tensor = torch.FloatTensor(X_test).to(device) |
| y_test_tensor = torch.FloatTensor(y_test).to(device) |
|
|
| |
| model = GlucosePredictor().to(device) |
|
|
| |
| criterion = torch.nn.MSELoss() |
| optimizer = torch.optim.Adam(model.parameters(), lr=0.001) |
|
|
| |
| epochs = 100 |
| for epoch in range(epochs): |
| |
| y_pred = model(X_train_tensor) |
| loss = criterion(y_pred, y_train_tensor) |
|
|
| |
| optimizer.zero_grad() |
| loss.backward() |
| optimizer.step() |
|
|
| if (epoch+1) % 10 == 0: |
| print(f'Epoch {epoch+1}/{epochs}, Loss: {loss.item():.4f}') |
|
|
| |
| model.eval() |
| with torch.no_grad(): |
| y_pred_test = model(X_test_tensor) |
| test_loss = criterion(y_pred_test, y_test_tensor) |
| print(f'Test Loss: {test_loss.item():.4f}') |
|
|
| return model, scaler_X, scaler_y |
|
|
| |
| def play_beep(glucose_value): |
| """Play different sounds based on glucose level""" |
| global pygame_initialized |
|
|
| if not pygame_initialized: |
| print("Pygame not initialized, skipping sound.") |
| return |
|
|
| try: |
| |
| if glucose_value < 70: |
| |
| freq = 300 |
| duration = 300 |
| |
| for _ in range(2): |
| sound_array = np.sin(2*np.pi*np.arange(44100)*freq/44100).astype(np.float32) |
| sound = pygame.sndarray.make_sound(sound_array) |
| sound.play() |
| time.sleep(duration/1000) |
| time.sleep(0.1) |
| elif glucose_value <= 140: |
| |
| freq = 440 |
| duration = 400 |
| sound_array = np.sin(2*np.pi*np.arange(44100)*freq/44100).astype(np.float32) |
| sound = pygame.sndarray.make_sound(sound_array) |
| sound.play() |
| time.sleep(duration/1000) |
| elif glucose_value <= 200: |
| |
| freq = 600 |
| duration = 400 |
| sound_array = np.sin(2*np.pi*np.arange(44100)*freq/44100).astype(np.float32) |
| sound = pygame.sndarray.make_sound(sound_array) |
| sound.play() |
| time.sleep(duration/1000) |
| else: |
| |
| freq = 800 |
| duration = 300 |
| |
| for _ in range(3): |
| sound_array = np.sin(2*np.pi*np.arange(44100)*freq/44100).astype(np.float32) |
| sound = pygame.sndarray.make_sound(sound_array) |
| sound.play() |
| time.sleep(duration/1000) |
| time.sleep(0.1) |
|
|
| print(f"Beep sound played for glucose level: {glucose_value}") |
| except Exception as e: |
| print(f"Error playing sound: {e}") |
|
|
| |
| def activate_camera(): |
| """ |
| In a real application, this would activate the device's camera |
| to measure blood perfusion in the fingertip. |
| Here we just simulate this process. |
| """ |
| print("Camera activated for blood perfusion analysis") |
|
|
| |
| try: |
| cap = cv2.VideoCapture(0) |
| if cap.isOpened(): |
| ret, frame = cap.read() |
| if ret: |
| |
| |
| print("Camera frame captured") |
| |
| |
|
|
| |
| frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB) |
| img = Image.fromarray(frame_rgb) |
|
|
| |
| cap.release() |
|
|
| return img |
| else: |
| print("Could not open camera - using simulated data") |
|
|
| except Exception as e: |
| print(f"Camera error: {e} - using simulated data") |
|
|
| |
| placeholder = np.ones((240, 320, 3), dtype=np.uint8) * 200 |
| |
| cv2.putText(placeholder, "Camera Simulation", (50, 120), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 0), 2) |
|
|
| return Image.fromarray(placeholder) |
|
|
| |
| def collect_finger_data(): |
| """ |
| In a real application, this would collect data from: |
| 1. Screen pressure sensors |
| 2. Touch area measurement |
| 3. Camera-based blood perfusion estimation |
| 4. Optional: PPG (photoplethysmography) if available |
| |
| Here we're just simulating the data. |
| """ |
| |
| pressure = np.random.normal(60, 10) |
| touch_area = np.random.normal(150, 20) |
| perfusion = np.random.normal(0.7, 0.1) |
| opacity = np.random.normal(0.5, 0.08) |
|
|
| |
| pressure = max(20, min(100, pressure)) |
| touch_area = max(80, min(220, touch_area)) |
| perfusion = max(0.3, min(1.0, perfusion)) |
| opacity = max(0.2, min(0.8, opacity)) |
|
|
| return pressure, touch_area, perfusion, opacity |
|
|
| |
| def predict_glucose(model, scaler_X, scaler_y, features): |
| |
| features_scaled = scaler_X.transform(np.array(features).reshape(1, -1)) |
|
|
| |
| features_tensor = torch.FloatTensor(features_scaled).to(device) |
| with torch.no_grad(): |
| prediction_scaled = model(features_tensor) |
|
|
| |
| prediction = scaler_y.inverse_transform(prediction_scaled.cpu().numpy()) |
|
|
| return prediction[0][0] |
|
|
| |
| def create_glucose_meter(glucose_value): |
| |
| if glucose_value < 70: |
| color = 'blue' |
| status = 'LOW' |
| elif glucose_value <= 140: |
| color = 'green' |
| status = 'NORMAL' |
| elif glucose_value <= 200: |
| color = 'orange' |
| status = 'ELEVATED' |
| else: |
| color = 'red' |
| status = 'HIGH' |
|
|
| |
| fig, ax = plt.subplots(figsize=(6, 4)) |
|
|
| |
| ax.add_patch(plt.Rectangle((-1, -1), 2, 2, fc='lightgray', ec='gray')) |
|
|
| |
| theta = np.linspace(-0.75 * np.pi, 0.75 * np.pi, 100) |
| r = 0.8 |
| x = r * np.cos(theta) |
| y = r * np.sin(theta) |
| ax.plot(x, y, 'k-', lw=2) |
|
|
| |
| for i in range(40, 401, 40): |
| angle = -0.75 * np.pi + (i - 40) / (400 - 40) * 1.5 * np.pi |
| x_tick = (r + 0.1) * np.cos(angle) |
| y_tick = (r + 0.1) * np.sin(angle) |
| ax.plot([r * np.cos(angle), x_tick], [r * np.sin(angle), y_tick], 'k-', lw=1) |
| ax.text(x_tick * 1.1, y_tick * 1.1, str(i), ha='center', va='center', fontsize=8) |
|
|
| |
| angle = -0.75 * np.pi + (glucose_value - 40) / (400 - 40) * 1.5 * np.pi |
| ax.plot([0, 0.9 * np.cos(angle)], [0, 0.9 * np.sin(angle)], color=color, lw=3) |
|
|
| |
| ax.add_patch(plt.Circle((0, 0), 0.05, fc=color, ec='k')) |
|
|
| |
| ax.text(0, -0.4, f"{glucose_value:.1f} mg/dL", ha='center', va='center', fontsize=14, fontweight='bold') |
| ax.text(0, -0.6, status, ha='center', va='center', fontsize=12, color=color, fontweight='bold') |
|
|
| |
| ax.set_xlim(-1.2, 1.2) |
| ax.set_ylim(-1.2, 1) |
| ax.axis('off') |
| ax.set_aspect('equal') |
|
|
| |
| ax.set_title('Digital Glucometer Reading', fontsize=16, pad=20) |
|
|
| |
| fig.text(0.5, 0.01, 'PROTOTYPE ONLY - NOT FOR MEDICAL USE', |
| ha='center', va='bottom', fontsize=10, style='italic', color='gray') |
|
|
| return fig |
| |
| def finger_press_simulation(): |
| """ |
| This function simulates the process of measuring glucose from a finger press |
| with progressive updates to show the process step by step. |
| """ |
| |
| collection_info = "Activating camera for blood perfusion analysis..." |
| camera_placeholder = np.ones((240, 320, 3), dtype=np.uint8) * 200 |
| cv2.putText(camera_placeholder, "Activating Camera...", (50, 120), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 0), 2) |
| camera_img = Image.fromarray(camera_placeholder) |
|
|
| meter_fig = plt.figure(figsize=(6, 4)) |
| plt.text(0.5, 0.5, "Measuring...", ha='center', va='center', fontsize=18) |
| plt.axis('off') |
|
|
| yield collection_info, camera_img, meter_fig |
|
|
| |
| time.sleep(1) |
| collection_info = "Camera activated, analyzing blood perfusion..." |
| try: |
| camera_img = activate_camera() |
| except Exception as e: |
| print(f"Error activating camera: {e}. Using placeholder image.") |
| camera_placeholder = np.ones((240, 320, 3), dtype=np.uint8) * 200 |
| cv2.putText(camera_placeholder, "Camera Error", (50, 120), |
| cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 0), 2) |
| camera_img = Image.fromarray(camera_placeholder) |
|
|
| yield collection_info, camera_img, meter_fig |
|
|
| |
| time.sleep(1) |
| collection_info = "Collecting finger press data..." |
| pressure, touch_area, perfusion, opacity = collect_finger_data() |
| yield collection_info, camera_img, meter_fig |
|
|
| |
| time.sleep(1) |
| collection_info = "Analyzing data and predicting glucose level..." |
| features = [pressure, touch_area, perfusion, opacity] |
| glucose_value = predict_glucose(model, scaler_X, scaler_y, features) |
| yield collection_info, camera_img, meter_fig |
|
|
| |
| time.sleep(1) |
| collection_info = f"Measurement complete!\nPressure: {pressure:.1f}\nTouch Area: {touch_area:.1f}\nPerfusion: {perfusion:.2f}\nOpacity: {opacity:.2f}" |
| meter_fig = create_glucose_meter(glucose_value) |
|
|
| |
| try: |
| play_beep(glucose_value) |
| except Exception as e: |
| print(f"Error playing sound: {e}. Skipping sound.") |
|
|
| yield collection_info, camera_img, meter_fig |
|
|
| |
| |
|
|
| if __name__ == "__main__": |
| |
| df = generate_simulated_data() |
| model, scaler_X, scaler_y = train_model(df) |
|
|
| |
| with gr.Blocks(title="Non-Invasive Glucose Monitor Prototype") as demo: |
| gr.Markdown("# Non-Invasive Glucose Monitor Prototype") |
| gr.Markdown("Press 'Measure Glucose' to simulate a finger-based glucose measurement") |
|
|
| with gr.Row(): |
| with gr.Column(scale=1): |
| measure_button = gr.Button("Measure Glucose") |
| info_output = gr.Textbox(label="Measurement Process") |
| with gr.Column(scale=1): |
| camera_output = gr.Image(label="Camera Feed") |
| with gr.Column(scale=1): |
| meter_output = gr.Plot(label="Glucose Meter") |
|
|
| gr.Markdown("*Note: This is a prototype simulation only - not for actual medical use*") |
|
|
| |
| measure_button.click( |
| fn=finger_press_simulation, |
| inputs=None, |
| outputs=[info_output, camera_output, meter_output] |
| ) |
|
|
| |
| try: |
| demo.launch() |
| except Exception as e: |
| print(f"Error launching Gradio: {e}") |
| print("Attempting to launch in share mode (for debugging).") |
| demo.launch(share=True) |