Cardiac cycle 10 marker physiology

Reading File
Reading File
Running Command

python -m markitdown "/home/daytona/workspace/attachments/201f26ba-58a1-4a21-8f8…

Finding Sources
Running Command

cd /home/daytona/workspace/attachments/201f26ba-58a1-4a21-8f85-c7a055c1bcf9 && p…

Reading File
Reading File
Running Command

cd /home/daytona/workspace/attachments/201f26ba-58a1-4a21-8f85-c7a055c1bcf9 && p…

Reading File
Loading Image
I now have everything needed for a comprehensive 10-marker answer. Here is the complete answer based on your PPTX content and the medical textbooks (Costanzo Physiology 7th Ed & Guyton and Hall):

The Cardiac Cycle - 10 Marks

Definition

The cardiac cycle refers to the sequence of changes in pressure and flow in the heart chambers and blood vessels between two subsequent cardiac contractions. At a normal heart rate of 75 beats/min, the total duration is 0.8 sec:
  • Ventricular systole: 0.3 sec
  • Ventricular diastole: 0.5 sec
  • Atrial systole: 0.1 sec
  • Atrial diastole: 0.7 sec
The cycle is initiated by the spontaneous action potential from the SA node, with the atria acting as primer pumps for the ventricles.

Wiggers Diagram (The Cardiac Cycle)

Wiggers Diagram showing all phases of the cardiac cycle

Phases of the Cardiac Cycle

The cycle has 7 phases (A through G per Costanzo; or grouped as Atrial Systole + Ventricular Systole + Ventricular Diastole):

I. ATRIAL SYSTOLE (Phase A) - Duration: 0.1 sec

  • Initiated by impulse from the SA node; preceded by the P wave on ECG
  • Atrial muscles contract; atrial pressure rises
    • Right atrial pressure rises to 4-6 mmHg
    • Left atrial pressure rises to 7-8 mmHg
  • AV valves (mitral and tricuspid) are open - blood is actively ejected into ventricles
  • Contributes 30% additional blood into the ventricles (70% already filled passively)
  • Reflected back to veins as the "a wave" of venous pulse
  • S4 (Fourth Heart Sound) - produced by vibrations during atrial contraction; normally inaudible

II. VENTRICULAR SYSTOLE - Total Duration: 0.3 sec

This has two phases:

(a) Isovolumetric Ventricular Contraction (Phase B)

  • Atrial contraction passes off; excitation invades the ventricles (marked by QRS complex on ECG)
  • Ventricular pressure rapidly exceeds atrial pressure → AV valves close → produces First Heart Sound (S1)
    • S1: prolonged, loud "LUBB"; duration 0.15 sec; frequency 25-45 Hz; heard best at mitral and tricuspid areas; coincides with R wave on ECG
  • Ventricles are now a closed chamber (all valves shut)
  • Ventricular pressure rises sharply but no change in volume (isovolumetric = no emptying)
  • Bulging of AV valves into atria causes a small rise in atrial pressure

(b) Ventricular Systole Proper / Ejection Phase (Phases C & D)

When LV pressure exceeds aortic pressure (80 mmHg) and RV pressure exceeds pulmonary artery pressure → semilunar valves open → ejection phase begins.
This is divided into 3 sub-divisions:
Sub-phaseFeature
(a) Rapid Ejection Phase~2/3 of stroke volume ejected; ventricular and arterial pressures rise steeply (Phase C, ST segment on ECG)
(b) Summit/PeakMaximum ventricular pressure reached; aortic pressure slightly exceeds ventricular pressure at peak
(c) Slow Ejection PhaseVentricular contraction subsides; ventricular pressure declines; remaining blood ejected slowly (Phase D, T wave on ECG)
Stroke Volume: 70-80 ml per beat; ~65% of End-Diastolic Volume (EDV = 120-140 ml); End-Systolic Volume (ESV) = ~50 ml.

III. VENTRICULAR DIASTOLE - Total Duration: 0.5 sec

Comprises 4 phases:

(a) Protodiastole (Duration: 0.04 sec)

  • At end of ventricular systole, ventricular pressure drops rapidly
  • Arterial pressure is better sustained by elastic recoil of vessel walls
  • Arterial pressure exceeds ventricular pressure → semilunar valves closeSecond Heart Sound (S2)
    • S2: short, sharp, high-pitched "DUBB"; duration 0.12 sec; frequency 50 Hz; heard best at aortic and pulmonary areas; coincides with end of T wave on ECG

(b) Isovolumetric Relaxation Phase (Phase E)

  • Begins after semilunar valve closure
  • Intraventricular pressure continues to drop rapidly
  • All valves closed; ventricular muscle relaxes with no change in ventricular volume

(c) Ventricular Diastole Proper (Phase F & G) - ~70% of filling is passive

Divided into 2 sub-divisions:
  • Rapid Ventricular Filling (Phase F): AV valves open (ventricular pressure falls below atrial pressure); ventricles fill rapidly and passively; Third Heart Sound (S3) produced by vibrations from the rush of blood into ventricles (short, soft, low-pitched; duration 0.1 sec; not normally audible with stethoscope)
  • Diastasis (Phase G): Slow filling phase; atrial and ventricular pressures are nearly equal; blood continues to trickle in

(d) Last Rapid Filling Phase (Phase A of next cycle)

  • Due to atrial systole - this terminates the cardiac cycle and begins the next

IV. ATRIAL DIASTOLE - Duration: 0.7 sec

  • Atrial muscles relax; atrial pressure gradually rises due to continuous venous return
  • Pressure drops to near zero with opening of AV valves
  • Pressure rises again during diastasis, following ventricular pressure

Heart Sounds - Summary Table

SoundCauseQualityDurationFrequencyBest HeardECG Timing
S1AV valve closure at start of ventricular systoleProlonged, loud "LUBB"0.15 sec25-45 HzMitral/tricuspid areasR wave
S2Semilunar valve closure at onset of diastoleShort, sharp, high-pitched "DUBB"0.12 sec50 HzAortic/pulmonary areasEnd of T wave
S3Blood rush during rapid ventricular fillingShort, soft, low-pitched0.1 secLowApexAfter T wave
S4Atrial contraction (last rapid filling)Soft, low-pitched-LowApexBefore QRS/P-R

Venous Pulse Waves During Cardiac Cycle

WavePhase
a waveAtrial systole
c waveAV valve bulging / isovolumetric contraction
v waveVenous filling during ventricular systole (before AV valve opens)

Key Numbers to Remember

ParameterValue
Total cycle duration (HR 75)0.8 sec
Ventricular systole0.3 sec
Ventricular diastole0.5 sec
Stroke volume70-80 ml
EDV120-140 ml
ESV~50 ml
Aortic valve opens whenLV pressure > 80 mmHg
Protodiastole0.04 sec

Sources: Costanzo Physiology 7th Edition (Table 4.5 & Fig 4.25); Guyton and Hall Textbook of Medical Physiology; Cardiac Cycle-1.pptx (your attached slides)

Generate a visual summary PDF of the 10 cardiac cycle markers

Reading File
Running Command

mkdir -p /home/daytona/workspace/cardiac-cycle-pdf && ls /home/daytona/workspace…

Writing File

~/cardiac-cycle-pdf/generate_pdf.py

#!/usr/bin/env python3
"""
Visual Summary PDF - The Cardiac Cycle (10 Markers)
Uses ReportLab to generate a rich, color-coded multi-page PDF.
"""

import urllib.request
import io
import os

from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
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,
    HRFlowable, KeepTogether, Image
)
from reportlab.graphics.shapes import Drawing, Rect, String, Line, Circle, Polygon
from reportlab.graphics import renderPDF
from reportlab.platypus.flowables import Flowable

# ── Colour palette ────────────────────────────────────────────────────────────
NAVY      = colors.HexColor("#0D1B2A")
DARK_BLUE = colors.HexColor("#1B4F72")
MED_BLUE  = colors.HexColor("#2E86C1")
LIGHT_BLU = colors.HexColor("#AED6F1")
SYSTOLE   = colors.HexColor("#C0392B")      # red family
SYS_LIGHT = colors.HexColor("#FADBD8")
DIASTOLE  = colors.HexColor("#1A5276")      # blue family
DIA_LIGHT = colors.HexColor("#D6EAF8")
ATRIAL    = colors.HexColor("#117A65")      # green family
ATR_LIGHT = colors.HexColor("#D1F2EB")
GOLD      = colors.HexColor("#D4AC0D")
GOLD_LT   = colors.HexColor("#FEF9E7")
WHITE     = colors.white
LIGHT_GRY = colors.HexColor("#F2F3F4")
MID_GRY   = colors.HexColor("#BDC3C7")
DARK_GRY  = colors.HexColor("#2C3E50")

W, H = A4

# ── Document setup ─────────────────────────────────────────────────────────────
OUTPUT = "/home/daytona/workspace/cardiac-cycle-pdf/Cardiac_Cycle_10_Markers.pdf"
doc = SimpleDocTemplate(
    OUTPUT,
    pagesize=A4,
    leftMargin=1.5*cm, rightMargin=1.5*cm,
    topMargin=1.5*cm,  bottomMargin=1.5*cm,
    title="The Cardiac Cycle – 10 Marker Visual Summary",
    author="Physiology Notes",
)

# ── Styles ─────────────────────────────────────────────────────────────────────
base = getSampleStyleSheet()

def S(name, **kw):
    return ParagraphStyle(name, **kw)

title_s   = S("TitleS",   fontName="Helvetica-Bold",  fontSize=22, textColor=WHITE,  alignment=TA_CENTER, leading=28)
sub_s     = S("SubS",     fontName="Helvetica",        fontSize=11, textColor=LIGHT_BLU, alignment=TA_CENTER, leading=14)
h1_s      = S("H1S",      fontName="Helvetica-Bold",  fontSize=13, textColor=WHITE,  alignment=TA_LEFT,   leading=17, leftIndent=4)
h2_s      = S("H2S",      fontName="Helvetica-Bold",  fontSize=11, textColor=DARK_GRY, alignment=TA_LEFT,  leading=14)
body_s    = S("BodyS",    fontName="Helvetica",        fontSize=9,  textColor=DARK_GRY, alignment=TA_JUSTIFY, leading=13)
body_bold = S("BodyBold", fontName="Helvetica-Bold",  fontSize=9,  textColor=DARK_GRY, alignment=TA_LEFT,   leading=13)
small_s   = S("SmallS",   fontName="Helvetica",        fontSize=8,  textColor=colors.HexColor("#555555"), alignment=TA_CENTER, leading=11)
phase_s   = S("PhaseS",   fontName="Helvetica-Bold",  fontSize=10, textColor=WHITE,   alignment=TA_CENTER, leading=14)
num_s     = S("NumS",     fontName="Helvetica-Bold",  fontSize=18, textColor=GOLD,    alignment=TA_CENTER, leading=22)
label_s   = S("LabelS",   fontName="Helvetica-Bold",  fontSize=8,  textColor=WHITE,   alignment=TA_CENTER, leading=11)
key_val_s = S("KeyValS",  fontName="Helvetica",        fontSize=8.5,textColor=DARK_GRY,alignment=TA_LEFT,   leading=12)
bold_val_s= S("BoldValS", fontName="Helvetica-Bold",  fontSize=8.5,textColor=DARK_BLUE,alignment=TA_LEFT,  leading=12)

