Explain me 10 common heart diseases with ECG changes and ECG images of those diseases
ECG electrocardiogram myocardial infarction STEMI changes
atrial fibrillation ECG rhythm strip
ventricular tachycardia ECG wide QRS
complete heart block third degree AV block ECG
left bundle branch block LBBB ECG pattern
hypertrophic cardiomyopathy ECG left ventricular hypertrophy
Wolff-Parkinson-White WPW syndrome delta wave ECG
pericarditis ECG saddle-shaped ST elevation diffuse
pulmonary embolism ECG S1Q3T3 right heart strain
long QT syndrome QT prolongation torsades de pointes ECG
| Stage | Changes |
|---|---|
| Hyperacute (minutes) | Tall, peaked "hyperacute" T-waves |
| Acute | Convex ("tombstone") ST elevation β₯1 mm in β₯2 contiguous leads |
| Evolving | T-wave inversion, pathological Q waves (>40 ms wide, >ΒΌ R height) |
| Old/chronic | Persistent Q waves, T-wave inversion |








| Stage | Features |
|---|---|
| Stage 1 (acute) | Diffuse concave ("saddle-shaped") ST elevation in most leads; PR depression (most leads); PR elevation in aVR |
| Stage 2 | ST normalizes, T-waves flatten |
| Stage 3 | Diffuse T-wave inversions |
| Stage 4 | ECG normalizes |

β οΈ Note: A normal ECG does not exclude PE. S1Q3T3 has low sensitivity (~20%) but is highly specific when present.


