Spaces:
Sleeping
Sleeping
| import os | |
| os.environ["KMP_DUPLICATE_LIB_OK"] = "TRUE" | |
| """Sentinel — causal sepsis early-warning demo (Gradio).""" | |
| import matplotlib | |
| matplotlib.use("Agg") | |
| import matplotlib.pyplot as plt | |
| import numpy as np | |
| import pandas as pd | |
| import gradio as gr | |
| from sentinel_core import SepsisPredictor, derive_alerts, LABEL | |
| THRESHOLD, PERSISTENCE, REFRACTORY = 0.70, 2, 6 | |
| predictor = SepsisPredictor("sentinel_causal.pt", "scaler.csv", threshold=THRESHOLD) | |
| EXAMPLES = { | |
| "Septic patient (deteriorates to sepsis)": "examples/septic_patient.psv", | |
| "Non-septic patient (stable)": "examples/nonseptic_patient.psv", | |
| } | |
| def _run(df): | |
| onset = None | |
| if LABEL in df.columns and (df[LABEL] == 1).any(): | |
| onset = int((df[LABEL] == 1).argmax()) + 6 # label shifted 6h pre-onset | |
| feats = df.drop(columns=[LABEL]) if LABEL in df.columns else df | |
| risk = predictor.predict(feats) | |
| alert_hours, _ = derive_alerts(risk, THRESHOLD, PERSISTENCE, REFRACTORY) | |
| return risk, alert_hours, onset | |
| def _plot(risk, alert_hours, onset): | |
| hours = list(range(len(risk))) | |
| fig, ax = plt.subplots(figsize=(10, 4)) | |
| ax.plot(hours, risk, color="#BD3F36", lw=2, label="Sepsis risk") | |
| ax.axhline(THRESHOLD, color="grey", ls="--", lw=1, label=f"Threshold ({THRESHOLD})") | |
| if onset is not None and onset <= len(risk): | |
| ax.axvline(onset, color="black", ls=":", lw=1.5, label=f"Onset (h{onset})") | |
| if alert_hours: | |
| ax.scatter(alert_hours, [risk[h] for h in alert_hours], color="#BD3F36", | |
| s=90, marker="v", zorder=5, label="Alert fired") | |
| ax.fill_between(hours, 0, risk, color="#BD3F36", alpha=0.08) | |
| ax.set(xlabel="ICU hour", ylabel="Predicted sepsis risk", ylim=(0, 1), | |
| title="Streaming sepsis risk (causal, real-time simulation)") | |
| ax.legend(loc="upper left", fontsize=9) | |
| fig.tight_layout() | |
| return fig | |
| def analyze(example_choice, uploaded): | |
| if uploaded is not None: | |
| df = pd.read_csv(uploaded.name, sep="|") | |
| source = "uploaded patient" | |
| else: | |
| df = pd.read_csv(EXAMPLES[example_choice], sep="|") | |
| source = example_choice | |
| risk, alert_hours, onset = _run(df) | |
| fig = _plot(risk, alert_hours, onset) | |
| peak = float(np.max(risk)) | |
| first = alert_hours[0] if alert_hours else None | |
| lines = [f"**{source}** — {len(risk)} ICU hours"] | |
| lines.append(f"Peak risk: {peak:.2f}") | |
| if onset is not None: | |
| lines.append(f"Documented clinical onset: ~hour {onset}") | |
| if first is not None: | |
| lead = f", ~{onset - first}h before onset" if onset else "" | |
| lines.append(f"First alert: hour {first}{lead}") | |
| lines.append(f"Total alerts: {len(alert_hours)} (at hours {alert_hours})") | |
| else: | |
| lines.append("No alerts fired (risk stayed below the sustained-alert policy).") | |
| return fig, "\n\n".join(lines) | |
| with gr.Blocks(title="Sentinel — Sepsis Early Warning") as demo: | |
| gr.Markdown( | |
| "# Sentinel — Causal Sepsis Early Warning\n" | |
| "Predicts hour-by-hour sepsis risk from streaming ICU vitals (PhysioNet 2019), " | |
| "with a fatigue-aware alerting policy. Risk at each hour uses only data up to " | |
| "that hour (a true causal simulation).\n\n" | |
| "*Research demonstration on public data. **Not for clinical use.***" | |
| ) | |
| with gr.Row(): | |
| with gr.Column(scale=1): | |
| choice = gr.Dropdown(list(EXAMPLES.keys()), value=list(EXAMPLES.keys())[0], | |
| label="Example patient") | |
| upload = gr.File(label="…or upload a PhysioNet .psv", file_types=[".psv"]) | |
| btn = gr.Button("Analyze", variant="primary") | |
| with gr.Column(scale=2): | |
| plot = gr.Plot(label="Risk trajectory") | |
| summary = gr.Markdown() | |
| btn.click(analyze, [choice, upload], [plot, summary]) | |
| demo.load(analyze, [choice, upload], [plot, summary]) | |
| if __name__ == "__main__": | |
| demo.launch() | |