cardiosense-ai / app.py
Ayushdevo's picture
Update app.py
bd467d4 verified
Raw
History Blame Contribute Delete
4.79 kB
import gradio as gr
import numpy as np
import pandas as pd
from io import BytesIO
import os
os.environ["TF_CPP_MIN_LOG_LEVEL"] = "2"
import tensorflow as tf
tf.config.set_visible_devices([], 'GPU')
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from scipy.signal import find_peaks, resample
from PIL import Image
# ----------- LOAD MODEL -----------
model = tf.keras.models.load_model("ecg_cnn_model.h5", compile=False)
# Auto-detect what input length the model expects
MODEL_INPUT_SHAPE = model.input_shape # e.g. (None, 187) or (None, 6016) etc.
if isinstance(MODEL_INPUT_SHAPE, list):
MODEL_INPUT_SHAPE = MODEL_INPUT_SHAPE[0]
EXPECTED_LEN = MODEL_INPUT_SHAPE[-1]
print(f"[CardioSense] Model expects input length: {EXPECTED_LEN}")
# ----------- PREPROCESS -----------
def preprocess(filepath):
try:
data = pd.read_csv(filepath, encoding='latin1', on_bad_lines='skip')
except Exception:
return None
data.columns = data.columns.str.replace("'", "").str.strip().str.upper()
if data.shape[1] < 1:
return None
if 'MLII' in data.columns:
signal = data['MLII'].dropna().values
elif data.shape[1] >= 2:
signal = data.iloc[:, 1].dropna().values
else:
signal = data.iloc[:, 0].dropna().values
if len(signal) < 100:
return None
signal = signal.astype(np.float32)
signal = (signal - np.mean(signal)) / (np.std(signal) + 1e-8)
# Resample raw signal to exactly what the model expects
raw_segment = signal[:min(len(signal), EXPECTED_LEN * 4)] # grab a generous chunk
resampled = resample(raw_segment, EXPECTED_LEN).astype(np.float32)
segment = resampled.reshape(1, EXPECTED_LEN)
return segment, signal
# ----------- PREDICT -----------
def predict_ecg(file):
if file is None:
return "❌ No file uploaded.", None
result_data = preprocess(file.name)
if result_data is None:
return "❌ Invalid ECG file. Make sure it has at least 100 rows of numeric data.", None
segment, signal = result_data
try:
pred = model.predict(segment, verbose=0)[0][0]
except Exception as e:
return f"❌ Prediction failed: {str(e)}", None
confidence = round(float(pred) * 100, 2) if pred > 0.5 else round((1 - float(pred)) * 100, 2)
result = "Abnormal" if pred > 0.5 else "Normal"
# Heart rate estimation
peaks, _ = find_peaks(signal, distance=150, height=0.5)
if len(peaks) > 1 and np.mean(np.diff(peaks)) > 0:
rr = np.diff(peaks) / 360.0
heart_rate = int(60 / np.mean(rr))
heart_rate = max(30, min(220, heart_rate)) # clamp to physiological range
else:
heart_rate = None
# Plot
plot_len = min(500, len(signal))
plt.figure(figsize=(8, 3))
plt.plot(signal[:plot_len], color='royalblue', label="ECG Signal", linewidth=0.8)
for p in peaks:
if p < plot_len:
plt.plot(p, signal[p], "ro", markersize=4)
bpm_label = f"{heart_rate} BPM" if heart_rate else "-- BPM"
plt.title(f"ECG Signal ({bpm_label})", fontsize=13)
plt.xlabel("Sample")
plt.ylabel("Amplitude")
plt.grid(alpha=0.3)
plt.tight_layout()
img_buf = BytesIO()
plt.savefig(img_buf, format='png', dpi=120)
img_buf.seek(0)
plt.close()
pil_img = Image.open(img_buf)
emoji = "🔴" if result == "Abnormal" else "🟢"
advice = "Please consult a cardiologist." if result == "Abnormal" else "Your ECG looks healthy."
output_text = (
f"{emoji} **Result: {result}**\n\n"
f"📊 Confidence: {confidence}%\n\n"
f"❤️ Heart Rate: {heart_rate if heart_rate else 'N/A'} BPM\n\n"
f"⚠️ {advice}\n\n"
f"_This tool is for educational purposes only. Always consult a doctor._"
)
return output_text, pil_img
# ----------- GRADIO UI -----------
with gr.Blocks(title="CardioSense AI") as demo:
gr.Markdown("""
# 💓 CardioSense AI
### ECG Heart Condition Detector
Upload your ECG data as a **CSV file** to detect if it is **Normal** or **Abnormal**.
> Supports MIT-BIH format CSVs (e.g. with MLII column) or any single-column numeric ECG data.
""")
with gr.Row():
with gr.Column():
file_input = gr.File(label="📂 Upload ECG CSV File", file_types=[".csv"])
predict_btn = gr.Button("🔍 Analyze ECG", variant="primary")
with gr.Column():
result_output = gr.Markdown(label="Result")
plot_output = gr.Image(label="📈 ECG Graph", type="pil")
predict_btn.click(
fn=predict_ecg,
inputs=file_input,
outputs=[result_output, plot_output]
)
gr.Markdown("> ⚠️ For educational use only. Not a substitute for medical advice.")
demo.launch()