Inspiration

Coronary artery disease kills more people than anything else on earth, yet most risk tools collapse the whole heart into a single number. A patient can be "moderate risk" because one artery is critical and two are clean — and a single number hides that completely. Worse, modern ML risk scores are black boxes: a clinician gets a probability with no way to audit it. We wanted to build the tool we'd want at the bedside: risk for each major vessel, rendered onto the anatomy it applies to, with the reasoning exposed feature by feature.

What it does

CardioMap 3D takes up to 55 routine clinical inputs — demographics, vitals, ECG, labs, and echocardiography — and returns calibrated risk for four targets: overall CAD plus the LAD, LCX, and RCA coronary vessels.

A 3D heart recolors each artery by its own predicted probability, so the risk geography is visible at a glance. Every prediction decomposes into SHAP attributions, ranked per target, with relative contribution percentages. A measurements table ranks all 55 patient values against clinical reference ranges. What-If sliders recolor the vessels live as you change inputs. A Model Validation panel exposes ROC-AUC, Brier score, and calibration reliability curves.

How we built it

ML — Python + pandas with a scikit-learn pipeline (ColumnTransformer → classifier) wrapped in CalibratedClassifierCV, trained per target. A hard leakage test asserts that LAD/LCX/RCA/Cath never enter the feature matrix. SHAP provides attributions. Backend — FastAPI + Pydantic v2, schema-driven, 5 endpoints (/predict, /schema, /metrics, /samples, /health). The front end renders its entire input form from /schema, so new biomarkers need zero UI changes. Frontend — React 18 + Vite, Three.js via @react-three/fiber and drei for the GLB heart (86k triangles, CC BY 4.0), Zustand for clinical state, GSAP + Framer Motion for motion, Recharts for SHAP/ROC/calibration, Tailwind for the dark clinical aesthetic. Deploy — FastAPI on Render, front end on Netlify, with a hostname-aware API base so local dev, previews, and production all resolve correctly. Performance — an explain:false fast path skips SHAP for slider drags (~250 ms saved) and numeric thread-pool capping trims ~10%, taking remote predict from ~2.2 s to ~1.65 s. Testing — a 10-test pytest suite plus Playwright end-to-end runs against the live deployment.

Challenges we ran into

Speed vs. correctness. We found we could 2× inference by pre-transforming once and bypassing the calibration members — but it broke probability parity (max difference 0.14). Root cause: each of the five fold-fitted members carries its own preprocessor. In a clinical tool a tenth of a point is not negotiable, so we rejected the optimization and documented why. The free-tier compute ceiling. Moving from laptop to Render revealed the host CPU is ~27–32× slower; server compute alone is ~1.35 s. That reframed "optimize the model" into "measure on the target host." The 3D had to mean something. Mapping SHAP output onto specific coronary anatomy — rather than glowing for effect — took real design work. Docs drift. Our README's API example had silently gone stale: all 7 documented keys didn't exist, and because unknown keys are imputed, following the docs returned a confident, meaningless prediction with no error.

Accomplishments that we're proud of

A genuine four-target, per-vessel calibrated model with leakage verified by test, not by hand. Explainability that's wired all the way through: SHAP → 3D anatomy → auditable measurements table. Honest failure UX — simulation labelling and imputation transparency instead of silent fake results. A live, end-to-end verified deployment: 10/10 backend tests and 15/15 live production checks (API resolution, live recalc, 3D render, SHAP content, zero console errors). Docs that can't drift again — we added a test that fails when the README example diverges from the schema. It immediately caught our own stale example.

What we learned

In clinical ML, calibration and explanation matter more than raw AUC. Redundant preprocessing inside cross-validation members can dominate inference cost — fix it by caching per-member transforms, never by skipping them. Measure latency on the host you actually deploy to. Anything machine-checkable in docs should be checked in CI. Tolerant input handling needs a loud signal; silently imputing an unknown key is a footgun.

What's next for CardioMap 3D

Add LIME alongside SHAP for local-faithfulness comparison. Parity-preserving speedup by caching each calibration member's preprocessor. Strict unknown-key rejection plus schema versioning. External / prospective validation and clinician review. Move to a paid host tier for sub-500 ms response times. Multi-modal inputs — ECG waveforms and imaging.

Built With

Share this project:

Updates

Submission history