# ── Helper: coloured banner ────────────────────────────────────────────────────
class ColourBanner(Flowable):
    def __init__(self, text, bg, fg=WHITE, height=1.0*cm, font="Helvetica-Bold", size=13):
        Flowable.__init__(self)
        self.text = text; self.bg = bg; self.fg = fg
        self.height = height; self.font = font; self.size = size
        self.width = W - 3*cm
    def draw(self):
        self.canv.setFillColor(self.bg)
        self.canv.roundRect(0, 0, self.width, self.height, 5, fill=1, stroke=0)
        self.canv.setFillColor(self.fg)
        self.canv.setFont(self.font, self.size)
        self.canv.drawCentredString(self.width/2, self.height/2 - self.size*0.35, self.text)

# ── Helper: phase card (coloured box with bullet points) ──────────────────────
def phase_card(number, title, duration, ecg, valves, sounds, bullets, colour, light):
    """Returns a KeepTogether block for one phase card."""
    items = []

    # Header row
    header_data = [[
        Paragraph(f"<b>{number}</b>", num_s),
        Paragraph(title, phase_s),
        Paragraph(f"<b>{duration}</b>", label_s),
    ]]
    header_style = TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), colour),
        ("ROWBACKGROUNDS",(0,0), (-1,-1), [colour]),
        ("ALIGN",         (0,0), ( 0,-1), "CENTER"),
        ("ALIGN",         (1,0), ( 1,-1), "CENTER"),
        ("ALIGN",         (2,0), ( 2,-1), "CENTER"),
        ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
        ("TOPPADDING",    (0,0), (-1,-1), 6),
        ("BOTTOMPADDING", (0,0), (-1,-1), 6),
        ("LEFTPADDING",   (0,0), (-1,-1), 8),
        ("RIGHTPADDING",  (0,0), (-1,-1), 8),
        ("ROUNDEDCORNERS",(0,0), (-1,-1), [6,6,0,0]),
    ])
    items.append(Table(header_data, colWidths=[1.8*cm, 11.0*cm, 3.0*cm], style=header_style))

    # Details row
    def kv(k, v):
        return [Paragraph(f"<b>{k}:</b>", key_val_s), Paragraph(v, key_val_s)]

    detail_rows = [kv("ECG", ecg), kv("Valves", valves), kv("Heart Sound", sounds)]
    detail_style = TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), light),
        ("ALIGN",         (0,0), (-1,-1), "LEFT"),
        ("VALIGN",        (0,0), (-1,-1), "TOP"),
        ("TOPPADDING",    (0,0), (-1,-1), 3),
        ("BOTTOMPADDING", (0,0), (-1,-1), 3),
        ("LEFTPADDING",   (0,0), (-1,-1), 8),
        ("RIGHTPADDING",  (0,0), (-1,-1), 8),
    ])
    items.append(Table(detail_rows, colWidths=[3.0*cm, 12.8*cm], style=detail_style))

    # Bullets
    bullet_rows = [[Paragraph(f"• {b}", body_s)] for b in bullets]
    bullet_style = TableStyle([
        ("BACKGROUND",    (0,0), (-1,-1), WHITE),
        ("BOX",           (0,0), (-1,-1), 0.5, MID_GRY),
        ("TOPPADDING",    (0,0), (-1,-1), 3),
        ("BOTTOMPADDING", (0,0), (-1,-1), 3),
        ("LEFTPADDING",   (0,0), (-1,-1), 12),
        ("RIGHTPADDING",  (0,0), (-1,-1), 8),
        ("ROUNDEDCORNERS",(0,0), (-1,-1), [0,0,6,6]),
    ])
    items.append(Table(bullet_rows, colWidths=[15.8*cm], style=bullet_style))
    items.append(Spacer(1, 0.25*cm))

    return KeepTogether(items)


# ══════════════════════════════════════════════════════════════════════════════
#  BUILD CONTENT
# ══════════════════════════════════════════════════════════════════════════════
story = []

# ── PAGE 1: TITLE BANNER ──────────────────────────────────────────────────────
title_banner_data = [[Paragraph("THE CARDIAC CYCLE", title_s)]]
title_banner_style = TableStyle([
    ("BACKGROUND",    (0,0), (-1,-1), NAVY),
    ("ALIGN",         (0,0), (-1,-1), "CENTER"),
    ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    ("TOPPADDING",    (0,0), (-1,-1), 18),
    ("BOTTOMPADDING", (0,0), (-1,-1), 6),
    ("ROUNDEDCORNERS",(0,0), (-1,-1), [8,8,8,8]),
])
story.append(Table(title_banner_data, colWidths=[W - 3*cm], style=title_banner_style))

story.append(Spacer(1, 0.15*cm))

subtitle_data = [[Paragraph("10-Marker Visual Summary  •  Physiology", sub_s)]]
subtitle_style = TableStyle([
    ("BACKGROUND",    (0,0), (-1,-1), DARK_BLUE),
    ("ALIGN",         (0,0), (-1,-1), "CENTER"),
    ("TOPPADDING",    (0,0), (-1,-1), 5),
    ("BOTTOMPADDING", (0,0), (-1,-1), 5),
    ("ROUNDEDCORNERS",(0,0), (-1,-1), [0,0,8,8]),
])
story.append(Table(subtitle_data, colWidths=[W - 3*cm], style=subtitle_style))
story.append(Spacer(1, 0.35*cm))

# ── DEFINITION BOX ────────────────────────────────────────────────────────────
def_rows = [[
    Paragraph("<b>DEFINITION</b>", S("DefH", fontName="Helvetica-Bold", fontSize=10, textColor=GOLD, leading=13)),
    Paragraph(
        "The sequence of changes in pressure and flow in the heart chambers and blood vessels "
        "between two subsequent cardiac contractions. "
        "<b>Duration: 0.8 sec</b> at HR of 75 beats/min  |  "
        "<b>Ventricular Systole: 0.3 s</b>  |  <b>Ventricular Diastole: 0.5 s</b>  |  "
        "<b>Atrial Systole: 0.1 s</b>  |  <b>Atrial Diastole: 0.7 s</b>",
        S("DefB", fontName="Helvetica", fontSize=8.5, textColor=WHITE, leading=13, alignment=TA_JUSTIFY))
]]
def_style = TableStyle([
    ("BACKGROUND",    (0,0), (-1,-1), DARK_BLUE),
    ("VALIGN",        (0,0), (-1,-1), "TOP"),
    ("TOPPADDING",    (0,0), (-1,-1), 8),
    ("BOTTOMPADDING", (0,0), (-1,-1), 8),
    ("LEFTPADDING",   (0,0), (-1,-1), 10),
    ("RIGHTPADDING",  (0,0), (-1,-1), 10),
    ("ROUNDEDCORNERS",(0,0), (-1,-1), [6,6,6,6]),
])
story.append(Table(def_rows, colWidths=[2.8*cm, 13.0*cm], style=def_style))
story.append(Spacer(1, 0.4*cm))

# ── OVERVIEW TIMELINE TABLE ────────────────────────────────────────────────────
story.append(ColourBanner("OVERVIEW: PHASES AT A GLANCE", DARK_BLUE, height=0.8*cm, size=11))
story.append(Spacer(1, 0.15*cm))

timeline_headers = [
    Paragraph("<b>#</b>", label_s),
    Paragraph("<b>Phase</b>", label_s),
    Paragraph("<b>Duration</b>", label_s),
    Paragraph("<b>ECG</b>", label_s),
    Paragraph("<b>Valves</b>", label_s),
    Paragraph("<b>Heart Sound</b>", label_s),
]
timeline_data = [timeline_headers,
    [Paragraph("1", phase_s), Paragraph("Atrial Systole", phase_s),
     Paragraph("0.1 s", small_s), Paragraph("P wave", small_s),
     Paragraph("MV open", small_s), Paragraph("S4 (silent)", small_s)],
    [Paragraph("2", phase_s), Paragraph("Isovolumetric Contraction", phase_s),
     Paragraph("0.05 s", small_s), Paragraph("QRS", small_s),
     Paragraph("MV closes", small_s), Paragraph("S1 – LUBB", small_s)],
    [Paragraph("3", phase_s), Paragraph("Rapid Ejection", phase_s),
     Paragraph("0.09 s", small_s), Paragraph("ST segment", small_s),
     Paragraph("AoV opens", small_s), Paragraph("—", small_s)],
    [Paragraph("4", phase_s), Paragraph("Slow Ejection", phase_s),
     Paragraph("0.13 s", small_s), Paragraph("T wave", small_s),
     Paragraph("AoV open", small_s), Paragraph("—", small_s)],
    [Paragraph("5", phase_s), Paragraph("Protodiastole", phase_s),
     Paragraph("0.04 s", small_s), Paragraph("End T wave", small_s),
     Paragraph("AoV closes", small_s), Paragraph("S2 – DUBB", small_s)],
    [Paragraph("6", phase_s), Paragraph("Isovolumetric Relaxation", phase_s),
     Paragraph("0.08 s", small_s), Paragraph("—", small_s),
     Paragraph("All closed", small_s), Paragraph("—", small_s)],
    [Paragraph("7", phase_s), Paragraph("Rapid Ventricular Filling", phase_s),
     Paragraph("0.11 s", small_s), Paragraph("—", small_s),
     Paragraph("MV opens", small_s), Paragraph("S3 (soft)", small_s)],
    [Paragraph("8", phase_s), Paragraph("Diastasis (Slow Filling)", phase_s),
     Paragraph("0.19 s", small_s), Paragraph("—", small_s),
     Paragraph("MV open", small_s), Paragraph("—", small_s)],
    [Paragraph("9", phase_s), Paragraph("Atrial Diastole", phase_s),
     Paragraph("0.7 s", small_s), Paragraph("—", small_s),
     Paragraph("—", small_s), Paragraph("—", small_s)],
    [Paragraph("10", phase_s), Paragraph("Stroke Volume / EDV / ESV", phase_s),
     Paragraph("—", small_s), Paragraph("—", small_s),
     Paragraph("—", small_s), Paragraph("SV=70–80 ml", small_s)],
]

row_bg = []
phase_colours = [ATRIAL, SYSTOLE, SYSTOLE, SYSTOLE, DIASTOLE, DIASTOLE, DIASTOLE, DIASTOLE, ATRIAL, DARK_BLUE]
for i, c in enumerate(phase_colours, start=1):
    row_bg.append(("BACKGROUND", (0,i), (-1,i), c))

