PulseML is an end-to-end AI health risk assessment system that classifies a patient's cardiovascular status into three risk levels — Normal, Medium, and High — using only two inputs: Heart Rate (BPM) and Blood Oxygen Saturation (SpO₂). The application runs entirely in the browser with no backend server, powered by a Random Forest model compiled to JavaScript via m2cgen and deployed on Firebase Hosting.
Disclaimer: This project is intended for research and educational purposes only. It is not a substitute for professional medical diagnosis or clinical equipment.
| Resource | File | Description |
|---|---|---|
| Presentation | Pulse ML.pptx | Full project presentation slides |
| Jupyter Notebook | Pulse_ML.ipynb | Complete ML training pipeline (Google Colab) |
| Notebook PDF | Pulse_ML.ipynb.pdf | Printable PDF export of the notebook |
| Trained Models | PulseML_Models.zip | pulse_ox_model.pkl + label_encoder.pkl |
| Demo Video | PulseML_mp4.mp4 | Full application walkthrough (MP4) |
The GIF below shows a live walkthrough of the application. Click the link beneath it to download the full-quality MP4 video.
The physical layer of PulseML uses a MAX30100 pulse oximeter sensor interfaced with an ESP8266 NodeMCU microcontroller over I²C. The sensor reads Heart Rate and SpO₂ values directly from the patient's fingertip and transmits them wirelessly over Wi-Fi to the Blynk cloud platform for real-time monitoring.
Figure 1 — Circuit Wiring Diagram. The MAX30100 sensor connects to the NodeMCU at 3.3V power, shared ground, SDA on D2 (GPIO4), and SCL on D1 (GPIO5). The NodeMCU is programmed via USB and thereafter operates independently on Wi-Fi, posting readings to the Blynk server every 500 ms.
The Blynk mobile dashboard provides a real-time visualization of the sensor stream. Virtual pins V1 (Heart Rate) and V2 (SpO₂) receive live data from the ESP8266. The dashboard includes a Gauge widget for SpO₂ percentage, a SuperChart for trend analysis, and LED alert indicators that activate when readings fall outside normal ranges.
Figure 2 — Blynk Mobile Dashboard. The companion app monitors the patient's vitals in real time. Alerts are configured to trigger when Heart Rate exceeds 100 BPM or SpO₂ drops below 95%, providing immediate visual notification on the clinician's or caregiver's mobile device.
The following table documents every screen state of the PulseML web application in the order a user would encounter them during a full session. Scroll the screenshot row horizontally to browse all screens.
The following slides are from the official project presentation. They cover the project's motivation, architecture, dataset, model training, results, and deployment strategy.
| Slide | Preview |
|---|---|
| 1 | ![]() |
| 2 | ![]() |
| 3 | ![]() |
| 4 | ![]() |
| 5 | ![]() |
| 6 | ![]() |
| 7 | ![]() |
| 8 | ![]() |
| 9 | ![]() |
| 10 | ![]() |
| 11 | ![]() |
| 12 | ![]() |
| 13 | ![]() |
Download Full Presentation (PPTX)
PulseML is structured in three distinct layers that together form a complete healthcare monitoring pipeline.
Layer 1 — IoT Hardware. A MAX30100 sensor attached to the fingertip captures Heart Rate and SpO₂ readings. An ESP8266 NodeMCU microcontroller reads the sensor data over I²C and transmits it in real time to the Blynk cloud platform over Wi-Fi. This provides continuous, live monitoring on a clinician's mobile device.
Layer 2 — Machine Learning. A Random Forest Classifier is trained on 60,000 synthetic patient records sourced from a Kaggle healthcare monitoring dataset. The model takes two input features — Heart Rate and Oxygen Saturation — and classifies each reading into one of three risk categories: Normal, Medium, or High. The trained model is then exported to a pure JavaScript function using the m2cgen library, eliminating any server-side Python dependency at inference time.
Layer 3 — Web Application. The JavaScript model runs entirely inside the user's browser. The application is deployed as a static site on Firebase Hosting, making it globally accessible with zero backend infrastructure. Firebase Anonymous Authentication and Firestore are used to log every assessment for aggregate analytics. When a High-risk result is detected, the application automatically locates the nearest hospitals using the browser's Geolocation API and the OpenStreetMap Overpass API, displaying directions and contact information within the same interface.
| Property | Value |
|---|---|
| Source | Kaggle — Patient Healthcare Monitoring System (gourangomandal) |
| Total Records | 60,000 synthetic patient records |
| Features Used | Heart Rate (BPM), Oxygen Saturation (%) |
| Target Column | Risk_Level — engineered from alert flags |
| Class Distribution | Normal: 23,685 · Medium: 24,392 · High: 11,923 |
The dataset does not include a ready-made risk label. Labels were derived from two binary alert columns:
Both alerts NORMAL → Normal
Both alerts ABNORMAL → High
One NORMAL, one ABNORMAL → Medium
| Property | Value |
|---|---|
| Algorithm | Random Forest Classifier |
| Number of Trees | 100 |
| Train / Test Split | 80% / 20% (48,000 train · 12,000 test) |
| Random State | 42 |
| Training Platform | Google Colab (NVIDIA T4 GPU) |
| Final Accuracy | 100.00% |
precision recall f1-score support
High 1.00 1.00 1.00 2374
Medium 1.00 1.00 1.00 4798
Normal 1.00 1.00 1.00 4828
accuracy 1.00 12000
macro avg 1.00 1.00 1.00 12000
weighted avg 1.00 1.00 1.00 12000
After training, the scikit-learn RandomForestClassifier object is converted to a self-contained JavaScript function using the m2cgen library. The output file model.js (≈242 KB) exposes a single score([bpm, spo2]) function that returns class probability arrays — no Python runtime, no Flask server, no network request required at inference time.
# export_model.py
import m2cgen as m2c, joblib
model = joblib.load("ml_models/pulse_ox_model.pkl")
code = m2c.export_to_javascript(model)
with open("public/model.js", "w") as f:
f.write(code)The browser calls score([bpm, spo2]) and receives a probability array [p_high, p_medium, p_normal]. The class with the highest probability becomes the predicted risk label, and its probability is reported as the AI confidence percentage.
The Jupyter Notebook Pulse_ML.ipynb documents the full training pipeline across six cells.
Cell 1 — Dataset Download. Uses kagglehub to download and extract the 744 KB Kaggle dataset directly into the Colab environment, then locates the CSV automatically using a glob search.
Cell 2 — Data Preprocessing. Renames the Heart Rate (bpm) and SpO2 Level (%) columns to the model-expected names Heart_Rate and Oxygen_Saturation. Applies the risk label engineering function row-wise to create the Risk_Level target column. Filters the DataFrame down to the three required columns.
Cell 3 — Label Encoding. Encodes the string class labels (High, Medium, Normal) to integer indices (0, 1, 2) using sklearn.preprocessing.LabelEncoder. Saves the fitted encoder to label_encoder.pkl for later use during the inverse-transform step at prediction time.
Cell 4 — Model Training. Initialises a RandomForestClassifier with 100 estimators, fits it on 48,000 training records, and evaluates it on 12,000 test records. Achieves 100% accuracy across all three classes. Saves the trained model to pulse_ox_model.pkl.
Cell 5 & 6 — Prediction Validation. Loads both saved files and runs a test prediction on HR = 145 BPM, SpO₂ = 88%. The model returns High Risk (100.00% Confidence) with the advice message "EMERGENCY: Immediate medical attention required." — confirming that the pipeline is production-ready.
┌──────────────────────────────────────────────────────────┐
│ FIREBASE HOSTING │
│ │
│ index.html ── style.css ── script.js ── model.js │
│ (UI) (Dark (App Logic) (m2cgen JS │
│ Theme) RF model) │
└────────────────────────┬─────────────────────────────────┘
│
┌───────────────┼──────────────────┐
│ │ │
┌────────▼───────┐ ┌─────▼────────┐ ┌──────▼───────────┐
│ Firebase Auth │ │ Firestore │ │ Overpass API + │
│ (Anonymous │ │ (Assessment │ │ Nominatim │
│ Sign-In) │ │ Logging) │ │ (Hospital Finder│
└────────────────┘ └──────────────┘ └──────────────────┘
| File | Role |
|---|---|
public/index.html |
Application UI — form inputs, result section, history panel, hospital finder modal |
public/style.css |
Dark-mode design system — glassmorphism cards, animations, responsive grid |
public/script.js |
Core application logic — boot sequence, prediction, Firestore writes, hospital API calls |
public/model.js |
242 KB m2cgen-compiled Random Forest — exposes the score() inference function |
firebase.json |
Firebase Hosting configuration and rewrite rules |
firestore.rules |
Firestore security rules permitting anonymous authenticated writes |
app.py |
Original Flask backend — retained for reference; replaced by client-side inference |
export_model.py |
Converts pulse_ox_model.pkl to model.js using m2cgen |
The header status indicator runs a randomised 3–5 second boot sequence on every page load. The colour transitions from Red (Initialising) to Yellow (Loading model) to Green (Model Active). Once active, the status pill becomes clickable and triggers a download of PulseML_Models.zip.
Prediction runs entirely in the browser by calling score([bpm, spo2]) from the compiled model.js. Input is validated against physiological bounds (BPM: 30–300, SpO₂: 0–100) before being passed to the model. Values outside these bounds return a Signal Quality Warning rather than a risk classification.
A 4.2-second animated overlay simulates the AI pipeline after the user submits the form. It cycles through four labelled stages — Reading vitals, Pre-processing data, Running AI model, Interpreting results — with a progress bar and smooth text fade transitions.
Each successful prediction is written anonymously to the Firestore assessments collection. The document stores the BPM, SpO₂, risk label, confidence score, advice text, user UID, and a server-generated timestamp.
Up to 10 previous assessments are stored in localStorage under the key pulseml_history. The history panel renders all entries sorted by recency. Each entry is an interactive card — clicking it re-renders the result panel exactly as it appeared at the time of prediction.
The hospital finder follows a multi-stage fallback flow:
High-risk result detected
│
▼
User selects "Find Nearby Hospitals"
│
▼
Geolocation API (GPS)
├── Success → Overpass API query (30 km radius)
│ ├── Results found → Display top 3 hospitals
│ └── No results → Retry at 50 km radius
│ ├── Found → Display top 3
│ └── None → "No hospitals found" screen
└── Denied → Manual location input
└── Nominatim geocoding → coordinates → Overpass query (same flow)
All hospital results are sorted by Haversine distance. Each card includes the facility name, distance, a Google Maps Directions deep-link, and a tap-to-call button where a phone number is available in OpenStreetMap. The emergency helpline 112 is always displayed at the bottom of the results screen.
| Component | Specification | Purpose |
|---|---|---|
| ESP8266 NodeMCU | v1.0 (CH340 USB) | Wi-Fi microcontroller |
| MAX30100 | I²C pulse oximeter | Heart Rate + SpO₂ sensing |
| Blynk App | iOS / Android | Real-time IoT dashboard |
| Jumper Wires | Male-to-Female | I²C interconnect |
| USB Cable | Micro-USB | Power and serial programming |
| MAX30100 Pin | NodeMCU Pin | Notes |
|---|---|---|
| VIN | 3.3V | Do not use 5V — damages sensor |
| GND | GND | Shared ground |
| SDA | D2 (GPIO4) | I²C data |
| SCL | D1 (GPIO5) | I²C clock |
| Widget | Virtual Pin | Data Source |
|---|---|---|
| Gauge | V2 | SpO₂ (%) |
| SuperChart | V1, V2 | Heart Rate + SpO₂ trend |
| LED Indicator | V3 | Abnormal alert flag |
git clone <repository-url>
cd "Pulse ML"
# Using Node.js
npx serve public
# Or using Python
python -m http.server 8080 --directory publicOpen http://localhost:8080 in a browser.
The original Flask server (app.py) loads pulse_ox_model.pkl and exposes a /predict REST endpoint. It is retained for reference but is not required for the deployed application.
pip install -r requirements.txt
python app.pyOpen http://localhost:5000.
- Open
Pulse_ML.ipynbin Google Colab. - Set the runtime to GPU (T4).
- Run all cells sequentially. The notebook downloads the dataset, trains the model, and saves
pulse_ox_model.pklandlabel_encoder.pkl. - Download both files into
ml_models/. - Run
export_model.pyto regeneratepublic/model.js.
Pulse ML/
├── public/
│ ├── index.html # Main application UI
│ ├── style.css # Dark-mode stylesheet
│ ├── script.js # App logic and API integrations
│ ├── model.js # m2cgen Random Forest (242 KB)
│ ├── Pulse_ML.ipynb # ML training notebook
│ ├── Pulse_ML.ipynb.pdf # Notebook PDF export
│ ├── Pulse ML.pptx # Project presentation
│ ├── PulseML_Models.zip # Trained model files (.pkl)
│ ├── PulseML_gif.gif # Demo animation
│ ├── PulseML_mp4.mp4 # Full demo video
│ ├── images/ # Application screenshots
│ │ ├── PulseML_homepage.png
│ │ ├── PulseML_processing.png
│ │ ├── PulseML_normalresult.png
│ │ ├── PulseML_highrisk.png
│ │ ├── PulseML_model.png
│ │ ├── PulseML_history.png
│ │ ├── PulseML_EmergencyDetected.png
│ │ ├── PulseML_detecinglocation.png
│ │ ├── PulseML_enterlocation.png
│ │ ├── PulseML_searching hospital.png
│ │ ├── PulseML_hospitalslist.png
│ │ ├── PulseML_112.png
│ │ ├── Circuit.png
│ │ └── Blynk app.png
│ └── ppt_pmg/ # PPT slide exports (PNG)
│ ├── page (1).png
│ ├── ...
│ └── page (13).png
├── ml_models/ # Scikit-learn model files
├── app.py # Legacy Flask backend
├── export_model.py # model.pkl → model.js converter
├── firebase.json # Firebase Hosting config
├── firestore.rules # Firestore security rules
├── requirements.txt # Python dependencies
└── README.md
| Layer | Technology |
|---|---|
| ML Training | Python 3, scikit-learn, pandas, Google Colab (T4 GPU) |
| Model Export | m2cgen |
| Frontend | HTML5, CSS3, JavaScript ES2022 (ES Modules) |
| Typography | Inter — Google Fonts |
| Authentication | Firebase Anonymous Auth |
| Database | Firebase Firestore |
| Hosting | Firebase Hosting |
| Geocoding | OpenStreetMap Nominatim |
| Hospital Search | OpenStreetMap Overpass API |
| Navigation | Google Maps Directions (deep-link) |
| IoT Hardware | ESP8266 NodeMCU + MAX30100 |
| IoT Dashboard | Blynk |
| Legacy Backend | Flask, Flask-CORS, joblib |
Made with ❤️ by Kunal Rabidas
PulseML · AI-Powered Diagnostic Interface · For Research Use Only















.png)
.png)
.png)
.png)
.png)
.png)
.png)
.png)
.png)
.png)
.png)
.png)
.png)