Anupam007's picture
Create app.py
ef216d5 verified
Raw
History Blame Contribute Delete
15.8 kB
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 # Import the 'os' module
# Flag to track if pygame is initialized successfully
pygame_initialized = False
try:
# Initialize pygame mixer for sound
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.")
# Check if GPU is available
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")
# Simulated dataset of finger pressure patterns and blood glucose readings
# In a real application, this would be replaced with actual training data
def generate_simulated_data(n_samples=1000):
# Generate synthetic features that might correlate with blood glucose
# In reality, these would be derived from screen pressure, touch area,
# blood perfusion estimated from camera, etc.
np.random.seed(42)
# Feature 1: Simulated pressure values (0-100)
pressure = np.random.normal(60, 15, n_samples).clip(20, 100)
# Feature 2: Simulated touch area (mm²)
touch_area = np.random.normal(150, 30, n_samples).clip(80, 220)
# Feature 3: Simulated blood perfusion level (0-1)
perfusion = np.random.normal(0.7, 0.15, n_samples).clip(0.3, 1.0)
# Feature 4: Simulated tissue opacity (0-1)
opacity = np.random.normal(0.5, 0.1, n_samples).clip(0.2, 0.8)
# Generate glucose levels with some correlation to the features
# Normal range: 70-140 mg/dL, with some values outside this range
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) # Set realistic min/max values
# Create a dataframe
df = pd.DataFrame({
'pressure': pressure,
'touch_area': touch_area,
'perfusion': perfusion,
'opacity': opacity,
'glucose': glucose
})
return df
# Simple PyTorch model for glucose prediction
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)
# Train the model
def train_model(df):
# Split features and target
X = df[['pressure', 'touch_area', 'perfusion', 'opacity']].values
y = df['glucose'].values.reshape(-1, 1)
# Scale the data
scaler_X = StandardScaler()
scaler_y = StandardScaler()
X_scaled = scaler_X.fit_transform(X)
y_scaled = scaler_y.fit_transform(y)
# Split into train and test sets
X_train, X_test, y_train, y_test = train_test_split(X_scaled, y_scaled, test_size=0.2, random_state=42)
# Convert to PyTorch tensors
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)
# Initialize the model and move to GPU if available
model = GlucosePredictor().to(device)
# Loss function and optimizer
criterion = torch.nn.MSELoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# Training loop
epochs = 100
for epoch in range(epochs):
# Forward pass
y_pred = model(X_train_tensor)
loss = criterion(y_pred, y_train_tensor)
# Backward pass and optimize
optimizer.zero_grad()
loss.backward()
optimizer.step()
if (epoch+1) % 10 == 0:
print(f'Epoch {epoch+1}/{epochs}, Loss: {loss.item():.4f}')
# Evaluate the model
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
# Function to play beep sound
def play_beep(glucose_value):
"""Play different sounds based on glucose level"""
global pygame_initialized # Access the global flag
if not pygame_initialized:
print("Pygame not initialized, skipping sound.")
return # Exit if pygame is not initialized
try:
# Define frequency and duration based on glucose level
if glucose_value < 70:
# Low glucose - urgent double beep (low frequency)
freq = 300
duration = 300
# Play twice with a short pause
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) # Pause between beeps
elif glucose_value <= 140:
# Normal glucose - standard beep (medium frequency)
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:
# Elevated glucose - medium alert beep (higher frequency)
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:
# High glucose - urgent beep (high frequency)
freq = 800
duration = 300
# Play twice with a short pause
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) # Pause between beeps
print(f"Beep sound played for glucose level: {glucose_value}")
except Exception as e:
print(f"Error playing sound: {e}")
# Simulate camera usage for blood perfusion measurement
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")
# Attempt to use real camera if available (for demonstration)
try:
cap = cv2.VideoCapture(0)
if cap.isOpened():
ret, frame = cap.read()
if ret:
# In a real app, we would analyze this frame
# For now, just display that we captured it
print("Camera frame captured")
# Here you'd analyze blood perfusion from the image
# For example by looking at the red channel intensity
# Convert to grayscale and use a placeholder image for demonstration
frame_rgb = cv2.cvtColor(frame, cv2.COLOR_BGR2RGB)
img = Image.fromarray(frame_rgb)
# Clean up
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")
# If camera isn't working, return a placeholder
placeholder = np.ones((240, 320, 3), dtype=np.uint8) * 200
# Add text to placeholder
cv2.putText(placeholder, "Camera Simulation", (50, 120),
cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0, 0, 0), 2)
return Image.fromarray(placeholder)
# Simulate finger press data collection
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.
"""
# Simulate data collection with some random values
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)
# Add some constraints to make values realistic
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
# Function to predict glucose level
def predict_glucose(model, scaler_X, scaler_y, features):
# Scale the features
features_scaled = scaler_X.transform(np.array(features).reshape(1, -1))
# Convert to tensor and predict
features_tensor = torch.FloatTensor(features_scaled).to(device)
with torch.no_grad():
prediction_scaled = model(features_tensor)
# Convert back to original scale
prediction = scaler_y.inverse_transform(prediction_scaled.cpu().numpy())
return prediction[0][0]
# Create a fancy glucose meter display
def create_glucose_meter(glucose_value):
# Define colors based on glucose range
if glucose_value < 70:
color = 'blue' # Low
status = 'LOW'
elif glucose_value <= 140:
color = 'green' # Normal
status = 'NORMAL'
elif glucose_value <= 200:
color = 'orange' # Elevated
status = 'ELEVATED'
else:
color = 'red' # High
status = 'HIGH'
# Create a figure
fig, ax = plt.subplots(figsize=(6, 4))
# Create a gauge-like visualization
ax.add_patch(plt.Rectangle((-1, -1), 2, 2, fc='lightgray', ec='gray'))
# Create a gauge arc
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)
# Create tick marks
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)
# Create the needle
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)
# Add a center circle
ax.add_patch(plt.Circle((0, 0), 0.05, fc=color, ec='k'))
# Add text
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')
# Remove axes
ax.set_xlim(-1.2, 1.2)
ax.set_ylim(-1.2, 1)
ax.axis('off')
ax.set_aspect('equal')
# Add title
ax.set_title('Digital Glucometer Reading', fontsize=16, pad=20)
# Add disclaimer
fig.text(0.5, 0.01, 'PROTOTYPE ONLY - NOT FOR MEDICAL USE',
ha='center', va='bottom', fontsize=10, style='italic', color='gray')
return fig
# Gradio interface for finger press simulation with yield for progressive updates
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.
"""
# Step 1: Display a message that the camera is activating
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
# Step 2: Activate the camera (simulated or real)
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
# Step 3: Collect finger data
time.sleep(1)
collection_info = "Collecting finger press data..."
pressure, touch_area, perfusion, opacity = collect_finger_data()
yield collection_info, camera_img, meter_fig
# Step 4: Predict glucose level
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
# Step 5: Display final results
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)
# Play sound based on glucose level
try:
play_beep(glucose_value)
except Exception as e:
print(f"Error playing sound: {e}. Skipping sound.")
yield collection_info, camera_img, meter_fig
# Main execution
# [Previous code remains unchanged up to the main execution block]
if __name__ == "__main__":
# Generate simulated data and train the model
df = generate_simulated_data()
model, scaler_X, scaler_y = train_model(df)
# Create Gradio interface
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*")
# Connect the button to the simulation function (removed _js parameter)
measure_button.click(
fn=finger_press_simulation,
inputs=None,
outputs=[info_output, camera_output, meter_output]
)
# Launch the interface
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) # Try share mode as a fallback