tl_style = TableStyle([
    ("BACKGROUND",    (0,0), (-1,0), NAVY),
    ("TEXTCOLOR",     (0,0), (-1,0), WHITE),
    ("ALIGN",         (0,0), (-1,-1), "CENTER"),
    ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    ("TOPPADDING",    (0,0), (-1,-1), 5),
    ("BOTTOMPADDING", (0,0), (-1,-1), 5),
    ("GRID",          (0,0), (-1,-1), 0.4, colors.HexColor("#CCCCCC")),
    ("ROWBACKGROUNDS",(0,1), (-1,-1), [colors.HexColor("#F9FAFB"), colors.HexColor("#EEF2F7")]),
] + row_bg)

story.append(Table(timeline_data,
    colWidths=[0.8*cm, 5.2*cm, 1.8*cm, 2.2*cm, 2.8*cm, 3.0*cm],
    style=tl_style))
story.append(Spacer(1, 0.5*cm))

# ── WIGGERS DIAGRAM NOTE ───────────────────────────────────────────────────────
wiggers_url = "https://cdn.orris.care/cdss_images/5379713ae38b7760f16c6d3143e59fb2744f746ac41304bb94abcda6a828d458.png"
wiggers_path = "/home/daytona/workspace/cardiac-cycle-pdf/wiggers.png"

if not os.path.exists(wiggers_path):
    try:
        urllib.request.urlretrieve(wiggers_url, wiggers_path)
        print("Wiggers diagram downloaded.")
    except Exception as e:
        print(f"Warning: could not download Wiggers diagram: {e}")
        wiggers_path = None

if wiggers_path and os.path.exists(wiggers_path):
    story.append(ColourBanner("WIGGERS DIAGRAM", DARK_BLUE, height=0.8*cm, size=11))
    story.append(Spacer(1, 0.2*cm))
    # Scale to fit width
    img_w = W - 4*cm
    img_h = img_w * 2.2   # approximate aspect ratio of this tall diagram
    max_h = 18*cm
    if img_h > max_h:
        scale = max_h / img_h
        img_w = img_w * scale
        img_h = max_h
    wig_img = Image(wiggers_path, width=img_w, height=img_h)
    wig_img.hAlign = "CENTER"
    story.append(wig_img)
    story.append(Paragraph(
        "<i>Fig: Wiggers Diagram – aortic pressure, LV pressure, LA pressure, ventricular volume, "
        "heart sounds, venous pulse (a,c,v waves) and ECG during one cardiac cycle "
        "(Costanzo Physiology 7e, Fig 4.25)</i>",
        S("FigCap", fontName="Helvetica-Oblique", fontSize=7.5, textColor=colors.HexColor("#555555"),
          alignment=TA_CENTER, leading=11)))
    story.append(Spacer(1, 0.5*cm))

# ══════════════════════════════════════════════════════════════════════════════
#  DETAILED PHASE CARDS  (one per marker)
# ══════════════════════════════════════════════════════════════════════════════
story.append(ColourBanner("DETAILED PHASE-BY-PHASE ANALYSIS", NAVY, height=0.9*cm, size=12))
story.append(Spacer(1, 0.3*cm))

# ── Marker 1 ──────────────────────────────────────────────────────────────────
story.append(phase_card(
    "1", "ATRIAL SYSTOLE", "0.1 sec",
    "P wave (atrial depolarisation)",
    "Mitral & Tricuspid valves OPEN; Semilunar valves closed",
    "S4 – Fourth Heart Sound (normally silent; heard in ventricular hypertrophy)",
    [
        "Initiated by SA node impulse; atria depolarise → contract",
        "Right atrial pressure rises to 4–6 mmHg; Left atrial pressure rises to 7–8 mmHg",
        "30% additional blood actively ejected into ventricles (70% already filled passively)",
        "Reflected back to jugular veins as the 'a wave' of venous pulse",
        "EDV just before ventricular systole: 120–140 ml",
    ],
    ATRIAL, ATR_LIGHT
))

# ── Marker 2 ──────────────────────────────────────────────────────────────────
story.append(phase_card(
    "2", "ISOVOLUMETRIC VENTRICULAR CONTRACTION", "~0.05 sec",
    "QRS complex (ventricular depolarisation)",
    "Mitral & Tricuspid valves CLOSE → S1 produced; Semilunar valves still closed",
    "S1 – LUBB (prolonged, loud; 0.15 s; 25–45 Hz; heard at mitral/tricuspid; at R wave)",
    [
        "Atrial contraction passes off; excitation wave invades ventricles",
        "Ventricular pressure rises rapidly and exceeds atrial pressure → AV valves close → S1",
        "Ventricles now form a CLOSED CHAMBER (all 4 valves shut)",
        "Pressure rises sharply as myocardium contracts on blood, but NO blood is ejected",
        "AV valves bulge into atria → small, sharp rise in atrial pressure (c wave of venous pulse)",
        "Volume unchanged = Isovolumetric (constant volume contraction)",
    ],
    SYSTOLE, SYS_LIGHT
))

# ── Marker 3 ──────────────────────────────────────────────────────────────────
story.append(phase_card(
    "3", "RAPID VENTRICULAR EJECTION", "~0.09 sec",
    "ST segment",
    "Aortic & Pulmonary valves OPEN (LV pressure > 80 mmHg aortic; RV > pulmonary artery pressure)",
    "None",
    [
        "LV pressure exceeds aortic diastolic pressure (80 mmHg) → aortic valve opens",
        "~2/3 of stroke volume (approx. 47–53 ml) ejected rapidly",
        "Ventricular and aortic pressures rise steeply and follow each other closely",
        "Aortic pressure rises toward peak systolic (~120 mmHg)",
    ],
    SYSTOLE, SYS_LIGHT
))

# ── Marker 4 ──────────────────────────────────────────────────────────────────
story.append(phase_card(
    "4", "SLOW (REDUCED) VENTRICULAR EJECTION", "~0.13 sec",
    "T wave (ventricular repolarisation begins)",
    "Aortic & Pulmonary valves remain OPEN",
    "None",
    [
        "Peak (summit) of LV pressure curve reached; aortic pressure slightly exceeds LV pressure",
        "Ventricular contraction begins to subside; ejection rate slows",
        "Remaining ~1/3 of stroke volume ejected at a slower rate",
        "End-Systolic Volume (ESV) reached: ~50 ml remains in each ventricle",
        "STROKE VOLUME = EDV – ESV = 70–80 ml per beat",
    ],
    SYSTOLE, SYS_LIGHT
))

# ── Marker 5 ──────────────────────────────────────────────────────────────────
story.append(phase_card(
    "5", "PROTODIASTOLE", "~0.04 sec",
    "End of T wave",
    "Aortic & Pulmonary valves CLOSE → S2 produced",
    "S2 – DUBB (short, sharp, high-pitched; 0.12 s; 50 Hz; heard at aortic/pulmonary areas; end T wave)",
    [
        "At end of ventricular systole, ventricular pressure drops more rapidly than arterial pressure",
        "Arterial pressure sustained by elastic recoil of aorta/pulmonary trunk",
        "Arterial pressure momentarily exceeds ventricular pressure → semilunar valves close → S2",
        "Dicrotic notch (incisura) visible on aortic pressure trace at valve closure",
        "Transition period between systole and diastole",
    ],
    DIASTOLE, DIA_LIGHT
))

# ── Marker 6 ──────────────────────────────────────────────────────────────────
story.append(phase_card(
    "6", "ISOVOLUMETRIC VENTRICULAR RELAXATION", "~0.08 sec",
    "After T wave (electrical silence)",
    "All valves CLOSED; Mitral/Tricuspid still shut (LV pressure still above LA pressure)",
    "None (S2 was just produced in Marker 5)",
    [
        "Begins immediately after semilunar valve closure",
        "Intraventricular pressure drops rapidly as ventricles continue to relax",
        "No change in ventricular volume (all valves remain closed) = Isovolumetric relaxation",
        "Once LV pressure falls below left atrial pressure → mitral valve opens → rapid filling begins",
        "This phase ends when AV valves open",
    ],
    DIASTOLE, DIA_LIGHT
))

# ── Marker 7 ──────────────────────────────────────────────────────────────────
story.append(phase_card(
    "7", "RAPID VENTRICULAR FILLING", "~0.11 sec",
    "After T wave (before next P wave)",
    "Mitral & Tricuspid valves OPEN",
    "S3 – Third Heart Sound (short, soft, low-pitched; 0.1 s; normally inaudible; heard at apex)",
    [
        "AV valves open as ventricular pressure falls below atrial pressure",
        "~70% of total ventricular filling occurs passively in this and diastasis phases",
        "Blood rushes from atria into ventricles down pressure gradient",
        "Ventricular pressure remains low; continued relaxation keeps compliance high",
        "Atrial pressure falls to within a fraction of a mmHg of ventricular pressure",
        "S3 caused by vibrations from the in-rush of blood into ventricle walls",
    ],
    DIASTOLE, DIA_LIGHT
))

# ── Marker 8 ──────────────────────────────────────────────────────────────────
story.append(phase_card(
    "8", "DIASTASIS (SLOW VENTRICULAR FILLING)", "~0.19 sec",
    "TP interval (flat baseline between T and next P)",
    "Mitral & Tricuspid valves remain OPEN",
    "None",
    [
        "Slow, passive filling phase as atrial and ventricular pressures equilibrate",
        "Very little blood enters ventricles (low pressure gradient)",
        "Atrial pressure slowly rises due to continuous venous return",
        "This phase is shortened or abolished when heart rate increases",
        "Terminates with the next atrial systole",
    ],
    DIASTOLE, DIA_LIGHT
))

# ── Marker 9 ──────────────────────────────────────────────────────────────────
story.append(phase_card(
    "9", "ATRIAL DIASTOLE", "0.7 sec",
    "Extends through QRS, ST, T, and TP intervals",
    "Initially closed; AV valves open in phase 7",
    "No heart sounds (contributes to S3 environment during filling)",
    [
        "Atrial muscles relax following atrial systole",
        "Atrial pressure gradually increases due to continuous venous return from great veins",
        "Pressure drops to near zero when AV valves open (rapid filling)",
        "Pressure rises again during diastasis following ventricular pressure",
        "Atrial diastole overlaps with most of ventricular systole and diastole",
        "Venous pulse: 'v wave' builds during atrial diastole (venous filling before AV valve opens)",
    ],
    ATRIAL, ATR_LIGHT
))

# ── Marker 10 ─────────────────────────────────────────────────────────────────
story.append(ColourBanner("10  KEY VOLUMES, PRESSURES & HEART SOUNDS – QUICK REFERENCE", DARK_BLUE, height=0.8*cm, size=10))
story.append(Spacer(1, 0.15*cm))

