generatehandwrittenpdf of ECG with clear explanation and I need it easy to remember add some mnemonics to remember and make it detailed with all good explanations.. I need it in a pdf format
ls -la /tmp/workspace/ 2>/dev/null || mkdir -p /tmp/workspace/ecg-pdf
mkdir -p /tmp/workspace/ecg-pdf && pip install reportlab --quiet 2>&1 | tail -1
~/ecg-pdf/generate_ecg_pdf.py
#!/usr/bin/env python3
"""
ECG Handwritten-Style PDF Generator
Generates a detailed, handwritten-style PDF about ECG with mnemonics.
"""
import math
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.pdfgen import canvas
from reportlab.lib.colors import HexColor, Color
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
import os
OUTPUT = "/tmp/workspace/ecg-pdf/ECG_Complete_Guide.pdf"
# ── Color palette (medical notebook aesthetic) ──────────────────────────────
BG_COLOR = HexColor("#FFFDF4") # warm cream paper
RULE_COLOR = HexColor("#D0E8F5") # blue ruled lines
MARGIN_COLOR = HexColor("#FFCDD2") # red margin line
INK_COLOR = HexColor("#1A237E") # deep navy ink (main text)
HEADING_COLOR = HexColor("#0D47A1") # bright blue headings
RED_INK = HexColor("#B71C1C") # red ink for mnemonics / warnings
GREEN_INK = HexColor("#1B5E20") # green for normal values
ORANGE_INK = HexColor("#E65100") # orange for boxes/highlights
PURPLE_INK = HexColor("#4A148C") # purple for wave labels
PENCIL_GRAY = HexColor("#546E7A") # gray for diagrams
ECG_COLOR = HexColor("#B71C1C") # red ECG trace
PAGE_W, PAGE_H = A4 # 595.27 x 841.89 pt
MARGIN_L = 60
MARGIN_R = PAGE_W - 30
CONTENT_W = MARGIN_R - MARGIN_L - 20
def new_page(c, page_num):
"""Draw the lined notebook background."""
c.showPage()
# Cream background
c.setFillColor(BG_COLOR)
c.rect(0, 0, PAGE_W, PAGE_H, fill=1, stroke=0)
# Horizontal ruled lines every 22pt
c.setStrokeColor(RULE_COLOR)
c.setLineWidth(0.5)
y = PAGE_H - 50
while y > 40:
c.line(MARGIN_L - 20, y, PAGE_W - 20, y)
y -= 22
# Red margin line
c.setStrokeColor(MARGIN_COLOR)
c.setLineWidth(1.2)
c.line(MARGIN_L - 2, 30, MARGIN_L - 2, PAGE_H - 30)
# Page number
c.setFillColor(PENCIL_GRAY)
c.setFont("Helvetica", 8)
c.drawCentredString(PAGE_W / 2, 18, f"— {page_num} — ECG Complete Study Guide")
def draw_heading(c, x, y, text, size=16, color=HEADING_COLOR, underline=True):
c.setFont("Helvetica-Bold", size)
c.setFillColor(color)
c.drawString(x, y, text)
if underline:
tw = c.stringWidth(text, "Helvetica-Bold", size)
c.setStrokeColor(color)
c.setLineWidth(1.5)
c.line(x, y - 3, x + tw, y - 3)
def draw_body(c, x, y, text, size=10.5, color=INK_COLOR, font="Helvetica"):
c.setFont(font, size)
c.setFillColor(color)
c.drawString(x, y, text)
def draw_wrapped(c, x, y, text, size=10.5, color=INK_COLOR, max_width=480, line_height=18, font="Helvetica"):
"""Draw wrapped text, returns final y."""
c.setFont(font, size)
c.setFillColor(color)
words = text.split()
line = ""
for word in words:
test_line = (line + " " + word).strip()
if c.stringWidth(test_line, font, size) <= max_width:
line = test_line
else:
c.drawString(x, y, line)
y -= line_height
line = word
if line:
c.drawString(x, y, line)
y -= line_height
return y
def draw_box(c, x, y, w, h, fill_color=HexColor("#E3F2FD"), stroke_color=HEADING_COLOR, stroke_w=1.5, radius=6):
c.setFillColor(fill_color)
c.setStrokeColor(stroke_color)
c.setLineWidth(stroke_w)
c.roundRect(x, y, w, h, radius, fill=1, stroke=1)
def draw_bullet(c, x, y, text, size=10.5, color=INK_COLOR, bullet="•", max_width=460):
c.setFont("Helvetica-Bold", size)
c.setFillColor(HEADING_COLOR)
c.drawString(x, y, bullet)
c.setFont("Helvetica", size)
c.setFillColor(color)
tw = c.stringWidth(bullet + " ", "Helvetica-Bold", size)
return draw_wrapped(c, x + 14, y, text, size=size, color=color, max_width=max_width - 14, line_height=17)
def draw_ecg_wave(c, x, y, scale=1.0):
"""Draw a stylized single ECG complex (PQRST) as a path."""
c.setStrokeColor(ECG_COLOR)
c.setLineWidth(1.8 * scale)
c.setFillColor(colors.white)
pts = [
(0, 0), # baseline start
(15, 0), # isoelectric line before P
(20, 8), # P wave up
(28, 8), # P wave plateau
(33, 0), # P wave down
(40, 0), # PR segment
(44, -4), # Q wave dip
(48, 40), # R wave peak
(52, -6), # S wave dip
(56, 0), # end of QRS
(62, 0), # ST segment start
(70, 6), # T wave up
(80, 18), # T wave peak
(90, 6), # T wave down
(100, 0), # T wave end / baseline
(115, 0), # baseline end
]
p = c.beginPath()
sx, sy = x + pts[0][0] * scale, y + pts[0][1] * scale
p.moveTo(sx, sy)
for px, py in pts[1:]:
p.lineTo(x + px * scale, y + py * scale)
c.drawPath(p, stroke=1, fill=0)
def draw_ecg_strip(c, x, y, width=480, repeats=4, scale=0.9):
"""Draw a full ECG strip with grid and multiple beats."""
h = 80
# Grid
c.setStrokeColor(HexColor("#FFCDD2"))
c.setLineWidth(0.3)
# Small squares (1mm)
sq = 2.8
for gx in range(0, int(width), int(sq)):
c.line(x + gx, y - h, x + gx, y + 10)
for gy in range(-int(h), 15, int(sq)):
c.line(x, y + gy, x + width, y + gy)
# Thick squares (5mm)
c.setStrokeColor(HexColor("#EF9A9A"))
c.setLineWidth(0.8)
sq5 = sq * 5
for gx in range(0, int(width), int(sq5)):
c.line(x + gx, y - h, x + gx, y + 10)
for gy in range(-int(h), 15, int(sq5)):
c.line(x, y + gy, x + width, y + gy)
# Baseline
c.setStrokeColor(PENCIL_GRAY)
c.setLineWidth(0.5)
c.line(x, y, x + width, y)
# ECG beats
beat_w = 115 * scale
cx = x + 5
for _ in range(repeats):
draw_ecg_wave(c, cx, y, scale=scale)
cx += beat_w
def label_ecg(c, x, y, scale=0.9):
"""Add P, QRS, T labels to ECG wave."""
c.setFont("Helvetica-Bold", 8)
# P wave label
c.setFillColor(PURPLE_INK)
c.drawString(x + 20 * scale, y + 22 * scale, "P")
# QRS label
c.setFillColor(RED_INK)
c.drawString(x + 45 * scale, y + 58 * scale, "QRS")
# T wave label
c.setFillColor(HexColor("#1B5E20"))
c.drawString(x + 78 * scale, y + 32 * scale, "T")
# Intervals
c.setFont("Helvetica", 7)
c.setFillColor(ORANGE_INK)
# PR interval arrow
c.drawString(x + 14 * scale, y - 18 * scale, "◀── PR interval ──▶")
# QT interval
c.drawString(x + 40 * scale, y - 30 * scale, "◀────── QT interval ──────▶")
# ═══════════════════════════════════════════════════════════════════════════
# BUILD THE PDF
# ═══════════════════════════════════════════════════════════════════════════
c = canvas.Canvas(OUTPUT, pagesize=A4)
c.setTitle("ECG Complete Study Guide — Handwritten Style")
c.setAuthor("Orris Medical Notes")
c.setSubject("Electrocardiography — Detailed Notes with Mnemonics")
page_num = 0
# ──────────────────────────────────────────────────────────────────────────
# COVER PAGE
# ──────────────────────────────────────────────────────────────────────────
page_num += 1
# Cream background
c.setFillColor(BG_COLOR)
c.rect(0, 0, PAGE_W, PAGE_H, fill=1, stroke=0)
# Ruled lines
c.setStrokeColor(RULE_COLOR)
c.setLineWidth(0.5)
y_line = PAGE_H - 50
while y_line > 40:
c.line(40, y_line, PAGE_W - 20, y_line)
y_line -= 22
# Margin
c.setStrokeColor(MARGIN_COLOR)
c.setLineWidth(1.5)
c.line(58, 30, 58, PAGE_H - 30)
# Header bar
draw_box(c, 40, PAGE_H - 110, PAGE_W - 80, 85, fill_color=HexColor("#0D47A1"), stroke_color=HexColor("#0D47A1"), radius=10)
c.setFillColor(colors.white)
c.setFont("Helvetica-Bold", 28)
c.drawCentredString(PAGE_W / 2, PAGE_H - 68, "⚡ ECG Complete Study Guide")
c.setFont("Helvetica", 14)
c.drawCentredString(PAGE_W / 2, PAGE_H - 92, "Electrocardiography • Explained Simply • With Mnemonics")
# ECG Strip on cover
draw_ecg_strip(c, 45, PAGE_H - 175, width=505, repeats=4, scale=0.88)
label_ecg(c, 48, PAGE_H - 175, scale=0.88)
# Table of Contents box
draw_box(c, 60, 200, 475, 360, fill_color=HexColor("#F3F8FF"), stroke_color=HEADING_COLOR, radius=8)
c.setFont("Helvetica-Bold", 14)
c.setFillColor(HEADING_COLOR)
c.drawCentredString(PAGE_W / 2, 542, "📋 Table of Contents")
c.setStrokeColor(HEADING_COLOR)
c.setLineWidth(1)
c.line(80, 537, PAGE_W - 80, 537)
toc = [
("1", "What is an ECG? — Basics & History", "Page 2"),
("2", "ECG Paper & Grid — The Ruler of the Heart", "Page 3"),
("3", "Cardiac Conduction System — The Wiring", "Page 3"),
("4", "The ECG Waveform — P, QRS, T Explained", "Page 4"),
("5", "Intervals & Segments — Normal Values", "Page 5"),
("6", "The 12-Lead ECG — Leads Explained", "Page 6"),
("7", "Heart Rate Calculation — 3 Easy Methods", "Page 7"),
("8", "Axis Determination — Quick Method", "Page 8"),
("9", "Normal Sinus Rhythm — The Gold Standard", "Page 8"),
("10","Common Arrhythmias — Recognition Guide", "Page 9"),
("11","ST Changes — STEMI, NSTEMI & More", "Page 10"),
("12","Bundle Branch Blocks — WiLLiaM MaRRoW", "Page 11"),
("13","Hypertrophy Patterns — Voltage Criteria", "Page 12"),
("14","Electrolyte Changes on ECG", "Page 12"),
("15","All Mnemonics Summary — Quick Reference", "Page 13"),
]
ty = 520
for num, title, page in toc:
c.setFont("Helvetica-Bold", 9.5)
c.setFillColor(ORANGE_INK)
c.drawString(75, ty, f"{num}.")
c.setFont("Helvetica", 9.5)
c.setFillColor(INK_COLOR)
c.drawString(95, ty, title)
c.setFillColor(PENCIL_GRAY)
c.drawRightString(PAGE_W - 75, ty, page)
ty -= 22
c.setFont("Helvetica-Oblique", 9)
c.setFillColor(PENCIL_GRAY)
c.drawCentredString(PAGE_W/2, 210, "Sources: Medical Physiology (Boron & Boulpaep) • Miller's Anesthesia 10e • Clinical ECG Principles")
c.setFont("Helvetica", 8)
c.drawCentredString(PAGE_W/2, 195, "— 1 —")
# ──────────────────────────────────────────────────────────────────────────
# PAGE 2 — What is an ECG?
# ──────────────────────────────────────────────────────────────────────────
page_num += 1
new_page(c, page_num)
Y = PAGE_H - 60
draw_heading(c, MARGIN_L, Y, "1. What is an ECG?", size=17)
Y -= 30
draw_box(c, MARGIN_L, Y - 55, CONTENT_W, 65, fill_color=HexColor("#E8F5E9"), stroke_color=GREEN_INK)
c.setFont("Helvetica-Bold", 11)
c.setFillColor(GREEN_INK)
c.drawString(MARGIN_L + 12, Y - 12, "Definition:")
c.setFont("Helvetica", 10.5)
c.setFillColor(INK_COLOR)
Y2 = draw_wrapped(c, MARGIN_L + 12, Y - 28,
"An ECG (Electrocardiogram) is a non-invasive recording of the electrical activity of the heart "
"over time using electrodes placed on the skin. It provides a direct measurement of rate, rhythm, "
"and the time-dependent electrical vector of the heart.",
max_width=CONTENT_W - 20, line_height=16)
Y -= 75
draw_heading(c, MARGIN_L, Y, "Why is ECG Important?", size=12, color=ORANGE_INK)
Y -= 22
bullets_why = [
"Diagnose arrhythmias (irregular heartbeats)",
"Detect myocardial infarction (heart attack) — ST changes visible within minutes",
"Identify electrolyte imbalances (K⁺, Ca²⁺, Mg²⁺)",
"Assess drug toxicity (digoxin, antiarrhythmics)",
"Monitor patients during surgery and in ICU",
"Detect conduction blocks (bundle branch blocks, AV blocks)",
"Evaluate hypertrophy (enlarged chambers)",
]
for b in bullets_why:
Y = draw_bullet(c, MARGIN_L, Y, b)
Y -= 4
Y -= 10
draw_heading(c, MARGIN_L, Y, "A Brief History", size=12, color=PURPLE_INK)
Y -= 22
Y = draw_wrapped(c, MARGIN_L, Y,
"Willem Einthoven (Dutch physician) invented the first practical ECG in 1903 using a "
"string galvanometer. He won the Nobel Prize in Physiology/Medicine in 1924. "
"He defined the classic limb leads (I, II, III) — now called 'Einthoven's Triangle'.",
max_width=CONTENT_W, line_height=17)
Y -= 10
# Einthoven's triangle diagram
draw_box(c, MARGIN_L, Y - 120, CONTENT_W, 125, fill_color=HexColor("#FFF8E1"), stroke_color=ORANGE_INK, radius=8)
c.setFont("Helvetica-Bold", 11)
c.setFillColor(ORANGE_INK)
c.drawCentredString(PAGE_W/2, Y - 15, "Einthoven's Triangle (Limb Leads I, II, III)")
# Triangle
cx, cy = PAGE_W / 2, Y - 70
tri_r = 45
pts_tri = [
(cx - tri_r * 1.1, cy - tri_r * 0.6), # Left Arm (LA)
(cx + tri_r * 1.1, cy - tri_r * 0.6), # Right Arm (RA)
(cx, cy + tri_r * 1.0), # Left Leg (LL)
]
c.setStrokeColor(ORANGE_INK)
c.setLineWidth(2)
p = c.beginPath()
p.moveTo(*pts_tri[0])
p.lineTo(*pts_tri[1])
p.lineTo(*pts_tri[2])
p.close()
c.drawPath(p, stroke=1, fill=0)
labels_tri = [("RA\n(Right Arm)", pts_tri[1][0]+8, pts_tri[1][1]-16),
("LA\n(Left Arm)", pts_tri[0][0]-40, pts_tri[0][1]-16),
("LL\n(Left Leg)", pts_tri[2][0]-12, pts_tri[2][1]+5)]
c.setFont("Helvetica-Bold", 9)
c.setFillColor(HEADING_COLOR)
for lbl, lx, ly in labels_tri:
for i, line in enumerate(lbl.split("\n")):
c.drawString(lx, ly - i*12, line)
# Lead labels
c.setFont("Helvetica-Bold", 8.5)
c.setFillColor(RED_INK)
c.drawCentredString(cx, cy - tri_r * 0.6 - 10, "Lead I (RA → LA)")
c.drawString(cx + 10, cy - 5, "Lead II")
c.drawString(cx - 55, cy - 5, "Lead III")
Y -= 128
# Mnemonic box
draw_box(c, MARGIN_L, Y - 60, CONTENT_W, 65, fill_color=HexColor("#FCE4EC"), stroke_color=RED_INK, radius=8)
c.setFont("Helvetica-Bold", 12)
c.setFillColor(RED_INK)
c.drawString(MARGIN_L + 12, Y - 15, "🧠 MNEMONIC — Einthoven's Law:")
c.setFont("Helvetica-BoldOblique", 13)
c.setFillColor(HexColor("#880E4F"))
c.drawCentredString(PAGE_W/2, Y - 35, "\" Lead I + Lead III = Lead II \"")
c.setFont("Helvetica", 9.5)
c.setFillColor(INK_COLOR)
c.drawCentredString(PAGE_W/2, Y - 52, "(Voltages add up: I + III = II always holds true)")
# ──────────────────────────────────────────────────────────────────────────
# PAGE 3 — ECG Paper + Conduction System
# ──────────────────────────────────────────────────────────────────────────
page_num += 1
new_page(c, page_num)
Y = PAGE_H - 60
draw_heading(c, MARGIN_L, Y, "2. ECG Paper & The Grid", size=17)
Y -= 28
draw_box(c, MARGIN_L, Y - 115, CONTENT_W, 118, fill_color=HexColor("#E3F2FD"), stroke_color=HEADING_COLOR, radius=8)
# Mini grid diagram
gx, gy = MARGIN_L + 15, Y - 25
sq_s = 10 # 1mm small box
sq_L = 50 # 5mm large box
c.setStrokeColor(HexColor("#90CAF9"))
c.setLineWidth(0.5)
for i in range(6):
c.line(gx + i * sq_s, gy - 50, gx + i * sq_s, gy)
c.line(gx, gy - i * sq_s, gx + 50, gy - i * sq_s)
c.setStrokeColor(HEADING_COLOR)
c.setLineWidth(1.5)
c.rect(gx, gy - 50, 50, 50, fill=0, stroke=1)
c.setFont("Helvetica-Bold", 10)
c.setFillColor(HEADING_COLOR)
c.drawString(gx + 60, Y - 10, "ECG Paper Calibration:")
c.setFont("Helvetica", 10)
c.setFillColor(INK_COLOR)
rows = [
("Small box (1mm)", "Horizontal: 0.04 sec | Vertical: 0.1 mV"),
("Large box (5mm)", "Horizontal: 0.20 sec | Vertical: 0.5 mV"),
("1 second", "= 5 large boxes = 25 small boxes"),
("Standard speed", "25 mm/sec (sometimes 50 mm/sec)"),
("Standard voltage","10 mm = 1 mV"),
]
ry = Y - 28
for label, val in rows:
c.setFont("Helvetica-Bold", 9.5)
c.setFillColor(ORANGE_INK)
c.drawString(gx + 60, ry, f"{label}:")
c.setFont("Helvetica", 9.5)
c.setFillColor(INK_COLOR)
c.drawString(gx + 60, ry - 13, val)
ry -= 26
Y -= 130
# Mnemonic for paper
draw_box(c, MARGIN_L, Y - 50, CONTENT_W, 52, fill_color=HexColor("#FCE4EC"), stroke_color=RED_INK, radius=6)
c.setFont("Helvetica-Bold", 11)
c.setFillColor(RED_INK)
c.drawString(MARGIN_L + 12, Y - 16, "🧠 MNEMONIC — Remember Grid Values:")
c.setFont("Helvetica-BoldOblique", 12)
c.setFillColor(HexColor("#880E4F"))
c.drawCentredString(PAGE_W / 2, Y - 35, '"Small box = 0.04 sec → SMALL is SHORT → 40ms"')
c.setFont("Helvetica", 9)
c.setFillColor(INK_COLOR)
c.drawCentredString(PAGE_W / 2, Y - 47, "Big box = 0.20 sec (5 × 0.04 = 0.20) Think: BIG = 5×SMALL")
Y -= 60
# Conduction system
draw_heading(c, MARGIN_L, Y, "3. Cardiac Conduction System", size=17)
Y -= 26
draw_box(c, MARGIN_L, Y - 170, CONTENT_W, 172, fill_color=HexColor("#F3E5F5"), stroke_color=PURPLE_INK, radius=8)
c.setFont("Helvetica-Bold", 11)
c.setFillColor(PURPLE_INK)
c.drawCentredString(PAGE_W / 2, Y - 15, "⚡ The Electrical Wiring of the Heart")
conduction_steps = [
("1. SA Node", "Sinoatrial node — the pacemaker. Rate: 60–100 bpm → Generates P wave"),
("2. Internodal", "Bachmann's bundle → spreads impulse across both atria"),
("3. AV Node", "Atrioventricular node — DELAYS impulse (0.1 sec) → allows ventricular filling"),
("4. Bundle of His", "Compact bundle connects atria to ventricles through fibrous skeleton"),
("5. Bundle Branches","Right Bundle Branch (RBB) + Left Bundle Branch (LBB)"),
("6. Purkinje Fibres","Fastest conduction (4 m/s) → activates all ventricular myocytes → QRS"),
]
sy = Y - 40
for step, desc in conduction_steps:
c.setFont("Helvetica-Bold", 9.5)
c.setFillColor(PURPLE_INK)
c.drawString(MARGIN_L + 12, sy, step)
c.setFont("Helvetica", 9.5)
c.setFillColor(INK_COLOR)
c.drawString(MARGIN_L + 130, sy, desc)
c.setStrokeColor(HexColor("#CE93D8"))
c.setLineWidth(0.5)
c.line(MARGIN_L + 12, sy - 4, MARGIN_R - 35, sy - 4)
sy -= 22
# Arrow indicating flow
c.setFont("Helvetica-Bold", 9)
c.setFillColor(RED_INK)
c.drawString(MARGIN_L + 12, sy + 5, "Flow: SA → Atria → AV Node (delay) → His → BB → Purkinje → Ventricles")
Y -= 182
# Mnemonic for conduction
draw_box(c, MARGIN_L, Y - 55, CONTENT_W, 58, fill_color=HexColor("#FCE4EC"), stroke_color=RED_INK, radius=6)
c.setFont("Helvetica-Bold", 12)
c.setFillColor(RED_INK)
c.drawString(MARGIN_L + 12, Y - 14, "🧠 MNEMONIC — Conduction System Order:")
c.setFont("Helvetica-BoldOblique", 14)
c.setFillColor(HexColor("#880E4F"))
c.drawCentredString(PAGE_W / 2, Y - 33, '"SAAAB — P"')
c.setFont("Helvetica", 9.5)
c.setFillColor(INK_COLOR)
c.drawCentredString(PAGE_W / 2, Y - 50, "SA node → Atria → AV node → Bundle of His → Bundle branches → Purkinje")
# ──────────────────────────────────────────────────────────────────────────
# PAGE 4 — ECG Waveform
# ──────────────────────────────────────────────────────────────────────────
page_num += 1
new_page(c, page_num)
Y = PAGE_H - 60
draw_heading(c, MARGIN_L, Y, "4. The ECG Waveform — P, QRS, T, U", size=17)
Y -= 28
# Large ECG wave diagram
draw_ecg_strip(c, MARGIN_L, Y - 20, width=CONTENT_W, repeats=3, scale=1.05)
label_ecg(c, MARGIN_L, Y - 20, scale=1.05)
# Add U wave label (small)
c.setFont("Helvetica-Bold", 8)
c.setFillColor(PENCIL_GRAY)
c.drawString(MARGIN_L + 108, Y + 4, "U")
Y -= 85
# Wave descriptions table
waves = [
("P Wave", PURPLE_INK,
"Atrial depolarization (SA node fires → both atria contract)",
"Duration: < 0.12 sec (3 small boxes) | Amplitude: < 2.5 mm",
"Upright in I, II, aVF, V4–V6 | Inverted in aVR"),
("PR Interval", HEADING_COLOR,
"Time from start of P to start of QRS (AV node conduction time)",
"Normal: 0.12 – 0.20 sec (3–5 small boxes)",
"Short PR = Pre-excitation (WPW) | Long PR = AV block"),
("QRS Complex", RED_INK,
"Ventricular depolarization (both ventricles contract simultaneously)",
"Normal duration: < 0.12 sec (3 small boxes)",
"Wide QRS = BBB / aberrant conduction / hyperkalemia"),
("ST Segment", GREEN_INK,
"Period between end of QRS and start of T wave — ventricles fully depolarized",
"Should be at ISOELECTRIC LINE (±0.5 mm from baseline)",
"Elevation = injury/STEMI | Depression = ischemia/NSTEMI"),
("T Wave", HexColor("#1B5E20"),
"Ventricular REPOLARIZATION (ventricles recovering, relaxing)",
"Normally upright in most leads. Amplitude < 5mm limb, < 10mm precordial",
"Peaked T = Hyperkalemia | Inverted T = ischemia, RBBB, LVH"),
("QT Interval", ORANGE_INK,
"Total ventricular activity (depolarization + repolarization)",
"Normal corrected QTc: < 440ms (male) / < 460ms (female)",
"Long QT → risk of Torsades de Pointes (dangerous arrhythmia!)"),
("U Wave", PENCIL_GRAY,
"Small deflection after T wave — likely Purkinje fibre repolarization",
"Amplitude < 1mm, same direction as T wave",
"Prominent U wave = Hypokalemia, Bradycardia, LVH"),
]
for wname, wcolor, desc1, desc2, desc3 in waves:
box_h = 58
draw_box(c, MARGIN_L, Y - box_h, CONTENT_W, box_h + 2,
fill_color=Color(wcolor.red, wcolor.green, wcolor.blue, alpha=0.08),
stroke_color=wcolor, stroke_w=1.2, radius=5)
c.setFont("Helvetica-Bold", 11)
c.setFillColor(wcolor)
c.drawString(MARGIN_L + 8, Y - 14, wname)
c.setFont("Helvetica", 9.5)
c.setFillColor(INK_COLOR)
c.drawString(MARGIN_L + 120, Y - 14, desc1)
c.setFont("Helvetica-Oblique", 9)
c.setFillColor(PENCIL_GRAY)
c.drawString(MARGIN_L + 8, Y - 30, desc2)
c.setFont("Helvetica", 9)
c.setFillColor(ORANGE_INK if "STEMI" in desc3 or "risk" in desc3 else INK_COLOR)
c.drawString(MARGIN_L + 8, Y - 45, f"⚠ {desc3}")
Y -= (box_h + 8)
# ──────────────────────────────────────────────────────────────────────────
# PAGE 5 — Normal Values + 12 Leads
# ──────────────────────────────────────────────────────────────────────────
page_num += 1
new_page(c, page_num)
Y = PAGE_H - 60
draw_heading(c, MARGIN_L, Y, "5. Normal ECG Intervals — Quick Reference Table", size=16)
Y -= 28
# Table header
table_data = [
("Parameter", "Normal Value", "Clinical Note"),
("P wave duration", "< 0.12 sec (< 3 sq)", "Prolonged = atrial enlargement"),
("P wave amplitude", "< 2.5 mm", "Peaked = R.A. enlargement (P-pulmonale)"),
("PR interval", "0.12 – 0.20 sec", "Short <0.12 = WPW; Long >0.20 = AV block"),
("QRS duration", "0.06 – 0.10 sec", "Wide >0.12 = BBB / hyperkalemia"),
("QRS axis", "–30° to +90°", "LAD < –30°; RAD > +90°"),
("ST segment", "Isoelectric (±0.5mm)", "Elevation = STEMI; Depression = ischemia"),
("T wave", "Positive most leads", "Invert in aVR (normal); V1–V2 may invert"),
("QT interval", "< 440ms (♂) <460ms (♀)","Corrected (QTc) = QT / √RR"),
("QTc formula", "Bazett: QTc=QT/√RR", "Long QT → Torsades de Pointes risk"),
("RR interval", "0.6 – 1.0 sec (60–100)","Measure P-P for rate"),
]
col_w = [130, 140, 210]
tx = MARGIN_L
row_h = 21
for ri, row in enumerate(table_data):
if ri == 0:
c.setFillColor(HEADING_COLOR)
c.rect(tx, Y - row_h, sum(col_w), row_h, fill=1, stroke=0)
c.setFillColor(colors.white)
c.setFont("Helvetica-Bold", 10)
else:
bg = HexColor("#EEF4FF") if ri % 2 == 0 else colors.white
c.setFillColor(bg)
c.rect(tx, Y - row_h, sum(col_w), row_h, fill=1, stroke=0)
c.setFillColor(INK_COLOR)
c.setFont("Helvetica", 9.5)
cx2 = tx + 5
for ci, cell in enumerate(row):
if ri == 0:
c.setFont("Helvetica-Bold", 10)
c.setFillColor(colors.white)
else:
c.setFont("Helvetica", 9.5)
c.setFillColor(GREEN_INK if ci == 1 else (RED_INK if ("block" in cell.lower() or "STEMI" in cell or "risk" in cell) else INK_COLOR))
c.drawString(cx2, Y - row_h + 5, cell)
cx2 += col_w[ci]
c.setStrokeColor(HexColor("#BBDEFB"))
c.setLineWidth(0.5)
c.rect(tx, Y - row_h, sum(col_w), row_h, fill=0, stroke=1)
Y -= row_h
Y -= 18
draw_heading(c, MARGIN_L, Y, "6. The 12-Lead ECG — What Each Lead Sees", size=16)
Y -= 26
draw_box(c, MARGIN_L, Y - 200, CONTENT_W, 202, fill_color=HexColor("#FFF3E0"), stroke_color=ORANGE_INK, radius=8)
c.setFont("Helvetica-Bold", 11)
c.setFillColor(ORANGE_INK)
c.drawCentredString(PAGE_W / 2, Y - 14, "12 Leads = 4 Groups — Each Views a Different Wall of the Heart")
leads_groups = [
("INFERIOR (II, III, aVF)", "Inferior wall of LV (supplied by RCA in 80%)", HexColor("#E65100")),
("LATERAL (I, aVL, V5, V6)", "Lateral wall of LV (supplied by LCx artery)", HexColor("#1565C0")),
("ANTERIOR (V1–V4)", "Anterior / septal wall of LV (supplied by LAD)", RED_INK),
("RECIPROCAL", "Changes opposite to injury (ST depression = mirror)", PENCIL_GRAY),
("aVR", "Right side cavity view — normally all inverted (QRS negative, T inverted)", PURPLE_INK),
]
ly = Y - 38
for grp, desc, gcol in leads_groups:
c.setFont("Helvetica-Bold", 10)
c.setFillColor(gcol)
c.drawString(MARGIN_L + 10, ly, grp)
c.setFont("Helvetica", 9.5)
c.setFillColor(INK_COLOR)
c.drawString(MARGIN_L + 10, ly - 14, desc)
c.setStrokeColor(gcol)
c.setLineWidth(0.4)
c.line(MARGIN_L + 10, ly - 18, MARGIN_R - 35, ly - 18)
ly -= 34
# Lead placement mnemonic
c.setFont("Helvetica-Bold", 10.5)
c.setFillColor(RED_INK)
c.drawString(MARGIN_L + 10, ly, "🧠 Precordial Lead Placement:")
c.setFont("Helvetica", 9.5)
c.setFillColor(INK_COLOR)
placements = [
"V1 — 4th ICS, Right Sternal Border",
"V2 — 4th ICS, Left Sternal Border",
"V3 — Between V2 and V4",
"V4 — 5th ICS, Midclavicular Line",
"V5 — Anterior Axillary Line (same level as V4)",
"V6 — Midaxillary Line (same level as V4 & V5)",
]
ly -= 16
for pl in placements:
c.drawString(MARGIN_L + 15, ly, f"• {pl}")
ly -= 15
Y -= 210
# Mnemonic
draw_box(c, MARGIN_L, Y - 55, CONTENT_W, 57, fill_color=HexColor("#FCE4EC"), stroke_color=RED_INK, radius=6)
c.setFont("Helvetica-Bold", 12)
c.setFillColor(RED_INK)
c.drawString(MARGIN_L + 12, Y - 14, "🧠 MNEMONIC — Chest Lead Placement:")
c.setFont("Helvetica-BoldOblique", 13)
c.setFillColor(HexColor("#880E4F"))
c.drawCentredString(PAGE_W / 2, Y - 33, '"V1,2 on the Right → V4 on the Nipple → V5,6 to the Side"')
c.setFont("Helvetica", 9)
c.setFillColor(INK_COLOR)
c.drawCentredString(PAGE_W / 2, Y - 50, "(V3 fills the GAP between V2 and V4)")
# ──────────────────────────────────────────────────────────────────────────
# PAGE 6 — Heart Rate + Axis
# ──────────────────────────────────────────────────────────────────────────
page_num += 1
new_page(c, page_num)
Y = PAGE_H - 60
draw_heading(c, MARGIN_L, Y, "7. Heart Rate Calculation — 3 Methods", size=17)
Y -= 28
methods = [
("Method 1 — The 300 Rule (REGULAR rhythm)",
"Count the number of LARGE boxes between two R waves.",
"Divide 300 by that number → Rate = 300 ÷ (# large boxes)",
"Memory aid: 300 → 150 → 100 → 75 → 60 → 50 (1,2,3,4,5,6 boxes)",
HexColor("#1A237E")),
("Method 2 — The 1500 Rule (precise, any rhythm)",
"Count the number of SMALL boxes between two R waves.",
"Divide 1500 by that number → Rate = 1500 ÷ (# small boxes)",
"More accurate for fast rates when boxes are fewer",
HexColor("#1B5E20")),
("Method 3 — 6-Second Strip (IRREGULAR rhythm like AF)",
"Count the number of QRS complexes in a 6-second strip (30 large boxes).",
"Multiply by 10 → Rate = (# of QRS) × 10",
"Best for atrial fibrillation or ectopic rhythms",
HexColor("#B71C1C")),
]
for title, l1, l2, l3, col in methods:
draw_box(c, MARGIN_L, Y - 72, CONTENT_W, 75, fill_color=Color(col.red, col.green, col.blue, alpha=0.07), stroke_color=col, radius=6)
c.setFont("Helvetica-Bold", 11)
c.setFillColor(col)
c.drawString(MARGIN_L + 10, Y - 15, title)
c.setFont("Helvetica", 10)
c.setFillColor(INK_COLOR)
c.drawString(MARGIN_L + 10, Y - 30, l1)
c.setFont("Helvetica-Bold", 10.5)
c.setFillColor(col)
c.drawString(MARGIN_L + 10, Y - 45, l2)
c.setFont("Helvetica-Oblique", 9.5)
c.setFillColor(PENCIL_GRAY)
c.drawString(MARGIN_L + 10, Y - 60, l3)
Y -= 84
# Mnemonic
draw_box(c, MARGIN_L, Y - 48, CONTENT_W, 50, fill_color=HexColor("#FCE4EC"), stroke_color=RED_INK, radius=6)
c.setFont("Helvetica-Bold", 12)
c.setFillColor(RED_INK)
c.drawString(MARGIN_L + 12, Y - 14, "🧠 MNEMONIC — The 300 Rule Sequence:")
c.setFont("Helvetica-BoldOblique", 14)
c.setFillColor(HexColor("#880E4F"))
c.drawCentredString(PAGE_W / 2, Y - 33, '"300 – 150 – 100 – 75 – 60 – 50"')
c.setFont("Helvetica", 9)
c.setFillColor(INK_COLOR)
c.drawCentredString(PAGE_W / 2, Y - 47, "TIP: 3 goes into 300, 150, and 300÷4=75... memorize these 6 numbers!")
Y -= 60
draw_heading(c, MARGIN_L, Y, "8. Cardiac Axis Determination", size=17)
Y -= 26
draw_box(c, MARGIN_L, Y - 155, CONTENT_W, 157, fill_color=HexColor("#F3E5F5"), stroke_color=PURPLE_INK, radius=8)
c.setFont("Helvetica-Bold", 11)
c.setFillColor(PURPLE_INK)
c.drawCentredString(PAGE_W / 2, Y - 14, "Quick Axis Method — Look at Lead I and Lead aVF")
axis_data = [
("Lead I", "Lead aVF", "Axis", "Meaning", PENCIL_GRAY),
("↑ (+)", "↑ (+)", "NORMAL (0°–90°)", "Normal sinus", GREEN_INK),
("↑ (+)", "↓ (–)", "LEFT AXIS DEV", "LAD (<–30°) — LAFB, LVH", ORANGE_INK),
("↓ (–)", "↑ (+)", "RIGHT AXIS DEV", "RAD (>90°) — RVH, LPFB, PE", RED_INK),
("↓ (–)", "↓ (–)", "EXTREME AXIS", "NW axis — rare, ventricular rhythms", HexColor("#880E4F")),
]
ay = Y - 35
for row in axis_data:
l1, l2, ax, meaning, col = row
if l1 == "Lead I":
c.setFillColor(PURPLE_INK)
c.setFont("Helvetica-Bold", 10)
labels_ax = ["Lead I", "Lead aVF", "Axis", "Meaning"]
axs = [MARGIN_L+10, MARGIN_L+80, MARGIN_L+160, MARGIN_L+280]
for lbl, lx in zip(labels_ax, axs):
c.drawString(lx, ay, lbl)
else:
c.setFont("Helvetica-Bold", 11)
c.setFillColor(col)
c.drawString(MARGIN_L + 10, ay, l1)
c.drawString(MARGIN_L + 80, ay, l2)
c.setFont("Helvetica-Bold", 10)
c.drawString(MARGIN_L + 160, ay, ax)
c.setFont("Helvetica", 9.5)
c.setFillColor(INK_COLOR)
c.drawString(MARGIN_L + 280, ay, meaning)
c.setStrokeColor(HexColor("#CE93D8"))
c.setLineWidth(0.4)
c.line(MARGIN_L + 10, ay - 5, MARGIN_R - 35, ay - 5)
ay -= 24
c.setFont("Helvetica", 9.5)
c.setFillColor(INK_COLOR)
c.drawString(MARGIN_L + 10, ay + 5, "Causes of LAD: LBBB, LVH, Inferior MI, WPW, LAFB, Emphysema")
c.drawString(MARGIN_L + 10, ay - 10, "Causes of RAD: RVH, RBBB, PE, Lateral MI, Normal in children/tall people, Dextrocardia")
Y -= 165
# Normal Sinus Rhythm Criteria
draw_heading(c, MARGIN_L, Y, "9. Normal Sinus Rhythm (NSR) — 5 Criteria", size=15)
Y -= 22
draw_box(c, MARGIN_L, Y - 115, CONTENT_W, 117, fill_color=HexColor("#E8F5E9"), stroke_color=GREEN_INK, radius=8)
nsr_criteria = [
"1. P wave BEFORE every QRS complex (and after every QRS)",
"2. P wave UPRIGHT in Lead II, inverted in aVR",
"3. PR interval 0.12–0.20 sec (constant)",
"4. QRS duration < 0.12 sec (narrow)",
"5. Rate 60–100 bpm, REGULAR rhythm (constant RR interval)",
]
ny = Y - 20
for crit in nsr_criteria:
c.setFont("Helvetica", 10.5)
c.setFillColor(INK_COLOR)
c.drawString(MARGIN_L + 10, ny, crit)
ny -= 20
c.setFont("Helvetica-Bold", 10)
c.setFillColor(GREEN_INK)
c.drawString(MARGIN_L + 10, ny, "🧠 MNEMONIC — 'PRQST in order, rate 60–100, narrow QRS = NSR!'")
# ──────────────────────────────────────────────────────────────────────────
# PAGE 7 — Arrhythmias
# ──────────────────────────────────────────────────────────────────────────
page_num += 1
new_page(c, page_num)
Y = PAGE_H - 60
draw_heading(c, MARGIN_L, Y, "10. Common Arrhythmias — Recognition Guide", size=17)
Y -= 26
arrhythmias = [
("Sinus Bradycardia", "Rate < 60, Normal P, Normal QRS. Cause: athletes, hypothyroid, drugs (BB, CCB)", GREEN_INK, "Treat only if symptomatic: atropine → pacing"),
("Sinus Tachycardia", "Rate >100, Normal P, Normal QRS. Cause: fever, pain, anemia, anxiety, PE", ORANGE_INK, "Treat underlying cause"),
("Atrial Fibrillation", "IRREGULARLY irregular, NO P waves → 'fibrillatory baseline', narrow QRS", RED_INK, "Rate control: BB/CCB. Rhythm control: cardioversion. Anticoagulate!"),
("Atrial Flutter", "Regular, rate ~300 atrial / 150 ventricular. SAWTOOTH P waves (best in II,III,aVF)", HexColor("#E65100"), "2:1 or 4:1 block typical. Cardioversion or ablation"),
("SVT (AVNRT)", "Rate 150–250, NARROW QRS, P hidden in/after QRS. Abrupt onset/offset", PURPLE_INK, "Vagal maneuvers → Adenosine 6mg IV → cardioversion"),
("1° AV Block", "Prolonged PR > 0.20s. Every P followed by QRS. Benign.", PENCIL_GRAY, "No treatment usually needed"),
("2° AV Block Mobitz I", "Progressively LENGTHENING PR until a QRS drops. Then resets. (Wenckebach)", HexColor("#1565C0"), "Benign, monitor. Atropine if symptomatic"),
("2° AV Block Mobitz II", "CONSTANT PR, then SUDDEN DROP of QRS with no warning. HIGH RISK!", RED_INK, "⚠ URGENT — needs pacemaker (can progress to complete heart block)"),
("3° AV Block (CHB)", "P waves and QRS COMPLETELY dissociated. Atria and ventricles beat independently", HexColor("#880E4F"), "⚠ EMERGENCY — temporary transcutaneous pacing → permanent pacemaker"),
("VT (Ventricular Tach)", "Rate >120, WIDE QRS (>0.12s), no P wave visible. Can be unstable", RED_INK, "Stable: amiodarone. Unstable: cardioversion. Pulseless: CPR + defibrillation"),
("VF (Ventricular Fib)", "Chaotic, irregular, no recognizable complexes. CARDIAC ARREST!", HexColor("#B71C1C"), "⚡ IMMEDIATE defibrillation! CPR, epinephrine, amiodarone (ACLS)"),
("WPW Syndrome", "SHORT PR < 0.12s + DELTA WAVE (slurred QRS upstroke). Risk of AF → VF!", HexColor("#E65100"), "Avoid AV-nodal drugs. Ablation is definitive treatment"),
]
for aname, ecg_feat, col, mgmt in arrhythmias:
box_h = 50
draw_box(c, MARGIN_L, Y - box_h, CONTENT_W, box_h + 2,
fill_color=Color(col.red, col.green, col.blue, alpha=0.07),
stroke_color=col, stroke_w=1.0, radius=4)
c.setFont("Helvetica-Bold", 10)
c.setFillColor(col)
c.drawString(MARGIN_L + 8, Y - 13, aname)
c.setFont("Helvetica", 9)
c.setFillColor(INK_COLOR)
c.drawString(MARGIN_L + 8, Y - 27, f"ECG: {ecg_feat}")
c.setFont("Helvetica-Oblique", 9)
c.setFillColor(RED_INK if "⚠" in mgmt or "⚡" in mgmt else GREEN_INK)
c.drawString(MARGIN_L + 8, Y - 41, f"Rx: {mgmt}")
Y -= (box_h + 5)
# ──────────────────────────────────────────────────────────────────────────
# PAGE 8 — ST Changes + BBB
# ──────────────────────────────────────────────────────────────────────────
page_num += 1
new_page(c, page_num)
Y = PAGE_H - 60
draw_heading(c, MARGIN_L, Y, "11. ST Segment Changes — Ischemia & Infarction", size=17)
Y -= 26
draw_box(c, MARGIN_L, Y - 170, CONTENT_W, 172, fill_color=HexColor("#FFEBEE"), stroke_color=RED_INK, radius=8)
st_items = [
("ST Elevation", RED_INK, "INJURY / STEMI",
"Convex (tombstone) elevation ≥ 1mm in 2+ contiguous leads",
"Causes: STEMI, Pericarditis (saddle-shaped, diffuse), LV aneurysm, Brugada, BER"),
("ST Depression", ORANGE_INK,"ISCHEMIA / NSTEMI",
"Horizontal or downsloping ≥ 0.5mm in 2+ contiguous leads",
"Causes: NSTEMI/UA, subendocardial ischemia, digoxin toxicity, LVH strain"),
("ST Elevation in aVR", HexColor("#880E4F"), "LEFT MAIN / 3-VESSEL DISEASE",
"Global ischemia pattern: ST elevation in aVR + ST depression in multiple leads",
"This is a critical finding — emergent catheterization needed"),
]
sy2 = Y - 20
for name, col, dx, feat, causes in st_items:
c.setFont("Helvetica-Bold", 10.5)
c.setFillColor(col)
c.drawString(MARGIN_L + 10, sy2, f"{name} → {dx}")
c.setFont("Helvetica", 9.5)
c.setFillColor(INK_COLOR)
c.drawString(MARGIN_L + 10, sy2 - 14, feat)
c.setFont("Helvetica-Oblique", 9)
c.setFillColor(PENCIL_GRAY)
c.drawString(MARGIN_L + 10, sy2 - 28, f"Causes: {causes}")
sy2 -= 48
# STEMI Territory Table
c.setFont("Helvetica-Bold", 10.5)
c.setFillColor(RED_INK)
c.drawString(MARGIN_L + 10, sy2, "STEMI Localisation — Which Leads Tell You Which Wall?")
sy2 -= 18
stemi_loc = [
("Inferior STEMI", "II, III, aVF", "RCA (80%) or LCx (20%)"),
("Anterior STEMI", "V1–V4", "LAD (Left Anterior Descending)"),
("Lateral STEMI", "I, aVL, V5, V6", "LCx (Left Circumflex)"),
("Septal STEMI", "V1–V2", "Septal branches of LAD"),
("Posterior STEMI", "ST depression V1–V3 + tall R in V1 (mirror image)", "RCA or LCx"),
("RV STEMI", "ST elevation in V4R (right-sided lead)", "Proximal RCA"),
]
for loc, leads, artery in stemi_loc:
c.setFont("Helvetica-Bold", 9)
c.setFillColor(RED_INK)
c.drawString(MARGIN_L + 10, sy2, loc)
c.setFont("Helvetica", 9)
c.setFillColor(INK_COLOR)
c.drawString(MARGIN_L + 125, sy2, leads)
c.drawString(MARGIN_L + 280, sy2, artery)
sy2 -= 16
Y -= 182
# Mnemonic STEMI
draw_box(c, MARGIN_L, Y - 45, CONTENT_W, 47, fill_color=HexColor("#FCE4EC"), stroke_color=RED_INK, radius=6)
c.setFont("Helvetica-Bold", 11)
c.setFillColor(RED_INK)
c.drawString(MARGIN_L + 12, Y - 14, "🧠 MNEMONIC — STEMI Territory (IAL):")
c.setFont("Helvetica-BoldOblique", 13)
c.setFillColor(HexColor("#880E4F"))
c.drawCentredString(PAGE_W / 2, Y - 30, '"I Am Late" → Inferior (RCA) • Anterior (LAD) • Lateral (LCx)')
c.setFont("Helvetica", 8.5)
c.setFillColor(INK_COLOR)
c.drawCentredString(PAGE_W / 2, Y - 43, "Posterior = mirror of anterior | RV = right-sided leads")
Y -= 58
draw_heading(c, MARGIN_L, Y, "12. Bundle Branch Blocks (BBB)", size=17)
Y -= 26
draw_box(c, MARGIN_L, Y - 155, CONTENT_W, 157, fill_color=HexColor("#F3E5F5"), stroke_color=PURPLE_INK, radius=8)
c.setFont("Helvetica-Bold", 13)
c.setFillColor(PURPLE_INK)
c.drawCentredString(PAGE_W / 2, Y - 16, "🧠 THE KEY MNEMONIC: WiLLiaM MaRRoW")
c.setFont("Helvetica", 10)
c.setFillColor(INK_COLOR)
c.drawCentredString(PAGE_W / 2, Y - 30, "(W pattern in V1, M pattern in V6 = LBBB) | (M in V1, W in V6 = RBBB)")
bbb_data = [
("LBBB (Left Bundle Branch Block)", PURPLE_INK, [
"QRS duration > 0.12 sec (wide)",
"V1: deep broad S wave (W shape) → the 'WiLL' in WiLLiaM",
"V6: broad notched R (M shape) → the 'iam' in WiLLiaM",
"No septal Q waves in I, V5, V6 (sign of LBBB)",
"Causes: Hypertension, Cardiomyopathy, Aortic stenosis, IHD",
"Clinical note: New LBBB + chest pain = treat as STEMI equivalent!",
]),
("RBBB (Right Bundle Branch Block)", HEADING_COLOR, [
"QRS duration > 0.12 sec (wide)",
"V1: rsR' pattern (M shape / 'bunny ears') → the 'M' in MaRRoW",
"V6: wide slurred S wave (W shape) → the 'W' in MaRRoW",
"T-wave inversion V1–V3 (secondary changes — normal in RBBB)",
"Causes: PE, ASD, RV pressure/volume overload, idiopathic",
"New RBBB can indicate acute PE — check for S1Q3T3!",
]),
]
by = Y - 50
for bname, bcol, points in bbb_data:
c.setFont("Helvetica-Bold", 10.5)
c.setFillColor(bcol)
c.drawString(MARGIN_L + 10, by, bname)
by -= 16
for pt in points:
c.setFont("Helvetica", 9.5)
c.setFillColor(INK_COLOR)
c.drawString(MARGIN_L + 20, by, f"• {pt}")
by -= 14
by -= 8
# ──────────────────────────────────────────────────────────────────────────
# PAGE 9 — Hypertrophy + Electrolytes + Summary Mnemonics
# ──────────────────────────────────────────────────────────────────────────
page_num += 1
new_page(c, page_num)
Y = PAGE_H - 60
draw_heading(c, MARGIN_L, Y, "13. Chamber Hypertrophy Patterns", size=17)
Y -= 26
hypertrophy = [
("LVH (Left Ventricular Hypertrophy)", HexColor("#1A237E"), [
"Sokolow-Lyon Criteria: S in V1 + R in V5 or V6 ≥ 35mm",
"Cornell Criteria: S in V3 + R in aVL > 28mm (men) / > 20mm (women)",
"Left axis deviation common",
"Strain pattern: ST depression + T inversion in lateral leads (I, aVL, V5, V6)",
"Causes: Hypertension, Aortic stenosis, HCM, Coarctation",
]),
("RVH (Right Ventricular Hypertrophy)", RED_INK, [
"Tall R wave in V1 (R > S in V1) — dominant R in V1",
"Deep S waves in V5, V6 (normally R dominant here)",
"Right axis deviation (QRS axis > +90°)",
"Strain: ST depression + T inversion in V1–V3, II, III, aVF",
"Causes: COPD, PE (acute), Pulmonary hypertension, ASD, PS",
]),
("LAE — Left Atrial Enlargement", PURPLE_INK, [
"P wave duration > 0.12 sec (wide, bifid P = 'P mitrale') in Lead II",
"Biphasic P in V1 with wide/deep terminal negative component",
"Causes: Mitral stenosis, MR, LV failure, Hypertension",
]),
("RAE — Right Atrial Enlargement", ORANGE_INK, [
"Tall peaked P wave > 2.5 mm in II, III, aVF ('P-pulmonale')",
"Causes: COPD, Tricuspid stenosis, Pulmonary hypertension",
]),
]
for hname, hcol, hpoints in hypertrophy:
bh = len(hpoints) * 15 + 20
draw_box(c, MARGIN_L, Y - bh, CONTENT_W, bh + 2,
fill_color=Color(hcol.red, hcol.green, hcol.blue, alpha=0.07),
stroke_color=hcol, stroke_w=1.0, radius=4)
c.setFont("Helvetica-Bold", 10.5)
c.setFillColor(hcol)
c.drawString(MARGIN_L + 8, Y - 14, hname)
hy = Y - 28
for pt in hpoints:
c.setFont("Helvetica", 9.5)
c.setFillColor(INK_COLOR)
c.drawString(MARGIN_L + 14, hy, f"• {pt}")
hy -= 15
Y -= (bh + 8)
draw_heading(c, MARGIN_L, Y, "14. Electrolyte Changes on ECG", size=16)
Y -= 24
electrolytes = [
("HYPERKALEMIA (High K⁺)", RED_INK, [
("Mild (K 5.5–6.5)", "Tall PEAKED T waves (tent-shaped, symmetric, narrow base)"),
("Moderate (K 6.5–7.5)", "Prolonged PR + Widened QRS (broad, bizarre)"),
("Severe (K > 7.5)", "Sine wave pattern → VF / asystole. EMERGENCY!"),
]),
("HYPOKALEMIA (Low K⁺)", HexColor("#1B5E20"), [
("Mild", "Flat or inverted T waves + prominent U waves (U > T in same lead)"),
("Moderate", "ST depression, PR prolongation, QT prolongation"),
("Severe", "U wave merges with T (apparent long QT) → Torsades risk"),
]),
("HYPERCALCEMIA (High Ca²⁺)", ORANGE_INK, [
("Effect", "SHORT QT interval (QTc < 350ms). Bradycardia possible. Osborn J waves rare"),
]),
("HYPOCALCEMIA (Low Ca²⁺)", HEADING_COLOR, [
("Effect", "LONG QT interval (prolonged ST segment specifically). Normal T wave shape"),
]),
]
for ename, ecol, epoints in electrolytes:
bh = len(epoints) * 16 + 18
draw_box(c, MARGIN_L, Y - bh, CONTENT_W, bh + 2,
fill_color=Color(ecol.red, ecol.green, ecol.blue, alpha=0.07),
stroke_color=ecol, stroke_w=1.0, radius=4)
c.setFont("Helvetica-Bold", 10)
c.setFillColor(ecol)
c.drawString(MARGIN_L + 8, Y - 13, ename)
ey = Y - 27
for sev, desc in epoints:
c.setFont("Helvetica-Bold", 9)
c.setFillColor(ecol)
c.drawString(MARGIN_L + 14, ey, f"{sev}:")
c.setFont("Helvetica", 9.5)
c.setFillColor(INK_COLOR)
c.drawString(MARGIN_L + 130, ey, desc)
ey -= 16
Y -= (bh + 6)
# ──────────────────────────────────────────────────────────────────────────
# PAGE 10 — All Mnemonics Summary (Final Page)
# ──────────────────────────────────────────────────────────────────────────
page_num += 1
new_page(c, page_num)
Y = PAGE_H - 60
draw_heading(c, MARGIN_L, Y, "15. All Mnemonics — Master Quick Reference", size=17, color=RED_INK)
Y -= 28
mnemonics = [
("The Conduction Pathway", "SAAABP",
"SA node → Atria → AV node → Bundle of His → Bundle Branches → Purkinje"),
("Normal Values — Intervals", "PR=3-5, QRS<3, QT<11",
"PR: 3–5 small boxes | QRS: <3 small boxes | QT: <11 small boxes @ 60bpm"),
("Heart Rate — 300 Rule", "300–150–100–75–60–50",
"For 1–6 large boxes between R waves. Just memorise these 6 numbers!"),
("LBBB vs RBBB", "WiLLiaM MaRRoW",
"LBBB: W in V1, M in V6 | RBBB: M in V1 (bunny ears), W in V6"),
("STEMI Territory", "I Am Late",
"Inferior (RCA) → II,III,aVF | Anterior (LAD) → V1–V4 | Lateral (LCx) → I,aVL,V5,V6"),
("Lead Placement", "V1,2 Right • V4 Nipple • V5,6 Side",
"V3 fills the gap. V1 = 4th ICS RSB. V4 = 5th ICS MCL."),
("Axis — Quick Check", "Thumbs Up Both = Normal",
"Lead I ↑ + aVF ↑ = Normal axis. Both ↓ = Extreme axis (bizarre!)"),
("Hyperkalemia ECG", "The 5 P's (pseudo-mnemonic)",
"Peaked T → PR long → QRS wide → P disappears → Sine wave → VF"),
("AV Blocks", "Longer Longer DROP (Wenckebach) then Suddenly STOP (Mobitz II)",
"Mobitz I = lengthening → drop | Mobitz II = constant PR → sudden missing QRS"),
("AF Recognition", "Irregularly Irregular with NO P waves",
"Ventricular rate ~100–160 if uncontrolled. Base line fibrillation between QRS."),
("Torsades de Pointes", "Long QT → Twisting Torsades",
"Causes: Drugs (amiodarone, sotalol, erythromycin), Hypo K/Mg/Ca, congenital LQTS"),
("Normal Sinus Rhythm", "Every P has a QRS and every QRS has a P",
"Rate 60–100, regular, narrow QRS, constant PR 0.12–0.20 sec"),
("WPW Syndrome", "Short PR + Delta wave + Wide QRS = Wolf",
"Avoid AV nodal agents in AF! Risk of AF degenerating to VF via accessory pathway"),
("Einthoven's Law", "I + III = II",
"Voltage in Lead I + Lead III always equals Lead II. Useful for verifying recordings."),
("P-wave changes", "P-mitrale (wide) vs P-pulmonale (tall)",
"Wide bifid P = LAE (mitral disease) | Tall peaked P > 2.5mm = RAE (pulmonary)"),
]
for mname, mnemo, explanation in mnemonics:
bh = 52
draw_box(c, MARGIN_L, Y - bh, CONTENT_W, bh + 2,
fill_color=HexColor("#FFF8E1"), stroke_color=RED_INK, stroke_w=1.0, radius=5)
c.setFont("Helvetica-Bold", 9.5)
c.setFillColor(ORANGE_INK)
c.drawString(MARGIN_L + 8, Y - 13, mname)
c.setFont("Helvetica-BoldOblique", 13)
c.setFillColor(HexColor("#880E4F"))
c.drawString(MARGIN_L + 8, Y - 29, f"🧠 {mnemo}")
c.setFont("Helvetica", 9)
c.setFillColor(INK_COLOR)
c.drawString(MARGIN_L + 8, Y - 44, explanation)
Y -= (bh + 5)
# Final note
if Y > 60:
draw_box(c, MARGIN_L, Y - 48, CONTENT_W, 50, fill_color=HexColor("#E8F5E9"), stroke_color=GREEN_INK, radius=6)
c.setFont("Helvetica-BoldOblique", 11)
c.setFillColor(GREEN_INK)
c.drawCentredString(PAGE_W / 2, Y - 20, "\"Practice reading ECGs daily — pattern recognition is the key!\"")
c.setFont("Helvetica", 9.5)
c.setFillColor(INK_COLOR)
c.drawCentredString(PAGE_W / 2, Y - 36, "Sources: Medical Physiology (Boron & Boulpaep) | Miller's Anesthesia 10e | Clinical Cardiology Principles")
c.drawCentredString(PAGE_W / 2, Y - 49, f"Generated by Orris Medical Notes • {page_num} Pages • Comprehensive ECG Study Guide")
c.save()
print(f"PDF saved: {OUTPUT}")
print(f"Pages: {page_num}")
cd /tmp/workspace/ecg-pdf && python3 generate_ecg_pdf.py 2>&1
ls -lh /tmp/workspace/ecg-pdf/ECG_Complete_Guide.pdf
ECG Complete Guide
PDF Document · PDF
| Page | Content |
|---|---|
| Cover | Title banner, ECG strip diagram with labeled P, QRS, T waves, full Table of Contents |
| Page 2 | What is an ECG? Definition, clinical uses, Einthoven's Triangle diagram, Einthoven's Law mnemonic |
| Page 3 | ECG paper calibration table, grid values mnemonic, complete Cardiac Conduction System (SA→Purkinje) with SAAABP mnemonic |
| Page 4 | Full waveform breakdown - P wave, PR interval, QRS, ST, T wave, QT, U wave - each with normal values, clinical meaning, and warnings |
| Page 5 | Normal values quick reference table + 12-Lead groups (Inferior/Lateral/Anterior) + chest lead placement with mnemonic |
| Page 6 | 3 heart rate calculation methods (300 rule, 1500 rule, 6-sec strip) + cardiac axis table (Normal/LAD/RAD/Extreme) + Normal Sinus Rhythm 5 criteria |
| Page 7 | 12 arrhythmias: Sinus Brady/Tachy, AF, Flutter, SVT, AV blocks (1°/2° Mobitz I & II/CHB), VT, VF, WPW - each with ECG features + management |
| Page 8 | ST changes (STEMI vs NSTEMI), STEMI localisation table (territory → leads → artery), "I Am Late" mnemonic + BBB with WiLLiaM MaRRoW mnemonic |
| Page 9 | LVH/RVH/LAE/RAE criteria + all electrolyte changes (Hyper/Hypo K⁺, Ca²⁺) |
| Page 10 | 15 master mnemonics on one page - complete quick-reference summary |