CoolFace
Modelpublic

rakibmist/ecg-cardiac-cnn

sourceHugging Facemitupdated 2d agoView on Hugging Face
0likes47downloads
Model Card

ECG Cardiac Patient Classifier

Four-class classification of ECG scan images using three ImageNet-pretrained backbones fine-tuned with a small classification head.

FileBackboneTrainable head
ResNet50.kerasResNet50, frozen, pooling='avg'Dense(16, relu) → Dense(4, softmax)
VGG16.kerasVGG16, frozen, pooling='avg'Dense(16, relu) → Dense(4, softmax)
EfficientNetB7.kerasEfficientNetB7, frozen, pooling='avg'Dense(16, relu) → Dense(4, softmax)

Classes

Softmax index order (alphabetical, as produced by image_dataset_from_directory):

IndexLabelMeaning
0HBHistory of myocardial infarction / abnormal heartbeat
1MIMyocardial infarction
2NormalNormal ECG
3PMIPrevious myocardial infarction

Input contract — read this

Input is a `(1, 224, 224, 3)` float32 tensor in the 0–255 range.

The training pipeline fed raw image_dataset_from_directory output straight into the backbone. A Resizing + Rescaling(1./255) block was defined in the notebook but never attached to the model graph, so the weights are adapted to 0–255 inputs.

  • —Do not divide by 255.
  • —Do not call tf.keras.applications.resnet50.preprocess_input (or the VGG16 / EfficientNet equivalents).
  • —Resize with bilinear interpolation to 224×224 without preserving aspect ratio.

Getting this wrong does not raise an error — it silently returns confident, wrong predictions.

Usage

python
import numpy as np, tensorflow as tf
from PIL import Image
from huggingface_hub import hf_hub_download

path = hf_hub_download("YOUR_USERNAME/ecg-cardiac-cnn", "ResNet50.keras")
model = tf.keras.models.load_model(path, compile=False)

img = Image.open("ecg.png").convert("RGB")
arr = np.asarray(img, dtype=np.float32)                      # 0-255, no rescale
arr = tf.image.resize(arr, [224, 224], method="bilinear", antialias=False)
probs = model.predict(tf.expand_dims(arr, 0))[0]

classes = ["HB", "MI", "Normal", "PMI"]
print(classes[int(np.argmax(probs))], float(np.max(probs)))

Training

Adam (lr 1e-3), sparse categorical cross-entropy, 50 epochs, batch size 128, 80/20 train/test split, backbones frozen throughout. Evaluated with accuracy, weighted F1 / precision / recall, MCC, per-class ROC AUC and a confusion matrix.

Limitations and intended use

Research and educational use only. This is not a medical device, has not been clinically validated, and must not be used for diagnosis, triage, or treatment decisions. The classifier reads rendered ECG images, not raw signal data, so results depend heavily on the scan style, resolution and layout of the source dataset — expect degradation on ECGs captured or rendered differently. With frozen backbones and a 16-unit head, capacity is limited, and the held-out split was drawn from the same source dataset, so reported metrics are an optimistic estimate of real-world performance. Class balance was not corrected for.