kv_data = [
    [Paragraph("<b>Parameter</b>", label_s), Paragraph("<b>Value</b>", label_s),
     Paragraph("<b>Parameter</b>", label_s), Paragraph("<b>Value</b>", label_s)],
    [Paragraph("Cardiac Cycle Duration", key_val_s), Paragraph("<b>0.8 sec</b> (HR 75)", bold_val_s),
     Paragraph("Stroke Volume (SV)", key_val_s), Paragraph("<b>70–80 ml</b>", bold_val_s)],
    [Paragraph("Ventricular Systole", key_val_s), Paragraph("<b>0.3 sec</b>", bold_val_s),
     Paragraph("End-Diastolic Volume (EDV)", key_val_s), Paragraph("<b>120–140 ml</b>", bold_val_s)],
    [Paragraph("Ventricular Diastole", key_val_s), Paragraph("<b>0.5 sec</b>", bold_val_s),
     Paragraph("End-Systolic Volume (ESV)", key_val_s), Paragraph("<b>~50 ml</b>", bold_val_s)],
    [Paragraph("Atrial Systole", key_val_s), Paragraph("<b>0.1 sec</b>", bold_val_s),
     Paragraph("Ejection Fraction (EF)", key_val_s), Paragraph("<b>~65%</b> (SV/EDV)", bold_val_s)],
    [Paragraph("Atrial Diastole", key_val_s), Paragraph("<b>0.7 sec</b>", bold_val_s),
     Paragraph("Aortic valve opens at", key_val_s), Paragraph("<b>LV > 80 mmHg</b>", bold_val_s)],
    [Paragraph("Atrial contribution to filling", key_val_s), Paragraph("<b>30%</b>", bold_val_s),
     Paragraph("Peak aortic systolic pressure", key_val_s), Paragraph("<b>~120 mmHg</b>", bold_val_s)],
    [Paragraph("Passive ventricular filling", key_val_s), Paragraph("<b>70%</b>", bold_val_s),
     Paragraph("Aortic diastolic pressure", key_val_s), Paragraph("<b>~80 mmHg</b>", bold_val_s)],
]
kv_style = TableStyle([
    ("BACKGROUND",    (0,0), (-1,0), NAVY),
    ("TEXTCOLOR",     (0,0), (-1,0), WHITE),
    ("ALIGN",         (0,0), (-1,-1), "LEFT"),
    ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    ("TOPPADDING",    (0,0), (-1,-1), 5),
    ("BOTTOMPADDING", (0,0), (-1,-1), 5),
    ("LEFTPADDING",   (0,0), (-1,-1), 8),
    ("RIGHTPADDING",  (0,0), (-1,-1), 8),
    ("ROWBACKGROUNDS",(0,1), (-1,-1), [LIGHT_GRY, WHITE]),
    ("GRID",          (0,0), (-1,-1), 0.4, MID_GRY),
    ("LINEAFTER",     (1,0), (1,-1), 1.0, MID_GRY),
])
story.append(Table(kv_data, colWidths=[4.8*cm, 3.0*cm, 5.0*cm, 3.0*cm], style=kv_style))
story.append(Spacer(1, 0.35*cm))

# ── HEART SOUNDS TABLE ────────────────────────────────────────────────────────
story.append(ColourBanner("HEART SOUNDS SUMMARY", DARK_BLUE, height=0.8*cm, size=11))
story.append(Spacer(1, 0.15*cm))

hs_headers = [Paragraph(f"<b>{h}</b>", label_s) for h in
              ["Sound", "Word", "Cause", "Duration", "Frequency", "Best Heard", "ECG Timing"]]
hs_data = [hs_headers,
    [Paragraph("<b>S1</b>", S("S1S", fontName="Helvetica-Bold", fontSize=9, textColor=SYSTOLE)),
     Paragraph("LUBB", small_s), Paragraph("AV valve closure (start of ventricular systole)", small_s),
     Paragraph("0.15 s", small_s), Paragraph("25–45 Hz", small_s),
     Paragraph("Mitral & Tricuspid areas", small_s), Paragraph("R wave", small_s)],
    [Paragraph("<b>S2</b>", S("S2S", fontName="Helvetica-Bold", fontSize=9, textColor=DIASTOLE)),
     Paragraph("DUBB", small_s), Paragraph("Semilunar valve closure (onset of diastole)", small_s),
     Paragraph("0.12 s", small_s), Paragraph("50 Hz", small_s),
     Paragraph("Aortic & Pulmonary areas", small_s), Paragraph("End T wave", small_s)],
    [Paragraph("<b>S3</b>", S("S3S", fontName="Helvetica-Bold", fontSize=9, textColor=DARK_BLUE)),
     Paragraph("(soft)", small_s), Paragraph("Blood in-rush during rapid ventricular filling", small_s),
     Paragraph("0.1 s", small_s), Paragraph("Low", small_s),
     Paragraph("Apex (left lateral decubitus)", small_s), Paragraph("After T wave", small_s)],
    [Paragraph("<b>S4</b>", S("S4S", fontName="Helvetica-Bold", fontSize=9, textColor=ATRIAL)),
     Paragraph("(silent)", small_s), Paragraph("Atrial contraction (last rapid filling phase)", small_s),
     Paragraph("—", small_s), Paragraph("Very low", small_s),
     Paragraph("Apex (heard in LV hypertrophy)", small_s), Paragraph("Before QRS / P-R", small_s)],
]
hs_style = TableStyle([
    ("BACKGROUND",    (0,0), (-1,0), NAVY),
    ("ALIGN",         (0,0), (-1,-1), "CENTER"),
    ("VALIGN",        (0,0), (-1,-1), "MIDDLE"),
    ("TOPPADDING",    (0,0), (-1,-1), 5),
    ("BOTTOMPADDING", (0,0), (-1,-1), 5),
    ("LEFTPADDING",   (0,0), (-1,-1), 6),
    ("RIGHTPADDING",  (0,0), (-1,-1), 6),
    ("ROWBACKGROUNDS",(0,1), (-1,-1), [SYS_LIGHT, DIA_LIGHT, colors.HexColor("#EBF5FB"), ATR_LIGHT]),
    ("GRID",          (0,0), (-1,-1), 0.4, MID_GRY),
])
story.append(Table(hs_data, colWidths=[1.2*cm, 1.4*cm, 4.8*cm, 1.5*cm, 1.8*cm, 3.2*cm, 1.9*cm], style=hs_style))
story.append(Spacer(1, 0.35*cm))

# ── VENOUS PULSE TABLE ────────────────────────────────────────────────────────
story.append(ColourBanner("VENOUS PULSE WAVES (JVP)", MED_BLUE, height=0.7*cm, size=10))
story.append(Spacer(1, 0.15*cm))

vp_data = [
    [Paragraph("<b>Wave</b>", label_s), Paragraph("<b>Cause</b>", label_s), Paragraph("<b>Phase</b>", label_s)],
    [Paragraph("a wave", body_bold), Paragraph("Atrial systole (atrial contraction)", body_s), Paragraph("Atrial Systole (Phase 1)", body_s)],
    [Paragraph("c wave", body_bold), Paragraph("AV valve bulging into atria / isovolumetric contraction", body_s), Paragraph("Isovolumetric Contraction (Phase 2)", body_s)],
    [Paragraph("v wave", body_bold), Paragraph("Venous filling of atrium while AV valve is closed during ventricular systole", body_s), Paragraph("Ventricular Ejection (Phases 3–4)", body_s)],
]
vp_style = TableStyle([
    ("BACKGROUND",    (0,0), (-1,0), MED_BLUE),
    ("ALIGN",         (0,0), (-1,-1), "LEFT"),
    ("VALIGN",        (0,0), (-1,-1), "TOP"),
    ("TOPPADDING",    (0,0), (-1,-1), 5),
    ("BOTTOMPADDING", (0,0), (-1,-1), 5),
    ("LEFTPADDING",   (0,0), (-1,-1), 8),
    ("RIGHTPADDING",  (0,0), (-1,-1), 8),
    ("ROWBACKGROUNDS",(0,1), (-1,-1), [ATR_LIGHT, SYS_LIGHT, DIA_LIGHT]),
    ("GRID",          (0,0), (-1,-1), 0.4, MID_GRY),
])
story.append(Table(vp_data, colWidths=[2.2*cm, 9.0*cm, 4.6*cm], style=vp_style))
story.append(Spacer(1, 0.35*cm))

# ── CLINICAL POINTS BOX ───────────────────────────────────────────────────────
story.append(ColourBanner("CLINICAL CORRELATION POINTS", colors.HexColor("#6C3483"), height=0.7*cm, size=10))
story.append(Spacer(1, 0.1*cm))

clinical_bullets = [
    "<b>↑ Heart Rate:</b> Diastasis is shortened first; at very high HR, diastole encroaches on rapid filling → incomplete ventricular filling",
    "<b>S3 pathological</b> in adults > 40 yrs → ventricular failure (dilated cardiomyopathy, MR, AR)",
    "<b>S4 audible</b> in stiff ventricles: LV hypertrophy (hypertension, AS), acute MI",
    "<b>Isovolumetric phases</b> are prolonged in heart failure and shortened in high-output states",
    "<b>Ejection Fraction (EF):</b> Normal ≥ 55%; HFrEF < 40%; HFmrEF 40–49%",
    "<b>Dicrotic notch</b> on aortic pressure trace = aortic valve closure (S2); used in intra-aortic balloon pump timing",
]
clinical_rows = [[Paragraph(f"• {b}", body_s)] for b in clinical_bullets]
clin_style = TableStyle([
    ("BACKGROUND",    (0,0), (-1,-1), colors.HexColor("#F9F0FF")),
    ("BOX",           (0,0), (-1,-1), 0.5, colors.HexColor("#6C3483")),
    ("TOPPADDING",    (0,0), (-1,-1), 4),
    ("BOTTOMPADDING", (0,0), (-1,-1), 4),
    ("LEFTPADDING",   (0,0), (-1,-1), 12),
    ("RIGHTPADDING",  (0,0), (-1,-1), 8),
])
story.append(Table(clinical_rows, colWidths=[15.8*cm], style=clin_style))
story.append(Spacer(1, 0.3*cm))

# ── FOOTER ────────────────────────────────────────────────────────────────────
story.append(HRFlowable(width="100%", thickness=1, color=MID_GRY))
story.append(Spacer(1, 0.1*cm))
story.append(Paragraph(
    "Sources: Costanzo Physiology 7th Ed (Table 4.5, Fig 4.25)  •  "
    "Guyton and Hall Textbook of Medical Physiology  •  Cardiac Cycle-1.pptx",
    S("Footer", fontName="Helvetica-Oblique", fontSize=7, textColor=colors.HexColor("#888888"),
      alignment=TA_CENTER, leading=10)))

