mgbam commited on
Commit
e47d4ee
·
verified ·
1 Parent(s): 15947fa

Upload 2 files

Browse files
Files changed (2) hide show
  1. README.md +6 -5
  2. app_space.py +98 -0
README.md CHANGED
@@ -1,10 +1,11 @@
1
  ---
2
- license: mit
3
  sdk: gradio
4
- colorFrom: yellow
5
- colorTo: green
6
- sdk_version: 6.0.2
7
  ---
 
8
  # Sundew Health Backend
9
 
10
  Neurosymbolic, energy-aware ECG monitoring backend using FastAPI, PyTorch, and PostgreSQL.
@@ -28,4 +29,4 @@ Run `pytest` to execute the test suite.
28
  ## Notes
29
 
30
  - Gating uses `sundew-algorithms` (significance + hysteresis) ahead of model inference.
31
- - Adaptive Sparse Training (`adaptive-sparse-training`) is installed; torch shims are applied to load it, but training still uses the simpler loop until AST wiring is added.
 
1
  ---
2
+ title: Sundew Health Demo
3
  sdk: gradio
4
+ sdk_version: 4.44.0
5
+ app_file: app_space.py
6
+ pinned: false
7
  ---
8
+
9
  # Sundew Health Backend
10
 
11
  Neurosymbolic, energy-aware ECG monitoring backend using FastAPI, PyTorch, and PostgreSQL.
 
29
  ## Notes
30
 
31
  - Gating uses `sundew-algorithms` (significance + hysteresis) ahead of model inference.
32
+ - Adaptive Sparse Training (`adaptive-sparse-training`) is installed; torch shims are applied to load it, but training still uses the simpler loop until AST wiring is added.
app_space.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import io
2
+ import json
3
+ from typing import Any, Dict, List
4
+
5
+ import gradio as gr
6
+ import matplotlib.pyplot as plt
7
+
8
+ from app.ml.gating import gate_signal
9
+ from app.ml.inference import infer_ecg, load_model
10
+ from app.rules.engine import evaluate_ecg_rules
11
+
12
+
13
+ # Preload model (uses ./checkpoints/ecg_classifier.pt if present)
14
+ load_model()
15
+
16
+
17
+ def parse_signal(text: str | List[float]) -> List[float]:
18
+ if isinstance(text, list):
19
+ return [float(x) for x in text]
20
+ try:
21
+ return [float(x) for x in json.loads(text)]
22
+ except Exception:
23
+ raise gr.Error("Provide ECG samples as a JSON list, e.g., [0.1, 0.2, 0.3]")
24
+
25
+
26
+ def run_infer(signal_text: str) -> Dict[str, Any]:
27
+ sig = parse_signal(signal_text)
28
+ gated, gating_meta = gate_signal(sig, return_windows=True)
29
+ model_output: Dict[str, Any] = infer_ecg(
30
+ gated,
31
+ original_len=len(sig),
32
+ gating_meta=gating_meta,
33
+ )
34
+ patient_context = {"patient_id": "demo"}
35
+ rules_result = evaluate_ecg_rules(patient_context, model_output)
36
+ explanations = [*(model_output.get("gating", {}).get("explanations", []) if isinstance(model_output.get("gating"), dict) else []),
37
+ *rules_result.get("explanations", [])]
38
+ return {
39
+ "label": model_output.get("label"),
40
+ "score": round(float(model_output.get("score", 0.0)), 3),
41
+ "hr": model_output.get("hr"),
42
+ "alert_level": rules_result.get("alert_level", "none"),
43
+ "gated_ratio": round(model_output.get("gated_ratio", 1.0), 3),
44
+ "gating": gating_meta,
45
+ "explanations": explanations,
46
+ }
47
+
48
+
49
+ def plot_gating(signal_text: str):
50
+ sig = parse_signal(signal_text)
51
+ gated, meta = gate_signal(sig, return_windows=True)
52
+ fig, axes = plt.subplots(2, 1, figsize=(6, 4))
53
+ axes[0].plot(sig, color="#0066ff", linewidth=1)
54
+ axes[0].set_title("Raw signal")
55
+ axes[1].plot(gated, color="#ff6600", linewidth=1)
56
+ axes[1].set_title(f"Gated signal (ratio={meta['ratio']:.2f})")
57
+ fig.tight_layout()
58
+ buf = io.BytesIO()
59
+ fig.savefig(buf, format="png", dpi=120)
60
+ plt.close(fig)
61
+ buf.seek(0)
62
+ return buf
63
+
64
+
65
+ demo_normal = [0.05 for _ in range(256)]
66
+ demo_afib = [0.3 for _ in range(256)]
67
+
68
+ with gr.Blocks(title="Sundew ECG Demo") as demo:
69
+ gr.Markdown("### Neurosymbolic ECG • Sundew Gating + Rules")
70
+ with gr.Tabs():
71
+ with gr.Tab("Upload/Infer"):
72
+ inp = gr.Textbox(
73
+ label="ECG samples (JSON list)",
74
+ value=json.dumps(demo_afib[:128]),
75
+ )
76
+ out = gr.JSON(label="Inference")
77
+ btn = gr.Button("Run")
78
+ btn.click(run_infer, inputs=inp, outputs=out)
79
+ with gr.Tab("Gating Preview"):
80
+ inp2 = gr.Textbox(
81
+ label="ECG samples (JSON list)",
82
+ value=json.dumps(demo_afib[:128]),
83
+ )
84
+ img = gr.Image(type="filepath", label="Raw vs Gated")
85
+ btn2 = gr.Button("Show gating")
86
+ btn2.click(plot_gating, inputs=inp2, outputs=img)
87
+ with gr.Tab("Demos"):
88
+ out_demo = gr.JSON()
89
+ btn_n = gr.Button("Normal")
90
+ btn_a = gr.Button("Arrhythmia-ish")
91
+ hidden_n = gr.Textbox(value=json.dumps(demo_normal), visible=False)
92
+ hidden_a = gr.Textbox(value=json.dumps(demo_afib), visible=False)
93
+ btn_n.click(run_infer, inputs=hidden_n, outputs=out_demo)
94
+ btn_a.click(run_infer, inputs=hidden_a, outputs=out_demo)
95
+
96
+
97
+ if __name__ == "__main__":
98
+ demo.launch()