Explain 10 common heart dieseases with ecg images in detail
ECG electrocardiogram myocardial infarction STEMI
atrial fibrillation ECG irregular rhythm absent P waves
heart block complete AV block ECG bradycardia P wave dissociation
ventricular tachycardia ECG wide QRS tachycardia
left ventricular hypertrophy ECG high voltage Sokolow-Lyon criteria
pericarditis ECG saddle-shaped ST elevation diffuse PR depression
Wolff-Parkinson-White WPW syndrome ECG delta wave short PR interval
heart failure dilated cardiomyopathy ECG bundle branch block low voltage
hypertrophic cardiomyopathy ECG deep Q waves lateral leads
long QT syndrome ECG prolonged QT interval torsades de pointes
| Phase | Finding |
|---|---|
| Hyperacute | Tall, peaked "hyperacute" T-waves |
| Acute (hours) | ST-segment elevation (convex/tombstone shape) |
| Evolving (days) | Pathological Q-waves (>40 ms, >25% QRS height), T-wave inversion |
| Reciprocal changes | ST depression in leads facing the opposite wall |


| Feature | Finding |
|---|---|
| P waves | Absent β replaced by fine fibrillatory baseline (best seen in V1) |
| Rhythm | Irregularly irregular R-R intervals |
| Rate | Ventricular rate 100β180 bpm (uncontrolled); <100 bpm (controlled) |
| QRS | Usually narrow (unless aberrant conduction or pre-excitation) |

| Feature | Finding |
|---|---|
| P waves | Regular, at faster rate (60β100 bpm) |
| QRS | Independent, slower rate (20β50 bpm) |
| PR interval | No fixed relationship β P waves and QRS completely dissociated |
| QRS morphology | Narrow if junctional escape; Wide (>120 ms) if ventricular escape |

| Feature | Finding |
|---|---|
| Rate | 100β250 bpm |
| QRS | Wide (>120 ms), bizarre morphology |
| P waves | Absent or dissociated from QRS (AV dissociation) |
| Axis | Often extreme axis deviation |
| Fusion/capture beats | Pathognomonic when present |

| Stage | Finding |
|---|---|
| Stage I (acute) | Diffuse concave ("saddle-shaped") ST elevation in all leads except aVR and V1; PR depression (most specific sign) |
| Stage II (days) | ST returns to baseline; T-waves flatten |
| Stage III (weeks) | T-wave inversion |
| Stage IV | ECG normalises |

| Feature | Finding |
|---|---|
| Sokolow-Lyon criterion | S in V1 + R in V5 or V6 β₯35 mm |
| Cornell criterion | R in aVL + S in V3 β₯28 mm (men), β₯20 mm (women) |
| Strain pattern | ST depression and T-wave inversion in lateral leads (I, aVL, V5, V6) |
| Left axis deviation | Common |
| P wave | Broad, notched P (P mitrale) suggesting left atrial enlargement |

| Feature | Finding |
|---|---|
| PR interval | Short (<120 ms) |
| Delta wave | Slurred upstroke at start of QRS |
| QRS | Widened (>120 ms) due to delta wave |
| ST-T changes | Discordant β secondary to abnormal depolarisation |
| Pseudo-infarction | Negative delta waves can mimic Q-waves |

| Feature | Finding |
|---|---|
| Voltage | High QRS voltage (LVH criteria met in ~90%) |
| Q-waves | Deep narrow Q-waves in lateral/inferior leads (due to septal hypertrophy β septal depolarisation vector is abnormally prominent) |
| T-wave inversions | Deep symmetric T-inversion in lateral leads; "giant" T-inversion in apical HCM (Yamaguchi syndrome, >10 mm) |
| Left axis deviation | Common |
| Arrhythmias | AF, VT β common in advanced disease |

| Feature | Finding |
|---|---|
| LBBB | QRS >120 ms, broad slurred R in I, aVL, V5βV6; rS in V1 β most common finding |
| RBBB | RSR' in V1, broad S in I, V5βV6 |
| Low voltage | QRS amplitude <5 mm in limb leads (if extensive fibrosis) |
| PR prolongation | 1st degree AV block common |
| AF | Frequent β marker of poor prognosis |
| Nonspecific ST-T changes | Diffuse T-inversion |

| Feature | Finding |
|---|---|
| QTc | β₯450 ms (men), β₯460 ms (women) β abnormal; β₯500 ms β high risk |
| QTc formula | QT Γ· βRR (Bazett formula) |
| T-wave morphology | LQT1: broad-based T; LQT2: bifid/notched T; LQT3: long flat ST segment, peaked T |
| T-wave alternans | Beat-to-beat variation in T amplitude β precursor to TdP |
| Torsades de Pointes | Polymorphic VT with QRS complexes "twisting" around the isoelectric line; initiated by "short-long-short" sequence |