| # | Disease | Key ECG Finding |
|---|---|---|
| 1 | STEMI | Convex ST elevation + Q waves in territory |
| 2 | Atrial Fibrillation | No P waves, irregularly irregular RR |
| 3 | Ventricular Tachycardia | Wide QRS tachycardia, AV dissociation |
| 4 | Complete Heart Block | P & QRS march independently (AV dissociation) |
| 5 | LBBB | Wide QRS, monophasic R in V5-V6, rS in V1 |
| 6 | HCM | LVH + giant T-wave inversions (V2-V5) |
| 7 | WPW | Short PR + delta wave + wide QRS |
| 8 | Pericarditis | Diffuse concave ST elevation + PR depression |
| 9 | Pulmonary Embolism | S1Q3T3 + sinus tachycardia + RV strain |
| 10 | Long QT Syndrome | QTc >500 ms + Torsades de Pointes |
Clinical pearl: ECG interpretation is always contextual β the same ECG finding can mean different things depending on the clinical scenario (e.g., new LBBB + chest pain = possible STEMI equivalent; diffuse ST elevation + fever + pleuritic chest pain = pericarditis, not MI).
Create a quick reference PDF guide of these 10 heart diseases and their ECG patterns
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace dir not found"
mkdir -p /home/daytona/workspace/ecg-guide && python3 -c "import reportlab; prinβ¦
~/ecg-guide/create_ecg_pdf.py
#!/usr/bin/env python3
"""
Create a comprehensive ECG Quick Reference PDF for 10 common heart diseases.
"""
import os
import requests
from io import BytesIO
from PIL import Image
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,
Image as RLImage, HRFlowable, PageBreak, KeepTogether
)
from reportlab.platypus.flowables import HRFlowable
from reportlab.pdfgen import canvas
from reportlab.lib.colors import HexColor
OUTPUT_PATH = "/home/daytona/workspace/ecg-guide/ECG_Quick_Reference_Guide.pdf"
# ββ Colour palette ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
DARK_RED = HexColor("#B71C1C")
MEDIUM_RED = HexColor("#D32F2F")
LIGHT_RED = HexColor("#FFEBEE")
DARK_BLUE = HexColor("#0D47A1")
MEDIUM_BLUE = HexColor("#1565C0")
LIGHT_BLUE = HexColor("#E3F2FD")
DARK_GREY = HexColor("#212121")
MID_GREY = HexColor("#424242")
LIGHT_GREY = HexColor("#F5F5F5")
TABLE_ALT = HexColor("#FAFAFA")
WHITE = colors.white
ACCENT_GREEN = HexColor("#1B5E20")
LIGHT_GREEN = HexColor("#E8F5E9")
# ββ ECG Image URLs βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
IMAGE_URLS = {
"stemi_anterior": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_cdfb20f9fc1a8412ffb27a5e6e1ae510547606f1b0c520ad6dc5bda4ec26caeb.jpg",
"stemi_inferior": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_5342e382e64f43dd39173eaf875b1588a49cc8c50a1751e548df704019996777.jpg",
"af": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_3c76fbc6e31a1f92963db97bfbae29eae90022cbdb45c2b5f37af6c14bee4bfc.jpg",
"vt": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_d15a92de4e89b46f273b22d254d1e30fcd6a190c6d581b193e6dd338338c6bb6.jpg",
"chb": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_8406f3f9d6a209bfd199f5a59f790f58cd2a726f5674a8e7fbed222b0a72b837.jpg",
"lbbb": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_0be77a66b30cb075621a21dd8998f7155ef974dd06a6228a0372c6857541db8d.jpg",
"hcm": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_2910b31808487d7cd1b8678e2a6942b2ca7518030d03f48787ac4b092bd7e92e.jpg",
"wpw": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_fd0d58cc1913acd56cbc62cbf42657e4a465f052a9bb2ea0d3893584ef6f70e6.jpg",
"pericarditis": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_cd4008531c1b1a2a99ab6d9b4282dcdcedfcf77c302b6ecbb7f7e01c5c985cf9.jpg",
"pe": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_5f391caa5a24ec425e9f48d4d9f73620a3b91f92cea400318772fda12e.jpg",
"lqts": "https://cdn.orris.care/cdss_images/HARRISON_1763034143291_d1a2f9c1-7f66-4dec-a3f8-19b81f2ba927.png",
}
def download_image(url, max_size=(500, 300)):
"""Download image and return a ReportLab Image object, or None on failure."""
try:
resp = requests.get(url, timeout=15)
resp.raise_for_status()
img = Image.open(BytesIO(resp.content)).convert("RGB")
img.thumbnail(max_size, Image.LANCZOS)
buf = BytesIO()
img.save(buf, format="JPEG", quality=85)
buf.seek(0)
rl_img = RLImage(buf)
# Scale to fit width
w, h = img.size
target_w = 160 * mm
scale = target_w / w
rl_img.drawWidth = target_w
rl_img.drawHeight = h * scale
return rl_img
except Exception as e:
print(f" [WARN] Could not load image {url}: {e}")
return None
# ββ Page template with header/footer βββββββββββββββββββββββββββββββββββββββββ
class NumberedCanvas(canvas.Canvas):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._saved_page_states = []
def showPage(self):
self._saved_page_states.append(dict(self.__dict__))
self._startPage()
def save(self):
num_pages = len(self._saved_page_states)
for state in self._saved_page_states:
self.__dict__.update(state)
self.draw_page_number(num_pages)
super().showPage()
super().save()
def draw_page_number(self, page_count):
self.saveState()
page_num = self._pageNumber
# Footer bar
self.setFillColor(DARK_RED)
self.rect(0, 0, A4[0], 18*mm, fill=1, stroke=0)
self.setFillColor(WHITE)
self.setFont("Helvetica", 8)
self.drawString(20*mm, 7*mm, "ECG Quick Reference Guide | 10 Common Heart Diseases")
self.drawRightString(A4[0]-20*mm, 7*mm, f"Page {page_num} of {page_count}")
# Header bar (skip cover page = page 1)
if page_num > 1:
self.setFillColor(DARK_BLUE)
self.rect(0, A4[1]-14*mm, A4[0], 14*mm, fill=1, stroke=0)
self.setFillColor(WHITE)
self.setFont("Helvetica-Bold", 9)
self.drawString(20*mm, A4[1]-9*mm, "ECG QUICK REFERENCE GUIDE")
self.drawRightString(A4[0]-20*mm, A4[1]-9*mm, "Clinical Cardiology")
self.restoreState()
# ββ Styles ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def build_styles():
base = getSampleStyleSheet()
styles = {
"cover_title": ParagraphStyle(
"cover_title", fontName="Helvetica-Bold", fontSize=32,
textColor=WHITE, alignment=TA_CENTER, leading=40, spaceAfter=6
),
"cover_sub": ParagraphStyle(
"cover_sub", fontName="Helvetica", fontSize=14,
textColor=HexColor("#BBDEFB"), alignment=TA_CENTER, leading=20
),
"cover_byline": ParagraphStyle(
"cover_byline", fontName="Helvetica-Oblique", fontSize=10,
textColor=HexColor("#90CAF9"), alignment=TA_CENTER
),
"section_num": ParagraphStyle(
"section_num", fontName="Helvetica-Bold", fontSize=22,
textColor=WHITE, alignment=TA_CENTER
),
"section_title": ParagraphStyle(
"section_title", fontName="Helvetica-Bold", fontSize=17,
textColor=WHITE, alignment=TA_LEFT, leading=22
),
"section_sub": ParagraphStyle(
"section_sub", fontName="Helvetica-Oblique", fontSize=10,
textColor=HexColor("#FFCDD2"), alignment=TA_LEFT
),
"body": ParagraphStyle(
"body", fontName="Helvetica", fontSize=9,
textColor=DARK_GREY, leading=14, spaceAfter=3
),
"bullet": ParagraphStyle(
"bullet", fontName="Helvetica", fontSize=9,
textColor=DARK_GREY, leading=13, spaceAfter=2,
leftIndent=10, bulletIndent=0
),
"bold_label": ParagraphStyle(
"bold_label", fontName="Helvetica-Bold", fontSize=9,
textColor=DARK_BLUE, leading=13
),
"warning_box": ParagraphStyle(
"warning_box", fontName="Helvetica-Oblique", fontSize=8.5,
textColor=ACCENT_GREEN, leading=13
),
"caption": ParagraphStyle(
"caption", fontName="Helvetica-Oblique", fontSize=7.5,
textColor=MID_GREY, alignment=TA_CENTER, leading=11
),
"toc_item": ParagraphStyle(
"toc_item", fontName="Helvetica", fontSize=10,
textColor=DARK_GREY, leading=16
),
"toc_bold": ParagraphStyle(
"toc_bold", fontName="Helvetica-Bold", fontSize=10,
textColor=DARK_BLUE, leading=16
),
"summary_head": ParagraphStyle(
"summary_head", fontName="Helvetica-Bold", fontSize=11,
textColor=DARK_BLUE, alignment=TA_CENTER, spaceAfter=4
),
}
return styles
# ββ Helper builders βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def header_banner(num_text, title_text, subtitle_text, styles, w=170*mm):
"""Red banner for each disease section."""
inner = Table(
[[
Paragraph(num_text, styles["section_num"]),
[Paragraph(title_text, styles["section_title"]),
Paragraph(subtitle_text, styles["section_sub"])]
]],
colWidths=[22*mm, w-22*mm]
)
inner.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_RED),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING",(0,0), (-1,-1), 8),
("LINEAFTER", (0,0), (0,-1), 1, HexColor("#EF9A9A")),
]))
return inner
def ecg_changes_table(rows, styles, w=170*mm):
"""Two-column table: Feature | Detail."""
data = [[Paragraph("<b>ECG Feature</b>", styles["bold_label"]),
Paragraph("<b>Description</b>", styles["bold_label"])]]
for feat, desc in rows:
data.append([
Paragraph(f"<b>{feat}</b>", styles["bullet"]),
Paragraph(desc, styles["bullet"])
])
col_w = [52*mm, w-52*mm]
t = Table(data, colWidths=col_w, repeatRows=1)
ts = TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_BLUE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("ROWBACKGROUNDS",(0,1),(-1,-1), [LIGHT_GREY, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, HexColor("#BDBDBD")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING",(0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
])
t.setStyle(ts)
return t
def info_box(text, styles, bg=LIGHT_BLUE, border=DARK_BLUE):
t = Table([[Paragraph(text, styles["warning_box"])]], colWidths=[170*mm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("BOX", (0,0), (-1,-1), 1, border),
("LEFTPADDING", (0,0),(-1,-1), 8),
("RIGHTPADDING",(0,0),(-1,-1), 8),
("TOPPADDING", (0,0),(-1,-1), 6),
("BOTTOMPADDING",(0,0),(-1,-1), 6),
]))
return t
# ββ Disease data ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
DISEASES = [
{
"num": "01", "name": "ST-Elevation Myocardial Infarction",
"abbr": "STEMI",
"subtitle": "Acute complete coronary artery occlusion β transmural infarction",
"ecg_rows": [
("Hyperacute T-waves", "Tall, peaked T-waves β earliest sign (minutes after occlusion)"),
("ST Elevation", "Convex ('tombstone') ST elevation β₯1 mm in β₯2 contiguous leads"),
("Reciprocal Changes", "ST depression in leads opposite to the infarct territory"),
("Pathological Q waves", "Width >40 ms, depth >ΒΌ R height β indicates necrosis"),
("T-wave inversion", "Develops hoursβdays after occlusion; may persist"),
],
"localisation": [
("Anterior (LAD)", "V1βV4 ST elevation"),
("Inferior (RCA)", "II, III, aVF elevation + reciprocal depression in I, aVL"),
("Lateral (LCx)", "I, aVL, V5βV6 elevation"),
("Posterior (RCA/LCx)", "ST depression V1βV3 (mirror image); tall R in V1βV2"),
],
"note": "Clinical pearl: New LBBB with chest pain may be STEMI equivalent (Sgarbossa criteria). Always compare with prior ECGs.",
"images": ["stemi_anterior", "stemi_inferior"],
"img_captions": [
"Anterolateral STEMI: tombstone ST elevation V1βV5, hyperacute T-waves, reciprocal inferior depression.",
"Inferior STEMI: ST elevation in II, III, aVF with classic reciprocal changes in I and aVL."
],
},
{
"num": "02", "name": "Atrial Fibrillation",
"abbr": "AF",
"subtitle": "Most common sustained arrhythmia β chaotic atrial electrical activity",
"ecg_rows": [
("P waves", "Absent β replaced by irregular fibrillatory (f) waves"),
("f waves", "Rapid, irregular baseline oscillations (best in V1 and II); rate 350β600/min"),
("RR intervals", "Irregularly irregular β hallmark finding"),
("QRS morphology", "Narrow unless aberrant conduction or WPW coexists"),
("Ventricular rate", "Variable: 100β160 bpm uncontrolled; can be slower if treated or AV block present"),
],
"note": "AF with regular RR intervals suggests complete AV block with junctional/ventricular escape β a dangerous combination requiring urgent pacing assessment.",
"images": ["af"],
"img_captions": [
"Classic AF: absent P waves, fibrillatory baseline, and irregularly irregular ventricular response."
],
},
{
"num": "03", "name": "Ventricular Tachycardia",
"abbr": "VT",
"subtitle": "β₯3 consecutive ventricular beats at rate β₯100 bpm β medical emergency",
"ecg_rows": [
("Rate", "100β250 bpm; regular rhythm"),
("QRS duration", ">120 ms; bizarre, wide morphology"),
("AV dissociation", "P waves march independently from QRS (pathognomonic when visible)"),
("Fusion beats", "Normal + ectopic QRS hybrid β diagnostic of VT"),
("Capture beats", "Narrow QRS within VT β confirms AV dissociation"),
("Concordance", "All precordial QRS complexes positive (or all negative) β strong VT indicator"),
("Axis", "Extreme right or northwest axis common"),
],
"note": "Treat all wide-complex tachycardias as VT until proven otherwise. Haemodynamic instability β immediate DC cardioversion.",
"images": ["vt"],
"img_captions": [
"Monomorphic VT: regular wide QRS tachycardia, no visible P waves, positive concordance in precordial leads."
],
},
{
"num": "04", "name": "Complete (3rd Degree) AV Block",
"abbr": "CHB",
"subtitle": "Total failure of AV conduction β atria and ventricles beat independently",
"ecg_rows": [
("AV dissociation", "P waves and QRS complexes completely independent (P 'marches through' QRS/T)"),
("Atrial rate", "Normal (60β100 bpm); regular P-P intervals"),
("Ventricular escape","Slow: 40β60 bpm if junctional; 20β40 bpm if ventricular"),
("QRS morphology", "Narrow if junctional escape; wide (>120 ms) if infra-Hisian escape"),
("PR interval", "No fixed PR relationship β varies randomly"),
],
"note": "Complete heart block is a pacemaker indication. Causes: inferior MI (usually transient), Lyme disease, drug toxicity (digoxin, beta-blockers), congenital, calcific degeneration.",
"images": ["chb"],
"img_captions": [
"Complete AV block: independent P waves and wide ventricular escape complexes with no fixed PR relationship."
],
},
{
"num": "05", "name": "Left Bundle Branch Block",
"abbr": "LBBB",
"subtitle": "Delayed LV depolarization β may mask or mimic myocardial infarction",
"ecg_rows": [
("QRS duration", "β₯120 ms (complete LBBB); 110β119 ms (incomplete LBBB)"),
("V1", "Broad, deep rS or QS pattern (predominantly negative deflection)"),
("V5, V6, I, aVL", "Broad, notched monophasic R wave ('W' in V1, 'M' in V6 β William mnemonic)"),
("Septal Q waves", "Absent in I, V5, V6 (abnormal septal depolarization)"),
("ST/T changes", "Discordant: ST-T direction opposite to main QRS deflection (secondary changes)"),
("Axis", "Normal or left axis deviation"),
],
"note": "Sgarbossa criteria for STEMI in LBBB: (1) concordant ST elevation β₯1 mm in any lead [5 pts], (2) concordant ST depression β₯1 mm in V1βV3 [3 pts], (3) discordant ST elevation β₯5 mm [2 pts]. Score β₯3 = STEMI.",
"images": ["lbbb"],
"img_captions": [
"Complete LBBB: broad QRS, deep rS in V1, monophasic notched R waves laterally, discordant ST-T changes."
],
},
{
"num": "06", "name": "Hypertrophic Cardiomyopathy",
"abbr": "HCM",
"subtitle": "Asymmetric LV hypertrophy β leading cause of SCD in young athletes",
"ecg_rows": [
("LVH voltage", "Sokolow-Lyon: S(V1) + R(V5/V6) β₯35 mm; Cornell: R(aVL) + S(V3) β₯28 mm (men)"),
("Giant T inversions","Deep, symmetric T-wave inversions V2βV5 β classic apical HCM (Yamaguchi)"),
("Strain pattern", "Downsloping ST depression with asymmetric T-wave inversion in lateral leads"),
("Absent septal Q", "No septal Q waves in I, aVL, V5, V6 (abnormal septal hypertrophy)"),
("Axis", "Left axis deviation common"),
("Arrhythmias", "AF, non-sustained VT, Wolff-Parkinson-White pattern possible"),
],
"note": "80% of HCM patients have an abnormal ECG. ECG abnormalities often precede symptom onset. Annual ECG monitoring is standard in known HCM.",
"images": ["hcm"],
"img_captions": [
"HCM (septal variant): high-voltage QRS, ST depression, deep T-wave inversions V2βV6, absent septal Q waves."
],
},
{
"num": "07", "name": "Wolff-Parkinson-White Syndrome",
"abbr": "WPW",
"subtitle": "Accessory pathway bypasses AV node β ventricular pre-excitation",
"ecg_rows": [
("Short PR interval", "<120 ms β AV node delay bypassed by accessory pathway"),
("Delta wave", "Slurred upstroke at start of QRS β slow conduction through ventricular myocardium"),
("Wide QRS", ">120 ms β fusion of delta pre-excitation and normal conduction"),
("ST-T changes", "Secondary discordant ST-T changes (opposite main QRS direction)"),
("Pseudo-Q waves", "Negative delta waves in some leads may mimic infarction Q waves"),
("Tachyarrhythmia", "AVRT (most common); AF with rapid ventricular response (dangerous β may cause VF)"),
],
"note": "NEVER give AV nodal blocking agents (digoxin, verapamil, diltiazem, adenosine) in WPW with AF β may accelerate conduction via accessory pathway causing VF. Use procainamide or DC cardioversion.",
"images": ["wpw"],
"img_captions": [
"WPW: classic triad of short PR (<120 ms), delta wave (slurred QRS upstroke), and QRS widening with secondary ST-T changes."
],
},
{
"num": "08", "name": "Acute Pericarditis",
"abbr": "Pericarditis",
"subtitle": "Pericardial inflammation β classically viral; evolves through 4 ECG stages",
"ecg_rows": [
("Stage 1 (acute)", "Diffuse concave ('saddle-shaped') ST elevation in most leads; PR depression"),
("Stage 2", "ST normalises; T-waves flatten"),
("Stage 3", "Diffuse T-wave inversions develop"),
("Stage 4", "ECG normalises (weeks to months)"),
("PR depression", "Most specific sign β best seen in lead II; reciprocal PR elevation in aVR"),
("Spodick's sign", "Downsloping TP segment β highly specific for pericarditis"),
("aVR", "Reciprocal ST depression and PR elevation"),
],
"note": "Key STEMI vs pericarditis differentiators: (1) concave not convex ST, (2) diffuse not territorial distribution, (3) PR depression, (4) no Q waves, (5) no true reciprocal depression (except aVR).",
"images": ["pericarditis"],
"img_captions": [
"Acute pericarditis Stage 1: diffuse saddle-shaped ST elevation, PR depression in II and most leads, PR elevation in aVR."
],
},
{
"num": "09", "name": "Pulmonary Embolism",
"abbr": "PE",
"subtitle": "Pulmonary vascular obstruction β acute right heart strain",
"ecg_rows": [
("Sinus tachycardia", "Most common ECG finding (~44% of cases)"),
("S1Q3T3", "Prominent S in lead I + Q wave in lead III + T-wave inversion in lead III"),
("RV strain", "T-wave inversions V1βV4 (right precordial leads)"),
("RBBB", "Incomplete (or complete) right bundle branch block due to RV dilation"),
("Right axis deviation","Shift of QRS axis rightward"),
("P pulmonale", "Peaked P waves >2.5 mm in lead II β right atrial strain"),
("Normal ECG", "Present in ~30% of PE cases β does NOT exclude diagnosis"),
],
"note": "S1Q3T3 has ~20% sensitivity but high specificity. The most common ECG finding is sinus tachycardia. ECG is adjunctive β CT pulmonary angiography remains the gold standard for diagnosis.",
"images": ["pe"],
"img_captions": [
"Acute PE: classic S1Q3T3 pattern (annotated circles) with sinus tachycardia and RV strain pattern in precordial leads."
],
},
{
"num": "10", "name": "Long QT Syndrome",
"abbr": "LQTS",
"subtitle": "Prolonged ventricular repolarization β Torsades de Pointes β sudden death",
"ecg_rows": [
("QTc interval", "Corrected QT (Bazett: QT/βRR): >440 ms men, >460 ms women; HIGH RISK >500 ms"),
("T-wave morphology", "LQTS1: broad-based; LQTS2: bifid/notched 'hump'; LQTS3: late-onset narrow T"),
("U waves", "Prominent U waves (especially LQTS3/drug-induced)"),
("R-on-T phenomenon", "PVC landing on T-wave triggers Torsades de Pointes"),
("Torsades de Pointes","Polymorphic VT with characteristic 'twisting' QRS morphology around isoelectric line"),
("Bradycardia", "Relative or absolute bradycardia may unmask QT prolongation"),
],
"note": "Common drug causes: antiarrhythmics (amiodarone, sotalol), antibiotics (azithromycin, fluoroquinolones), antipsychotics (haloperidol, quetiapine), antiemetics (ondansetron, domperidone). Check QTc before prescribing.",
"images": ["lqts"],
"img_captions": [
"Long QT Syndrome: Panel A shows marked QTc prolongation (680 ms) with prominent U waves; Panel B shows R-on-T initiation of polymorphic VT (Torsades de Pointes). [Harrison's Principles of Internal Medicine]"
],
},
]
# ββ Summary table data ββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
SUMMARY_ROWS = [
["#", "Disease", "Key ECG Finding", "Emergency Action"],
["01", "STEMI", "Convex ST elevation + Q waves", "PCI within 90 min"],
["02", "Atrial Fibrillation", "No P waves; irregularly irregular RR", "Rate control / anticoagulation"],
["03", "Ventricular Tachycardia", "Wide QRS tachy + AV dissociation", "DC cardioversion if unstable"],
["04", "Complete Heart Block", "P & QRS dissociated; slow escape", "Temporary/permanent pacemaker"],
["05", "LBBB", "QRS β₯120 ms; rS V1; R in V6", "Rule out STEMI (Sgarbossa)"],
["06", "HCM", "LVH + giant T inversions", "Beta-blocker; ICD if high risk"],
["07", "WPW", "Short PR + delta wave + wide QRS", "Avoid AV nodal blockers in AF"],
["08", "Pericarditis", "Diffuse concave ST + PR depression", "NSAIDs + colchicine"],
["09", "Pulmonary Embolism", "S1Q3T3 + sinus tachycardia", "Anticoagulation; thrombolysis"],
["10", "Long QT Syndrome", "QTc >500 ms β Torsades de Pointes", "Stop offending drugs; Mg IV"],
]
# ββ Build PDF βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def build_pdf():
print("Building PDF...")
styles = build_styles()
doc = SimpleDocTemplate(
OUTPUT_PATH,
pagesize=A4,
leftMargin=20*mm, rightMargin=20*mm,
topMargin=22*mm, bottomMargin=24*mm,
title="ECG Quick Reference Guide",
author="Orris Medical AI",
subject="10 Common Heart Diseases & ECG Patterns",
)
story = []
W = 170*mm # usable width
# ββ COVER PAGE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# Full-page dark blue background via a big table
cover_bg = Table(
[[
Paragraph("β€", ParagraphStyle("h", fontName="Helvetica-Bold", fontSize=48,
textColor=MEDIUM_RED, alignment=TA_CENTER)),
],
[Paragraph("ECG Quick Reference Guide", styles["cover_title"])],
[Paragraph("10 Common Heart Diseases & Their ECG Patterns", styles["cover_sub"])],
[Spacer(1, 8*mm)],
[Paragraph("A comprehensive visual guide for clinicians, residents & medical students",
styles["cover_byline"])],
[Spacer(1, 5*mm)],
[HRFlowable(width=80*mm, thickness=1, color=HexColor("#5C6BC0"), spaceAfter=8)],
[Paragraph("Orris Medical AI β’ May 2026", styles["cover_byline"])],
],
colWidths=[W]
)
cover_bg.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), DARK_BLUE),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING",(0,0),(-1,-1), 6),
("BOX", (0,0), (-1,-1), 3, MEDIUM_RED),
("LEFTPADDING",(0,0), (-1,-1), 20),
("RIGHTPADDING",(0,0),(-1,-1), 20),
]))
story.append(Spacer(1, 30*mm))
story.append(cover_bg)
story.append(PageBreak())
# ββ TABLE OF CONTENTS ββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(Spacer(1, 8*mm))
story.append(Paragraph("Table of Contents", ParagraphStyle(
"toc_h", fontName="Helvetica-Bold", fontSize=16, textColor=DARK_BLUE,
spaceAfter=6, alignment=TA_CENTER)))
story.append(HRFlowable(width=W, thickness=2, color=DARK_RED, spaceAfter=8))
toc_data = [
["#", "Disease", "Abbr."],
] + [[d["num"], d["name"], d["abbr"]] for d in DISEASES] + [
["", "Quick Reference Summary Table", ""],
]
toc_t = Table(toc_data, colWidths=[15*mm, 125*mm, 30*mm])
toc_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_BLUE),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 9),
("ROWBACKGROUNDS",(0,1), (-1,-1), [LIGHT_GREY, WHITE]),
("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
("TEXTCOLOR", (0,1), (0,-1), MEDIUM_RED),
("FONTNAME", (1,1), (1,-1), "Helvetica"),
("FONTSIZE", (0,1), (-1,-1), 9),
("GRID", (0,0), (-1,-1), 0.4, HexColor("#BDBDBD")),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("BACKGROUND", (0,-1), (-1,-1), LIGHT_BLUE),
("FONTNAME", (0,-1), (-1,-1), "Helvetica-Bold"),
("TEXTCOLOR", (0,-1), (-1,-1), DARK_BLUE),
]))
story.append(toc_t)
story.append(PageBreak())
# ββ DISEASE SECTIONS βββββββββββββββββββββββββββββββββββββββββββββββββββ
for d in DISEASES:
print(f" Processing disease {d['num']}: {d['abbr']}...")
story.append(KeepTogether([
header_banner(d["num"], d["name"], d["subtitle"], styles, W),
Spacer(1, 4*mm),
]))
# ECG changes table
story.append(Paragraph("ECG Changes", styles["bold_label"]))
story.append(Spacer(1, 2*mm))
story.append(ecg_changes_table(d["ecg_rows"], styles, W))
story.append(Spacer(1, 3*mm))
# Localisation table (STEMI only)
if "localisation" in d:
story.append(Paragraph("Localisation by Territory", styles["bold_label"]))
story.append(Spacer(1, 2*mm))
loc_data = [[Paragraph("<b>Territory</b>", styles["bullet"]),
Paragraph("<b>Leads Affected</b>", styles["bullet"])]]
for terr, leads in d["localisation"]:
loc_data.append([Paragraph(terr, styles["bullet"]), Paragraph(leads, styles["bullet"])])
loc_t = Table(loc_data, colWidths=[52*mm, W-52*mm])
loc_t.setStyle(TableStyle([
("BACKGROUND", (0,0),(-1,0), MEDIUM_BLUE),
("TEXTCOLOR", (0,0),(-1,0), WHITE),
("ROWBACKGROUNDS",(0,1),(-1,-1),[LIGHT_BLUE, WHITE]),
("GRID", (0,0),(-1,-1), 0.4, HexColor("#BDBDBD")),
("TOPPADDING", (0,0),(-1,-1), 4),
("BOTTOMPADDING",(0,0),(-1,-1), 4),
("LEFTPADDING", (0,0),(-1,-1), 6),
("VALIGN", (0,0),(-1,-1), "TOP"),
]))
story.append(loc_t)
story.append(Spacer(1, 3*mm))
# Clinical note
story.append(info_box(f"<b>Clinical Pearl:</b> {d['note']}", styles,
bg=LIGHT_GREEN, border=ACCENT_GREEN))
story.append(Spacer(1, 4*mm))
# ECG images
story.append(Paragraph("Representative ECG", styles["bold_label"]))
story.append(Spacer(1, 2*mm))
img_items = []
for key, cap in zip(d["images"], d["img_captions"]):
url = IMAGE_URLS[key]
rl_img = download_image(url, max_size=(900, 500))
if rl_img:
# Constrain height so it doesn't overflow
max_h = 55*mm
if rl_img.drawHeight > max_h:
scale = max_h / rl_img.drawHeight
rl_img.drawHeight = max_h
rl_img.drawWidth = rl_img.drawWidth * scale
img_items.append([rl_img, Paragraph(cap, styles["caption"])])
else:
img_items.append([
Paragraph(f"[Image unavailable: {key}]", styles["caption"]),
Paragraph(cap, styles["caption"])
])
if len(img_items) == 2:
# Two images side by side
row_data = [[img_items[0][0], img_items[1][0]],
[img_items[0][1], img_items[1][1]]]
img_t = Table(row_data, colWidths=[W/2 - 2*mm, W/2 - 2*mm])
img_t.setStyle(TableStyle([
("ALIGN", (0,0),(-1,-1), "CENTER"),
("VALIGN", (0,0),(-1,-1), "TOP"),
("TOPPADDING", (0,0),(-1,-1), 2),
("BOTTOMPADDING",(0,0),(-1,-1), 2),
]))
story.append(img_t)
else:
for img_el, cap_el in img_items:
t = Table([[img_el], [cap_el]], colWidths=[W])
t.setStyle(TableStyle([
("ALIGN", (0,0),(-1,-1), "CENTER"),
("TOPPADDING", (0,0),(-1,-1), 2),
("BOTTOMPADDING",(0,0),(-1,-1), 2),
]))
story.append(t)
story.append(PageBreak())
# ββ QUICK REFERENCE SUMMARY TABLE βββββββββββββββββββββββββββββββββββββ
story.append(Spacer(1, 6*mm))
story.append(Paragraph("Quick Reference Summary Table", ParagraphStyle(
"sum_h", fontName="Helvetica-Bold", fontSize=14, textColor=DARK_BLUE,
spaceAfter=4, alignment=TA_CENTER)))
story.append(Paragraph("10 Heart Diseases at a Glance", ParagraphStyle(
"sum_sub", fontName="Helvetica-Oblique", fontSize=9,
textColor=MID_GREY, spaceAfter=8, alignment=TA_CENTER)))
story.append(HRFlowable(width=W, thickness=2, color=DARK_RED, spaceAfter=6))
sum_data = []
for i, row in enumerate(SUMMARY_ROWS):
sum_data.append([Paragraph(cell, ParagraphStyle(
f"s{i}", fontName="Helvetica-Bold" if i==0 else "Helvetica",
fontSize=8.5, textColor=WHITE if i==0 else DARK_GREY, leading=12
)) for cell in row])
sum_t = Table(sum_data, colWidths=[10*mm, 48*mm, 65*mm, 47*mm], repeatRows=1)
sum_t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), DARK_RED),
("TEXTCOLOR", (0,0), (-1,0), WHITE),
("ROWBACKGROUNDS",(0,1),(-1,-1), [LIGHT_GREY, WHITE]),
("GRID", (0,0), (-1,-1), 0.4, HexColor("#BDBDBD")),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING",(0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
("FONTNAME", (0,1), (0,-1), "Helvetica-Bold"),
("TEXTCOLOR", (0,1), (0,-1), MEDIUM_RED),
# Alternate deeper blue on even rows
("BACKGROUND", (0,2), (-1,2), LIGHT_BLUE),
("BACKGROUND", (0,4), (-1,4), LIGHT_BLUE),
("BACKGROUND", (0,6), (-1,6), LIGHT_BLUE),
("BACKGROUND", (0,8), (-1,8), LIGHT_BLUE),
("BACKGROUND", (0,10),(-1,10), LIGHT_BLUE),
]))
story.append(sum_t)
story.append(Spacer(1, 8*mm))
story.append(info_box(
"<b>Disclaimer:</b> This guide is intended as a quick clinical reference tool for educational purposes. "
"ECG interpretation should always be combined with full clinical assessment, patient history, and "
"appropriate investigations. ECG images sourced from peer-reviewed PMC publications.",
styles, bg=LIGHT_RED, border=DARK_RED
))
# ββ Build ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
print("Assembling PDF...")
doc.build(story, canvasmaker=NumberedCanvas)
print(f"Done! Saved to: {OUTPUT_PATH}")
if __name__ == "__main__":
build_pdf()
cd /home/daytona/workspace/ecg-guide && python3 create_ecg_pdf.py 2>&1
Download the ECG reference PDF from workspace