Give me 10 ecg changes with ecg example
ECG changes examples with images STEMI LBBB hyperkalemia LVH atrial fibrillation
ECG examples site:litfl.com OR site:ecglibrary.com 10 classic ECG patterns images
https://litfl.com/ecg-library
site:litfl.com ECG atrial fibrillation STEMI hyperkalemia WPW QT prolongation images


| Lead | Finding |
|---|---|
| All leads | No discernible P waves |
| Rhythm | Completely irregular |
| V1 | Fibrillatory baseline most visible |
| K⁺ Level | ECG Change |
|---|---|
| 5.5-6.5 mmol/L | Tall, peaked ("tented") T waves - narrow base, symmetric |
| 6.5-7.0 mmol/L | Prolonged PR interval, widened QRS |
| 7.0-8.0 mmol/L | Loss of P waves, further QRS widening |
| >8.0 mmol/L | Sine wave pattern, VF, asystole |
| # | ECG Change | Key Feature | Cause |
|---|---|---|---|
| 1 | STEMI | ST elevation + reciprocal depression | Coronary occlusion |
| 2 | Atrial Fibrillation | No P waves, irregularly irregular | Multiple atrial foci |
| 3 | LBBB | WiLLiaM pattern, QRS >120 ms | Conduction disease, MI |
| 4 | Hyperkalemia | Peaked T waves → sine wave | Renal failure, drugs |
| 5 | WPW | Delta wave, short PR | Accessory pathway |
| 6 | Pulmonary Embolism | S1Q3T3, sinus tachycardia | RV strain |
| 7 | 3rd Degree AV Block | P-QRS dissociation | Ischemia, Lyme, drugs |
| 8 | Brugada Syndrome | Coved ST V1-V3 | Sodium channel mutation |
| 9 | QT Prolongation/TdP | QTc >440 ms, twisting VT | Drugs, electrolytes |
| 10 | LVH | Tall QRS voltages + strain | Hypertension, aortic stenosis |
Create a visual ECG reference card (PDF) with all 10 patterns
mkdir -p /home/daytona/workspace/ecg-reference-card && ls /home/daytona/workspace/
~/ecg-reference-card/generate_ecg_card.py
"""
ECG Reference Card PDF - 10 Classic ECG Patterns
Uses ReportLab to create a professional A4 reference card with hand-drawn ECG waveforms.
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.platypus import SimpleDocTemplate, Spacer
from reportlab.pdfgen import canvas
from reportlab.lib.colors import HexColor, white, black
import math
W, H = A4 # 595 x 842 pts
# ── Color palette ────────────────────────────────────────────────────────────
BG_DARK = HexColor("#0D1B2A") # deep navy background
BG_CARD = HexColor("#112233") # card background
GRID_COLOR = HexColor("#1A3A5C") # ECG grid lines
ECG_GREEN = HexColor("#00FF88") # ECG trace (bright green like monitors)
ECG_YELLOW = HexColor("#FFD700") # highlight / accent
ECG_RED = HexColor("#FF4560") # danger/alert labels
ECG_BLUE = HexColor("#00B4D8") # info labels
ECG_ORANGE = HexColor("#FF8C00") # annotation
TEXT_WHITE = HexColor("#FFFFFF")
TEXT_LIGHT = HexColor("#B0C4DE")
TEXT_DIM = HexColor("#607B8B")
HEADER_BG = HexColor("#001F3F")
ACCENT = HexColor("#00FF88")
MARGIN = 14*mm
COLS = 2
ROWS = 5
HEADER_H = 28*mm
FOOTER_H = 10*mm
GRID_W = (W - 2*MARGIN - 4*mm) / COLS
GRID_H = (H - HEADER_H - FOOTER_H - 2*MARGIN - (ROWS-1)*3*mm) / ROWS
def draw_ecg_grid(c, x, y, w, h):
"""Draw ECG grid background."""
c.saveState()
c.setFillColor(BG_CARD)
c.rect(x, y, w, h, fill=1, stroke=0)
# Fine grid (1mm equivalent in points = ~2.83 pts)
step = 5
c.setStrokeColor(GRID_COLOR)
c.setLineWidth(0.3)
xi = x
while xi <= x + w:
c.line(xi, y, xi, y + h)
xi += step
yi = y
while yi <= y + h:
c.line(x, yi, x + w, yi)
yi += step
# Bold grid every 5 steps
c.setLineWidth(0.7)
xi = x
while xi <= x + w:
c.line(xi, y, xi, y + h)
xi += step * 5
yi = y
while yi <= y + h:
c.line(x, yi, x + w, yi)
yi += step * 5
c.restoreState()
def draw_label_box(c, x, y, w, h, title, color, subtitle=""):
"""Draw card header with colored title."""
c.saveState()
c.setFillColor(color)
c.rect(x, y + h - 14, w, 14, fill=1, stroke=0)
c.setFillColor(BG_DARK)
c.setFont("Helvetica-Bold", 8)
c.drawCentredString(x + w/2, y + h - 10, title)
if subtitle:
c.setFillColor(TEXT_DIM)
c.setFont("Helvetica", 6)
c.drawCentredString(x + w/2, y + h - 20, subtitle)
c.restoreState()
def draw_annotation(c, x, y, text, color=ECG_YELLOW, size=5.5):
c.saveState()
c.setFillColor(color)
c.setFont("Helvetica-Bold", size)
c.drawString(x, y, text)
c.restoreState()
def draw_arrow_label(c, x1, y1, x2, y2, text, color=ECG_YELLOW):
"""Draw an arrow pointing to ECG feature with label."""
c.saveState()
c.setStrokeColor(color)
c.setFillColor(color)
c.setLineWidth(0.7)
c.line(x1, y1, x2, y2)
# arrowhead
angle = math.atan2(y2 - y1, x2 - x1)
al = 4
c.line(x2, y2,
x2 - al * math.cos(angle - 0.4),
y2 - al * math.sin(angle - 0.4))
c.line(x2, y2,
x2 - al * math.cos(angle + 0.4),
y2 - al * math.sin(angle + 0.4))
c.setFont("Helvetica-Bold", 5)
c.drawString(x1 - 2, y1 + 2, text)
c.restoreState()
def trace(c, points, color=ECG_GREEN, lw=1.2):
"""Draw ECG trace from list of (x,y) points."""
c.saveState()
c.setStrokeColor(color)
c.setLineWidth(lw)
p = c.beginPath()
p.moveTo(points[0][0], points[0][1])
for pt in points[1:]:
p.lineTo(pt[0], pt[1])
c.drawPath(p, stroke=1, fill=0)
c.restoreState()
def ecg_baseline(x, y, w, count=3):
"""Return baseline points for 'count' repeating patterns across width w."""
return [(x, y), (x + w, y)]
# ─────────────────────────────────────────────────────────────────────────────
# ECG PATTERN DRAWING FUNCTIONS
# Each takes (c, x, y, w, h) where x,y = bottom-left of card area
# ─────────────────────────────────────────────────────────────────────────────
def draw_normal_ecg(c, x, y, w, h):
"""Normal sinus rhythm for reference."""
mid = y + h * 0.45
seg = w / 3
def beat(ox):
return [
(ox, mid),
(ox + seg*0.10, mid),
(ox + seg*0.13, mid + 3), # P wave up
(ox + seg*0.17, mid - 3), # P wave down
(ox + seg*0.20, mid),
(ox + seg*0.24, mid),
(ox + seg*0.26, mid - 5), # Q
(ox + seg*0.30, mid + 22), # R peak
(ox + seg*0.34, mid - 8), # S
(ox + seg*0.40, mid),
(ox + seg*0.50, mid + 3), # T wave
(ox + seg*0.60, mid),
(ox + seg*1.0, mid),
]
pts = beat(x + 2)
pts2 = beat(x + 2 + seg)
pts3 = beat(x + 2 + 2*seg)
trace(c, pts)
trace(c, pts2)
trace(c, pts3)
def draw_stemi(c, x, y, w, h):
mid = y + h * 0.42
seg = w / 2.8
def beat(ox):
return [
(ox, mid),
(ox + seg*0.10, mid),
(ox + seg*0.13, mid + 4), # P wave
(ox + seg*0.18, mid),
(ox + seg*0.22, mid),
(ox + seg*0.25, mid - 4), # Q
(ox + seg*0.30, mid + 28), # Tall R
(ox + seg*0.35, mid + 10), # ST elevation start
(ox + seg*0.55, mid + 10), # Flat ST elevation
(ox + seg*0.70, mid + 6), # T wave peak
(ox + seg*0.80, mid),
(ox + seg*1.00, mid),
]
for i in range(2):
pts = beat(x + 4 + i * seg)
trace(c, pts, ECG_RED, 1.4)
# Annotations
draw_annotation(c, x + seg*0.55 + 4, mid + 12, "ST↑", ECG_RED, 6)
draw_annotation(c, x + 8, mid + 31, "Hyperacute T", ECG_YELLOW, 5)
# Reciprocal depression hint
mid2 = y + h * 0.25
rec = [
(x + 4, mid2), (x + seg*0.5 + 4, mid2),
(x + seg*0.55 + 4, mid2 - 7),
(x + seg*0.7 + 4, mid2 - 7),
(x + seg*0.85 + 4, mid2),
(x + seg + 4, mid2),
]
trace(c, rec, ECG_ORANGE, 0.9)
draw_annotation(c, x + seg*0.55 + 4, mid2 - 12, "Reciprocal↓", ECG_ORANGE, 4.5)
def draw_af(c, x, y, w, h):
mid = y + h * 0.45
import random
random.seed(42)
# Irregular baseline (fibrillatory)
pts = [(x + 3, mid)]
bx = x + 3
while bx < x + w - 10:
bx += random.uniform(1.5, 3.5)
dy = random.uniform(-3, 3)
pts.append((bx, mid + dy))
trace(c, pts, HexColor("#607B8B"), 0.6)
# Irregularly irregular QRS - no P waves
beats = [x + 18, x + 42, x + 58, x + 82, x + 102, x + 120, x + 148]
for bx in beats:
qrs = [
(bx, mid),
(bx + 2, mid - 4), # Q
(bx + 4, mid + 22), # R
(bx + 6, mid - 6), # S
(bx + 10, mid),
(bx + 16, mid + 2), # T wave small
(bx + 22, mid),
]
trace(c, qrs, ECG_GREEN, 1.3)
draw_annotation(c, x + 5, y + h - 26, "No P waves", ECG_RED, 5.5)
draw_annotation(c, x + 5, y + h - 34, "Irregular R-R", ECG_YELLOW, 5.5)
def draw_lbbb(c, x, y, w, h):
mid = y + h * 0.45
seg = w / 2.5
def beat(ox):
return [
(ox, mid),
(ox + seg*0.10, mid),
(ox + seg*0.13, mid + 3), # P wave
(ox + seg*0.18, mid),
(ox + seg*0.22, mid),
# Broad notched M-shape R in V6 (no Q wave)
(ox + seg*0.25, mid + 8), # first R hump
(ox + seg*0.32, mid + 4), # notch
(ox + seg*0.40, mid + 20), # second R hump (broad)
(ox + seg*0.48, mid),
(ox + seg*0.60, mid - 6), # ST depression (discordant)
(ox + seg*0.75, mid - 8), # T wave inverted (discordant)
(ox + seg*0.85, mid),
(ox + seg*1.00, mid),
]
for i in range(2):
pts = beat(x + 4 + i * seg)
trace(c, pts, ECG_GREEN, 1.3)
draw_annotation(c, x + 8, y + h - 26, "Broad notched R (M-shape)", ECG_YELLOW, 5)
draw_annotation(c, x + 8, y + h - 34, "QRS >120ms | No septal Q", ECG_ORANGE, 5)
# QRS width arrow
c.saveState()
c.setStrokeColor(ECG_BLUE)
c.setLineWidth(0.7)
bx = x + 4 + seg*0.22
ex = x + 4 + seg*0.48
by = mid - 12
c.line(bx, by, ex, by)
c.line(bx, by - 2, bx, by + 2)
c.line(ex, by - 2, ex, by + 2)
c.setFillColor(ECG_BLUE)
c.setFont("Helvetica-Bold", 5)
c.drawCentredString((bx + ex)/2, by - 8, ">120ms")
c.restoreState()
def draw_hyperkalemia(c, x, y, w, h):
mid = y + h * 0.45
seg = w / 2.8
def beat(ox, k_level="high"):
if k_level == "moderate":
# Peaked T, widened QRS, flat P
return [
(ox, mid),
(ox + seg*0.10, mid),
(ox + seg*0.13, mid + 1), # Flat P wave
(ox + seg*0.18, mid),
(ox + seg*0.22, mid),
(ox + seg*0.25, mid - 4), # Q
(ox + seg*0.30, mid + 18), # R
(ox + seg*0.36, mid - 5), # S
(ox + seg*0.42, mid),
# Tall narrow peaked T
(ox + seg*0.50, mid + 2),
(ox + seg*0.56, mid + 20), # PEAKED T
(ox + seg*0.62, mid + 2),
(ox + seg*0.70, mid),
(ox + seg*1.00, mid),
]
else:
# Severe: wide QRS, sine wave
return [
(ox, mid),
(ox + seg*0.05, mid + 6),
(ox + seg*0.20, mid + 22), # broad R
(ox + seg*0.35, mid),
(ox + seg*0.45, mid - 10), # broad S
(ox + seg*0.60, mid + 4),
(ox + seg*0.80, mid + 18), # sine T
(ox + seg*1.00, mid + 2),
]
# Two moderate beats
for i in range(2):
pts = beat(x + 4 + i * seg, "moderate")
trace(c, pts, ECG_GREEN, 1.2)
# One severe beat
pts2 = beat(x + 4 + 2*seg, "high")
trace(c, pts2, ECG_RED, 1.3)
draw_annotation(c, x + 8, y + h - 26, "Peaked T waves → Sine wave", ECG_YELLOW, 5)
draw_annotation(c, x + 8, y + h - 34, "Wide QRS | Flat/absent P", ECG_RED, 5)
# Label peaked T
tx = x + 4 + seg*0.56
ty = mid + 22
c.saveState()
c.setStrokeColor(ECG_ORANGE)
c.setFillColor(ECG_ORANGE)
c.setLineWidth(0.6)
c.line(tx, ty + 2, tx, ty + 8)
c.setFont("Helvetica-Bold", 5)
c.drawCentredString(tx, ty + 10, "Peaked T")
c.restoreState()
def draw_wpw(c, x, y, w, h):
mid = y + h * 0.45
seg = w / 2.8
def beat(ox):
return [
(ox, mid),
(ox + seg*0.08, mid),
(ox + seg*0.10, mid + 3), # P wave
(ox + seg*0.15, mid),
# Short PR then delta wave
(ox + seg*0.17, mid), # short PR
(ox + seg*0.20, mid + 3), # delta wave slur start
(ox + seg*0.28, mid + 22), # R peak (broad QRS)
(ox + seg*0.35, mid - 6), # S
(ox + seg*0.42, mid),
(ox + seg*0.52, mid + 4), # T wave
(ox + seg*0.62, mid),
(ox + seg*1.00, mid),
]
for i in range(2):
pts = beat(x + 4 + i * seg)
trace(c, pts, ECG_GREEN, 1.3)
draw_annotation(c, x + 8, y + h - 26, "Short PR (<120ms)", ECG_YELLOW, 5)
draw_annotation(c, x + 8, y + h - 34, "Delta wave | Broad QRS", ECG_ORANGE, 5)
# Delta wave annotation
dx = x + 4 + seg*0.20
dy = mid + 3
c.saveState()
c.setStrokeColor(ECG_RED)
c.setFillColor(ECG_RED)
c.setLineWidth(0.7)
c.line(dx, dy + 2, dx - 4, dy + 12)
c.setFont("Helvetica-Bold", 5)
c.drawString(dx - 16, dy + 14, "δ wave")
# PR bracket
c.setStrokeColor(ECG_BLUE)
p_start = x + 4 + seg*0.10
p_end = x + 4 + seg*0.17
c.line(p_start, mid - 8, p_end, mid - 8)
c.line(p_start, mid - 6, p_start, mid - 10)
c.line(p_end, mid - 6, p_end, mid - 10)
c.setFillColor(ECG_BLUE)
c.setFont("Helvetica-Bold", 5)
c.drawCentredString((p_start + p_end)/2, mid - 16, "Short PR")
c.restoreState()
def draw_pe(c, x, y, w, h):
mid = y + h * 0.50
seg = w / 2.8
# Lead I: S wave
def beat_I(ox):
return [
(ox, mid), (ox + seg*0.08, mid),
(ox + seg*0.11, mid + 3), # P
(ox + seg*0.16, mid),
(ox + seg*0.20, mid),
(ox + seg*0.23, mid - 2),
(ox + seg*0.26, mid + 16), # R
(ox + seg*0.30, mid - 10), # Deep S in I
(ox + seg*0.40, mid),
(ox + seg*0.50, mid + 3),
(ox + seg*0.60, mid),
(ox + seg*1.00, mid),
]
# Lead III: Q wave + T inversion
def beat_III(ox):
return [
(ox, mid), (ox + seg*0.08, mid),
(ox + seg*0.11, mid + 2), # P
(ox + seg*0.16, mid),
(ox + seg*0.20, mid - 6), # Q wave in III
(ox + seg*0.24, mid + 14), # R
(ox + seg*0.29, mid - 2),
(ox + seg*0.35, mid),
(ox + seg*0.45, mid - 5), # T inversion in III
(ox + seg*0.60, mid),
(ox + seg*1.00, mid),
]
# Two strips: top = I, bottom = III
mid_I = y + h * 0.65
mid_III = y + h * 0.30
for i in range(2):
trace(c, beat_I(x + 4 + i*seg), ECG_GREEN, 1.1)
for i in range(2):
pts3 = beat_III(x + 4 + i*seg)
pts3_shifted = [(px, py - (mid - mid_III)) for px, py in pts3]
trace(c, pts3_shifted, HexColor("#88DDFF"), 1.1)
draw_annotation(c, x + 5, mid_I + 8, "Lead I: Deep S", ECG_GREEN, 5)
draw_annotation(c, x + 5, mid_III + 8, "Lead III: Q+T inv", ECG_BLUE, 5)
draw_annotation(c, x + 5, y + h - 26, "S1Q3T3 Pattern", ECG_YELLOW, 5.5)
draw_annotation(c, x + 5, y + h - 34, "Sinus tachycardia | RV strain", ECG_ORANGE, 5)
def draw_chb(c, x, y, w, h):
"""Complete (3rd degree) heart block."""
mid = y + h * 0.5
p_mid = y + h * 0.72 # P wave level
qrs_mid = y + h * 0.40 # QRS level
seg_p = (w - 8) / 5 # P waves more frequent
seg_qrs = (w - 8) / 3 # QRS slower
# P waves (atrial rate ~75)
for i in range(5):
ox = x + 4 + i * seg_p
pw = [
(ox, p_mid), (ox + 3, p_mid + 4), (ox + 6, p_mid), (ox + seg_p, p_mid)
]
trace(c, pw, ECG_ORANGE, 1.0)
# QRS complexes (ventricular escape rate ~35)
for i in range(3):
ox = x + 4 + i * seg_qrs
qrs = [
(ox, qrs_mid),
(ox + 4, qrs_mid),
(ox + 6, qrs_mid - 5),
(ox + 9, qrs_mid + 18),
(ox + 13, qrs_mid - 6),
(ox + 18, qrs_mid),
(ox + 26, qrs_mid + 5), # T wave
(ox + 36, qrs_mid),
(ox + seg_qrs, qrs_mid),
]
trace(c, qrs, ECG_GREEN, 1.3)
draw_annotation(c, x + 5, y + h - 22, "P waves (orange): regular ~75bpm", ECG_ORANGE, 5)
draw_annotation(c, x + 5, y + h - 30, "QRS (green): regular ~35bpm", ECG_GREEN, 5)
draw_annotation(c, x + 5, y + h - 38, "No fixed PR - Complete dissociation", ECG_RED, 5)
def draw_brugada(c, x, y, w, h):
mid = y + h * 0.45
seg = w / 2.8
def beat(ox):
return [
(ox, mid),
(ox + seg*0.08, mid),
(ox + seg*0.11, mid + 3), # P
(ox + seg*0.16, mid),
(ox + seg*0.20, mid),
(ox + seg*0.22, mid - 3), # small Q
(ox + seg*0.26, mid + 20), # R
# Coved ST elevation: downsloping ST
(ox + seg*0.30, mid + 16),
(ox + seg*0.40, mid + 8),
(ox + seg*0.50, mid + 2),
(ox + seg*0.55, mid - 6), # T wave INVERTED (negative)
(ox + seg*0.65, mid),
(ox + seg*1.00, mid),
]
for i in range(2):
pts = beat(x + 4 + i * seg)
trace(c, pts, ECG_GREEN, 1.3)
draw_annotation(c, x + 8, y + h - 26, "Coved ST↑ ≥2mm in V1-V3", ECG_RED, 5)
draw_annotation(c, x + 8, y + h - 34, "Downsloping ST + T inversion", ECG_ORANGE, 5)
# Coved shape annotation
cx1 = x + 4 + seg*0.26
cx2 = x + 4 + seg*0.55
cy = mid + 20
c.saveState()
c.setStrokeColor(ECG_YELLOW)
c.setLineWidth(0.5)
c.setDash([2, 2])
c.line(cx1, cy, cx2, mid - 6)
c.setDash([])
c.setFillColor(ECG_YELLOW)
c.setFont("Helvetica-Bold", 4.5)
c.drawString(cx1 + 4, cy + 2, "Coved")
c.restoreStyle()
c.restoreState()
def draw_qt_prolongation(c, x, y, w, h):
mid = y + h * 0.45
seg = w / 2.2
def beat(ox, long_qt=False):
qt_end = seg * (0.80 if long_qt else 0.58)
return [
(ox, mid),
(ox + seg*0.08, mid),
(ox + seg*0.11, mid + 3),
(ox + seg*0.16, mid),
(ox + seg*0.20, mid),
(ox + seg*0.22, mid - 4),
(ox + seg*0.26, mid + 20),
(ox + seg*0.31, mid - 6),
(ox + seg*0.37, mid),
# long QT: T wave comes much later
(ox + seg*0.50, mid + 1),
(ox + qt_end - seg*0.08, mid + 2),
(ox + qt_end - seg*0.04, mid + 14), # T peak
(ox + qt_end, mid),
(ox + seg, mid),
]
# Normal beat
pts1 = beat(x + 4, False)
trace(c, pts1, ECG_BLUE, 1.0)
# Prolonged QT beat
pts2 = beat(x + 4 + seg, True)
trace(c, pts2, ECG_RED, 1.3)
# QT bracket for long beat
qt_x1 = x + 4 + seg + seg*0.22
qt_x2 = x + 4 + seg + seg*0.80
bry = mid - 14
c.saveState()
c.setStrokeColor(ECG_RED)
c.setLineWidth(0.8)
c.line(qt_x1, bry, qt_x2, bry)
c.line(qt_x1, bry-2, qt_x1, bry+2)
c.line(qt_x2, bry-2, qt_x2, bry+2)
c.setFillColor(ECG_RED)
c.setFont("Helvetica-Bold", 5.5)
c.drawCentredString((qt_x1+qt_x2)/2, bry - 10, "Prolonged QTc >440ms")
c.restoreState()
draw_annotation(c, x + 5, y + h - 26, "Blue = normal QT", ECG_BLUE, 5)
draw_annotation(c, x + 5, y + h - 34, "Red = prolonged QT (TdP risk)", ECG_RED, 5)
def draw_lvh(c, x, y, w, h):
mid = y + h * 0.45
seg = w / 2.8
def beat(ox):
return [
(ox, mid),
(ox + seg*0.08, mid),
(ox + seg*0.11, mid + 3),
(ox + seg*0.16, mid + 3), # broad P (P mitrale)
(ox + seg*0.20, mid),
(ox + seg*0.23, mid),
(ox + seg*0.25, mid - 4), # Q (dagger)
(ox + seg*0.29, mid + 32), # Tall R (voltage!)
(ox + seg*0.33, mid - 8), # S
(ox + seg*0.38, mid),
(ox + seg*0.44, mid - 3), # ST depression (strain)
(ox + seg*0.52, mid - 10), # T inversion (strain pattern)
(ox + seg*0.65, mid),
(ox + seg*1.00, mid),
]
for i in range(2):
pts = beat(x + 4 + i * seg)
trace(c, pts, ECG_GREEN, 1.3)
# Voltage measurement arrow
ox = x + 4 + seg*0.29
c.saveState()
c.setStrokeColor(ECG_YELLOW)
c.setLineWidth(0.7)
c.line(ox, mid, ox + 5, mid + 32)
c.setFillColor(ECG_YELLOW)
c.setFont("Helvetica-Bold", 5)
c.drawString(ox + 6, mid + 28, "Tall R")
c.restoreState()
draw_annotation(c, x + 8, y + h - 26, "Tall R (SV1+RV5 >35mm)", ECG_YELLOW, 5)
draw_annotation(c, x + 8, y + h - 34, "ST depression + T inversion (strain)", ECG_ORANGE, 5)
# ─────────────────────────────────────────────────────────────────────────────
# PATTERNS REGISTRY
# ─────────────────────────────────────────────────────────────────────────────
PATTERNS = [
{
"title": "1. STEMI",
"subtitle": "ST-Elevation MI",
"color": ECG_RED,
"draw_fn": draw_stemi,
"keys": ["ST elevation ≥1-2mm in ≥2 leads", "Reciprocal ST depression", "Hyperacute T waves", "Q waves = necrosis"],
"danger": "EMERGENCY - PCI <90min",
},
{
"title": "2. Atrial Fibrillation",
"subtitle": "AF",
"color": ECG_BLUE,
"draw_fn": draw_af,
"keys": ["No P waves (f-waves)", "Irregularly irregular R-R", "Rate 100-160 bpm (uncontrolled)", "Narrow QRS (usually)"],
"danger": "Stroke risk - CHA₂DS₂-VASc",
},
{
"title": "3. LBBB",
"subtitle": "Left Bundle Branch Block",
"color": HexColor("#8B5CF6"),
"draw_fn": draw_lbbb,
"keys": ["QRS ≥120ms", "M-shaped R in I, aVL, V5-V6", "No Q in V5-V6", "Discordant ST/T"],
"danger": "New LBBB + chest pain = urgent!",
},
{
"title": "4. Hyperkalemia",
"subtitle": "High K⁺",
"color": ECG_ORANGE,
"draw_fn": draw_hyperkalemia,
"keys": ["Peaked T waves (early)", "PR prolongation", "Wide QRS / absent P", "Sine wave → VF (late)"],
"danger": "Give Ca²⁺ if wide QRS / sine wave",
},
{
"title": "5. WPW Syndrome",
"subtitle": "Wolff-Parkinson-White",
"color": HexColor("#10B981"),
"draw_fn": draw_wpw,
"keys": ["Short PR <120ms", "Delta wave (slurred upstroke)", "Broad QRS >110ms", "Secondary ST/T changes"],
"danger": "Avoid verapamil/adenosine in AF+WPW",
},
{
"title": "6. Pulmonary Embolism",
"subtitle": "PE - Right Heart Strain",
"color": HexColor("#06B6D4"),
"draw_fn": draw_pe,
"keys": ["Sinus tachycardia (most common)", "S1Q3T3 pattern", "RBBB or incomplete RBBB", "T inversion V1-V4"],
"danger": "ECG not specific - use Wells + CTPA",
},
{
"title": "7. Complete Heart Block",
"subtitle": "3rd Degree AV Block",
"color": HexColor("#F59E0B"),
"draw_fn": draw_chb,
"keys": ["P waves dissociated from QRS", "P rate > QRS rate", "Escape rhythm (40-60 bpm)", "Regular P-P, regular R-R"],
"danger": "May need emergency pacing",
},
{
"title": "8. Brugada Syndrome",
"subtitle": "Na⁺ Channel Channelopathy",
"color": HexColor("#EF4444"),
"draw_fn": draw_brugada,
"keys": ["Coved ST ≥2mm in V1-V3", "Downsloping ST segment", "T wave inversion V1-V3", "Unmasked by fever/Na⁺ blockers"],
"danger": "VF/SCD risk - consider ICD",
},
{
"title": "9. Prolonged QT / TdP",
"subtitle": "Torsades de Pointes Risk",
"color": HexColor("#F97316"),
"draw_fn": draw_qt_prolongation,
"keys": ["QTc >440ms (M), >460ms (F)", "Risk: drugs, hypoK, hypoMg", "TdP: twisting VT morphology", "Bazett formula: QTc=QT/√RR"],
"danger": "TdP → IV Mg 2g stat",
},
{
"title": "10. LVH",
"subtitle": "Left Ventricular Hypertrophy",
"color": HexColor("#8B5CF6"),
"draw_fn": draw_lvh,
"keys": ["SV1 + RV5/V6 >35mm (Sokolov)", "Strain: ST↓ + T inversion lateral", "Left axis deviation", "Broad P wave (P mitrale)"],
"danger": "HTN end-organ damage marker",
},
]
# ─────────────────────────────────────────────────────────────────────────────
# MAIN DRAWING FUNCTION
# ─────────────────────────────────────────────────────────────────────────────
def create_ecg_reference_card(output_path):
c = canvas.Canvas(output_path, pagesize=A4)
c.setTitle("ECG Reference Card - 10 Classic Patterns")
c.setAuthor("Orris Medical Education")
c.setSubject("ECG Patterns Quick Reference")
# ── Background ──────────────────────────────────────────────────────────
c.setFillColor(BG_DARK)
c.rect(0, 0, W, H, fill=1, stroke=0)
# ── Header ──────────────────────────────────────────────────────────────
c.setFillColor(HEADER_BG)
c.rect(0, H - HEADER_H, W, HEADER_H, fill=1, stroke=0)
# ECG trace decoration in header
c.saveState()
c.setStrokeColor(HexColor("#003366"))
c.setLineWidth(0.5)
for i in range(0, int(W), 6):
c.line(i, H - HEADER_H, i, H)
c.restoreState()
# Decorative ECG line in header
header_pts = []
hx = 0
hmid = H - HEADER_H/2
beat_w = 80
while hx < W:
header_pts += [
(hx, hmid),
(hx + beat_w*0.20, hmid),
(hx + beat_w*0.23, hmid + 4),
(hx + beat_w*0.27, hmid),
(hx + beat_w*0.30, hmid),
(hx + beat_w*0.32, hmid - 4),
(hx + beat_w*0.35, hmid + 12),
(hx + beat_w*0.38, hmid - 5),
(hx + beat_w*0.43, hmid),
(hx + beat_w*0.53, hmid + 3),
(hx + beat_w*0.63, hmid),
(hx + beat_w, hmid),
]
hx += beat_w
trace(c, header_pts, HexColor("#003d00"), 0.6)
# Green active ECG strip
trace(c, header_pts[:80], ACCENT, 0.9)
# Header text
c.setFillColor(ACCENT)
c.setFont("Helvetica-Bold", 18)
c.drawCentredString(W/2, H - 17, "ECG REFERENCE CARD")
c.setFillColor(TEXT_LIGHT)
c.setFont("Helvetica", 9)
c.drawCentredString(W/2, H - 26, "10 Classic ECG Patterns • Emergency & Clinical Reference • Educational Use Only")
# ── Layout cards ────────────────────────────────────────────────────────
card_w = GRID_W
card_h = GRID_H
for idx, pattern in enumerate(PATTERNS):
col = idx % COLS
row = idx // COLS
cx = MARGIN + col * (card_w + 4*mm)
cy = H - HEADER_H - MARGIN - (row + 1) * card_h - row * 3*mm
# Card outer border
c.saveState()
c.setStrokeColor(pattern["color"])
c.setLineWidth(1.2)
c.setFillColor(BG_CARD)
c.roundRect(cx, cy, card_w, card_h, 3, fill=1, stroke=1)
c.restoreState()
# Color bar at top of card
c.saveState()
c.setFillColor(pattern["color"])
c.roundRect(cx, cy + card_h - 13, card_w, 13, 3, fill=1, stroke=0)
# Title
c.setFillColor(BG_DARK)
c.setFont("Helvetica-Bold", 7.5)
c.drawString(cx + 4, cy + card_h - 9.5, pattern["title"])
c.setFont("Helvetica", 6)
c.drawRightString(cx + card_w - 4, cy + card_h - 9.5, pattern["subtitle"])
c.restoreState()
# ECG grid area (upper half of card body)
ecg_area_h = card_h * 0.50
ecg_y = cy + card_h - 13 - ecg_area_h - 2
draw_ecg_grid(c, cx + 2, ecg_y, card_w - 4, ecg_area_h)
# Draw ECG pattern
pattern["draw_fn"](c, cx + 2, ecg_y, card_w - 4, ecg_area_h)
# Divider
c.saveState()
c.setStrokeColor(HexColor("#1E3A5F"))
c.setLineWidth(0.5)
c.line(cx + 4, ecg_y - 2, cx + card_w - 4, ecg_y - 2)
c.restoreState()
# Key features (lower half)
feat_y = ecg_y - 6
c.setFont("Helvetica-Bold", 5.2)
c.setFillColor(TEXT_LIGHT)
c.drawString(cx + 4, feat_y, "KEY FEATURES:")
feat_y -= 7
for kf in pattern["keys"]:
c.saveState()
# Bullet dot
c.setFillColor(pattern["color"])
c.circle(cx + 6, feat_y + 1.5, 1.5, fill=1, stroke=0)
c.setFillColor(TEXT_LIGHT)
c.setFont("Helvetica", 5.0)
c.drawString(cx + 10, feat_y, kf)
c.restoreState()
feat_y -= 6.5
# Danger/clinical pearl box
danger_y = cy + 3
c.saveState()
c.setFillColor(HexColor("#1A1A2E"))
c.setStrokeColor(pattern["color"])
c.setLineWidth(0.6)
c.roundRect(cx + 2, danger_y, card_w - 4, 11, 2, fill=1, stroke=1)
c.setFillColor(pattern["color"])
c.setFont("Helvetica-Bold", 5.0)
c.drawCentredString(cx + card_w/2, danger_y + 3.5, "⚠ " + pattern["danger"])
c.restoreState()
# ── Footer ───────────────────────────────────────────────────────────────
fy = 4
c.setFillColor(TEXT_DIM)
c.setFont("Helvetica", 5.5)
c.drawString(MARGIN, fy, "ECG Reference Card • For educational use only • Always correlate with clinical context")
c.drawRightString(W - MARGIN, fy, "Sources: Tintinalli's Emergency Medicine, LITFL ECG Library • © 2026 Orris")
# ── Page border ──────────────────────────────────────────────────────────
c.saveState()
c.setStrokeColor(ACCENT)
c.setLineWidth(1.5)
c.rect(4, 4, W - 8, H - 8, fill=0, stroke=1)
c.restoreState()
c.save()
print(f"PDF saved to: {output_path}")
if __name__ == "__main__":
output = "/home/daytona/workspace/ecg-reference-card/ECG_Reference_Card.pdf"
create_ecg_reference_card(output)
cd /home/daytona/workspace/ecg-reference-card && python generate_ecg_card.py 2>&1
~/ecg-reference-card/generate_ecg_card.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/ecg-reference-card/generate_ecg_card.py'
cd /home/daytona/workspace/ecg-reference-card && python generate_ecg_card.py 2>&1