Give 10 heart disease with possible chages in ECG
10 heart diseases ECG changes classic findings
| Phase | ECG Finding |
|---|---|
| Hyperacute (minutes) | Tall, peaked (hyperacute) T waves |
| Acute (hours) | ST-segment elevation ≥1 mm in 2+ contiguous leads; reciprocal ST depression in opposing leads |
| Evolving (hours-days) | T-wave inversion |
| Old MI | Pathological Q waves (>1 mm wide, >25% of R-wave height) |
| Stage | Timing | Finding |
|---|---|---|
| I | Day 1-2 | Diffuse saddle-shaped ST elevation in most leads (except aVR, V1); PR depression |
| II | Days 3-7 | ST normalizes; T waves flatten |
| III | 1-3 weeks | T-wave inversion (diffuse) |
| IV | Weeks-months | ECG normalizes |
Note: This is a preliminary visual impression only. The resolution and orientation of the ECG makes definitive interpretation difficult. A qualified physician should review the original tracing with full clinical context. The machine-generated interpretation at the bottom of the strip should be confirmed by a clinician.
| # | Disease | Key ECG Feature |
|---|---|---|
| 1 | STEMI | ST elevation + Q waves |
| 2 | NSTEMI/UA | ST depression + T-wave inversion |
| 3 | LVH | High voltage + strain pattern |
| 4 | RVH | R>S in V1 + right axis deviation |
| 5 | Atrial Fibrillation | No P waves + irregular rhythm |
| 6 | WPW | Delta wave + short PR |
| 7 | Complete Heart Block | P-QRS dissociation |
| 8 | HCM | Dagger Q waves + giant T inversions |
| 9 | Pericarditis | Diffuse saddle ST + PR depression |
| 10 | DCM | LBBB + pseudo-infarct Q waves |
Create a quick-reference PDF guide to ECG patterns in heart disease
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or missing"
mkdir -p /home/daytona/workspace/ecg-guide && python3 -c "import reportlab; prin…
~/ecg-guide/build_ecg_guide.py
"""
ECG Quick-Reference Guide PDF Builder
Uses reportlab to produce a professional multi-page PDF
with colour-coded sections, ECG waveform diagrams, and tables.
"""
import math
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
PageBreak, HRFlowable, KeepTogether
)
from reportlab.platypus.flowables import Flowable
from reportlab.pdfgen import canvas as pdfcanvas
# ──────────────────────────────────────────────────────────────────────────────
# COLOUR PALETTE
# ──────────────────────────────────────────────────────────────────────────────
RED = colors.HexColor("#C0392B")
ORANGE = colors.HexColor("#E67E22")
BLUE = colors.HexColor("#1A5276")
TEAL = colors.HexColor("#117A65")
PURPLE = colors.HexColor("#6C3483")
DARK = colors.HexColor("#1C2833")
MID_GREY = colors.HexColor("#7F8C8D")
LIGHT_BG = colors.HexColor("#F8F9FA")
PALE_RED = colors.HexColor("#FADBD8")
PALE_BLU = colors.HexColor("#D6EAF8")
PALE_GRN = colors.HexColor("#D5F5E3")
PALE_ORG = colors.HexColor("#FDEBD0")
PALE_PUR = colors.HexColor("#E8DAEF")
PALE_TEA = colors.HexColor("#D0ECE7")
ECG_GRN = colors.HexColor("#27AE60")
GRID_COL = colors.HexColor("#FADBD8") # faint pink ECG grid
TRACE = colors.HexColor("#C0392B") # red ECG trace
W, H = A4 # 595.27 x 841.89 pts
# ──────────────────────────────────────────────────────────────────────────────
# ECG WAVEFORM FLOWABLE – draws a stylised trace with a grid background
# ──────────────────────────────────────────────────────────────────────────────
class ECGWaveform(Flowable):
"""
Draw a schematic ECG waveform for a given condition.
wave_type controls which morphology to render.
"""
TYPES = {
"normal": "normal",
"stemi": "stemi",
"nstemi": "nstemi",
"lvh": "lvh",
"rvh": "rvh",
"afib": "afib",
"wpw": "wpw",
"chb": "chb",
"hcm": "hcm",
"pericarditis":"pericarditis",
"dcm": "dcm",
}
def __init__(self, wave_type="normal", width=420, height=80, label=None):
super().__init__()
self.wave_type = wave_type
self.width = width
self.height = height
self.label = label
def wrap(self, *args):
return self.width, self.height + 4
def draw(self):
c = self._canvas
w, h = self.width, self.height
# --- grid background ---
c.saveState()
c.setFillColor(colors.HexColor("#FFF5F5"))
c.rect(0, 0, w, h, fill=1, stroke=0)
# minor grid (1 mm equivalent = 2.83 pt → use 5pt squares)
sq = 5
c.setStrokeColor(colors.HexColor("#FFCCCC"))
c.setLineWidth(0.3)
x = sq
while x < w:
c.line(x, 0, x, h)
x += sq
y = sq
while y < h:
c.line(0, y, w, y)
y += sq
# major grid (every 5 squares = 25pt)
c.setStrokeColor(colors.HexColor("#FF9999"))
c.setLineWidth(0.6)
x = 25
while x < w:
c.line(x, 0, x, h)
x += 25
y = 25
while y < h:
c.line(0, y, w, y)
y += 25
c.restoreState()
# --- trace ---
baseline = h * 0.38
c.setStrokeColor(TRACE)
c.setLineWidth(1.4)
c.setLineCap(1)
path = c.beginPath()
def pt(x, y):
return x, baseline + y
wt = self.wave_type
# We'll draw 2–3 beats depending on wave type
if wt == "normal":
beats = self._normal_beat()
self._draw_repeated(c, path, beats, w, baseline, repeats=3, gap=10)
elif wt == "stemi":
beats = self._stemi_beat()
self._draw_repeated(c, path, beats, w, baseline, repeats=3, gap=10)
elif wt == "nstemi":
beats = self._nstemi_beat()
self._draw_repeated(c, path, beats, w, baseline, repeats=3, gap=10)
elif wt == "lvh":
beats = self._lvh_beat()
self._draw_repeated(c, path, beats, w, baseline, repeats=3, gap=10)
elif wt == "rvh":
beats = self._rvh_beat()
self._draw_repeated(c, path, beats, w, baseline, repeats=3, gap=10)
elif wt == "afib":
self._draw_afib(c, w, baseline)
return
elif wt == "wpw":
beats = self._wpw_beat()
self._draw_repeated(c, path, beats, w, baseline, repeats=3, gap=10)
elif wt == "chb":
self._draw_chb(c, w, baseline, h)
return
elif wt == "hcm":
beats = self._hcm_beat()
self._draw_repeated(c, path, beats, w, baseline, repeats=3, gap=10)
elif wt == "pericarditis":
beats = self._pericarditis_beat()
self._draw_repeated(c, path, beats, w, baseline, repeats=3, gap=10)
elif wt == "dcm":
beats = self._dcm_beat()
self._draw_repeated(c, path, beats, w, baseline, repeats=2, gap=15)
c.drawPath(path, stroke=1, fill=0)
# ── helper: draw a sequence of (dx, dy) segments repeatedly ──────────────
def _draw_repeated(self, c, path, segments, total_w, baseline, repeats=3, gap=10):
beat_w = sum(abs(dx) for dx, dy in segments)
spacing = (total_w - gap * 2) / repeats
x = gap
first = True
for _ in range(repeats):
cx = x
if first:
path.moveTo(cx, baseline)
first = False
else:
path.lineTo(cx, baseline)
for dx, dy in segments:
cx += dx
path.lineTo(cx, baseline + dy)
x += spacing
# ── NORMAL beat ──────────────────────────────────────────────────────────
def _normal_beat(self):
# P-Q-R-S-T
return [
(8, 0), # flat → P start
(4, 6), # P up
(4, 0), # P down → isoelectric
(5, 0), # PR segment
(2, -4), # Q
(3, 28), # R up
(3,-28), # S down
(2, 4), # return to base
(6, 0), # ST segment
(5, 8), # T up
(6, 0), # T peak
(5, -8), # T down
(15, 0), # TP interval
]
# ── STEMI beat ────────────────────────────────────────────────────────────
def _stemi_beat(self):
return [
(8, 0),
(4, 5), # P
(4, 0),
(4, 0), # PR
(2, -4), # Q
(3, 28), # R
(3,-22), # S (partial return – ST elevated)
(2, 8), # ST elevation slope up
(8, 12), # ST elevation plateau
(5, 0),
(5, -4), # T beginning
(6, 6), # T peak
(5, -12), # T down to elevated baseline
(2, -8), # return baseline
(10, 0), # TP
]
# ── NSTEMI beat ───────────────────────────────────────────────────────────
def _nstemi_beat(self):
return [
(8, 0),
(4, 5),
(4, 0),
(4, 0),
(2, -4),
(3, 26),
(3,-26),
(2, 4),
(8, -8), # ST depression
(5, -5),
(5, -8), # inverted T
(6, 0),
(5, 8),
(2, 13), # return to baseline
(10, 0),
]
# ── LVH beat ──────────────────────────────────────────────────────────────
def _lvh_beat(self):
return [
(8, 0),
(5, 7), # prominent P
(5, 0),
(4, 0),
(2, -5),
(3, 38), # very tall R
(3,-38),
(2, 5),
(6, 0),
(4, -6), # ST depression (strain)
(6, -8), # inverted T
(6, 0),
(5, 14),
(12, 0),
]
# ── RVH beat ──────────────────────────────────────────────────────────────
def _rvh_beat(self):
return [
(8, 0),
(4, 6),
(4, 0),
(4, 0),
(2, 0), # no Q
(3, 22), # R in V1 tall
(3, -5), # S small (R>S)
(3,-17),
(2, 0),
(6, 0),
(5, -7), # T inversion (strain)
(6, 0),
(5, 7),
(12, 0),
]
# ── WPW beat ──────────────────────────────────────────────────────────────
def _wpw_beat(self):
return [
(4, 0),
(4, 5), # P
(2, 0), # short PR
(4, 4), # delta wave slurred upstroke
(2, 22), # R
(3,-26), # S
(2, 0),
(8, 0),
(5, 7),
(5, 0),
(5, -7),
(10, 0),
]
# ── HCM beat ──────────────────────────────────────────────────────────────
def _hcm_beat(self):
return [
(8, 0),
(4, 6),
(4, 0),
(4, 0),
(3,-10), # deep narrow dagger Q
(2, 10),
(2, 28), # tall R
(3,-28),
(2, 0),
(6, 0),
(4, -12), # giant negative T
(6,-10),
(6, 0),
(5, 22),
(10, 0),
]
# ── Pericarditis beat ─────────────────────────────────────────────────────
def _pericarditis_beat(self):
return [
(4, 0),
(3, 5), # P
(3, 0),
(2, -2), # PR depression (below baseline)
(4, -4), # PR depression segment
(2, -3),
(2, 3), # Q minimal
(3, 22),
(3,-22),
(2, 3),
(2, 3), # slight ST elevation (diffuse, concave)
(6, 8), # saddle-shaped ST elevation
(4, 5),
(4, -3),
(5, -8),
(2, -8), # back to baseline
(10, 0),
]
# ── DCM beat ──────────────────────────────────────────────────────────────
def _dcm_beat(self):
# LBBB morphology: wide, notched QRS, no Q in lateral
return [
(8, 0),
(5, 6),
(5, 0),
(5, 0), # long PR (prolonged conduction)
(2, 0), # no Q
(4, 14), # initial slurred upstroke (LBBB)
(3, 8), # notch
(3, 10), # R peak – wide
(4,-32), # S descent
(3, 0),
(8, 0), # wide ST
(5, -7), # discordant T
(6, 0),
(4, 7),
(15, 0),
]
# ── AF: irregular, no P, fibrillatory baseline ───────────────────────────
def _draw_afib(self, c, w, baseline):
import random
random.seed(42)
path = c.beginPath()
x = 5
path.moveTo(x, baseline)
# fibrillatory baseline with irregular QRS complexes
beats_x = [55, 115, 155, 220, 265, 340, 380]
bx_set = set(beats_x)
beat_idx = 0
while x < w - 5:
if beat_idx < len(beats_x) and x >= beats_x[beat_idx] - 2:
# QRS complex
path.lineTo(x, baseline)
path.lineTo(x+2, baseline - 4)
path.lineTo(x+3, baseline + 26)
path.lineTo(x+5, baseline - 26)
path.lineTo(x+7, baseline + 4)
path.lineTo(x+8, baseline)
x += 8
beat_idx += 1
else:
# fibrillatory f-waves
fib_y = baseline + random.uniform(-3, 3)
path.lineTo(x, fib_y)
x += random.uniform(1.5, 3)
path.lineTo(x, baseline)
c.drawPath(path, stroke=1, fill=0)
# ── Complete Heart Block: independent P and slow QRS ─────────────────────
def _draw_chb(self, c, w, baseline, h):
# P waves at ~70 bpm (fast), QRS at ~35 bpm (slow escape)
p_interval = 52 # pts between P waves
qrs_interval = 105 # pts between QRS complexes
path = c.beginPath()
path.moveTo(5, baseline)
# flat line first
path.lineTo(w - 5, baseline)
c.drawPath(path, stroke=1, fill=0)
# P waves (atrial, regular fast)
x = 18
c.setStrokeColor(colors.HexColor("#2980B9"))
c.setLineWidth(1.2)
while x + 8 < w:
p = c.beginPath()
p.moveTo(x, baseline)
p.lineTo(x+3, baseline + 5)
p.lineTo(x+6, baseline)
c.drawPath(p, stroke=1, fill=0)
x += p_interval
# QRS complexes (ventricular escape, regular slow, independent)
c.setStrokeColor(TRACE)
c.setLineWidth(1.6)
x = 45
while x + 10 < w:
q = c.beginPath()
q.moveTo(x, baseline)
q.lineTo(x+2, baseline - 4)
q.lineTo(x+3, baseline + 24)
q.lineTo(x+5, baseline - 22)
q.lineTo(x+7, baseline + 3)
q.lineTo(x+8, baseline)
# ST-T
q.lineTo(x+12, baseline)
q.lineTo(x+16, baseline + 7)
q.lineTo(x+19, baseline)
c.drawPath(q, stroke=1, fill=0)
x += qrs_interval
# legend note
c.setFont("Helvetica-Oblique", 6.5)
c.setFillColor(colors.HexColor("#2980B9"))
c.drawString(5, h - 10, "P (atrial, regular)")
c.setFillColor(TRACE)
c.drawString(5, h - 20, "QRS (ventricular escape, regular, independent)")
# ──────────────────────────────────────────────────────────────────────────────
# COLOUR BAR FLOWABLE
# ──────────────────────────────────────────────────────────────────────────────
class ColourBar(Flowable):
def __init__(self, colour, width=None, height=6):
super().__init__()
self._colour = colour
self._width = width or (W - 4*cm)
self._height = height
def wrap(self, *args):
return self._width, self._height
def draw(self):
c = self._canvas
c.setFillColor(self._colour)
c.rect(0, 0, self._width, self._height, fill=1, stroke=0)
# ──────────────────────────────────────────────────────────────────────────────
# PAGE TEMPLATES – header / footer via onPage callbacks
# ──────────────────────────────────────────────────────────────────────────────
def on_first_page(canvas, doc):
pass # cover page handled by story content
def on_later_pages(canvas, doc):
canvas.saveState()
# header bar
canvas.setFillColor(BLUE)
canvas.rect(0, H - 28, W, 28, fill=1, stroke=0)
canvas.setFillColor(colors.white)
canvas.setFont("Helvetica-Bold", 9)
canvas.drawString(1.5*cm, H - 18, "ECG QUICK-REFERENCE GUIDE | Heart Disease Patterns")
canvas.setFont("Helvetica", 8)
canvas.drawRightString(W - 1.5*cm, H - 18, f"Page {doc.page}")
# footer
canvas.setFillColor(MID_GREY)
canvas.setFont("Helvetica-Oblique", 7)
canvas.drawString(1.5*cm, 12, "For clinical education only. Always correlate with patient history and full clinical assessment.")
canvas.drawRightString(W - 1.5*cm, 12, "Sources: Braunwald's Heart Disease | Harrison's Principles | Tintinalli's EM")
canvas.restoreState()
# ──────────────────────────────────────────────────────────────────────────────
# ECG CONDITION DATA
# ──────────────────────────────────────────────────────────────────────────────
CONDITIONS = [
{
"num": 1,
"name": "ST-Elevation Myocardial Infarction (STEMI)",
"colour": RED,
"bg": PALE_RED,
"wave": "stemi",
"category": "Ischaemic",
"urgency": "EMERGENCY",
"urgency_col": RED,
"key_findings": [
("ST-segment elevation", ">=1 mm in >=2 contiguous leads (>=2 mm in V1-V3)"),
("Hyperacute T waves", "Tall peaked T waves - earliest sign (minutes)"),
("Reciprocal ST depression", "In leads electrically opposite to infarct territory"),
("Pathological Q waves", ">40 ms wide or >25% R-wave height - evolve over hours"),
("T-wave inversion", "Develops hours to days after onset"),
],
"territories": [
("Inferior (RCA)", "ST elevation in II, III, aVF"),
("Anterior (LAD)", "ST elevation in V1-V4"),
("Lateral (LCx)", "ST elevation in I, aVL, V5-V6"),
("Posterior (RCA/LCx)", "Tall R, ST depression V1-V3 (mirror image)"),
],
"pearls": "ST elevation in aVR > V1 plus ST depression in >=7 leads suggests left main occlusion.",
},
{
"num": 2,
"name": "NSTEMI / Unstable Angina",
"colour": ORANGE,
"bg": PALE_ORG,
"wave": "nstemi",
"category": "Ischaemic",
"urgency": "URGENT",
"urgency_col": ORANGE,
"key_findings": [
("ST depression", "Horizontal or downsloping >=0.5 mm; most specific for ischaemia"),
("T-wave inversion", "Deep, symmetrical; Wellens pattern in LAD disease"),
("No Q waves", "No pathological Q waves (differentiates from STEMI evolution)"),
("Transient ST changes", "May normalise between episodes (dynamic ischaemia)"),
("Normal ECG possible", "Up to 5% of NSTEMI have a normal presenting ECG"),
],
"territories": [
("Wellens Type A", "Biphasic T waves V2-V3 - proximal LAD disease"),
("Wellens Type B", "Deep symmetrical T inversion V2-V3 - proximal LAD disease"),
("De Winter T waves", "Upsloping ST depression + tall T waves V1-V6 (LAD equivalent)"),
],
"pearls": "Biomarker elevation (troponin) distinguishes NSTEMI from unstable angina - ECG cannot.",
},
{
"num": 3,
"name": "Left Ventricular Hypertrophy (LVH)",
"colour": BLUE,
"bg": PALE_BLU,
"wave": "lvh",
"category": "Structural",
"urgency": "MONITOR",
"urgency_col": BLUE,
"key_findings": [
("Sokolow-Lyon", "S(V1) + R(V5 or V6) > 35 mm"),
("Cornell criteria", "R(aVL) + S(V3) > 28 mm (men) / >20 mm (women)"),
("Left axis deviation", "QRS axis -30 degrees to -90 degrees"),
("Strain pattern", "ST depression + asymmetric T inversion in I, aVL, V5-V6"),
("P mitrale", "Broad notched P wave >120 ms in II if LA enlargement"),
],
"territories": [
("Lateral leads", "ST depression and T-wave inversion (strain)"),
("Precordial", "Voltage criteria best seen V1-V6"),
],
"pearls": "LVH on ECG is specific but insensitive (~50%). Echo is gold standard for LV mass.",
},
{
"num": 4,
"name": "Right Ventricular Hypertrophy (RVH)",
"colour": TEAL,
"bg": PALE_TEA,
"wave": "rvh",
"category": "Structural",
"urgency": "MONITOR",
"urgency_col": TEAL,
"key_findings": [
("Right axis deviation", "QRS axis > +110 degrees"),
("Tall R in V1", "R > S in V1; R wave >7 mm in V1"),
("Deep S in V5-V6", "S wave >7 mm in V5-V6"),
("RV strain pattern", "T-wave inversion V1-V3; sometimes V4"),
("P pulmonale", "Peaked P waves >2.5 mm in lead II (RA enlargement)"),
],
"territories": [
("Right precordial", "V1-V3: tall R, T inversion"),
("Inferior leads", "Peaked P waves in II, III, aVF"),
],
"pearls": "Causes: pulmonary hypertension, PE, mitral stenosis, COPD. S1Q3T3 pattern in acute PE.",
},
{
"num": 5,
"name": "Atrial Fibrillation (AF)",
"colour": PURPLE,
"bg": PALE_PUR,
"wave": "afib",
"category": "Arrhythmia",
"urgency": "URGENT",
"urgency_col": ORANGE,
"key_findings": [
("Absent P waves", "Replaced by irregular fibrillatory f-waves (350-600/min)"),
("Irregularly irregular RR", "No pattern to QRS intervals - hallmark finding"),
("Normal QRS", "Narrow QRS unless aberrant conduction or pre-excitation"),
("Variable ventricular rate", "Rate depends on AV nodal conduction"),
("Fine vs coarse AF", "Coarse f-waves often from valvular disease"),
],
"territories": [
("All leads", "Absent discrete P waves; chaotic baseline"),
("AF + WPW", "Wide bizarre QRS - life-threatening - avoid AV nodal drugs"),
],
"pearls": "AF with pre-excitation (WPW): avoid adenosine/verapamil/digoxin - risk of VF.",
},
{
"num": 6,
"name": "Wolff-Parkinson-White Syndrome (WPW)",
"colour": colors.HexColor("#8E44AD"),
"bg": PALE_PUR,
"wave": "wpw",
"category": "Conduction",
"urgency": "MONITOR/URGENT",
"urgency_col": ORANGE,
"key_findings": [
("Short PR interval", "< 120 ms - early ventricular activation via accessory pathway"),
("Delta wave", "Slurred initial QRS upstroke - pre-excitation of ventricle"),
("Widened QRS", "> 120 ms total QRS duration"),
("Discordant ST-T", "Secondary repolarisation changes opposite to delta wave"),
("Pseudo-infarct patterns", "Delta waves can simulate Q waves - 'pseudo-infarct'"),
],
"territories": [
("Type A (posterior)", "Positive delta wave V1 - left-sided accessory pathway"),
("Type B (anterior)", "Negative delta wave V1 - right-sided accessory pathway"),
],
"pearls": "Risk of sudden death via AF conducting rapidly through accessory pathway to ventricles (rate >250 bpm).",
},
{
"num": 7,
"name": "Complete (3rd Degree) Heart Block",
"colour": colors.HexColor("#1A5276"),
"bg": PALE_BLU,
"wave": "chb",
"category": "Conduction",
"urgency": "EMERGENCY",
"urgency_col": RED,
"key_findings": [
("AV dissociation", "P waves and QRS complexes completely independent"),
("Regular P waves", "Atrial rate normal 60-100 bpm; regular P-P intervals"),
("Slow escape rhythm", "Ventricular rate 20-60 bpm; regular R-R intervals"),
("Junctional escape", "Narrow QRS at 40-60 bpm if block at AV node"),
("Ventricular escape", "Wide QRS at 20-40 bpm if block below His bundle"),
],
"territories": [
("Junctional escape", "Narrow QRS, rate 40-60 - block at AV node level"),
("Ventricular escape", "Wide QRS, rate 20-40 - infranodal block (worse prognosis)"),
],
"pearls": "Never treat by speeding up the atrial rate - treat the ventricular rate. Transvenous pacing is definitive.",
},
{
"num": 8,
"name": "Hypertrophic Cardiomyopathy (HCM)",
"colour": colors.HexColor("#1E8449"),
"bg": PALE_GRN,
"wave": "hcm",
"category": "Structural",
"urgency": "MONITOR",
"urgency_col": TEAL,
"key_findings": [
("LVH voltage", "Most common finding - tall R waves with voltage criteria met"),
("Dagger Q waves", "Deep narrow Q waves in I, aVL, V5-V6 and inferior leads"),
("Giant T inversions", "Massive precordial T-wave inversion (apical HCM/Yamaguchi)"),
("Left axis deviation", "Common; may have left anterior fascicular block"),
("Short PR interval", "Occasionally; AF is a major complication in 20% patients"),
],
"territories": [
("Classic HCM", "LVH + dagger Q waves in lateral/inferior leads"),
("Apical HCM", "Giant negative T waves V3-V5 (>10 mm), minimal Q waves"),
],
"pearls": "ECG abnormality may precede structural changes by years. Dagger Q waves are septal depolarisation, not infarction.",
},
{
"num": 9,
"name": "Acute Pericarditis",
"colour": colors.HexColor("#BA4A00"),
"bg": PALE_ORG,
"wave": "pericarditis",
"category": "Inflammatory",
"urgency": "URGENT",
"urgency_col": ORANGE,
"key_findings": [
("Stage I - PR depression", "Downsloping PR in most leads except aVR - highly specific"),
("Stage I - ST elevation", "Diffuse saddle-shaped (concave up) ST elevation; all territories"),
("Stage II", "ST normalises; T waves flatten (days 3-7)"),
("Stage III", "Diffuse T-wave inversion (weeks 1-3)"),
("Stage IV", "ECG returns to normal (weeks-months)"),
],
"territories": [
("Diffuse (all leads)", "ST elevation in I, II, III, aVL, aVF, V2-V6 simultaneously"),
("aVR reciprocal", "ST depression in aVR and sometimes V1"),
],
"pearls": "Key differentiators from STEMI: PR depression, saddle shape, diffuse (not territorial), no reciprocal changes (except aVR).",
},
{
"num": 10,
"name": "Dilated Cardiomyopathy (DCM)",
"colour": colors.HexColor("#515A5A"),
"bg": LIGHT_BG,
"wave": "dcm",
"category": "Structural",
"urgency": "MONITOR",
"urgency_col": TEAL,
"key_findings": [
("LBBB", "Left bundle branch block: QRS >120 ms, broad notched R in I/V6, rS in V1"),
("Poor R progression", "Loss of R waves V1-V4 - pseudo-anterior MI pattern"),
("Pseudo-infarct Q waves", "Pathological Q waves despite no ischaemic history"),
("Left axis deviation", "Common; may have LAFB"),
("P mitrale + LVH", "Left atrial and ventricular enlargement pattern"),
],
"territories": [
("Lateral leads", "LBBB pattern with broad notched R in I, aVL, V5-V6"),
("Right precordial", "rS or QS pattern in V1-V3 (pseudo-infarct)"),
],
"pearls": "LBBB of new onset in heart failure carries poor prognosis and is an indication for CRT (cardiac resynchronisation therapy).",
},
]
# ──────────────────────────────────────────────────────────────────────────────
# STYLES
# ──────────────────────────────────────────────────────────────────────────────
def build_styles():
base = getSampleStyleSheet()
styles = {
"cover_title": ParagraphStyle(
"cover_title", parent=base["Title"],
fontSize=32, leading=38,
textColor=colors.white, alignment=TA_CENTER,
fontName="Helvetica-Bold",
),
"cover_sub": ParagraphStyle(
"cover_sub", parent=base["Normal"],
fontSize=13, leading=18,
textColor=colors.HexColor("#AED6F1"), alignment=TA_CENTER,
fontName="Helvetica",
),
"cover_note": ParagraphStyle(
"cover_note", parent=base["Normal"],
fontSize=9, leading=13,
textColor=colors.HexColor("#85929E"), alignment=TA_CENTER,
),
"section_num": ParagraphStyle(
"section_num", parent=base["Normal"],
fontSize=28, leading=30,
textColor=colors.white, alignment=TA_CENTER,
fontName="Helvetica-Bold",
),
"section_title": ParagraphStyle(
"section_title", parent=base["Heading1"],
fontSize=15, leading=18,
textColor=colors.white,
fontName="Helvetica-Bold",
spaceAfter=0,
),
"section_cat": ParagraphStyle(
"section_cat", parent=base["Normal"],
fontSize=8.5, leading=11,
textColor=colors.HexColor("#AED6F1"),
fontName="Helvetica",
),
"body": ParagraphStyle(
"body", parent=base["Normal"],
fontSize=9, leading=13,
textColor=DARK, alignment=TA_LEFT,
),
"body_bold": ParagraphStyle(
"body_bold", parent=base["Normal"],
fontSize=9, leading=13,
textColor=DARK, fontName="Helvetica-Bold",
),
"pearl": ParagraphStyle(
"pearl", parent=base["Normal"],
fontSize=8.5, leading=12,
textColor=colors.HexColor("#1A5276"),
fontName="Helvetica-Oblique",
leftIndent=6,
),
"toc_title": ParagraphStyle(
"toc_title", parent=base["Normal"],
fontSize=11, leading=14,
textColor=DARK, fontName="Helvetica-Bold",
spaceAfter=2,
),
"toc_entry": ParagraphStyle(
"toc_entry", parent=base["Normal"],
fontSize=9.5, leading=15,
textColor=DARK,
),
"wave_label": ParagraphStyle(
"wave_label", parent=base["Normal"],
fontSize=7.5, leading=10,
textColor=MID_GREY, alignment=TA_CENTER,
fontName="Helvetica-Oblique",
),
"summary_head": ParagraphStyle(
"summary_head", parent=base["Normal"],
fontSize=10, leading=13,
textColor=colors.white, fontName="Helvetica-Bold",
alignment=TA_CENTER,
),
}
return styles
# ──────────────────────────────────────────────────────────────────────────────
# BUILD STORY
# ──────────────────────────────────────────────────────────────────────────────
def build_story(styles):
story = []
COL_W = W - 4*cm # usable width
# ── COVER PAGE ────────────────────────────────────────────────────────────
story.append(Spacer(1, 1.8*cm))
# Big header block
cover_data = [[
Paragraph("ECG QUICK-REFERENCE GUIDE", styles["cover_title"]),
]]
cover_tbl = Table(cover_data, colWidths=[COL_W])
cover_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), BLUE),
("TOPPADDING", (0,0),(-1,-1), 28),
("BOTTOMPADDING", (0,0),(-1,-1), 28),
("LEFTPADDING", (0,0),(-1,-1), 20),
("RIGHTPADDING", (0,0),(-1,-1), 20),
("ROUNDEDCORNERS", [8]),
]))
story.append(cover_tbl)
story.append(Spacer(1, 0.5*cm))
story.append(Paragraph(
"10 Heart Diseases & Their Classic ECG Patterns",
styles["cover_sub"]
))
story.append(Spacer(1, 0.3*cm))
story.append(HRFlowable(width=COL_W, color=BLUE, thickness=1.5))
story.append(Spacer(1, 0.4*cm))
# Mini overview waveform strip on cover
story.append(ECGWaveform("stemi", width=COL_W, height=70))
story.append(Paragraph("Schematic ECG - STEMI pattern shown", styles["wave_label"]))
story.append(Spacer(1, 0.5*cm))
# Category summary boxes
cat_data = [
[Paragraph("ISCHAEMIC", styles["summary_head"]),
Paragraph("ARRHYTHMIA", styles["summary_head"]),
Paragraph("CONDUCTION", styles["summary_head"]),
Paragraph("STRUCTURAL", styles["summary_head"]),
Paragraph("INFLAMMATORY", styles["summary_head"])],
[Paragraph("STEMI\nNSTEMI/UA", styles["body"]),
Paragraph("Atrial\nFibrillation", styles["body"]),
Paragraph("WPW\n3rd Degree HB", styles["body"]),
Paragraph("LVH | RVH\nHCM | DCM", styles["body"]),
Paragraph("Acute\nPericarditis", styles["body"])],
]
cat_col = COL_W / 5
cat_tbl = Table(cat_data, colWidths=[cat_col]*5)
cat_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0),(0,0), RED),
("BACKGROUND", (1,0),(1,0), PURPLE),
("BACKGROUND", (2,0),(2,0), BLUE),
("BACKGROUND", (3,0),(3,0), TEAL),
("BACKGROUND", (4,0),(4,0), ORANGE),
("FONTNAME", (0,1),(-1,1), "Helvetica"),
("FONTSIZE", (0,1),(-1,1), 8),
("ALIGN", (0,0),(-1,-1), "CENTER"),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
("TOPPADDING", (0,0),(-1,0), 6),
("BOTTOMPADDING", (0,0),(-1,0), 6),
("TOPPADDING", (0,1),(-1,1), 6),
("BOTTOMPADDING", (0,1),(-1,1), 6),
("GRID", (0,0),(-1,-1), 0.5, colors.white),
("BACKGROUND", (0,1),(4,1), LIGHT_BG),
]))
story.append(cat_tbl)
story.append(Spacer(1, 0.6*cm))
story.append(Paragraph(
"For clinical education and quick-reference use only. "
"All findings should be correlated with full clinical history and examination. "
"Sources: Braunwald's Heart Disease | Harrison's Principles of Internal Medicine 22E | "
"Tintinalli's Emergency Medicine | Ganong's Physiology",
styles["cover_note"]
))
story.append(PageBreak())
# ── TABLE OF CONTENTS ─────────────────────────────────────────────────────
story.append(Spacer(1, 0.8*cm))
toc_header = Table([[Paragraph("TABLE OF CONTENTS", styles["section_title"])]],
colWidths=[COL_W])
toc_header.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), BLUE),
("TOPPADDING", (0,0),(-1,-1), 10),
("BOTTOMPADDING", (0,0),(-1,-1), 10),
("LEFTPADDING", (0,0),(-1,-1), 14),
]))
story.append(toc_header)
story.append(Spacer(1, 0.4*cm))
for cond in CONDITIONS:
row_bg = cond["bg"]
toc_row = Table([
[
Paragraph(f"<b>{cond['num']:02d}</b>", styles["body_bold"]),
Paragraph(cond["name"], styles["toc_entry"]),
Paragraph(f"<font color='#{cond['category'] and '555555'}'>{cond['category']}</font>", styles["body"]),
Paragraph(
f"<b><font color='#{cond['urgency_col'].hexval()[1:]}'>{cond['urgency']}</font></b>",
styles["body_bold"]
),
]
], colWidths=[1.2*cm, 10.5*cm, 3.2*cm, 2.7*cm])
toc_row.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), row_bg),
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING",(0,0),(-1,-1),5),
("LEFTPADDING", (0,0),(-1,-1), 8),
("LINEBELOW", (0,0),(-1,-1), 0.5, colors.white),
("ALIGN", (0,0),(0,0), "CENTER"),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
]))
story.append(toc_row)
story.append(Spacer(1, 0.5*cm))
# ECG basics legend
story.append(Paragraph("<b>ECG Waveform Basics</b>", styles["toc_title"]))
legend_data = [
["Component", "Normal Duration", "Normal Amplitude", "Significance"],
["P wave", "80-120 ms", "<2.5 mm", "Atrial depolarisation"],
["PR interval", "120-200 ms", "N/A", "AV nodal conduction time"],
["QRS complex", "60-100 ms", "Variable by lead", "Ventricular depolarisation"],
["ST segment", "Isoelectric", "+/-1 mm", "Early ventricular repolarisation"],
["T wave", "160 ms", "<6 mm limb / <10 mm precordial", "Ventricular repolarisation"],
["QT interval", "QTc <440 ms (M) / <460 ms (F)", "N/A", "Total ventricular activity"],
]
legend_tbl = Table(legend_data, colWidths=[3.2*cm, 3.2*cm, 5.8*cm, 5.4*cm])
legend_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), DARK),
("FONTNAME", (0,0),(-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0),(-1,0), 8.5),
("TEXTCOLOR", (0,0),(-1,0), colors.white),
("FONTNAME", (0,1),(-1,-1), "Helvetica"),
("FONTSIZE", (0,1),(-1,-1), 8),
("ROWBACKGROUNDS", (0,1),(-1,-1), [LIGHT_BG, colors.white]),
("GRID", (0,0),(-1,-1), 0.5, colors.HexColor("#CCCCCC")),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING",(0,0),(-1,-1),4),
("LEFTPADDING", (0,0),(-1,-1), 6),
("ALIGN", (0,0),(-1,-1), "LEFT"),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
]))
story.append(legend_tbl)
story.append(PageBreak())
# ── CONDITION PAGES ───────────────────────────────────────────────────────
for cond in CONDITIONS:
colour = cond["colour"]
bg = cond["bg"]
# Section header
header_data = [[
Paragraph(f"{cond['num']:02d}", styles["section_num"]),
[
Paragraph(cond["name"], styles["section_title"]),
Paragraph(f"{cond['category']} | Urgency: {cond['urgency']}", styles["section_cat"]),
]
]]
header_tbl = Table(header_data, colWidths=[2.2*cm, COL_W - 2.2*cm])
header_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), colour),
("TOPPADDING", (0,0),(-1,-1), 10),
("BOTTOMPADDING", (0,0),(-1,-1), 10),
("LEFTPADDING", (0,0),(-1,-1), 10),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
("ALIGN", (0,0),(0,0), "CENTER"),
]))
story.append(KeepTogether([header_tbl, Spacer(1, 0.3*cm)]))
# Waveform
story.append(ECGWaveform(cond["wave"], width=COL_W, height=80))
story.append(Paragraph(
f"Schematic ECG trace - {cond['name']} pattern",
styles["wave_label"]
))
story.append(Spacer(1, 0.35*cm))
# Key ECG Findings table
findings_header = Table(
[[Paragraph("<b>KEY ECG FINDINGS</b>", styles["summary_head"])]],
colWidths=[COL_W]
)
findings_header.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), colour),
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING", (0,0),(-1,-1), 5),
("LEFTPADDING", (0,0),(-1,-1), 10),
]))
story.append(findings_header)
find_rows = [["ECG Feature", "Detail"]]
for feat, detail in cond["key_findings"]:
find_rows.append([
Paragraph(f"<b>{feat}</b>", styles["body_bold"]),
Paragraph(detail, styles["body"]),
])
find_col1 = 5.5*cm
find_col2 = COL_W - find_col1
find_tbl = Table(find_rows, colWidths=[find_col1, find_col2])
find_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), DARK),
("TEXTCOLOR", (0,0),(-1,0), colors.white),
("FONTNAME", (0,0),(-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0),(-1,0), 8),
("ROWBACKGROUNDS", (0,1),(-1,-1), [bg, colors.white]),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#CCCCCC")),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING", (0,0),(-1,-1), 4),
("LEFTPADDING", (0,0),(-1,-1), 7),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
("FONTSIZE", (0,1),(-1,-1), 8.5),
]))
story.append(find_tbl)
story.append(Spacer(1, 0.3*cm))
# Territory / pattern table
if cond.get("territories"):
terr_header = Table(
[[Paragraph("<b>LEAD TERRITORIES / PATTERNS</b>", styles["summary_head"])]],
colWidths=[COL_W]
)
terr_header.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), colour),
("TOPPADDING", (0,0),(-1,-1), 5),
("BOTTOMPADDING", (0,0),(-1,-1), 5),
("LEFTPADDING", (0,0),(-1,-1), 10),
]))
story.append(terr_header)
terr_rows = [["Territory / Pattern", "ECG Leads / Finding"]]
for terr, finding in cond["territories"]:
terr_rows.append([
Paragraph(f"<b>{terr}</b>", styles["body_bold"]),
Paragraph(finding, styles["body"]),
])
terr_tbl = Table(terr_rows, colWidths=[5.5*cm, COL_W - 5.5*cm])
terr_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), DARK),
("TEXTCOLOR", (0,0),(-1,0), colors.white),
("FONTNAME", (0,0),(-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0),(-1,0), 8),
("ROWBACKGROUNDS", (0,1),(-1,-1), [bg, colors.white]),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#CCCCCC")),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING", (0,0),(-1,-1), 4),
("LEFTPADDING", (0,0),(-1,-1), 7),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
("FONTSIZE", (0,1),(-1,-1), 8.5),
]))
story.append(terr_tbl)
story.append(Spacer(1, 0.3*cm))
# Clinical pearl
pearl_box = Table(
[[Paragraph(f"<b>Clinical Pearl:</b> {cond['pearls']}", styles["pearl"])]],
colWidths=[COL_W]
)
pearl_box.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), colors.HexColor("#EBF5FB")),
("LEFTPADDING", (0,0),(-1,-1), 10),
("TOPPADDING", (0,0),(-1,-1), 6),
("BOTTOMPADDING", (0,0),(-1,-1), 6),
("BOX", (0,0),(-1,-1), 1, colors.HexColor("#2980B9")),
]))
story.append(pearl_box)
story.append(Spacer(1, 0.5*cm))
story.append(PageBreak())
# ── SUMMARY COMPARISON TABLE ──────────────────────────────────────────────
story.append(Spacer(1, 0.5*cm))
sum_header = Table(
[[Paragraph("QUICK-REFERENCE SUMMARY TABLE", styles["section_title"])]],
colWidths=[COL_W]
)
sum_header.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,-1), DARK),
("TOPPADDING", (0,0),(-1,-1), 10),
("BOTTOMPADDING", (0,0),(-1,-1), 10),
("LEFTPADDING", (0,0),(-1,-1), 14),
]))
story.append(sum_header)
story.append(Spacer(1, 0.3*cm))
sum_rows = [
["#", "Condition", "Category", "Key ECG Feature", "Urgency"],
]
for c in CONDITIONS:
sum_rows.append([
str(c["num"]),
c["name"],
c["category"],
c["key_findings"][0][0] + ": " + c["key_findings"][0][1],
c["urgency"],
])
sum_col_w = [0.7*cm, 5.5*cm, 2.8*cm, 6.8*cm, 2.0*cm]
sum_tbl = Table(
[[Paragraph(cell if i==0 else str(cell), ParagraphStyle(
"sh", parent=getSampleStyleSheet()["Normal"],
fontSize=7.5 if r > 0 else 8,
fontName="Helvetica-Bold" if r == 0 else "Helvetica",
leading=10,
textColor=colors.white if r == 0 else DARK,
)) for i, cell in enumerate(row)] for r, row in enumerate(sum_rows)],
colWidths=sum_col_w
)
row_colours = []
for i, c in enumerate(CONDITIONS):
row_colours.append(("BACKGROUND", (0, i+1), (-1, i+1), c["bg"]))
sum_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), DARK),
("GRID", (0,0),(-1,-1), 0.4, colors.HexColor("#CCCCCC")),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING", (0,0),(-1,-1), 4),
("LEFTPADDING", (0,0),(-1,-1), 5),
("ALIGN", (0,0),(0,-1), "CENTER"),
("VALIGN", (0,0),(-1,-1), "MIDDLE"),
] + row_colours))
story.append(sum_tbl)
story.append(Spacer(1, 1*cm))
# Footer note
story.append(HRFlowable(width=COL_W, color=MID_GREY, thickness=0.5))
story.append(Spacer(1, 0.2*cm))
story.append(Paragraph(
"References: Braunwald's Heart Disease (Elsevier) | "
"Harrison's Principles of Internal Medicine 22E (McGraw Hill, 2025) | "
"Tintinalli's Emergency Medicine (McGraw Hill) | "
"Ganong's Review of Medical Physiology | "
"Roberts and Hedges' Clinical Procedures in Emergency Medicine (Elsevier)",
ParagraphStyle("ref", parent=getSampleStyleSheet()["Normal"],
fontSize=7, textColor=MID_GREY, leading=10,
alignment=TA_CENTER)
))
return story
# ──────────────────────────────────────────────────────────────────────────────
# MAIN
# ──────────────────────────────────────────────────────────────────────────────
if __name__ == "__main__":
OUT = "/home/daytona/workspace/ecg-guide/ECG_Quick_Reference_Guide.pdf"
doc = SimpleDocTemplate(
OUT,
pagesize=A4,
leftMargin=2*cm, rightMargin=2*cm,
topMargin=2.5*cm, bottomMargin=1.8*cm,
title="ECG Quick-Reference Guide",
author="Orris Medical Reference",
subject="ECG Patterns in Heart Disease",
)
styles = build_styles()
story = build_story(styles)
doc.build(
story,
onFirstPage=on_first_page,
onLaterPages=on_later_pages,
)
print(f"PDF saved: {OUT}")
cd /home/daytona/workspace/ecg-guide && python3 build_ecg_guide.py 2>&1
~/ecg-guide/build_ecg_guide.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/ecg-guide/build_ecg_guide.py'
cd /home/daytona/workspace/ecg-guide && python3 build_ecg_guide.py 2>&1
grep -n "_canvas\|self\.canv" /home/daytona/workspace/ecg-guide/build_ecg_guide.…
sed -i 's/self\._canvas/self\.canv/g' /home/daytona/workspace/ecg-guide/build_ec…
python3 -c " content = open('/home/daytona/workspace/ecg-guide/build_ecg_guide.p…
cd /home/daytona/workspace/ecg-guide && python3 build_ecg_guide.py 2>&1
ls -lh /home/daytona/workspace/ecg-guide/ECG_Quick_Reference_Guide.pdf
ECG Quick Reference Guide
PDF Document · PDF
| Section | Content |
|---|---|
| Cover page | Title, category overview boxes (Ischaemic / Arrhythmia / Conduction / Structural / Inflammatory), schematic ECG trace |
| Table of Contents | All 10 conditions with category and urgency level; ECG waveform basics reference table (P wave, PR, QRS, ST, T, QT normals) |
| 10 Condition Pages | One full page per condition |
| Summary Table | All 10 conditions side-by-side at a glance |
Search for ECG practice cases to test your knowledge
ECG practice cases free online interactive self-test 2024 2025
best free ECG quiz websites cardiology students cases with answers
ECG practice site dr smith ECG blog 150 ECG cases free
https://litfl.com/top-100/ecg
| Stage | Resource | Focus |
|---|---|---|
| Beginner | Oxford Medical Education | Structured systematic reading |
| Intermediate | LITFL Top 150 ECG Library | Broad case variety, real tracings |
| Advanced | Dr. Smith's ECG Blog | Subtle ischaemia, occlusion-MI patterns |
| Rapid drilling | Practical Clinical Skills / SkillStat | Rhythm recognition under time pressure |
| Competitive | ECG Weekly Workout | High-difficulty mixed cases with scoring |