Update app.py
Browse files
app.py
CHANGED
|
@@ -27,31 +27,36 @@ CLASS_RISK = {
|
|
| 27 |
}
|
| 28 |
COLORS = ['#2ecc71','#3498db','#e74c3c','#f39c12','#9b59b6']
|
| 29 |
|
| 30 |
-
|
| 31 |
def build_model():
|
| 32 |
-
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
-
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 50 |
return m
|
| 51 |
|
| 52 |
model = build_model()
|
| 53 |
|
| 54 |
-
|
| 55 |
def predict_ecg(text_input):
|
| 56 |
try:
|
| 57 |
cleaned = text_input.replace(",", " ").replace("\n", " ").replace("\t", " ")
|
|
@@ -66,9 +71,12 @@ def predict_ecg(text_input):
|
|
| 66 |
|
| 67 |
scaler = MinMaxScaler()
|
| 68 |
signal_scaled = scaler.fit_transform(features.reshape(-1, 1)).reshape(1, 187, 1)
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
|
|
|
|
|
|
|
|
|
| 72 |
|
| 73 |
fig, axes = plt.subplots(1, 2, figsize=(14, 4))
|
| 74 |
fig.patch.set_facecolor('#0e1117')
|
|
@@ -89,7 +97,8 @@ def predict_ecg(text_input):
|
|
| 89 |
|
| 90 |
ax2 = axes[1]
|
| 91 |
ax2.set_facecolor('#1a1a2e')
|
| 92 |
-
bars = ax2.barh([CLASS_MAPPING[i] for i in range(5)],
|
|
|
|
| 93 |
for bar, val in zip(bars, probs):
|
| 94 |
ax2.text(val*100+0.5, bar.get_y()+bar.get_height()/2,
|
| 95 |
f"{val*100:.1f}%", va='center', color='white', fontsize=9)
|
|
@@ -102,10 +111,10 @@ def predict_ecg(text_input):
|
|
| 102 |
|
| 103 |
plt.tight_layout()
|
| 104 |
|
| 105 |
-
result
|
| 106 |
true_out = ""
|
| 107 |
if true_label is not None:
|
| 108 |
-
match
|
| 109 |
true_out = f"**π·οΈ True Label:** {CLASS_MAPPING.get(true_label,'Unknown')} β {match}"
|
| 110 |
stats = f"**Signal Stats:** R-peak={features.max():.4f} @ t={r_idx} | Min={features.min():.4f} | Mean={features.mean():.4f} | Std={features.std():.4f}"
|
| 111 |
|
|
@@ -114,6 +123,7 @@ def predict_ecg(text_input):
|
|
| 114 |
except Exception as e:
|
| 115 |
return None, f"β Error: {str(e)}", "", ""
|
| 116 |
|
|
|
|
| 117 |
with gr.Blocks(title="ECG Classification") as demo:
|
| 118 |
gr.Markdown("""
|
| 119 |
# π« ECG Arrhythmia Classification
|
|
@@ -122,8 +132,11 @@ with gr.Blocks(title="ECG Classification") as demo:
|
|
| 122 |
""")
|
| 123 |
with gr.Row():
|
| 124 |
with gr.Column(scale=1):
|
| 125 |
-
text_input = gr.Textbox(
|
| 126 |
-
|
|
|
|
|
|
|
|
|
|
| 127 |
predict_btn = gr.Button("βΆ Run Prediction", variant="primary")
|
| 128 |
gr.Markdown("""
|
| 129 |
| Label | Class | Risk |
|
|
@@ -140,9 +153,11 @@ with gr.Blocks(title="ECG Classification") as demo:
|
|
| 140 |
true_out = gr.Markdown()
|
| 141 |
stats_out = gr.Markdown()
|
| 142 |
|
| 143 |
-
predict_btn.click(
|
| 144 |
-
|
| 145 |
-
|
|
|
|
|
|
|
| 146 |
gr.Markdown("*DEPI Final Project 2025*")
|
| 147 |
|
| 148 |
demo.launch(ssr_mode=False)
|
|
|
|
| 27 |
}
|
| 28 |
COLORS = ['#2ecc71','#3498db','#e74c3c','#f39c12','#9b59b6']
|
| 29 |
|
| 30 |
+
# ββ Build model on CPU at startup βββββββββββββββββββββββββββββββββββββββββββββ
|
| 31 |
def build_model():
|
| 32 |
+
with tf.device('/CPU:0'):
|
| 33 |
+
m = models.Sequential([
|
| 34 |
+
layers.Input(shape=(187, 1)),
|
| 35 |
+
layers.Conv1D(64, 7, padding='same', activation='relu',
|
| 36 |
+
kernel_regularizer=regularizers.l2(1e-4)),
|
| 37 |
+
layers.BatchNormalization(), layers.MaxPooling1D(2), layers.Dropout(0.2),
|
| 38 |
+
layers.Conv1D(128, 5, padding='same', activation='relu',
|
| 39 |
+
kernel_regularizer=regularizers.l2(1e-4)),
|
| 40 |
+
layers.BatchNormalization(), layers.MaxPooling1D(2), layers.Dropout(0.25),
|
| 41 |
+
layers.Conv1D(256, 3, padding='same', activation='relu',
|
| 42 |
+
kernel_regularizer=regularizers.l2(1e-4)),
|
| 43 |
+
layers.BatchNormalization(), layers.MaxPooling1D(2), layers.Dropout(0.3),
|
| 44 |
+
layers.Conv1D(256, 3, padding='same', activation='relu',
|
| 45 |
+
kernel_regularizer=regularizers.l2(1e-4)),
|
| 46 |
+
layers.BatchNormalization(), layers.GlobalAveragePooling1D(), layers.Dropout(0.3),
|
| 47 |
+
layers.Dense(256, activation='relu', kernel_regularizer=regularizers.l2(1e-4)),
|
| 48 |
+
layers.BatchNormalization(), layers.Dropout(0.4),
|
| 49 |
+
layers.Dense(128, activation='relu', kernel_regularizer=regularizers.l2(1e-4)),
|
| 50 |
+
layers.Dropout(0.3),
|
| 51 |
+
layers.Dense(5, activation='softmax')
|
| 52 |
+
], name="CNN_ECG")
|
| 53 |
+
m.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['accuracy'])
|
| 54 |
+
m.load_weights("cnn_weights.weights.h5")
|
| 55 |
return m
|
| 56 |
|
| 57 |
model = build_model()
|
| 58 |
|
| 59 |
+
# ββ Predict βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 60 |
def predict_ecg(text_input):
|
| 61 |
try:
|
| 62 |
cleaned = text_input.replace(",", " ").replace("\n", " ").replace("\t", " ")
|
|
|
|
| 71 |
|
| 72 |
scaler = MinMaxScaler()
|
| 73 |
signal_scaled = scaler.fit_transform(features.reshape(-1, 1)).reshape(1, 187, 1)
|
| 74 |
+
|
| 75 |
+
with tf.device('/CPU:0'):
|
| 76 |
+
probs = model.predict(signal_scaled, verbose=0)[0]
|
| 77 |
+
|
| 78 |
+
pred_class = int(np.argmax(probs))
|
| 79 |
+
confidence = float(np.max(probs)) * 100
|
| 80 |
|
| 81 |
fig, axes = plt.subplots(1, 2, figsize=(14, 4))
|
| 82 |
fig.patch.set_facecolor('#0e1117')
|
|
|
|
| 97 |
|
| 98 |
ax2 = axes[1]
|
| 99 |
ax2.set_facecolor('#1a1a2e')
|
| 100 |
+
bars = ax2.barh([CLASS_MAPPING[i] for i in range(5)],
|
| 101 |
+
probs * 100, color=COLORS, alpha=0.85)
|
| 102 |
for bar, val in zip(bars, probs):
|
| 103 |
ax2.text(val*100+0.5, bar.get_y()+bar.get_height()/2,
|
| 104 |
f"{val*100:.1f}%", va='center', color='white', fontsize=9)
|
|
|
|
| 111 |
|
| 112 |
plt.tight_layout()
|
| 113 |
|
| 114 |
+
result = f"**π€ Predicted:** {CLASS_MAPPING[pred_class]}\n\n**π Confidence:** {confidence:.2f}%\n\n**βοΈ Risk:** {CLASS_RISK[pred_class]}"
|
| 115 |
true_out = ""
|
| 116 |
if true_label is not None:
|
| 117 |
+
match = "β
Correct!" if pred_class == true_label else "β Incorrect"
|
| 118 |
true_out = f"**π·οΈ True Label:** {CLASS_MAPPING.get(true_label,'Unknown')} β {match}"
|
| 119 |
stats = f"**Signal Stats:** R-peak={features.max():.4f} @ t={r_idx} | Min={features.min():.4f} | Mean={features.mean():.4f} | Std={features.std():.4f}"
|
| 120 |
|
|
|
|
| 123 |
except Exception as e:
|
| 124 |
return None, f"β Error: {str(e)}", "", ""
|
| 125 |
|
| 126 |
+
# ββ UI ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 127 |
with gr.Blocks(title="ECG Classification") as demo:
|
| 128 |
gr.Markdown("""
|
| 129 |
# π« ECG Arrhythmia Classification
|
|
|
|
| 132 |
""")
|
| 133 |
with gr.Row():
|
| 134 |
with gr.Column(scale=1):
|
| 135 |
+
text_input = gr.Textbox(
|
| 136 |
+
label="Paste 187 or 188 ECG values",
|
| 137 |
+
lines=8,
|
| 138 |
+
placeholder="0.5, 0.8, 0.3, 1.0 ..."
|
| 139 |
+
)
|
| 140 |
predict_btn = gr.Button("βΆ Run Prediction", variant="primary")
|
| 141 |
gr.Markdown("""
|
| 142 |
| Label | Class | Risk |
|
|
|
|
| 153 |
true_out = gr.Markdown()
|
| 154 |
stats_out = gr.Markdown()
|
| 155 |
|
| 156 |
+
predict_btn.click(
|
| 157 |
+
predict_ecg,
|
| 158 |
+
inputs=[text_input],
|
| 159 |
+
outputs=[plot_out, result_out, true_out, stats_out]
|
| 160 |
+
)
|
| 161 |
gr.Markdown("*DEPI Final Project 2025*")
|
| 162 |
|
| 163 |
demo.launch(ssr_mode=False)
|