| # | Disease | Key ECG Feature |
|---|---|---|
| 1 | Anterior STEMI | Convex/tombstone ST elevation V1βV4; Q-waves |
| 2 | Atrial Fibrillation | Absent P waves; irregularly irregular RR; fibrillatory baseline |
| 3 | Complete Heart Block | P waves and QRS totally dissociated; slow escape rhythm |
| 4 | Ventricular Tachycardia | Wide QRS >120 ms; rate >100; AV dissociation |
| 5 | Pericarditis | Diffuse saddle-shaped ST elevation; PR depression; Spodick's sign |
| 6 | LVH | High voltage (Sokolow β₯35 mm); lateral strain pattern |
| 7 | WPW Syndrome | Short PR <120 ms; delta wave; widened QRS |
| 8 | HCM | Deep narrow Q-waves lateral leads; giant T-inversions (apical) |
| 9 | DCM/Heart Failure | LBBB; 1st degree AV block; low voltage; AF |
| 10 | Long QT Syndrome | QTc β₯500 ms; T alternans; Torsades de Pointes |
Clinical note: ECG interpretation must always be correlated with clinical history, examination, and additional investigations (troponin, echocardiography, cardiac MRI). No single ECG finding is pathognomonic in isolation.
Create a pdf of this
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or not founβ¦
mkdir -p /home/daytona/workspace/heart-diseases-pdf
~/heart-diseases-pdf/generate_pdf.py
import requests
import os
from io import BytesIO
from reportlab.lib.pagesizes import A4
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.lib.colors import HexColor, white, black
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Image, Table, TableStyle,
PageBreak, HRFlowable, KeepTogether
)
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.lib import colors
from PIL import Image as PILImage
OUTPUT_DIR = "/home/daytona/workspace/heart-diseases-pdf"
OUTPUT_PDF = os.path.join(OUTPUT_DIR, "10_Common_Heart_Diseases_ECG.pdf")
# ββ Colours ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
PRIMARY = HexColor("#C0392B") # deep red
SECONDARY = HexColor("#2C3E50") # dark blue-grey
ACCENT = HexColor("#ECF0F1") # light grey
GOLD = HexColor("#F39C12")
LIGHT_RED = HexColor("#FDEDEC")
TABLE_HDR = HexColor("#C0392B")
TABLE_ROW = HexColor("#FDFEFE")
TABLE_ALT = HexColor("#FDEDEC")
# ββ Image URLs βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
IMAGES = {
"anterior_stemi": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_646aa0cfb0d976b56d82780dedb4cd025e7f67b7aa7d1163a2f05a58a08c514e.jpg",
"inferior_stemi": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_4ff7e7260a139d9714f42c31b67dc93d733282c66f59a068801ebd1d27bf5755.jpg",
"af": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_28d2cb3373e01d2a7923241cc616540e9e0ca97e3606845e1d284e6dc853b8a1.jpg",
"chb": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_c35f07d2fcf70adcbf7d659598ff56a2fb6afc98f9691c2b0a02cceecd406901.jpg",
"vt": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_d15a92de4e89b46f273b22d254d1e30fcd6a190c6d581b193e6dd338338c6bb6.jpg",
"pericarditis": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_cd4008531c1b1a2a99ab6d9b4282dcdcedfcf77c302b6ecbb7f7e01c5c985cf9.jpg",
"lvh": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_03152b722b1cb86ef1d57f4d755aa687989488df7da9defa36d7d35f9ab80e28.jpg",
"wpw": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_fd0d58cc1913acd56cbc62cbf42657e4a465f052a9bb2ea0d3893584ef6f70e6.jpg",
"hcm": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_e63f8c2bf7f414e8bcda02609659e537443d073bcd3511675eda15172cbf83d8.jpg",
"dcm": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_111d6dc25b3e3f5e713bbc1a0b5163136c3f000287b93b6e2067e605976164f1.jpg",
"lqts": "https://cdn.orris.care/cdss_images/pmc_clinical_VQA_e4cf687e3417d7c536bebc83c4c367527e3ec6bc5ab1f314f252ca25c3be49b8.jpg",
}
def download_image(url, name):
path = os.path.join(OUTPUT_DIR, f"{name}.jpg")
if os.path.exists(path):
return path
try:
r = requests.get(url, timeout=20)
r.raise_for_status()
img = PILImage.open(BytesIO(r.content)).convert("RGB")
img.save(path, "JPEG", quality=92)
print(f" β {name}")
return path
except Exception as e:
print(f" β {name}: {e}")
return None
def get_image_flowable(path, max_w=15*cm, max_h=7*cm):
if not path or not os.path.exists(path):
return Spacer(1, 0.5*cm)
img = PILImage.open(path)
w, h = img.size
ratio = w / h
if w / max_w > h / max_h:
nw = max_w
nh = max_w / ratio
else:
nh = max_h
nw = max_h * ratio
return Image(path, width=nw, height=nh)
# ββ Styles ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def build_styles():
base = getSampleStyleSheet()
styles = {}
styles["cover_title"] = ParagraphStyle(
"cover_title", parent=base["Title"],
fontSize=32, textColor=white, alignment=TA_CENTER,
spaceAfter=8, fontName="Helvetica-Bold"
)
styles["cover_sub"] = ParagraphStyle(
"cover_sub", parent=base["Normal"],
fontSize=14, textColor=HexColor("#ECF0F1"), alignment=TA_CENTER,
spaceAfter=4, fontName="Helvetica"
)
styles["disease_num"] = ParagraphStyle(
"disease_num", parent=base["Normal"],
fontSize=11, textColor=white, fontName="Helvetica-Bold",
alignment=TA_CENTER
)
styles["disease_title"] = ParagraphStyle(
"disease_title", parent=base["Heading1"],
fontSize=18, textColor=white, fontName="Helvetica-Bold",
spaceAfter=0, spaceBefore=0
)
styles["section_h2"] = ParagraphStyle(
"section_h2", parent=base["Heading2"],
fontSize=12, textColor=PRIMARY, fontName="Helvetica-Bold",
spaceBefore=10, spaceAfter=4, borderPadding=(0,0,2,0)
)
styles["body"] = ParagraphStyle(
"body", parent=base["Normal"],
fontSize=10, textColor=SECONDARY, fontName="Helvetica",
leading=15, spaceAfter=4, alignment=TA_JUSTIFY
)
styles["bullet"] = ParagraphStyle(
"bullet", parent=base["Normal"],
fontSize=10, textColor=SECONDARY, fontName="Helvetica",
leading=14, leftIndent=14, spaceAfter=3,
bulletIndent=4, bulletFontName="Helvetica"
)
styles["caption"] = ParagraphStyle(
"caption", parent=base["Normal"],
fontSize=8.5, textColor=HexColor("#7F8C8D"), fontName="Helvetica-Oblique",
alignment=TA_CENTER, spaceAfter=6
)
styles["img_label"] = ParagraphStyle(
"img_label", parent=base["Normal"],
fontSize=9.5, textColor=PRIMARY, fontName="Helvetica-Bold",
spaceBefore=8, spaceAfter=3
)
styles["footer_note"] = ParagraphStyle(
"footer_note", parent=base["Normal"],
fontSize=8, textColor=HexColor("#95A5A6"), fontName="Helvetica-Oblique",
alignment=TA_CENTER
)
styles["summary_title"] = ParagraphStyle(
"summary_title", parent=base["Heading1"],
fontSize=20, textColor=PRIMARY, fontName="Helvetica-Bold",
spaceAfter=12, alignment=TA_CENTER
)
return styles
# ββ Cover page ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def cover_page(styles):
story = []
# Red banner
banner = Table(
[[Paragraph("10 Common Heart Diseases", styles["cover_title"]),
Paragraph("with ECG Findings & Images", styles["cover_sub"])]],
colWidths=[17*cm],
rowHeights=[None]
)
banner.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), PRIMARY),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 30),
("BOTTOMPADDING", (0,0), (-1,-1), 30),
("LEFTPADDING", (0,0), (-1,-1), 20),
("RIGHTPADDING", (0,0), (-1,-1), 20),
]))
# Stack title lines in the banner
cover_tbl = Table(
[[Paragraph("10 Common Heart Diseases", styles["cover_title"])],
[Paragraph("with ECG Findings & Images", styles["cover_sub"])],
[Spacer(1, 8)],
[Paragraph("A Clinical ECG Reference Guide", ParagraphStyle(
"cs3", fontSize=12, textColor=HexColor("#BDC3C7"),
fontName="Helvetica", alignment=TA_CENTER
))]],
colWidths=[17*cm]
)
cover_tbl.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), PRIMARY),
("ALIGN", (0,0), (-1,-1), "CENTER"),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
]))
story.append(Spacer(1, 3*cm))
story.append(cover_tbl)
story.append(Spacer(1, 1.5*cm))
# TOC box
toc_data = [
["#", "Disease", "Key ECG Feature"],
["1", "Acute MI (STEMI)", "ST elevation, Q-waves"],
["2", "Atrial Fibrillation", "Absent P waves, irregular RR"],
["3", "Complete Heart Block", "AV dissociation, slow escape"],
["4", "Ventricular Tachycardia", "Wide QRS >120 ms, AV dissociation"],
["5", "Acute Pericarditis", "Saddle-shaped ST elevation, PR depression"],
["6", "Left Ventricular Hypertrophy", "High voltage, lateral strain pattern"],
["7", "WPW Syndrome", "Short PR, delta wave, widened QRS"],
["8", "Hypertrophic Cardiomyopathy", "Giant T-inversions, deep Q-waves"],
["9", "Dilated Cardiomyopathy", "LBBB, low voltage, AF"],
["10", "Long QT Syndrome", "QTc β₯500 ms, Torsades de Pointes"],
]
toc_style = TableStyle([
("BACKGROUND", (0,0), (-1,0), PRIMARY),
("TEXTCOLOR", (0,0), (-1,0), white),
("FONTNAME", (0,0), (-1,0), "Helvetica-Bold"),
("FONTSIZE", (0,0), (-1,0), 10),
("ALIGN", (0,0), (0,-1), "CENTER"),
("ALIGN", (1,0), (-1,-1), "LEFT"),
("FONTNAME", (0,1), (-1,-1), "Helvetica"),
("FONTSIZE", (0,1), (-1,-1), 9),
("TEXTCOLOR", (0,1), (-1,-1), SECONDARY),
("ROWBACKGROUNDS", (0,1), (-1,-1), [TABLE_ROW, TABLE_ALT]),
("GRID", (0,0), (-1,-1), 0.5, HexColor("#BDC3C7")),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
])
toc_table = Table(toc_data, colWidths=[1.2*cm, 5.8*cm, 9*cm])
toc_table.setStyle(toc_style)
story.append(toc_table)
story.append(Spacer(1, 2*cm))
story.append(Paragraph(
"Sources: Braunwald's Heart Disease (10th ed.) Β· Rosen's Emergency Medicine Β· PMC Clinical VQA Dataset",
styles["footer_note"]
))
story.append(PageBreak())
return story
# ββ Disease header ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def disease_header(num, title, styles):
num_cell = Paragraph(f"#{num}", styles["disease_num"])
title_cell = Paragraph(title, styles["disease_title"])
t = Table([[num_cell, title_cell]], colWidths=[1.5*cm, 15*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), PRIMARY),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
("TOPPADDING", (0,0), (-1,-1), 10),
("BOTTOMPADDING", (0,0), (-1,-1), 10),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("LINEBELOW", (0,0), (-1,0), 3, GOLD),
]))
return t
def ecg_table(data, styles):
"""Renders a 2-col ECG features table."""
header = [
Paragraph("ECG Feature", ParagraphStyle("th", fontSize=10, fontName="Helvetica-Bold", textColor=white)),
Paragraph("Finding", ParagraphStyle("th2", fontSize=10, fontName="Helvetica-Bold", textColor=white)),
]
rows = [header]
for feat, finding in data:
rows.append([
Paragraph(feat, ParagraphStyle("td1", fontSize=9.5, fontName="Helvetica-Bold", textColor=SECONDARY, leading=13)),
Paragraph(finding, ParagraphStyle("td2", fontSize=9.5, fontName="Helvetica", textColor=SECONDARY, leading=13)),
])
t = Table(rows, colWidths=[5.5*cm, 11*cm])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,0), SECONDARY),
("ROWBACKGROUNDS", (0,1), (-1,-1), [TABLE_ROW, TABLE_ALT]),
("GRID", (0,0), (-1,-1), 0.4, HexColor("#BDC3C7")),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]))
return t
# ββ ECG image block βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def ecg_block(img_path, label, caption, styles):
items = []
items.append(Paragraph(f"ECG: {label}", styles["img_label"]))
items.append(get_image_flowable(img_path))
items.append(Paragraph(caption, styles["caption"]))
return items
# ββ Main content ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def build_story(img_paths, styles):
story = []
story += cover_page(styles)
# ββ 1. STEMI ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(disease_header(1, "Acute Myocardial Infarction (STEMI)", styles))
story.append(Spacer(1, 6))
story.append(Paragraph(
"A <b>ST-Elevation Myocardial Infarction</b> occurs when complete coronary artery occlusion causes transmural (full-thickness) myocardial necrosis. Plaque rupture β thrombosis β complete occlusion β ischemia β injury β necrosis. It is a life-threatening emergency requiring immediate reperfusion (PCI within 90 minutes).",
styles["body"]
))
story.append(Paragraph("<b>ECG Findings:</b>", styles["section_h2"]))
story.append(ecg_table([
("Hyperacute phase", "Tall, peaked 'hyperacute' T-waves (minutes)"),
("Acute phase (hours)", "Convex/tombstone ST elevation β₯1 mm (β₯2 mm in V1βV3)"),
("Evolving (days)", "Pathological Q-waves (>40 ms, >25% QRS height), T-wave inversion"),
("Anterior STEMI", "ST elevation V1βV4; reciprocal depression II, III, aVF (LAD occlusion)"),
("Inferior STEMI", "ST elevation II, III, aVF; reciprocal depression I, aVL (RCA occlusion)"),
("Lateral STEMI", "ST elevation I, aVL, V5βV6 (LCx occlusion)"),
], styles))
story.append(Spacer(1, 8))
story += ecg_block(img_paths.get("anterior_stemi"), "Anterior STEMI",
"Convex 'tombstoning' ST elevation in V2βV5, QS waves indicating necrosis (LAD occlusion). Reciprocal ST depression in inferior leads.",
styles)
story += ecg_block(img_paths.get("inferior_stemi"), "Inferior STEMI",
"β₯2 mm convex ST elevation in II, III, aVF; pathologic Q-waves; reciprocal ST depression in I and aVL (RCA occlusion).",
styles)
story.append(PageBreak())
# ββ 2. AF βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(disease_header(2, "Atrial Fibrillation (AF)", styles))
story.append(Spacer(1, 6))
story.append(Paragraph(
"AF is the most common sustained cardiac arrhythmia, affecting over 37 million people worldwide. Disorganized atrial electrical activity produces an irregular, often rapid ventricular response with no effective atrial contraction, increasing stroke risk 5-fold. Multiple re-entrant circuits (often triggered by pulmonary vein foci) cause the AV node to conduct impulses irregularly.",
styles["body"]
))
story.append(Paragraph("<b>ECG Findings:</b>", styles["section_h2"]))
story.append(ecg_table([
("P waves", "Absent β replaced by fine fibrillatory baseline (best seen in V1)"),
("Rhythm", "Irregularly irregular R-R intervals"),
("Rate", "Ventricular rate 100β180 bpm (uncontrolled); <100 bpm (rate-controlled)"),
("QRS", "Usually narrow unless aberrant conduction or pre-excitation"),
], styles))
story.append(Spacer(1, 8))
story += ecg_block(img_paths.get("af"), "Atrial Fibrillation with Rapid Ventricular Response",
"No P waves, chaotic fibrillatory baseline in V1, classic irregularly irregular rhythm, narrow QRS complexes, rapid ventricular rate.",
styles)
story.append(PageBreak())
# ββ 3. Complete Heart Block βββββββββββββββββββββββββββββββββββββββββββββββ
story.append(disease_header(3, "Complete Heart Block (3rd Degree AV Block)", styles))
story.append(Spacer(1, 6))
story.append(Paragraph(
"<b>Complete AV block</b> is failure of all atrial impulses to conduct to the ventricles. The atria and ventricles beat independently. It requires urgent pacemaker implantation and can cause syncope or sudden death. Complete interruption at the AV node, Bundle of His, or below causes total AV dissociation; an escape rhythm (junctional or ventricular) maintains a slow heart rate.",
styles["body"]
))
story.append(Paragraph("<b>ECG Findings:</b>", styles["section_h2"]))
story.append(ecg_table([
("P waves", "Regular, at faster rate (60β100 bpm) β independent"),
("QRS complexes", "Regular, at slower escape rate (20β50 bpm) β independent"),
("PR interval", "No fixed relationship β complete AV dissociation"),
("QRS morphology", "Narrow if junctional escape; Wide (>120 ms) if ventricular escape"),
("Clinical", "Profound bradycardia, syncope (Stokes-Adams attacks), haemodynamic compromise"),
], styles))
story.append(Spacer(1, 8))
story += ecg_block(img_paths.get("chb"), "Complete Heart Block",
"Regular P waves at ~80 bpm completely independent from junctional escape QRS complexes at ~40 bpm. No fixed PR interval β hallmark of 3rd degree AV block.",
styles)
story.append(PageBreak())
# ββ 4. VT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(disease_header(4, "Ventricular Tachycardia (VT)", styles))
story.append(Spacer(1, 6))
story.append(Paragraph(
"VT is a potentially life-threatening arrhythmia arising from the ventricles, defined as β₯3 consecutive ventricular beats at rate >100 bpm. Sustained VT (>30 seconds) causes haemodynamic compromise and can degenerate to ventricular fibrillation (VF). Re-entry circuits within diseased ventricular myocardium (scar, ischaemia, cardiomyopathy) are the most common mechanism.",
styles["body"]
))
story.append(Paragraph("<b>ECG Findings:</b>", styles["section_h2"]))
story.append(ecg_table([
("Rate", "100β250 bpm, usually regular"),
("QRS duration", "Wide (>120 ms), bizarre morphology"),
("P waves", "Absent or dissociated from QRS (AV dissociation β pathognomonic)"),
("Axis", "Often extreme axis deviation (northwest axis)"),
("Fusion beats", "Pathognomonic when present β simultaneous sinus + VT activation"),
("Capture beats", "Narrow QRS during VT β confirms AV dissociation"),
("Concordance", "Positive concordance V1βV6 strongly suggests VT over SVT"),
], styles))
story.append(Spacer(1, 8))
story += ecg_block(img_paths.get("vt"), "Monomorphic Ventricular Tachycardia",
"High-amplitude wide QRS at ~170 bpm, positive concordance V1βV6, superior axis, no visible P waves. Consistent with VT from structural heart disease.",
styles)
story.append(PageBreak())
# ββ 5. Pericarditis βββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(disease_header(5, "Acute Pericarditis", styles))
story.append(Spacer(1, 6))
story.append(Paragraph(
"Pericarditis is inflammation of the pericardial sac, most commonly viral (Coxsackievirus, Echovirus) but also autoimmune, bacterial, or post-MI (Dressler's syndrome). Classic presentation: sharp pleuritic chest pain relieved by leaning forward. Pericardial inflammation causes superficial myocardial irritation producing <i>diffuse</i> repolarisation changes across all leads simultaneously β unlike STEMI which follows a single coronary territory.",
styles["body"]
))
story.append(Paragraph("<b>ECG Findings (4 Classic Stages):</b>", styles["section_h2"]))
story.append(ecg_table([
("Stage I (acute)", "Diffuse concave 'saddle-shaped' ST elevation in all leads except aVR and V1"),
("PR depression", "PR segment depression in inferior/lateral leads β MOST SPECIFIC sign"),
("aVR", "Reciprocal ST depression AND PR elevation in aVR"),
("Spodick's sign", "Downsloping TP segment β high specificity for pericarditis"),
("Stage II (days)", "ST returns to baseline; T-waves flatten"),
("Stage III (weeks)", "T-wave inversion in previously elevated leads"),
("vs STEMI", "Diffuse (not localised), concave (not convex), no reciprocal ST depression"),
], styles))
story.append(Spacer(1, 8))
story += ecg_block(img_paths.get("pericarditis"), "Acute Pericarditis",
"Diffuse saddle-shaped ST elevation in I, II, III, aVF, V2βV6; PR depression in II; PR elevation in aVR; Spodick's sign. Classic features distinguishing pericarditis from STEMI.",
styles)
story.append(PageBreak())
# ββ 6. LVH ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(disease_header(6, "Left Ventricular Hypertrophy (LVH)", styles))
story.append(Spacer(1, 6))
story.append(Paragraph(
"LVH is thickening of the left ventricular walls, most commonly from chronic hypertension, aortic stenosis, or hypertrophic cardiomyopathy. LVH is an independent risk factor for heart failure, arrhythmias, and sudden death. Chronic pressure overload causes myocyte hypertrophy β increased muscle mass β greater electrical voltage on ECG. Impaired subendocardial perfusion produces the characteristic 'strain' pattern.",
styles["body"]
))
story.append(Paragraph("<b>ECG Findings:</b>", styles["section_h2"]))
story.append(ecg_table([
("Sokolow-Lyon criterion", "S in V1 + R in V5 or V6 β₯35 mm"),
("Cornell criterion", "R in aVL + S in V3 β₯28 mm (men), β₯20 mm (women)"),
("Strain pattern", "ST depression + T-wave inversion in lateral leads (I, aVL, V5, V6)"),
("Left axis deviation", "Common in LVH"),
("P mitrale", "Broad, notched P wave β suggests associated left atrial enlargement"),
], styles))
story.append(Spacer(1, 8))
story += ecg_block(img_paths.get("lvh"), "LVH with Lateral Strain Pattern",
"Tall R-waves in V5βV6 and deep S-waves in V1βV2 (meeting Sokolow-Lyon criteria); ST depression and T-inversion in II, III, aVF, V5, V6 β classic pressure-overload 'strain pattern'.",
styles)
story.append(PageBreak())
# ββ 7. WPW ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(disease_header(7, "Wolff-Parkinson-White (WPW) Syndrome", styles))
story.append(Spacer(1, 6))
story.append(Paragraph(
"WPW is a pre-excitation syndrome caused by an accessory pathway (Bundle of Kent) bypassing the AV node, connecting atria directly to ventricles. The accessory pathway conducts faster than the AV node, activating part of the ventricle early β producing the delta wave and widened QRS. During tachycardia, re-entry circuits use both the AV node and accessory pathway. Risk of sudden death via rapid conduction of AF to the ventricles.",
styles["body"]
))
story.append(Paragraph("<b>ECG Findings (Classic Triad):</b>", styles["section_h2"]))
story.append(ecg_table([
("PR interval", "Short <120 ms (bypasses AV nodal delay)"),
("Delta wave", "Slurred upstroke at start of QRS β hallmark"),
("QRS duration", "Widened >120 ms due to delta wave"),
("ST-T changes", "Discordant β secondary to abnormal depolarisation"),
("Pseudo-infarction", "Negative delta waves can mimic Q-waves (inferior leads)"),
("Localisation", "Delta wave polarity in V1 and inferior leads identifies accessory pathway location"),
], styles))
story.append(Spacer(1, 8))
story += ecg_block(img_paths.get("wpw"), "Wolff-Parkinson-White Syndrome",
"Short PR (<120 ms), prominent delta waves (slurred QRS upstroke) in II, III, aVF, V2βV6, secondary ST-T discordance β classic triad of ventricular pre-excitation.",
styles)
story.append(PageBreak())
# ββ 8. HCM ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(disease_header(8, "Hypertrophic Cardiomyopathy (HCM)", styles))
story.append(Spacer(1, 6))
story.append(Paragraph(
"HCM is a genetic disorder (autosomal dominant, most commonly <i>MYH7</i> or <i>MYBPC3</i> mutations) causing asymmetric left ventricular hypertrophy, usually of the interventricular septum. Myofiber disarray and fibrosis disrupt normal electrical conduction. It is the leading cause of sudden cardiac death in young athletes. Dynamic LV outflow tract obstruction causes exertional dyspnea, syncope, and chest pain.",
styles["body"]
))
story.append(Paragraph("<b>ECG Findings:</b>", styles["section_h2"]))
story.append(ecg_table([
("LVH voltage", "High QRS voltage meeting LVH criteria in ~90% of patients"),
("Q-waves", "Deep narrow Q-waves in lateral/inferior leads (abnormal septal depolarisation)"),
("T-wave inversions", "Deep symmetric T-inversion in lateral leads; 'giant' T-inversion in apical HCM (Yamaguchi, >10 mm)"),
("Left axis deviation", "Common"),
("Apical HCM (Yamaguchi)", "Giant symmetric T-wave inversions V3βV6, high precordial voltage β 'ace of spades' LV on imaging"),
("Arrhythmias", "AF, VT common in advanced disease; risk of sudden cardiac death"),
], styles))
story.append(Spacer(1, 8))
story += ecg_block(img_paths.get("hcm"), "Apical HCM (Yamaguchi Syndrome)",
"Sinus rhythm, high-voltage precordial QRS, giant symmetric T-wave inversions most prominent in V4βV6 with ST depression β hallmark of apical hypertrophic cardiomyopathy.",
styles)
story.append(PageBreak())
# ββ 9. DCM ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(disease_header(9, "Dilated Cardiomyopathy (DCM) / Heart Failure with Reduced EF", styles))
story.append(Spacer(1, 6))
story.append(Paragraph(
"DCM is characterised by ventricular dilatation and systolic dysfunction (reduced EF <40%). Causes include idiopathic, viral myocarditis, alcohol, chemotherapy, and genetic. Myocyte loss and fibrosis impair both contraction and electrical conduction. Ventricular dilation distorts the His-Purkinje system, commonly producing bundle branch blocks. Fibrosis creates re-entry substrates for VT and sudden cardiac death.",
styles["body"]
))
story.append(Paragraph("<b>ECG Findings:</b>", styles["section_h2"]))
story.append(ecg_table([
("LBBB", "QRS >120 ms, broad slurred R in I, aVL, V5βV6; rS in V1 β most common finding in DCM"),
("RBBB", "RSR' in V1, broad S in I, V5βV6"),
("Low voltage", "QRS <5 mm in limb leads (extensive fibrosis/pericardial effusion)"),
("PR prolongation", "1st degree AV block common"),
("Atrial fibrillation", "Frequent β marker of poor prognosis"),
("ST-T changes", "Diffuse nonspecific T-inversion, secondary repolarisation changes"),
("Indication for CRT", "LBBB with QRS >150 ms + EF β€35% β cardiac resynchronisation therapy"),
], styles))
story.append(Spacer(1, 8))
story += ecg_block(img_paths.get("dcm"), "DCM with LBBB β Indication for CRT",
"1st degree AV block (PR = 280 ms) and wide LBBB (QRS = 145 ms) in a patient with NYHA Class IV DCM. Indicates ventricular dyssynchrony β key indication for Cardiac Resynchronization Therapy.",
styles)
story.append(PageBreak())
# ββ 10. LQTS ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(disease_header(10, "Long QT Syndrome (LQTS)", styles))
story.append(Spacer(1, 6))
story.append(Paragraph(
"LQTS is a disorder of ventricular repolarisation causing a prolonged QT interval. It can be <b>congenital</b> (mutations in cardiac ion channels: <i>KCNQ1</i> β LQT1, <i>KCNH2</i> β LQT2, <i>SCN5A</i> β LQT3) or <b>acquired</b> (drugs, hypokalaemia, hypomagnesaemia). Reduced outward KβΊ current or increased inward NaβΊ/CaΒ²βΊ current prolongs action potential phase 3 β early afterdepolarisations β <b>Torsades de Pointes (TdP)</b> β polymorphic VT that can degenerate to VF and sudden death.",
styles["body"]
))
story.append(Paragraph("<b>ECG Findings:</b>", styles["section_h2"]))
story.append(ecg_table([
("QTc (Bazett)", "QT Γ· βRR; β₯450 ms (men), β₯460 ms (women) = abnormal; β₯500 ms = high risk"),
("LQT1 morphology", "Broad-based, symmetric T-wave (K channel defect; triggers with exercise)"),
("LQT2 morphology", "Bifid or notched T-wave (triggers with sudden auditory stimuli)"),
("LQT3 morphology", "Long flat isoelectric ST, late-peaked narrow T-wave (triggers at rest/sleep)"),
("T-wave alternans", "Beat-to-beat variation in T amplitude β precursor to TdP"),
("Torsades de Pointes", "Polymorphic VT; QRS 'twisting' around isoelectric line; initiated by short-long-short sequence"),
], styles))
story.append(Spacer(1, 8))
story += ecg_block(img_paths.get("lqts"), "Long QT Syndrome and Torsades de Pointes",
"Panel (a): Markedly prolonged QTc (478 ms) in sinus rhythm. Panel (b): Classic 'twisting' polymorphic Torsades de Pointes triggered by the prolonged repolarisation β a precursor to ventricular fibrillation.",
styles)
story.append(PageBreak())
# ββ Summary Table βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
story.append(Paragraph("Quick Reference Summary", styles["summary_title"]))
story.append(HRFlowable(width="100%", thickness=2, color=PRIMARY))
story.append(Spacer(1, 10))
summary_data = [
["#", "Disease", "Key ECG Feature", "Emergency?"],
["1", "Anterior STEMI", "Convex ST elevation V1βV4; Q-waves", "YES"],
["2", "Atrial Fibrillation", "Absent P waves; irregularly irregular", "If RVR"],
["3", "Complete Heart Block", "AV dissociation; slow escape rhythm", "YES"],
["4", "Ventricular Tachycardia", "Wide QRS >120 ms; AV dissociation", "YES"],
["5", "Acute Pericarditis", "Saddle ST elevation; PR depression", "Monitor"],
["6", "LVH", "High voltage β₯35 mm; lateral strain", "No"],
["7", "WPW Syndrome", "Short PR; delta wave; widened QRS", "If SVT/AF"],
["8", "Hypertrophic CMP", "Giant T-inversions V3βV6; deep Q", "Athlete SCD"],
["9", "Dilated CMP / HFrEF", "LBBB; 1st AV block; low voltage; AF", "If decompensated"],
["10", "Long QT Syndrome", "QTc β₯500 ms; T alternans; TdP", "YES"],
]
def cell(txt, bold=False, color=SECONDARY, size=9.5):
style = ParagraphStyle("sc", fontSize=size, fontName="Helvetica-Bold" if bold else "Helvetica",
textColor=color, leading=13)
return Paragraph(txt, style)
formatted = [[cell(r[0], bold=True), cell(r[1], bold=True), cell(r[2]), cell(r[3])]
for r in summary_data[1:]]
header_row = [cell(h, bold=True, color=white, size=10) for h in summary_data[0]]
sum_style = TableStyle([
("BACKGROUND", (0,0), (-1,0), PRIMARY),
("ROWBACKGROUNDS", (0,1), (-1,-1), [TABLE_ROW, TABLE_ALT]),
("GRID", (0,0), (-1,-1), 0.4, HexColor("#BDC3C7")),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("ALIGN", (0,0), (0,-1), "CENTER"),
("ALIGN", (-1,1), (-1,-1), "CENTER"),
("BACKGROUND", (-1,1), (-1,1), HexColor("#E74C3C")), # YES red
("BACKGROUND", (-1,3), (-1,3), HexColor("#E74C3C")),
("BACKGROUND", (-1,4), (-1,4), HexColor("#E74C3C")),
("BACKGROUND", (-1,10), (-1,10), HexColor("#E74C3C")),
("TEXTCOLOR", (-1,1), (-1,1), white),
("TEXTCOLOR", (-1,3), (-1,3), white),
("TEXTCOLOR", (-1,4), (-1,4), white),
("TEXTCOLOR", (-1,10), (-1,10), white),
("FONTNAME", (-1,1), (-1,1), "Helvetica-Bold"),
("FONTNAME", (-1,3), (-1,3), "Helvetica-Bold"),
("FONTNAME", (-1,4), (-1,4), "Helvetica-Bold"),
("FONTNAME", (-1,10), (-1,10), "Helvetica-Bold"),
])
sum_tbl = Table([header_row] + formatted, colWidths=[1*cm, 5*cm, 8.5*cm, 2.5*cm])
sum_tbl.setStyle(sum_style)
story.append(sum_tbl)
story.append(Spacer(1, 1.5*cm))
story.append(HRFlowable(width="100%", thickness=1, color=HexColor("#BDC3C7")))
story.append(Spacer(1, 6))
story.append(Paragraph(
"β Clinical Note: ECG interpretation must always be correlated with clinical history, physical examination, and additional investigations (troponin, echocardiography, cardiac MRI). No single ECG finding is pathognomonic in isolation.",
ParagraphStyle("note", fontSize=9, fontName="Helvetica-Oblique", textColor=HexColor("#7F8C8D"),
alignment=TA_CENTER, leading=13)
))
story.append(Spacer(1, 4))
story.append(Paragraph(
"Sources: Braunwald's Heart Disease (10th ed.) Β· Rosen's Emergency Medicine Β· PMC Clinical VQA Dataset",
styles["footer_note"]
))
return story
# ββ Page template with header/footer βββββββββββββββββββββββββββββββββββββββββββ
def on_page(canvas, doc):
canvas.saveState()
w, h = A4
# Header bar on non-cover pages
if doc.page > 1:
canvas.setFillColor(PRIMARY)
canvas.rect(0, h - 1.2*cm, w, 1.2*cm, fill=1, stroke=0)
canvas.setFont("Helvetica-Bold", 9)
canvas.setFillColor(white)
canvas.drawString(1.5*cm, h - 0.8*cm, "10 Common Heart Diseases β ECG Reference Guide")
canvas.drawRightString(w - 1.5*cm, h - 0.8*cm, f"Page {doc.page}")
# Footer
canvas.setFont("Helvetica", 7.5)
canvas.setFillColor(HexColor("#95A5A6"))
canvas.drawCentredString(w/2, 0.7*cm, "Sources: Braunwald's Heart Disease Β· Rosen's Emergency Medicine Β· PMC Clinical VQA")
canvas.restoreState()
# ββ Build PDF βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
def main():
print("Downloading ECG images...")
img_paths = {}
for name, url in IMAGES.items():
img_paths[name] = download_image(url, name)
print("Building PDF...")
styles = build_styles()
story = build_story(img_paths, styles)
doc = SimpleDocTemplate(
OUTPUT_PDF, pagesize=A4,
leftMargin=2*cm, rightMargin=2*cm,
topMargin=1.8*cm, bottomMargin=1.6*cm,
title="10 Common Heart Diseases β ECG Reference Guide",
author="Orris Medical AI",
subject="Cardiology ECG Reference"
)
doc.build(story, onFirstPage=on_page, onLaterPages=on_page)
print(f"\nβ PDF created: {OUTPUT_PDF}")
if __name__ == "__main__":
main()