# ══════════════════════════════════════════════════════════════════════════════
#  BUILD
# ══════════════════════════════════════════════════════════════════════════════
doc.build(story)
print(f"PDF created: {OUTPUT}")
Running Command

cd /home/daytona/workspace/cardiac-cycle-pdf && python3 generate_pdf.py 2>&1

Running Command

ls -lh /home/daytona/workspace/cardiac-cycle-pdf/

Your visual summary PDF is ready (356 KB):

Cardiac Cycle 10 Markers

PDF Document · PDF


What's inside the PDF:
SectionContent
Title BannerColour-coded header with definition and all durations
Overview TableAll 10 markers at a glance - phase, duration, ECG, valves, heart sounds
Wiggers DiagramFull annotated diagram from Costanzo Physiology 7e (Fig 4.25)
Phase Cards 1-9Each phase as a colour-coded card: header (green=atrial, red=systolic, blue=diastolic) + ECG/valve/sound details + bullet-point explanation
Marker 10 - Key NumbersQuick-reference table: SV, EDV, ESV, EF, pressures
Heart Sounds TableS1-S4: cause, quality, duration, frequency, auscultation site, ECG timing
JVP Wavesa, c, v wave causes and phase correlation
Clinical PointsHFrEF EF thresholds, S3/S4 pathology, dicrotic notch, effect of ↑HR

Giver heart sound pdf

Finding Sources
Reading File
Reading File
Finding Sources
Reading File
Reading File
Reading File
Loading Image
Loading Image
Finding Sources
Running Command

cd /home/daytona/workspace/cardiac-cycle-pdf && python3 -c " import urllib.reque…

Writing File

~/cardiac-cycle-pdf/generate_heart_sounds_pdf.py

#!/usr/bin/env python3
"""
Heart Sounds – Comprehensive Visual Summary PDF
Covers: S1–S4, S2 splitting, murmurs, auscultation areas, clinical correlations
"""

from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.lib.styles import ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    HRFlowable, KeepTogether, Image
)
from reportlab.platypus.flowables import Flowable
import os

W, H = A4
OUTPUT = "/home/daytona/workspace/cardiac-cycle-pdf/Heart_Sounds_Visual_Summary.pdf"

# ── Palette ───────────────────────────────────────────────────────────────────
NAVY      = colors.HexColor("#0B2545")
DARK_BLUE = colors.HexColor("#1B4F72")
MED_BLUE  = colors.HexColor("#2874A6")
LIGHT_BLU = colors.HexColor("#AED6F1")
S1_COL    = colors.HexColor("#922B21")   # deep red
S1_LT     = colors.HexColor("#FADBD8")
S2_COL    = colors.HexColor("#1A5276")   # deep blue
S2_LT     = colors.HexColor("#D6EAF8")
S3_COL    = colors.HexColor("#1D6A39")   # deep green
S3_LT     = colors.HexColor("#D5F5E3")
S4_COL    = colors.HexColor("#7D3C98")   # purple
S4_LT     = colors.HexColor("#E8DAEF")
MUR_COL   = colors.HexColor("#B7770D")   # amber
MUR_LT    = colors.HexColor("#FDEBD0")
CLIN_COL  = colors.HexColor("#117A65")
CLIN_LT   = colors.HexColor("#D1F2EB")
WHITE     = colors.white
LIGHT_GRY = colors.HexColor("#F4F6F7")
MID_GRY   = colors.HexColor("#BDC3C7")
DARK_GRY  = colors.HexColor("#2C3E50")
GOLD      = colors.HexColor("#D4AC0D")

# ── Doc ────────────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
    OUTPUT, pagesize=A4,
    leftMargin=1.5*cm, rightMargin=1.5*cm,
    topMargin=1.5*cm, bottomMargin=1.5*cm,
    title="Heart Sounds – Visual Summary", author="Physiology Notes"
)

# ── Styles ─────────────────────────────────────────────────────────────────────
def S(name, **kw): return ParagraphStyle(name, **kw)

title_s  = S("Ts",  fontName="Helvetica-Bold",  fontSize=24, textColor=WHITE,    alignment=TA_CENTER, leading=30)
sub_s    = S("Ss",  fontName="Helvetica",        fontSize=11, textColor=LIGHT_BLU,alignment=TA_CENTER, leading=15)
h2_s     = S("H2s", fontName="Helvetica-Bold",  fontSize=11, textColor=WHITE,    alignment=TA_LEFT,   leading=14, leftIndent=4)
body_s   = S("Bs",  fontName="Helvetica",        fontSize=9,  textColor=DARK_GRY, alignment=TA_JUSTIFY,leading=13)
body_b   = S("Bb",  fontName="Helvetica-Bold",  fontSize=9,  textColor=DARK_GRY, alignment=TA_LEFT,  leading=13)
small_s  = S("Sm",  fontName="Helvetica",        fontSize=8,  textColor=DARK_GRY, alignment=TA_CENTER,leading=11)
small_b  = S("SmB", fontName="Helvetica-Bold",  fontSize=8,  textColor=DARK_GRY, alignment=TA_CENTER,leading=11)
lbl_s    = S("Lb",  fontName="Helvetica-Bold",  fontSize=8,  textColor=WHITE,    alignment=TA_CENTER,leading=11)
num_s    = S("Nm",  fontName="Helvetica-Bold",  fontSize=20, textColor=GOLD,     alignment=TA_CENTER,leading=24)
phase_s  = S("Ph",  fontName="Helvetica-Bold",  fontSize=11, textColor=WHITE,    alignment=TA_CENTER,leading=15)
word_s   = S("Wd",  fontName="Helvetica-Bold",  fontSize=16, textColor=GOLD,     alignment=TA_CENTER,leading=20)
fig_s    = S("Fg",  fontName="Helvetica-Oblique",fontSize=7.5,textColor=colors.HexColor("#555"),alignment=TA_CENTER,leading=10)

# ── Coloured banner ────────────────────────────────────────────────────────────
class Banner(Flowable):
    def __init__(self, text, bg, fg=WHITE, h=0.85*cm, size=11, bold=True):
        Flowable.__init__(self)
        self.text=text; self.bg=bg; self.fg=fg; self.h=h; self.size=size; self.bold=bold
        self.width=W-3*cm
    def draw(self):
        self.canv.setFillColor(self.bg)
        self.canv.roundRect(0,0,self.width,self.h,5,fill=1,stroke=0)
        self.canv.setFillColor(self.fg)
        fn = "Helvetica-Bold" if self.bold else "Helvetica"
        self.canv.setFont(fn, self.size)
        self.canv.drawCentredString(self.width/2, self.h/2-self.size*0.35, self.text)

# ── Sound card ─────────────────────────────────────────────────────────────────
def sound_card(num, word, full_name, colour, light,
               cause, mechanism, characteristics, ecg_timing,
               auscultation, normal_or_path, bullets):
    items=[]
    # Header
    hd = [[Paragraph(num, num_s), Paragraph(f'<b>{word}</b>', word_s),
           Paragraph(full_name, phase_s)]]
    hd_st = TableStyle([
        ("BACKGROUND",(0,0),(-1,-1),colour),
        ("ALIGN",(0,0),(-1,-1),"CENTER"),
        ("VALIGN",(0,0),(-1,-1),"MIDDLE"),
        ("TOPPADDING",(0,0),(-1,-1),8),
        ("BOTTOMPADDING",(0,0),(-1,-1),8),
        ("LEFTPADDING",(0,0),(-1,-1),6),
        ("RIGHTPADDING",(0,0),(-1,-1),6),
        ("ROUNDEDCORNERS",(0,0),(-1,-1),[6,6,0,0]),
    ])
    items.append(Table(hd, colWidths=[1.8*cm,3.0*cm,11.0*cm], style=hd_st))

    # Quick-facts row
    def kv(k,v): return [Paragraph(f"<b>{k}</b>", S("kk",fontName="Helvetica-Bold",fontSize=8,textColor=DARK_BLUE,leading=12)),
                          Paragraph(v, S("vv",fontName="Helvetica",fontSize=8,textColor=DARK_GRY,leading=12))]
    qf_rows = [kv("Cause",cause), kv("Mechanism",mechanism),
               kv("Characteristics",characteristics),
               kv("ECG Timing",ecg_timing), kv("Best Heard",auscultation),
               kv("Normal / Pathological",normal_or_path)]
    qf_st = TableStyle([
        ("BACKGROUND",(0,0),(-1,-1),light),
        ("ALIGN",(0,0),(-1,-1),"LEFT"),
        ("VALIGN",(0,0),(-1,-1),"TOP"),
        ("TOPPADDING",(0,0),(-1,-1),3),
        ("BOTTOMPADDING",(0,0),(-1,-1),3),
        ("LEFTPADDING",(0,0),(-1,-1),8),
        ("RIGHTPADDING",(0,0),(-1,-1),8),
        ("LINEAFTER",(0,0),(0,-1),0.5,MID_GRY),
    ])
    items.append(Table(qf_rows, colWidths=[3.5*cm,12.3*cm], style=qf_st))

    # Bullets
    bl_rows=[[Paragraph(f"• {b}", body_s)] for b in bullets]
    bl_st = TableStyle([
        ("BACKGROUND",(0,0),(-1,-1),WHITE),
        ("BOX",(0,0),(-1,-1),0.5,MID_GRY),
        ("TOPPADDING",(0,0),(-1,-1),3),
        ("BOTTOMPADDING",(0,0),(-1,-1),3),
        ("LEFTPADDING",(0,0),(-1,-1),12),
        ("RIGHTPADDING",(0,0),(-1,-1),8),
        ("ROUNDEDCORNERS",(0,0),(-1,-1),[0,0,6,6]),
    ])
    items.append(Table(bl_rows, colWidths=[15.8*cm], style=bl_st))
    items.append(Spacer(1,0.3*cm))
    return KeepTogether(items)


# ══════════════════════════════════════════════════════════════════════════════
#  BUILD STORY
# ══════════════════════════════════════════════════════════════════════════════
story=[]

# ── TITLE ─────────────────────────────────────────────────────────────────────
tb=[[Paragraph("HEART SOUNDS", title_s)]]
ts=TableStyle([("BACKGROUND",(0,0),(-1,-1),NAVY),("ALIGN",(0,0),(-1,-1),"CENTER"),
               ("TOPPADDING",(0,0),(-1,-1),18),("BOTTOMPADDING",(0,0),(-1,-1),6),
               ("ROUNDEDCORNERS",(0,0),(-1,-1),[8,8,8,8])])
