hmt_ecgnet / api.py
mahboobalam0's picture
Upload folder using huggingface_hub
43a9675 verified
Raw
History Blame Contribute Delete
3.04 kB
import os
import json
import numpy as np
import torch
from fastapi import FastAPI
from pydantic import BaseModel
from typing import List
from models.hmt_ecgnet import HMT_ECGNet
from config import N_LEADS
from transforms import preprocess_signal
# Setup
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
DIAG_CLASSES = ["NORM", "MI", "STTC", "CD", "HYP"]
with open("artifacts/multilabel_thresholds.json") as f:
MULTI_THRESHOLDS = json.load(f)
MI_BINARY_THRESHOLD = 0.05
app = FastAPI(title="ECG Diagnostic API")
# Find latest checkpoint automatically
def find_ckpt():
for root, _, files in os.walk("artifacts"):
if "multilabel_best.pth" in files:
return os.path.join(root, "multilabel_best.pth")
raise FileNotFoundError("multilabel_best.pth not found in artifacts/")
# Load model ONCE at startup
def load_model():
ckpt_path = find_ckpt()
model = HMT_ECGNet(num_classes=5, num_leads=N_LEADS).to(DEVICE)
# IMPORTANT FIX for PyTorch 2.6
ckpt = torch.load(ckpt_path, map_location=DEVICE, weights_only=False)
model.load_state_dict(ckpt["model_state_dict"])
model.eval()
return model
model = load_model()
# ECG Request Schema (real device style)
class ECGRequest(BaseModel):
lead1: List[float]
lead2: List[float]
lead3: List[float]
lead4: List[float]
lead5: List[float]
lead6: List[float]
lead7: List[float]
lead8: List[float]
lead9: List[float]
lead10: List[float]
lead11: List[float]
lead12: List[float]
# Prediction Endpoint
@app.post("/predict")
def predict_ecg(req: ECGRequest):
try:
# Combine leads into array
raw = np.array([
req.lead1, req.lead2, req.lead3, req.lead4,
req.lead5, req.lead6, req.lead7, req.lead8,
req.lead9, req.lead10, req.lead11, req.lead12
])
# Auto-select correct 10s window (like real systems)
fs = 500
window = 10 * fs
if raw.shape[1] > window:
start = raw.shape[1] // 2 - window // 2
raw = raw[:, start:start + window]
# Preprocess exactly like training
ecg = preprocess_signal(raw)
x = torch.tensor(ecg, dtype=torch.float32).unsqueeze(0).to(DEVICE)
# Model inference
with torch.no_grad():
probs = torch.sigmoid(model(x)).cpu().numpy()[0]
result = {}
predicted = []
for cls, p in zip(DIAG_CLASSES, probs):
thr = MULTI_THRESHOLDS[cls]
result[cls] = float(p)
if p >= thr:
predicted.append(cls)
mi_prob = float(probs[1])
return {
"probabilities": result,
"predicted_classes": predicted,
"mi_probability": mi_prob,
"mi_risk": mi_prob >= MI_BINARY_THRESHOLD,
}
except Exception as e:
return {"error": str(e)}