🏆 1st Place — EWH × SUABE Designathon 2026, Fiji
A low-cost digital stethoscope that screens for signs of Rheumatic Heart Disease using machine learning — designed for community health workers in remote Fiji.
⚠️ CardioScan is a research and education prototype, not a medical device. It is not a diagnosis. It is validated only on the PhysioNet dataset and is unvalidated on real device hardware, where accuracy can drop sharply (see Honest evaluation). Always consult a qualified clinician.
Rheumatic Heart Disease (RHD) is largely preventable with a $0.50 penicillin injection — but only if caught early. In remote Fiji there are few cardiologists, little echocardiography, and no affordable screening tools.
CardioScan is a proof of concept that puts AI-assisted cardiac screening in the hands of community health workers for under $150:
- ⏱ 15-second scan — plug in, place on chest, get a screening result
- 🤖 Random Forest model trained on 3,240 PhysioNet clinical recordings
- 🌐 Browser-based — runs in Chrome via the Web Serial API, no install needed
- 🔌 Hardware + software — an Arduino captures heart sounds, Python + Flask extracts features and runs the model, which returns Clear / Refer / Inconclusive
Browser (Chrome / Edge)
│ Web Serial API (USB) ──► Arduino Nano + microphone (streams ADC samples)
│ POST /api/analyse { samples: [...] }
▼
Flask server (server.py)
│ features.py → 90-dim feature vector (resampled to 500 Hz)
│ rhd_model.pkl → Random Forest → P(abnormal)
│ decision.py → Clear / Refer / Inconclusive (data-driven thresholds)
▼
Website displays the verdict + probabilities
The feature pipeline lives in one shared module (features.py) imported by training, the server, and the CLI detector, so training and inference can never drift apart.
The model is strong within the PhysioNet distribution but does not generalize to unseen recording setups — the honest predictor for real hardware. Both numbers are printed by train_model.py, and the study is reproduced in experiments/.
| Metric | What it measures | Result |
|---|---|---|
| Same-distribution (shuffled 5-fold) | Train/test from the same sources | ROC-AUC 0.951 ± 0.009 |
| Held-out test accuracy | 648 unseen PhysioNet recordings | 92.75% |
| Cross-source (leave-one-source-out) | A recording setup never seen in training | ROC-AUC 0.569 ± 0.132 |
Feature engineering can't close that gap — band-pass, rhythm features, and augmentation all hit the same ~0.57 ceiling (see experiments/README.md). The real fix is collecting labeled audio from the actual device.
Instead of the naive 0.5 cutoff, the abnormal probability is turned into a three-way result using thresholds derived from out-of-fold reliability (train_model.py → model/thresholds.json, applied by decision.py):
| P(abnormal) | Verdict |
|---|---|
< 0.35 |
Clear (confidently normal) |
0.35 – 0.53 |
Inconclusive (model unreliable here — repeat / refer) |
≥ 0.53 |
Refer (confidently abnormal) |
This flags ~89% of abnormal recordings (refer or inconclusive) versus 71% at the default 0.5 cutoff — the right trade-off for a referral tool, where a missed case is far costlier than an extra referral. Inconclusive results still lead to follow-up, so nothing is falsely cleared.
- Python 3.11–3.14
- Chrome or Edge (for the Web Serial API), if using the hardware
git clone https://github.com/7tharva/CardioScan.git
cd CardioScan
python -m venv .venv
.venv\Scripts\pip install -r requirements.txt # Windows
# .venv/bin/pip install -r requirements.txt # macOS/Linux.venv\Scripts\python server.pyOpen http://localhost:5000. Plug in the Arduino and click Begin Scan, or use the built-in demo clips (no hardware needed):
Shift + N→ a normal-heart demoShift + A→ an abnormal/RHD demo
The server binds to 0.0.0.0, so from a phone or laptop on the same Wi-Fi open http://<this-PC-IP>:5000 (allow port 5000 through the firewall). The frontend calls whatever origin served the page, so LAN and tunnels work with no code edits.
Download PhysioNet/CinC 2016 from https://physionet.org/content/challenge-2016/1.0.0/, then point CARDIOSCAN_DATA_DIR at the folder containing training-a … training-f:
set CARDIOSCAN_DATA_DIR=path\to\training
.venv\Scripts\python train_model.pyThis regenerates model/rhd_model.pkl and model/thresholds.json and prints the full evaluation.
Self-contained smoke tests (only need the repo — model + demo clips):
.venv\Scripts\pip install -r requirements-dev.txt
.venv\Scripts\python -m pytest -qThey check the feature length, model compatibility, the decision bands, and that the bundled demo clips still classify correctly. CI runs them on every push (.github/workflows/tests.yml).
Host the app on Hugging Face Spaces (Docker) for a public URL anyone can use —
the bundled demo needs no dataset or hardware, and with an Arduino on the
visitor's own laptop the live Web Serial scan works too (over HTTPS). A
Dockerfile is included; see DEPLOY.md for step-by-step setup.
CardioScan/
├── index.html # Frontend (Web Serial capture, results UI)
├── server.py # Flask backend (inference, demo endpoints)
├── features.py # Shared 90-dim feature pipeline (used everywhere)
├── decision.py # Clear / Refer / Inconclusive thresholding
├── train_model.py # Training + evaluation + threshold derivation
├── detector.py # Standalone CLI detector over serial
├── test.py # Sanity-check predictions on dataset files
├── find_abnormal.py # Rank dataset clips by confidence (demo picking)
├── demo_audio/ # Bundled PhysioNet clips for the no-hardware demo
├── experiments/ # Cross-source generalization study
├── tests/ # pytest smoke tests
├── RHD_Arduino/ # Arduino firmware (.ino)
├── model/
│ ├── rhd_model.pkl # Trained Random Forest
│ └── thresholds.json # Data-driven decision thresholds
├── Dockerfile # Container for free hosting (see DEPLOY.md)
├── requirements.txt # Runtime dependencies (pinned)
├── requirements-dev.txt # + test dependencies
└── README.md
| Component | Specification |
|---|---|
| Microcontroller | Arduino Nano |
| Microphone | Electret microphone module (pin A0) |
| Connection | USB serial → Chrome Web Serial API |
Known limitation (the biggest open issue): the firmware streams samples over serial at 9600 baud, where transmission time — not the intended delay — sets the real sample rate (~130 Hz, not the assumed 500 Hz). Combined with the electret front-end, the device does not yet capture the full cardiac frequency range, and its recordings are an unseen domain for the model. A higher baud rate + fixed-timer sampling, a MEMS acoustic sensor, and on-device data collection are the priorities for a field-ready version.
- Fix the hardware sampling — 115200 baud + fixed-timer capture so the device delivers a known, consistent rate.
- Collect local device data — partner with CWM Hospital, Suva; label real recordings and retrain on the target domain (the only reliable fix for the cross-source gap).
- MEMS microphone upgrade — capture the full cardiac frequency range.
- Standalone deployment — Raspberry Pi / STM32 with a lightweight model.
- Community pilot — deploy across remote island clinics in Fiji.
| Layer | Technology |
|---|---|
| Frontend | HTML · CSS · Vanilla JS · Web Serial API |
| Backend | Python · Flask · flask-cors |
| ML / audio | scikit-learn · librosa · NumPy · SciPy |
| Hardware | Arduino Nano · electret microphone |
- PhysioNet/CinC Challenge 2016 — https://physionet.org/content/challenge-2016/
- Wyber et al. (2024), Rheumatic heart disease in the Pacific, The Lancet Regional Health
- Watkins et al. (2017), Global burden of rheumatic heart disease, NEJM
Built at the EWH × SUABE Designathon 2026. Thanks to organisers Sakura Brennan and Ayan Towhid.
MIT — free to use, modify, and distribute with attribution. Not a medical device.
CardioScan · Team HeartStoppers · University of Sydney · 2026