story.append(Table(tb,colWidths=[W-3*cm],style=ts))
story.append(Spacer(1,0.15*cm))
sb=[[Paragraph("Visual Summary  •  S1 – S4  •  Splitting  •  Murmurs  •  Auscultation  •  Clinical Correlations", sub_s)]]
ss=TableStyle([("BACKGROUND",(0,0),(-1,-1),DARK_BLUE),("ALIGN",(0,0),(-1,-1),"CENTER"),
               ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
               ("ROUNDEDCORNERS",(0,0),(-1,-1),[0,0,8,8])])
story.append(Table(sb,colWidths=[W-3*cm],style=ss))
story.append(Spacer(1,0.4*cm))

# ── OVERVIEW TABLE ─────────────────────────────────────────────────────────────
story.append(Banner("OVERVIEW – FOUR HEART SOUNDS AT A GLANCE", DARK_BLUE, h=0.8*cm, size=11))
story.append(Spacer(1,0.15*cm))

ov_hdr=[Paragraph(f"<b>{h}</b>",lbl_s) for h in
        ["Sound","Word","Valve/Event","Timing","Duration","Frequency","Best Heard","Status"]]
ov_data=[ov_hdr,
  [Paragraph("<b>S1</b>",S("s1h",fontName="Helvetica-Bold",fontSize=10,textColor=S1_COL)),
   Paragraph("LUBB",small_b), Paragraph("AV valve closure\n(Mitral + Tricuspid)",small_s),
   Paragraph("Start of\nventricular systole",small_s), Paragraph("0.14 sec",small_s),
   Paragraph("25–45 Hz",small_s), Paragraph("Mitral area / LLSB",small_s),
   Paragraph("Normal",S("nrm",fontName="Helvetica-Bold",fontSize=8,textColor=S3_COL,leading=11))],
  [Paragraph("<b>S2</b>",S("s2h",fontName="Helvetica-Bold",fontSize=10,textColor=S2_COL)),
   Paragraph("DUBB",small_b), Paragraph("Semilunar closure\n(Aortic + Pulmonary)",small_s),
   Paragraph("End of ventricular\nsystole / onset diastole",small_s), Paragraph("0.11 sec",small_s),
   Paragraph("50 Hz",small_s), Paragraph("Aortic/Pulmonic area\nLUSB",small_s),
   Paragraph("Normal",S("nrm2",fontName="Helvetica-Bold",fontSize=8,textColor=S3_COL,leading=11))],
  [Paragraph("<b>S3</b>",S("s3h",fontName="Helvetica-Bold",fontSize=10,textColor=S3_COL)),
   Paragraph("(ken-TUC-ky)",small_b), Paragraph("Rapid ventricular\nfilling",small_s),
   Paragraph("Early diastole\n(after S2)",small_s), Paragraph("0.1 sec",small_s),
   Paragraph("Low (<50 Hz)",small_s), Paragraph("Apex (left lateral\ndecubitus)",small_s),
   Paragraph("Normal <40 yrs\nPathological >40 yrs",S("mix",fontName="Helvetica",fontSize=7.5,textColor=MUR_COL,leading=11))],
  [Paragraph("<b>S4</b>",S("s4h",fontName="Helvetica-Bold",fontSize=10,textColor=S4_COL)),
   Paragraph("(TEN-nes-see)",small_b), Paragraph("Atrial systole into\nstiff ventricle",small_s),
   Paragraph("Late diastole\n(presystolic)",small_s), Paragraph("Very short",small_s),
   Paragraph("Very low (<20 Hz)",small_s), Paragraph("Apex",small_s),
   Paragraph("Always pathological\n(rarely in athletes)",S("path",fontName="Helvetica-Bold",fontSize=7.5,textColor=S1_COL,leading=11))],
]
ov_cols=[1.0*cm,2.0*cm,3.0*cm,2.8*cm,1.6*cm,1.8*cm,2.4*cm,2.2*cm]
ov_st=TableStyle([
    ("BACKGROUND",(0,0),(-1,0),NAVY),
    ("ALIGN",(0,0),(-1,-1),"CENTER"),("VALIGN",(0,0),(-1,-1),"MIDDLE"),
    ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
    ("GRID",(0,0),(-1,-1),0.4,MID_GRY),
    ("BACKGROUND",(0,1),(-1,1),S1_LT),
    ("BACKGROUND",(0,2),(-1,2),S2_LT),
    ("BACKGROUND",(0,3),(-1,3),S3_LT),
    ("BACKGROUND",(0,4),(-1,4),S4_LT),
])
story.append(Table(ov_data,colWidths=ov_cols,style=ov_st))
story.append(Spacer(1,0.45*cm))

# ── S1 CARD ────────────────────────────────────────────────────────────────────
story.append(sound_card(
    "S1","LUBB","First Heart Sound",S1_COL,S1_LT,
    "Closure of Mitral (M1) and Tricuspid (T1) valves at start of ventricular systole",
    "Ventricles contract → back-surging blood hits AV valves → valves close and bulge into atria → "
    "chordae tendineae arrest the bulge → elastic recoil causes blood to bounce forward → "
    "vibrations in blood, walls, and taut valves radiate to chest wall",
    "Prolonged, loud, low-pitched 'LUBB'; Duration 0.14 sec; Frequency 25–45 Hz",
    "Coincides with R wave on ECG (onset of QRS)",
    "Loudest at Mitral area (5th ICS, midclavicular line / apex) and Left Lower Sternal Border (LLSB) for tricuspid",
    "NORMAL in all individuals",
    [
        "M1 (mitral closure) precedes T1 (tricuspid closure) slightly, but they are heard as one sound",
        "Loud S1: Mitral stenosis (MS), short PR interval, hyperdynamic states (fever, anemia, thyrotoxicosis)",
        "Soft S1: Mitral regurgitation, long PR interval (1st degree heart block), poor LV function",
        "Variable S1: Complete heart block (PR interval varies), AF (variable RR interval)",
        "Splitting of S1: RBBB, Ebstein anomaly (wide split), or heard as normal variant",
    ]
))

# ── S2 CARD ────────────────────────────────────────────────────────────────────
story.append(sound_card(
    "S2","DUBB","Second Heart Sound",S2_COL,S2_LT,
    "Closure of Aortic (A2) and Pulmonary (P2) valves at end of systole / onset of diastole",
    "Semilunar valves close → bulge backward toward ventricles → elastic recoil bounces blood back into "
    "arteries → reverberation between arterial walls and valve leaflets → vibrations transmitted along vessel walls",
    "Short, sharp, high-pitched 'DUBB'; Duration 0.11 sec; Frequency 50 Hz (higher pitch than S1 due to taut semilunar valves)",
    "Coincides with end of T wave on ECG",
    "Loudest at Aortic area (2nd ICS, right sternal border) and Pulmonic area (2nd ICS, left sternal border = LUSB)",
    "NORMAL in all individuals",
    [
        "A2 (aortic closure) normally precedes P2 (pulmonary closure)",
        "S2 is normally higher-pitched than S1 due to: (1) tautness of semilunar valves, (2) elastic arterial walls",
        "Loud A2: Systemic hypertension; Loud P2: Pulmonary hypertension (hallmark sign)",
        "Soft A2: Severe aortic stenosis (calcified, immobile valve); Soft P2: Pulmonary stenosis, TOF",
        "Dicrotic notch on the aortic pressure tracing marks the moment of aortic valve closure = S2",
    ]
))

# ── S2 SPLITTING SECTION ───────────────────────────────────────────────────────
story.append(Banner("SPLITTING OF THE SECOND HEART SOUND (S2)", MED_BLUE, h=0.8*cm, size=11))
story.append(Spacer(1,0.15*cm))

# S2 splitting image
s2_img_path = "/home/daytona/workspace/cardiac-cycle-pdf/s2_splitting.png"
if os.path.exists(s2_img_path):
    img_w = W - 5*cm
    img_h = img_w * 0.5
    s2_img = Image(s2_img_path, width=img_w, height=img_h)
    s2_img.hAlign = "CENTER"
    story.append(s2_img)
    story.append(Paragraph(
        "<i>Fig: Types of S2 splitting – Normal (physiologic), Wide/Persistent, Fixed, Paradoxical</i>",
        fig_s))
    story.append(Spacer(1,0.2*cm))

split_hdr=[Paragraph(f"<b>{h}</b>",lbl_s) for h in
           ["Type","Expiration","Inspiration","Common Causes"]]
split_data=[split_hdr,
  [Paragraph("Physiologic\n(Normal)",small_b),
   Paragraph("Single S2\n(A2 & P2 fused)",small_s),
   Paragraph("Split S2\n(A2 then P2)",small_s),
   Paragraph("Normal variant in children and young adults",small_s)],
  [Paragraph("Wide / Persistent\nSplitting",small_b),
   Paragraph("Split present\n(wide)",small_s),
   Paragraph("Widens further\non inspiration",small_s),
   Paragraph("RBBB (most common), Pulmonary stenosis, Pulmonary hypertension",small_s)],
  [Paragraph("Fixed Splitting",small_b),
   Paragraph("Split present",small_s),
   Paragraph("Split does NOT\nchange",small_s),
   Paragraph("Atrial Septal Defect (ASD) – classic sign",small_s)],
  [Paragraph("Paradoxical\nSplitting",small_b),
   Paragraph("Split present\n(P2 then A2)",small_s),
   Paragraph("Narrows / single\n(closes on inspiration)",small_s),
   Paragraph("LBBB, Severe AS, HOCM, RV pacing, WPW type B",small_s)],
  [Paragraph("Single S2",small_b),
   Paragraph("Single",small_s),
   Paragraph("Single",small_s),
   Paragraph("Pulm. hypertension, one semilunar valve (aortic/pulm. atresia), severe AS, TOF",small_s)],
]
split_st=TableStyle([
    ("BACKGROUND",(0,0),(-1,0),DARK_BLUE),
    ("ALIGN",(0,0),(-1,-1),"CENTER"),("VALIGN",(0,0),(-1,-1),"MIDDLE"),
    ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
    ("GRID",(0,0),(-1,-1),0.4,MID_GRY),
    ("ROWBACKGROUNDS",(0,1),(-1,-1),[S2_LT,colors.HexColor("#EBF5FB"),S2_LT,colors.HexColor("#EBF5FB"),S2_LT]),
])
story.append(Table(split_data,colWidths=[3.2*cm,3.2*cm,3.2*cm,6.2*cm],style=split_st))
story.append(Spacer(1,0.4*cm))

# ── S3 CARD ────────────────────────────────────────────────────────────────────
story.append(sound_card(
    "S3","ken-TUC-ky","Third Heart Sound  (Ventricular Gallop)",S3_COL,S3_LT,
    "Rapid rush of blood from atria into ventricles during early diastolic rapid filling phase",
    "Blood in-rush causes vibrations in the ventricular walls → low-frequency sound radiates to chest",
    "Short, soft, low-pitched rumble; Duration ~0.1 sec; Frequency very low (<50 Hz); often inaudible without Bell of stethoscope",
    "Early diastole – after S2, before S4 (during rapid filling phase, after T wave)",
    "Apex in left lateral decubitus position; use Bell of stethoscope",
    "Normal in children, young adults, athletes, pregnancy. PATHOLOGICAL in adults > 40 years",
    [
        "S3 gallop rhythm: 'ken-TUC-ky' cadence = S1-S2-S3",
        "Pathological S3 indicates: Dilated cardiomyopathy, Heart failure with reduced EF (HFrEF), "
        "Mitral regurgitation (MR), Ventricular septal defect (VSD) with large shunt",
        "Best heard with the Bell (low-frequency mode) of the stethoscope at the apex",
        "Patient positioned in left lateral decubitus to bring apex closer to chest wall",
        "Physiologic S3 disappears with age as ventricular compliance decreases",
        "Right-sided S3 (from RV failure): heard at LLSB, increases with inspiration",
    ]
))

# ── S4 CARD ────────────────────────────────────────────────────────────────────
story.append(sound_card(
    "S4","TEN-nes-see","Fourth Heart Sound  (Atrial Gallop / Presystolic Gallop)",S4_COL,S4_LT,
    "Forceful atrial contraction pushing blood into a non-compliant, stiff ventricle",
    "Atria contract against a ventricle with decreased compliance → vibrations from blood in-rush "
    "and ventricular wall → very low-frequency presystolic sound",
    "Soft, low-pitched; Frequency very low (~20 Hz or less, often below hearing threshold); "
    "usually recorded on phonocardiogram, not heard by stethoscope in normal adults",
    "Late diastole – just before S1 (presystolic, coincides with P wave / atrial contraction)",
    "Apex; use Bell of stethoscope",
    "ALWAYS pathological (rarely may be heard in trained athletes with slow heart rate)",
    [
        "S4 gallop rhythm: 'TEN-nes-see' cadence = S4-S1-S2",
        "S4 = stiff ventricle: Left ventricular hypertrophy (hypertension, aortic stenosis)",
        "S4 heard in: Acute MI (newly reduced compliance), HOCM, restrictive cardiomyopathy",
        "Right-sided S4 (from RV non-compliance): Pulmonary hypertension, pulmonary stenosis; "
        "heard at LLSB, increases with inspiration",
        "S4 cannot occur in atrial fibrillation (no atrial systole)",
        "Summation gallop: S3 + S4 merge at fast HR → single loud mid-diastolic sound",
    ]
))

# ── AUSCULTATION AREAS ────────────────────────────────────────────────────────
story.append(Banner("AUSCULTATION AREAS", DARK_BLUE, h=0.8*cm, size=11))
story.append(Spacer(1,0.2*cm))

aus_img_path = "/home/daytona/workspace/cardiac-cycle-pdf/auscultation_areas.png"
aus_tbl_data=[
    [Paragraph("<b>Area</b>",lbl_s), Paragraph("<b>Location</b>",lbl_s), Paragraph("<b>Sound Heard Best</b>",lbl_s), Paragraph("<b>Mnemonic</b>",lbl_s)],
    [Paragraph("Aortic Area",body_b), Paragraph("2nd ICS, Right Sternal Border",body_s),
     Paragraph("Aortic valve sounds, AS murmur, A2 of S2",body_s), Paragraph("A – All the way Right",body_s)],
    [Paragraph("Pulmonic Area",body_b), Paragraph("2nd ICS, Left Sternal Border (LUSB)",body_s),
     Paragraph("Pulmonary valve sounds, P2 of S2, PS murmur, S2 splitting",body_s), Paragraph("P – Pulmonic = Left top",body_s)],
    [Paragraph("Tricuspid Area\n(Erb's Point = 3rd ICS LSB)",body_b), Paragraph("4th–5th ICS, Left Sternal Border (LLSB)",body_s),
     Paragraph("Tricuspid valve sounds, TR murmur, VSD murmur",body_s), Paragraph("T – Third / Lower Left",body_s)],
    [Paragraph("Mitral Area\n(Apex)",body_b), Paragraph("5th ICS, Midclavicular Line (apex)",body_s),
     Paragraph("Mitral valve sounds, S1, S3, S4, MR/MS murmurs",body_s), Paragraph("M – Midclavicular / Apex",body_s)],
]
aus_tbl_st=TableStyle([
    ("BACKGROUND",(0,0),(-1,0),NAVY),
    ("ALIGN",(0,0),(-1,-1),"LEFT"),("VALIGN",(0,0),(-1,-1),"TOP"),
    ("TOPPADDING",(0,0),(-1,-1),5),("BOTTOMPADDING",(0,0),(-1,-1),5),
    ("LEFTPADDING",(0,0),(-1,-1),6),("RIGHTPADDING",(0,0),(-1,-1),6),
    ("GRID",(0,0),(-1,-1),0.4,MID_GRY),
    ("ROWBACKGROUNDS",(0,1),(-1,-1),[S1_LT,S2_LT,MUR_LT,S3_LT]),
])

if os.path.exists(aus_img_path):
    img_h2 = 9*cm
    img_w2 = img_h2 * 0.72
    aus_img = Image(aus_img_path, width=img_w2, height=img_h2)
    aus_img.hAlign="CENTER"
    layout=[[aus_img, Table(aus_tbl_data, colWidths=[2.8*cm,3.5*cm,4.4*cm,2.4*cm], style=aus_tbl_st)]]
    layout_st=TableStyle([("ALIGN",(0,0),(-1,-1),"CENTER"),("VALIGN",(0,0),(-1,-1),"MIDDLE"),
                          ("LEFTPADDING",(0,0),(-1,-1),2),("RIGHTPADDING",(0,0),(-1,-1),2)])
    story.append(Table(layout, colWidths=[6.7*cm, 9.1*cm], style=layout_st))
else:
    story.append(Table(aus_tbl_data, colWidths=[2.8*cm,3.5*cm,4.4*cm,2.4*cm], style=aus_tbl_st))

story.append(Paragraph("<i>Fig: Auscultation areas on the chest wall (Guyton & Hall, Fig 23.2). "
    "Note: Areas are NOT directly over the valves – sounds are transmitted along blood flow direction.</i>", fig_s))
story.append(Spacer(1,0.4*cm))

# ── MURMURS ────────────────────────────────────────────────────────────────────
story.append(Banner("HEART MURMURS – OVERVIEW", MUR_COL, h=0.8*cm, size=11))
story.append(Spacer(1,0.15*cm))

# Grading
story.append(Paragraph("<b>Grading of Heart Murmurs (Levine Scale I–VI):</b>", body_b))
story.append(Spacer(1,0.1*cm))

grade_data=[
    [Paragraph("<b>Grade</b>",lbl_s), Paragraph("<b>Description</b>",lbl_s), Paragraph("<b>Thrill?</b>",lbl_s)],
    [Paragraph("I",small_b), Paragraph("Barely audible; requires quiet room and concentration",small_s), Paragraph("No",small_s)],
    [Paragraph("II",small_b), Paragraph("Soft but easily heard; softer than heart sounds",small_s), Paragraph("No",small_s)],
    [Paragraph("III",small_b), Paragraph("Moderately loud; as loud as heart sounds",small_s), Paragraph("No",small_s)],
    [Paragraph("IV",small_b), Paragraph("Loud; louder than heart sounds",small_s), Paragraph("YES",S("thr",fontName="Helvetica-Bold",fontSize=8,textColor=S1_COL,leading=11))],
    [Paragraph("V",small_b), Paragraph("Very loud; audible with stethoscope barely on chest",small_s), Paragraph("YES",S("thr2",fontName="Helvetica-Bold",fontSize=8,textColor=S1_COL,leading=11))],
    [Paragraph("VI",small_b), Paragraph("Audible with stethoscope OFF the chest",small_s), Paragraph("YES",S("thr3",fontName="Helvetica-Bold",fontSize=8,textColor=S1_COL,leading=11))],
]
grade_st=TableStyle([
    ("BACKGROUND",(0,0),(-1,0),DARK_BLUE),
    ("ALIGN",(0,0),(-1,-1),"CENTER"),("VALIGN",(0,0),(-1,-1),"MIDDLE"),
    ("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
    ("GRID",(0,0),(-1,-1),0.4,MID_GRY),
    ("ROWBACKGROUNDS",(0,1),(-1,-1),[LIGHT_GRY,WHITE,LIGHT_GRY,MUR_LT,MUR_LT,MUR_LT]),
])
story.append(Table(grade_data, colWidths=[1.5*cm,11.5*cm,2.8*cm], style=grade_st))
story.append(Spacer(1,0.3*cm))

# Key murmurs table
story.append(Paragraph("<b>Key Valvular Murmurs:</b>", body_b))
story.append(Spacer(1,0.1*cm))

mur_hdr=[Paragraph(f"<b>{h}</b>",lbl_s) for h in
         ["Condition","Timing","Quality","Best Heard","Radiation","Key Features"]]
mur_data=[mur_hdr,
  [Paragraph("Aortic Stenosis (AS)",small_b), Paragraph("Systolic\n(ejection)",small_s),
   Paragraph("Harsh, crescendo-decrescendo",small_s), Paragraph("Aortic area (2nd ICS RSB)",small_s),
   Paragraph("Carotids\n(neck)",small_s),
   Paragraph("Pulsus parvus et tardus; Thrill at base; Soft/absent A2; Paradoxical S2 split (severe)",small_s)],
  [Paragraph("Aortic Regurgitation (AR)",small_b), Paragraph("Diastolic\n(early)",small_s),
   Paragraph("High-pitched, blowing, decrescendo",small_s), Paragraph("Left sternal border\n(leaning forward)",small_s),
   Paragraph("—",small_s),
   Paragraph("Water-hammer pulse; Wide pulse pressure; Austin Flint murmur (functional MS); S3",small_s)],
  [Paragraph("Mitral Regurgitation (MR)",small_b), Paragraph("Systolic\n(pansystolic/holosystolic)",small_s),
   Paragraph("High-pitched, blowing, uniform throughout systole",small_s), Paragraph("Apex",small_s),
   Paragraph("Axilla / back",small_s),
   Paragraph("Radiates to axilla; S3 common; Soft S1; Wide split S2; Hyperdynamic apex beat",small_s)],
  [Paragraph("Mitral Stenosis (MS)",small_b), Paragraph("Diastolic\n(mid-late)",small_s),
   Paragraph("Low-pitched, rumbling; presystolic accentuation",small_s), Paragraph("Apex (left lateral\ndecubitus, Bell)",small_s),
   Paragraph("—",small_s),
   Paragraph("Opening snap after S2; Loud S1; Loud P2 (pulm. HTN); AF common",small_s)],
  [Paragraph("Mitral Valve Prolapse (MVP)",small_b), Paragraph("Mid-systolic\nclick + late systolic murmur",small_s),
   Paragraph("Click: sharp, high-pitched; Murmur: blowing",small_s), Paragraph("Apex",small_s),
   Paragraph("Axilla",small_s),
   Paragraph("Click moves earlier with standing; moves later with squatting; young females",small_s)],
  [Paragraph("Pulmonary Stenosis (PS)",small_b), Paragraph("Systolic\n(ejection)",small_s),
   Paragraph("Harsh, ejection type",small_s), Paragraph("Pulmonic area (LUSB)",small_s),
   Paragraph("Left shoulder",small_s),
   Paragraph("Ejection click; Wide split S2; Soft P2; RV heave",small_s)],
  [Paragraph("VSD",small_b), Paragraph("Pansystolic",small_s),
   Paragraph("Harsh, loud",small_s), Paragraph("LLSB",small_s),
   Paragraph("Throughout\nprecordium",small_s),
   Paragraph("Grade 4–5/6; Thrill at LLSB; Smaller VSD = louder murmur ('maladie de Roger')",small_s)],
  [Paragraph("Tricuspid Regurgitation (TR)",small_b), Paragraph("Pansystolic",small_s),
   Paragraph("Blowing",small_s), Paragraph("LLSB",small_s),
   Paragraph("—",small_s),
   Paragraph("Increases with inspiration (Carvallo's sign); Pulsatile liver; Giant v-wave in JVP",small_s)],
]
mur_st=TableStyle([
    ("BACKGROUND",(0,0),(-1,0),NAVY),
    ("ALIGN",(0,0),(-1,-1),"LEFT"),("VALIGN",(0,0),(-1,-1),"TOP"),
    ("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
    ("LEFTPADDING",(0,0),(-1,-1),5),("RIGHTPADDING",(0,0),(-1,-1),5),
    ("GRID",(0,0),(-1,-1),0.4,MID_GRY),
    ("ROWBACKGROUNDS",(0,1),(-1,-1),[MUR_LT,LIGHT_GRY,MUR_LT,LIGHT_GRY,MUR_LT,LIGHT_GRY,MUR_LT,LIGHT_GRY]),
])
story.append(Table(mur_data, colWidths=[2.7*cm,1.8*cm,2.8*cm,2.5*cm,1.8*cm,4.2*cm], style=mur_st))
story.append(Spacer(1,0.35*cm))

# ── EXTRA SOUNDS ──────────────────────────────────────────────────────────────
story.append(Banner("ADDITIONAL HEART SOUNDS (CLICKS, SNAPS, RUBS)", CLIN_COL, h=0.75*cm, size=10))
story.append(Spacer(1,0.15*cm))

extra_data=[
    [Paragraph("<b>Sound</b>",lbl_s), Paragraph("<b>Timing</b>",lbl_s), Paragraph("<b>Cause</b>",lbl_s), Paragraph("<b>Clinical Significance</b>",lbl_s)],
    [Paragraph("Ejection Click",body_b), Paragraph("Early systole\n(just after S1)",body_s),
     Paragraph("Sudden tensing of aortic/pulmonary valve domes",body_s),
     Paragraph("Bicuspid aortic valve; AS (pliable valve); PS; Dilated aorta/PA",body_s)],
    [Paragraph("Midsystolic Click",body_b), Paragraph("Mid-systole",body_s),
     Paragraph("Sudden prolapse of mitral leaflets into LA",body_s),
     Paragraph("Mitral Valve Prolapse (MVP) – classic; young women",body_s)],
    [Paragraph("Opening Snap (OS)",body_b), Paragraph("Early diastole\n(after S2)",body_s),
     Paragraph("Forceful opening of stenotic mitral/tricuspid valve",body_s),
     Paragraph("Mitral stenosis (most common); shorter S2–OS interval = more severe MS",body_s)],
    [Paragraph("Pericardial Rub",body_b), Paragraph("Systolic +\nDiastolic",body_s),
     Paragraph("Friction between parietal and visceral pericardium",body_s),
     Paragraph("Pericarditis; 'scratching', 'to and fro' sound; louder on leaning forward / inspiration",body_s)],
    [Paragraph("Summation Gallop",body_b), Paragraph("Mid-diastole",body_s),
     Paragraph("S3 + S4 merge at fast heart rate (tachycardia)",body_s),
     Paragraph("Severe heart failure; single loud extra sound at fast HR",body_s)],
    [Paragraph("Pericardial Knock",body_b), Paragraph("Early diastole",body_s),
     Paragraph("Sudden deceleration of ventricular filling by thickened pericardium",body_s),
     Paragraph("Constrictive pericarditis; earlier and higher-pitched than S3",body_s)],
]
extra_st=TableStyle([
    ("BACKGROUND",(0,0),(-1,0),CLIN_COL),
    ("ALIGN",(0,0),(-1,-1),"LEFT"),("VALIGN",(0,0),(-1,-1),"TOP"),
    ("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
    ("LEFTPADDING",(0,0),(-1,-1),6),("RIGHTPADDING",(0,0),(-1,-1),6),
    ("GRID",(0,0),(-1,-1),0.4,MID_GRY),
    ("ROWBACKGROUNDS",(0,1),(-1,-1),[CLIN_LT,WHITE,CLIN_LT,WHITE,CLIN_LT,WHITE]),
])
story.append(Table(extra_data, colWidths=[2.8*cm,2.0*cm,4.0*cm,7.0*cm], style=extra_st))
story.append(Spacer(1,0.35*cm))

# ── CLINICAL CORRELATIONS ─────────────────────────────────────────────────────
story.append(Banner("CLINICAL CORRELATION & MEMORY AIDS", NAVY, h=0.75*cm, size=10))
story.append(Spacer(1,0.1*cm))

mem_rows=[
    [Paragraph("<b>Memory Aid / Rule</b>",lbl_s), Paragraph("<b>Detail</b>",lbl_s)],
    [Paragraph("ALL systolic murmurs are pathological (except innocent flow murmurs)", body_b),
     Paragraph("Innocent murmurs: present in >80% of children; blowing, soft (≤2/6), no radiation, no thrill; normal ECG/CXR", body_s)],
    [Paragraph("Manoeuvres that ↑ preload (lying down, leg raise, squatting): murmurs of AS, MR louder", body_b),
     Paragraph("Exception: HOCM and MVP murmurs DECREASE with manoeuvres that increase preload (ventricular cavity enlarges, outflow obstruction decreases)", body_s)],
    [Paragraph("Standing / Valsalva (↓ preload): HOCM and MVP murmurs become LOUDER (earlier onset)", body_b),
     Paragraph("HOCM: dynamic obstruction worsens when cavity is smaller; MVP: click moves earlier", body_s)],
    [Paragraph("Inspiration increases RIGHT-sided murmurs and sounds", body_b),
     Paragraph("Carvallo's sign (TR louder on inspiration); Right-sided S3/S4 increase with inspiration", body_s)],
    [Paragraph("S3 + Tachycardia = Gallop rhythm → always investigate for heart failure", body_b),
     Paragraph("S3 is the most specific sign of elevated LV filling pressure / dilated cardiomyopathy", body_s)],
    [Paragraph("S4 CANNOT occur in AF", body_b),
     Paragraph("No atrial contraction in AF → no late diastolic ventricular filling → no S4", body_s)],
    [Paragraph("Phonocardiogram mnemonic for timing", body_b),
     Paragraph("S1–[systole]–S2–[diastole: S3–S4]–S1. Diastole is LONGER than systole at normal HR.", body_s)],
]
mem_st=TableStyle([
    ("BACKGROUND",(0,0),(-1,0),NAVY),
    ("ALIGN",(0,0),(-1,-1),"LEFT"),("VALIGN",(0,0),(-1,-1),"TOP"),
    ("TOPPADDING",(0,0),(-1,-1),4),("BOTTOMPADDING",(0,0),(-1,-1),4),
    ("LEFTPADDING",(0,0),(-1,-1),8),("RIGHTPADDING",(0,0),(-1,-1),8),
    ("GRID",(0,0),(-1,-1),0.4,MID_GRY),
    ("ROWBACKGROUNDS",(0,1),(-1,-1),[LIGHT_GRY,WHITE,LIGHT_GRY,WHITE,LIGHT_GRY,WHITE,LIGHT_GRY]),
    ("LINEAFTER",(0,0),(0,-1),0.5,MID_GRY),
])
story.append(Table(mem_rows, colWidths=[7.2*cm,8.6*cm], style=mem_st))
story.append(Spacer(1,0.3*cm))

# ── FOOTER ────────────────────────────────────────────────────────────────────
story.append(HRFlowable(width="100%",thickness=1,color=MID_GRY))
story.append(Spacer(1,0.1*cm))
story.append(Paragraph(
    "Sources: Guyton & Hall Textbook of Medical Physiology (Figs 23.1–23.3)  •  "
    "Harriet Lane Handbook 23e (Box 7.1, Table 7.1)  •  Costanzo Physiology 7e  •  Harrison's Principles 22e",
    S("Ft", fontName="Helvetica-Oblique", fontSize=7, textColor=colors.HexColor("#888"),
      alignment=TA_CENTER, leading=10)))

doc.build(story)
print(f"PDF created: {OUTPUT}")
Running Command

cd /home/daytona/workspace/cardiac-cycle-pdf && python3 generate_heart_sounds_pd…

Running Command

ls -lh /home/daytona/workspace/cardiac-cycle-pdf/Heart_Sounds_Visual_Summary.pdf

Your Heart Sounds Visual Summary PDF is ready (489 KB):

Heart Sounds Visual Summary

PDF Document · PDF


What's inside:
SectionContent
Overview TableS1–S4 at a glance: word, valve event, timing, duration, frequency, best heard, normal vs pathological
S1 Card (LUBB)Mechanism of AV valve closure, loud/soft/variable S1 causes, splitting of S1
S2 Card (DUBB)Semilunar valve closure, A2 vs P2, dicrotic notch, intensity changes
S2 Splitting TablePhysiologic, Wide/Persistent, Fixed (ASD), Paradoxical - causes for each with diagram
S3 CardVentricular gallop, causes (HFrEF, MR, VSD), "ken-TUC-ky" rhythm, Bell of stethoscope
S4 CardAtrial gallop, stiff ventricle causes (LVH, HCM, acute MI), cannot occur in AF
Auscultation AreasDiagram + table: Aortic, Pulmonic, Tricuspid, Mitral areas with ICS positions
Murmur GradingLevine scale I-VI with thrill presence
Key Murmurs TableAS, AR, MR, MS, MVP, PS, VSD, TR - timing, quality, radiation, key features
Extra SoundsEjection clicks, Opening snap (MS), Pericardial rub, Summation gallop, Pericardial knock
Clinical Memory AidsManoeuvres (HOCM/MVP), inspiration rule, S4 in AF, gallop rhythm rules
This is a shared conversation. Sign in to Orris to start your own chat.