How to read brain CT and MRI and differentiate between bleed and infarct along with the timeline of events, acute, chronic, subacute

Reading File
Now let me search the medical library for brain CT/MRI reading, hemorrhage, and infarct content in parallel.
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Finding Sources
Excellent - found great sources. Now let me read these key sections in parallel:
Reading File
Reading File
Reading File
Excellent data. Now let me read the MRI hemorrhage timeline and CT hemorrhage content:
Reading File
Reading File
Finding Sources
Found the exact hemorrhage MRI timeline table. Let me read it:
Reading File
Reading File
I now have comprehensive data. Let me also fetch a few key images for this response:
Loading Image
Loading Image
Loading Image
I now have all the material needed to write a thorough, well-illustrated answer. Here is the complete guide:

Reading Brain CT and MRI: Bleed vs. Infarct with Temporal Staging


Part 1 - How to Approach a Brain CT

The Systematic "ABCDE" Window Approach

Always view brain CT in at least two window settings:
  • Brain window (WW ~80, WL ~35): evaluates parenchyma, gray-white differentiation, blood
  • Bone window (WW ~2000, WL ~300): evaluates skull fractures, calvarium
For stroke, a "stroke window" (WW ~35, WL ~35) improves sensitivity for early ischemic changes by widening the contrast between normal and ischemic parenchyma.

CT Density (Hounsfield Units - HU)

StructureHU
Air-1000
Fat-100
Water/CSF0
Normal gray matter~35-40
Normal white matter~25-30
Acute blood~50-90
Bone~700-1000
The golden rule: blood is white (hyperdense), infarct is dark (hypodense) on CT.

Part 2 - CT Appearance: Hemorrhage vs. Infarct

Hemorrhage on CT

Acute hemorrhage is hyperdense (bright white) relative to brain. This is due to clot retraction and increased protein concentration of coagulated blood.
"Fresh intracranial hemorrhage coagulates nearly immediately and therefore shows up on CT scans as hyperdense areas relative to brain. Fresh hemorrhage may appear about as white as bone."
  • Neuroanatomy through Clinical Cases, 3rd Ed.
"The area of ischemic injury on brain CT scan appears as a relative hypodensity, in contrast to brain hemorrhage, which appears hyperdense compared with the surrounding brain."
  • Goldman-Cecil Medicine
Timeline of hemorrhage on CT:
PhaseTimeCT Appearance
Hyperacute0-6 hrsHyperdense (white, ~60-80 HU)
Acute1-3 daysHyperdense, may develop hypodense ring (edema)
Subacute3-14 daysBecomes isodense (similar to brain, ~35 HU) - can be missed!
Chronic>14 daysHypodense (darker than brain), may calcify; subdurals form crescent-shaped dark collections
Important: In severely anemic patients, acute blood can appear isodense or even hypodense - always correlate clinically.

Infarct on CT

Ischemic infarction appears hypodense (dark) and is often subtle in the first 6 hours.
Early CT signs of ischemic stroke (first 6 hours):
  1. Loss of gray-white differentiation - the insular cortex and basal ganglia lose their normal contrast with white matter
  2. "Insular ribbon sign" - loss of the normally sharp insular cortex outline (sensitive for MCA territory ischemia)
  3. Sulcal effacement - gyri swell and compress the sulci
  4. Hyperdense vessel sign - a dense MCA or basilar artery indicates acute thrombosis
  5. "One-third rule" - if more than 1/3 of the MCA territory is already hypodense, thrombolysis is contraindicated (increased hemorrhage risk)
"Brain swelling on CT without accompanying low density does not always progress to infarction. Such cases may be due to abnormal perfusion, but a compensatory increase in CBV rather than a reduction."
  • Grainger & Allison's Diagnostic Radiology
CT Timeline of Infarction:
PhaseTimeCT Appearance
Hyperacute0-6 hrsNormal OR very subtle hypodensity, sulcal effacement
Acute6-24 hrsDefinite hypodensity in affected territory, gray-white blurring
Subacute1-7 daysProgressively more hypodense, mass effect peaks at 3-5 days
Fogging effect~1-3 wksTemporarily appears isodense (macrophage infiltration, petechial hemorrhage) - can be falsely normal
Chronic>3 wksWell-defined hypodense area, ex-vacuo ventricular dilation (encephalomalacia)

Part 3 - MRI Approach and Sequences

Each sequence has a specific role. From Adams and Victor's Principles of Neurology:
SequenceBest ForBrightDark
T1Anatomy, subacute bloodSubacute blood, fat, methemoglobinCSF, edema, deoxyhemoglobin
T2Edema, infarcts, inflammationCSF, edema, late subacute bloodAcute blood (deoxyhemoglobin), calcium
FLAIRPeriventricular lesions, SAH, subacute infarctEdema, gliosis, SAHCSF (suppressed)
DWIAcute infarction (gold standard)Acute ischemia (minutes to ~7 days)Blood after several days
ADC mapConfirm true restriction vs. T2 shine-through-True infarct (dark = restricted diffusion)
SWI/GREMicrohemorrhages, venous blood-All blood products, calcification (blooms dark)

The DWI-ADC Rule

This is the most important concept in acute stroke MRI:
  • Acute infarct: DWI bright + ADC dark = true restricted diffusion = infarction
  • Chronic lesion (gliosis): DWI dark + ADC bright = T2 shine-through without restriction
  • T2 shine-through (subacute): DWI bright + ADC bright/normal = not infarction, just T2 effect
"DWI has a preeminent role in acute stroke imaging due to its extremely high sensitivity and specificity, with parenchymal hyperintensity as early as 5 minutes following the onset of infarction."
  • Grainger & Allison's Diagnostic Radiology
Acute left MCA infarct - CT (A) shows low attenuation in the left frontal operculum; T2 and FLAIR (B, C) show hyperintensity; DWI (D) and ADC (E) confirm the full extent
Acute left MCA infarct (7 hours after onset). CT (A) shows subtle hypodensity, T2/FLAIR (B, C) show parenchymal signal change, DWI (D) shows full hyperintensity, ADC (E) confirms restricted diffusion - Grainger & Allison's Diagnostic Radiology
Acute MCA infarct on DWI (A) and ADC map (B)
Acute left MCA infarct: (A) DWI shows bright hyperintensity with mass effect; (B) ADC map confirms corresponding hypointensity (restricted diffusion) - Bradley and Daroff's Neurology in Clinical Practice

Part 4 - MRI Timeline of Intracerebral Hemorrhage

The appearance of blood on MRI depends entirely on the oxidation state of hemoglobin and its location (intracellular vs. extracellular). This is from Neuroanatomy through Clinical Cases, 3rd Ed.:
StageTimeframeHemoglobin FormT1T2Mechanism
Hyperacute0-6 hoursIntracellular oxyhemoglobinGray (isointense)Light gray (slightly bright)Oxyhemoglobin has no unpaired electrons - no T1 shortening
Acute1-3 daysIntracellular deoxyhemoglobinGray (isointense)Dark gray (hypointense)Deoxyhemoglobin has unpaired electrons - T2 shortening; intact RBCs prevent T1 effect
Early subacute3-7 daysIntracellular methemoglobinWhite (bright)Dark gray (hypointense)Methemoglobin shortens T1 strongly; still intracellular so T2 remains dark
Late subacute7->30 daysExtracellular methemoglobinWhite (bright)White (bright)RBC lysis releases methemoglobin extracellularly - now both T1 and T2 bright
Chronic>14 daysHemosiderin (outer rim)Dark grayBlack (markedly hypointense)Hemosiderin (ferritin/hemosiderin in macrophages) causes susceptibility effect - "blooms" dark
"The actual sequence of changes can be fairly complicated and variable, depending on brain microenvironment and individual scanners."
Memory aid: "Be A Bright White Rim"
  • Be = acute (Black on T2 at 1-3 days, deoxy)
  • A = acute-subacute transition (starts to brighten T1)
  • Bright White = subacute (methemoglobin, bright on both)
  • Rim = chronic (dark hemosiderin rim on T2/SWI)

Part 5 - MRI Timeline of Ischemic Infarction

From Bradley and Daroff's Neurology in Clinical Practice:
PhaseTimeframeDWIADCT2/FLAIRT1
Hyperacute0-6 hrsBright (from ~5 min)DarkSubtle/normalNormal
Acute6 hrs - 5 daysBrightDarkClearly hyperintense + gyral swellingHypointense
Subacute5-14 daysFading bright (T2 shine-through takes over)Normalizing (pseudonormalization ~days 7-10)Bright, mass effect peaks then resolvesHypointense
Chronic>3 weeksDark (gliosis, unrestricted)BrightBright (gliosis)Hypointense; may see gyral enhancement with contrast
Pseudonormalization trap: Around days 7-14, ADC may normalize due to vasogenic edema developing, making the ADC map look normal even though infarction has occurred. Always use clinical context + FLAIR/T2.
"Initially, the hyperintense signal on DWI is caused by decreased water diffusivity due to swelling of the ischemic nerve cells (for the first 5-7 days); then it increasingly results from the abnormal T2 properties of the infarcted tissue (T2 shine-through). For this reason, a reliable estimation of the age of the ischemic lesion is not possible by looking at DWI images alone."
  • Bradley and Daroff's Neurology in Clinical Practice

Part 6 - Key Differentiating Features at a Glance

CT: Bleed vs. Infarct

FeatureHemorrhageInfarct
DensityHyperdense (white) acutelyHypodense (dark)
Timing of visibilityImmediately visibleSubtle <6 hrs; clear >6 hrs
ShapeRound/oval, well-definedFollows vascular territory (wedge-shaped or territorial)
LocationAny (hypertensive: basal ganglia, thalamus, cerebellum, pons)Cortex + subcortex in artery territory
Mass effectYes - surrounding edemaPeaks at 3-5 days with large infarcts
Hemorrhagic transformationN/ACan occur (petechial or confluent) in large infarcts
Contrast enhancementPeripheral ring (subacute)Gyral enhancement after ~1-2 weeks

MRI: Quick Reference Summary

PhaseInfarct DWIInfarct ADCHemorrhage T1Hemorrhage T2/SWI
Hyperacute (0-6h)BrightDarkGrayLight gray / Bright
Acute (1-3d)BrightDarkGrayDark (deoxy)
Early subacute (3-7d)BrightDarkBrightDark (intracel methHb)
Late subacute (7d-4wks)FadingPseudonormBrightBright (extracel methHb)
Chronic (>4wks)DarkBrightDarkBlack rim (hemosiderin)

Part 7 - Practical Workflow in Emergency Setting

  1. First: Non-contrast CT - rule out hemorrhage, mass lesion, or hemorrhagic transformation before giving thrombolytics
  2. If CT negative but stroke suspected within 6 hours: MRI with DWI/ADC is the gold standard - bright DWI + dark ADC = confirmed ischemic infarct
  3. CTA (CT Angiography): Aortic arch to intracranial vessels - look for large vessel occlusion (LVO), hyperdense vessel sign, and collateral flow
  4. CT perfusion / MR perfusion: Identifies the penumbra (salvageable ischemic tissue = MTT prolonged but CBV preserved) vs. core infarct (CBV reduced)
  5. SWI/GRE: Detects microhemorrhages (amyloid angiopathy), thrombosed veins, and acute thrombus in vessels with "blooming" effect
"DWI is the most sensitive imaging sequence for the detection of acute ischemic infarction and can show infarcted tissue within minutes of stroke onset, considerably earlier than CT and other MRI sequences."
  • Adams and Victor's Principles of Neurology, 12th Ed.

Quick Memory Tables

Hemorrhage on CT (Rule of 2 weeks)

  • <7 days: White (hyperdense)
  • ~7-14 days: Isodense (can be invisible - look for mass effect)
  • >14 days: Dark (hypodense), may calcify or become cystic

FLAIR in Stroke

  • Infarct <4.5 hours: DWI bright but FLAIR normal or subtle = safe thrombolysis window ("DWI-FLAIR mismatch" for wake-up strokes)
  • Infarct >4.5 hours: Both DWI and FLAIR bright
  • SAH: Hyperintense CSF in sulci on FLAIR (FLAIR more sensitive than CT at 24-48 hrs)

Sources: Adams and Victor's Principles of Neurology 12th Ed. | Bradley and Daroff's Neurology in Clinical Practice | Grainger & Allison's Diagnostic Radiology | Neuroanatomy through Clinical Cases 3rd Ed. | Goldman-Cecil Medicine

I need images for everything so that it’s easy to Understand

Your credits are exhausted. You can purchase a top-up or subscribe to a plan to continue using Orris. If you're already subscribed, your daily credits will reset tomorrow.

Create a PDF reference guide with timeline diagrams and labeled imaging examples

Your credits are exhausted. You can purchase a top-up or subscribe to a plan to continue using Orris. If you're already subscribed, your daily credits will reset tomorrow.

Create a PDF reference guide with timeline diagrams and labeled imaging examples

Reading File
Running Command

mkdir -p /home/daytona/workspace/brain-imaging-guide && ls /home/daytona/workspace/brain-imaging-guide/

Writing File

~/brain-imaging-guide/build_pdf.py

#!/usr/bin/env python3
"""
Brain CT & MRI Reference Guide PDF Generator
Uses ReportLab to create a comprehensive, visually rich reference guide.
"""

from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.units import cm, mm
from reportlab.platypus import (
    SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
    HRFlowable, KeepTogether, PageBreak
)
from reportlab.platypus.flowables import Flowable
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.graphics.shapes import Drawing, Rect, String, Line, Circle, Polygon, Path
from reportlab.graphics.shapes import PolyLine, Group
from reportlab.graphics import renderPDF
from reportlab.platypus import Image as RLImage
import io
import math

# ── Color Palette ─────────────────────────────────────────────────────────────
C_NAVY      = colors.HexColor('#0D1B2A')
C_BLUE      = colors.HexColor('#1B4F72')
C_LIGHTBLUE = colors.HexColor('#2E86AB')
C_TEAL      = colors.HexColor('#17A589')
C_RED       = colors.HexColor('#C0392B')
C_ORANGE    = colors.HexColor('#E67E22')
C_YELLOW    = colors.HexColor('#F1C40F')
C_GREEN     = colors.HexColor('#1E8449')
C_PURPLE    = colors.HexColor('#7D3C98')
C_DARK_GRAY = colors.HexColor('#2C3E50')
C_MID_GRAY  = colors.HexColor('#7F8C8D')
C_LIGHT_GRAY= colors.HexColor('#ECF0F1')
C_WHITE     = colors.white
C_BLACK     = colors.black
C_CREAM     = colors.HexColor('#FDFEFE')

# CT density shades
C_CT_BLOOD  = colors.HexColor('#F0F0F0')   # hyperdense (bright white)
C_CT_BRAIN  = colors.HexColor('#AAAAAA')   # normal brain gray
C_CT_INFARCT= colors.HexColor('#606060')   # hypodense (dark)
C_CT_CSF    = colors.HexColor('#222222')   # very dark (CSF/old infarct)

PAGE_W, PAGE_H = A4

# ── Helper Flowable: SectionHeader ───────────────────────────────────────────
class SectionHeader(Flowable):
    def __init__(self, title, subtitle="", bg=C_BLUE, w=None):
        Flowable.__init__(self)
        self.title    = title
        self.subtitle = subtitle
        self.bg       = bg
        self.w        = w or (PAGE_W - 4*cm)
        self.h        = 1.4*cm if subtitle else 1.1*cm

    def draw(self):
        c = self.canv
        c.setFillColor(self.bg)
        c.roundRect(0, 0, self.w, self.h, 5, fill=1, stroke=0)
        c.setFillColor(C_WHITE)
        c.setFont("Helvetica-Bold", 13)
        c.drawString(0.4*cm, self.h - 0.55*cm, self.title)
        if self.subtitle:
            c.setFont("Helvetica", 9)
            c.setFillColor(colors.HexColor('#D6EAF8'))
            c.drawString(0.4*cm, 0.2*cm, self.subtitle)

    def wrap(self, aw, ah):
        return self.w, self.h

# ── Helper Flowable: LegendBox ────────────────────────────────────────────────
class LegendBox(Flowable):
    """Small colored square + label pair"""
    def __init__(self, items, cols=3, w=None):
        Flowable.__init__(self)
        self.items = items   # list of (color, label)
        self.cols  = cols
        self.w     = w or (PAGE_W - 4*cm)
        rows = math.ceil(len(items)/cols)
        self.h = rows * 0.55*cm + 0.2*cm

    def draw(self):
        c   = self.canv
        col_w = self.w / self.cols
        for i, (clr, lbl) in enumerate(self.items):
            col = i % self.cols
            row = i // self.cols
            x   = col * col_w
            y   = self.h - (row+1)*0.55*cm
            c.setFillColor(clr)
            c.setStrokeColor(C_DARK_GRAY)
            c.rect(x, y+0.05*cm, 0.45*cm, 0.38*cm, fill=1, stroke=1)
            c.setFillColor(C_DARK_GRAY)
            c.setFont("Helvetica", 8)
            c.drawString(x + 0.55*cm, y+0.1*cm, lbl)

    def wrap(self, aw, ah):
        return self.w, self.h

# ── Flowable: CT Brain Schematic ─────────────────────────────────────────────
class CTBrainSchematic(Flowable):
    """
    Side-by-side CT schematic: Left = hemorrhage (bright), Right = infarct (dark)
    """
    def __init__(self, w=None, h=None):
        Flowable.__init__(self)
        self.w = w or (PAGE_W - 4*cm)
        self.h = h or 6.5*cm

    def draw(self):
        c   = self.canv
        w, h = self.w, self.h
        mid = w / 2
        r   = min(h*0.42, mid*0.7)

        # ---- LEFT: HEMORRHAGE ----
        cx1 = mid * 0.5
        cy  = h * 0.52

        # Skull ring
        c.setFillColor(colors.HexColor('#E8E8E8'))
        c.setStrokeColor(C_BLACK)
        c.setLineWidth(0.5)
        c.circle(cx1, cy, r+0.18*cm, fill=1, stroke=1)

        # Brain background
        c.setFillColor(C_CT_BRAIN)
        c.circle(cx1, cy, r, fill=1, stroke=0)

        # Ventricles (dark)
        c.setFillColor(C_CT_CSF)
        c.ellipse(cx1-0.25*cm, cy-0.15*cm, cx1+0.25*cm, cy+0.3*cm, fill=1, stroke=0)
        c.ellipse(cx1-0.18*cm, cy-0.4*cm, cx1-0.02*cm, cy-0.15*cm, fill=1, stroke=0)
        c.ellipse(cx1+0.02*cm, cy-0.4*cm, cx1+0.18*cm, cy-0.15*cm, fill=1, stroke=0)

        # Hemorrhage spot - bright white
        c.setFillColor(C_CT_BLOOD)
        c.setStrokeColor(colors.HexColor('#CCCCCC'))
        c.setLineWidth(0.3)
        c.circle(cx1+0.55*cm, cy+0.25*cm, 0.45*cm, fill=1, stroke=1)

        # Surrounding edema halo
        c.setFillColor(colors.HexColor('#888888'))
        # Draw as a ring manually
        c.saveState()
        c.setFillColor(colors.HexColor('#888888'))
        c.circle(cx1+0.55*cm, cy+0.25*cm, 0.62*cm, fill=1, stroke=0)
        c.setFillColor(C_CT_BLOOD)
        c.circle(cx1+0.55*cm, cy+0.25*cm, 0.45*cm, fill=1, stroke=0)
        c.restoreState()

        # Arrow + label
        c.setStrokeColor(C_RED)
        c.setLineWidth(1.2)
        c.line(cx1+0.85*cm, cy+0.55*cm, cx1+1.5*cm, cy+1.1*cm)
        c.setFillColor(C_RED)
        c.setFont("Helvetica-Bold", 7.5)
        c.drawString(cx1+1.55*cm, cy+1.08*cm, "HYPERDENSE")
        c.setFont("Helvetica", 7)
        c.drawString(cx1+1.55*cm, cy+0.88*cm, "(Acute Blood ~60-80 HU)")

        # Title
        c.setFillColor(C_RED)
        c.setFont("Helvetica-Bold", 10)
        c.drawCentredString(cx1, 0.3*cm, "HEMORRHAGE on CT")

        # ---- DIVIDER ----
        c.setStrokeColor(C_MID_GRAY)
        c.setLineWidth(0.8)
        c.setDash(4, 3)
        c.line(mid, 0.1*cm, mid, h-0.1*cm)
        c.setDash()

        # ---- RIGHT: INFARCT ----
        cx2 = mid + mid * 0.5
        c.setFillColor(colors.HexColor('#E8E8E8'))
        c.setStrokeColor(C_BLACK)
        c.setLineWidth(0.5)
        c.circle(cx2, cy, r+0.18*cm, fill=1, stroke=1)

        c.setFillColor(C_CT_BRAIN)
        c.circle(cx2, cy, r, fill=1, stroke=0)

        # Ventricles
        c.setFillColor(C_CT_CSF)
        c.ellipse(cx2-0.25*cm, cy-0.15*cm, cx2+0.25*cm, cy+0.3*cm, fill=1, stroke=0)
        c.ellipse(cx2-0.18*cm, cy-0.4*cm, cx2-0.02*cm, cy-0.15*cm, fill=1, stroke=0)
        c.ellipse(cx2+0.02*cm, cy-0.4*cm, cx2+0.18*cm, cy-0.15*cm, fill=1, stroke=0)

        # Infarct - wedge shaped, dark
        c.setFillColor(C_CT_INFARCT)
        # Draw wedge (MCA territory) using a path
        c.saveState()
        p = c.beginPath()
        cx_off = cx2 - 0.3*cm
        p.moveTo(cx_off, cy+0.1*cm)
        p.lineTo(cx2 - r*0.85, cy + r*0.45)
        p.lineTo(cx2 - r*0.55, cy + r*0.78)
        p.lineTo(cx2 - r*0.1, cy + r*0.75)
        p.lineTo(cx2 + r*0.1, cy + r*0.55)
        p.lineTo(cx_off, cy+0.1*cm)
        p.close()
        c.setFillColor(C_CT_INFARCT)
        c.drawPath(p, fill=1, stroke=0)
        c.restoreState()

        # Sulcal effacement hint (lines closer together)
        c.setStrokeColor(colors.HexColor('#777777'))
        c.setLineWidth(0.4)
        for i in range(3):
            angle = 110 + i*15
            rad = math.radians(angle)
            x1 = cx2 + (r*0.55)*math.cos(rad)
            y1 = cy + (r*0.55)*math.sin(rad)
            x2 = cx2 + (r*0.72)*math.cos(rad)
            y2 = cy + (r*0.72)*math.sin(rad)
            c.line(x1, y1, x2, y2)

        # Arrow + label
        c.setStrokeColor(C_BLUE)
        c.setLineWidth(1.2)
        c.line(cx2 - r*0.5, cy + r*0.65, cx2 - r*0.9, cy + r*1.05)
        c.setFillColor(C_BLUE)
        c.setFont("Helvetica-Bold", 7.5)
        # left-align label
        lx = cx2 - r*0.9 - 1.8*cm
        c.drawString(lx, cy + r*1.05, "HYPODENSE")
        c.setFont("Helvetica", 7)
        c.drawString(lx, cy + r*0.85, "(Ischemic territory, <30 HU)")

        # Title
        c.setFillColor(C_BLUE)
        c.setFont("Helvetica-Bold", 10)
        c.drawCentredString(cx2, 0.3*cm, "INFARCT on CT")

        # Top labels
        c.setFillColor(C_DARK_GRAY)
        c.setFont("Helvetica-BoldOblique", 8)
        c.drawCentredString(w/2, h-0.2*cm, "NON-CONTRAST CT — Axial Brain Window   |   Left = Hemorrhage   |   Right = Infarct")

    def wrap(self, aw, ah):
        return self.w, self.h

# ── Flowable: CT Timeline Diagram ────────────────────────────────────────────
class CTTimelineDiagram(Flowable):
    """Timeline bar showing CT changes for hemorrhage and infarct"""
    def __init__(self, w=None, h=None):
        Flowable.__init__(self)
        self.w = w or (PAGE_W - 4*cm)
        self.h = h or 5.8*cm

    def draw(self):
        c   = self.canv
        w, h = self.w, self.h

        phases = [
            ("0-6 hrs\nHyperacute", 0.18),
            ("6-24 hrs\nAcute",     0.18),
            ("1-7 days\nSubacute",  0.22),
            ("1-3 wks\nLate\nSubacute", 0.22),
            (">3 wks\nChronic",    0.20),
        ]

        hem_colors = [
            colors.HexColor('#F5F5F5'),  # hyperacute - bright white
            colors.HexColor('#EEEEEE'),  # acute - white
            colors.HexColor('#AAAAAA'),  # isodense
            colors.HexColor('#888888'),  # becoming darker
            colors.HexColor('#555555'),  # chronic - dark
        ]
        hem_labels = [
            "BRIGHT\nHyperdense",
            "BRIGHT\nHyperdense",
            "ISODENSE\n(may be missed!)",
            "ISODENSE\n→ Hypodense",
            "HYPODENSE\n(dark)",
        ]
        inf_colors = [
            colors.HexColor('#AAAAAA'),   # normal / subtle
            colors.HexColor('#888888'),   # early hypodense
            colors.HexColor('#666666'),   # clearly dark
            colors.HexColor('#555555'),   # fogging
            colors.HexColor('#3A3A3A'),   # chronic dark
        ]
        inf_labels = [
            "NORMAL or\nSubtle",
            "HYPODENSE\nGray-white blur",
            "CLEARLY\nHypodense",
            "FOGGING\n(may look normal)",
            "ENCEPHALO-\nMALACIA",
        ]

        # Row heights
        label_h = 0.8*cm
        row_h   = 1.6*cm
        gap     = 0.25*cm
        y_hem   = h - label_h - row_h
        y_inf   = y_hem - row_h - gap
        bar_top = h - label_h

        # Phase separators & labels
        x = 0.0
        for i, (phase_label, frac) in enumerate(phases):
            bw = w * frac
            # Phase header box
            bg = C_LIGHT_GRAY if i%2==0 else colors.HexColor('#D5D8DC')
            c.setFillColor(bg)
            c.setStrokeColor(C_DARK_GRAY)
            c.setLineWidth(0.4)
            c.rect(x, bar_top - label_h, bw, label_h, fill=1, stroke=1)
            c.setFillColor(C_DARK_GRAY)
            c.setFont("Helvetica-Bold", 7)
            lines = phase_label.split('\n')
            for li, ln in enumerate(lines):
                c.drawCentredString(x + bw/2, bar_top - 0.25*cm - li*0.28*cm, ln)

            # Hemorrhage row
            c.setFillColor(hem_colors[i])
            c.setStrokeColor(C_DARK_GRAY)
            c.setLineWidth(0.4)
            c.rect(x, y_hem, bw, row_h, fill=1, stroke=1)
            c.setFillColor(C_DARK_GRAY if hem_colors[i].hexval() > '#999999' else C_WHITE)
            # text color contrast
            hex_v = hem_colors[i]
            # just use dark text for bright backgrounds, white for dark
            r_val = int(hem_colors[i].hexval()[1:3], 16)
            txt_c = C_DARK_GRAY if r_val > 160 else C_WHITE
            c.setFillColor(txt_c)
            c.setFont("Helvetica-Bold", 6.5)
            lns2 = hem_labels[i].split('\n')
            for li, ln in enumerate(lns2):
                c.drawCentredString(x + bw/2, y_hem + row_h - 0.28*cm - li*0.28*cm, ln)

            # Infarct row
            c.setFillColor(inf_colors[i])
            c.setStrokeColor(C_DARK_GRAY)
            c.setLineWidth(0.4)
            c.rect(x, y_inf, bw, row_h, fill=1, stroke=1)
            r_val2 = int(inf_colors[i].hexval()[1:3], 16)
            txt_c2 = C_DARK_GRAY if r_val2 > 160 else C_WHITE
            c.setFillColor(txt_c2)
            c.setFont("Helvetica-Bold", 6.5)
            lns3 = inf_labels[i].split('\n')
            for li, ln in enumerate(lns3):
                c.drawCentredString(x + bw/2, y_inf + row_h - 0.28*cm - li*0.28*cm, ln)

            x += bw

        # Row labels on right
        c.setFillColor(C_RED)
        c.setFont("Helvetica-Bold", 8)
        c.drawRightString(-0.15*cm, y_hem + row_h*0.45, "BLEED")
        c.setFillColor(C_BLUE)
        c.drawRightString(-0.15*cm, y_inf + row_h*0.45, "INFARCT")

        # Bottom note
        c.setFillColor(C_MID_GRAY)
        c.setFont("Helvetica-Oblique", 7)
        c.drawString(0, y_inf - 0.35*cm, "* Fogging effect: ~1-3 wks post-infarct, CT can appear falsely normal due to macrophage infiltration")

    def wrap(self, aw, ah):
        return self.w, self.h

# ── Flowable: MRI Sequence Grid ───────────────────────────────────────────────
class MRISequenceGrid(Flowable):
    """
    Grid showing simulated MRI signal for each sequence across phases
    for HEMORRHAGE.
    """
    def __init__(self, w=None, h=None):
        Flowable.__init__(self)
        self.w = w or (PAGE_W - 4*cm)
        self.h = h or 7.5*cm

    def draw(self):
        c   = self.canv
        w, h = self.w, self.h

        phases = ["Hyperacute\n0-6 h\nOxyHb", "Acute\n1-3 d\nDeoxyHb",
                  "Early\nSubacute\n3-7 d\nIntracel\nMetHb",
                  "Late\nSubacute\n7-30 d\nExtracel\nMetHb",
                  "Chronic\n>14 d\nHemosiderin"]
        seqs   = ["T1", "T2", "SWI/GRE"]
        # signal: 0=dark, 0.5=iso, 1=bright
        # Based on standard MRI hemorrhage table
        signals = {
            "T1":  [0.5, 0.5, 1.0,  1.0,  0.2],
            "T2":  [0.8, 0.1, 0.15, 0.9,  0.0],
            "SWI/GRE": [0.3, 0.05, 0.05, 0.1, 0.0],
        }
        labels_map = {
            "T1":  ["Isointense\n(Gray)", "Isointense\n(Gray)", "BRIGHT\n(White)", "BRIGHT\n(White)", "Hypointense\n(Dark)"],
            "T2":  ["Bright\n(Lt Gray)", "DARK\n(Black)", "DARK\n(Black)", "BRIGHT\n(White)", "BLACK\n(Very dark)"],
            "SWI/GRE": ["Dark", "Very Dark\n'Blooms'", "Very Dark\n'Blooms'", "Dark", "BLACK\n'Blooms'"],
        }

        n_phases = len(phases)
        n_seqs   = len(seqs)
        header_w = 1.5*cm
        col_w    = (w - header_w) / n_phases
        row_h    = (h - 1.2*cm) / (n_seqs + 1)  # +1 for phase header row

        # Draw phase headers
        x = header_w
        for i, ph in enumerate(phases):
            bg = C_LIGHT_GRAY if i%2==0 else colors.HexColor('#D5D8DC')
            c.setFillColor(bg)
            c.setStrokeColor(C_DARK_GRAY)
            c.setLineWidth(0.4)
            c.rect(x, h - row_h, col_w, row_h, fill=1, stroke=1)
            c.setFillColor(C_DARK_GRAY)
            c.setFont("Helvetica-Bold", 6.0)
            lines = ph.split('\n')
            n = len(lines)
            for li, ln in enumerate(lines):
                c.drawCentredString(x + col_w/2,
                    h - 0.18*cm - li*(row_h/(n+0.5)), ln)
            x += col_w

        # Corner cell
        c.setFillColor(C_NAVY)
        c.setStrokeColor(C_DARK_GRAY)
        c.setLineWidth(0.4)
        c.rect(0, h - row_h, header_w, row_h, fill=1, stroke=1)
        c.setFillColor(C_WHITE)
        c.setFont("Helvetica-Bold", 7)
        c.drawCentredString(header_w/2, h - row_h/2 - 0.1*cm, "SEQ")

        # Draw sequence rows
        for si, seq in enumerate(seqs):
            y = h - (si+2)*row_h
            # Seq label
            c.setFillColor(C_NAVY)
            c.setStrokeColor(C_DARK_GRAY)
            c.setLineWidth(0.4)
            c.rect(0, y, header_w, row_h, fill=1, stroke=1)
            c.setFillColor(C_WHITE)
            c.setFont("Helvetica-Bold", 8)
            c.drawCentredString(header_w/2, y + row_h/2 - 0.1*cm, seq)

            x = header_w
            for pi in range(n_phases):
                sig = signals[seq][pi]
                # signal → gray shade (0=black, 1=white)
                shade = sig
                cell_color = colors.Color(shade, shade, shade)
                c.setFillColor(cell_color)
                c.setStrokeColor(C_DARK_GRAY)
                c.setLineWidth(0.3)
                c.rect(x, y, col_w, row_h, fill=1, stroke=1)
                # text
                txt_c = C_WHITE if shade < 0.55 else C_DARK_GRAY
                c.setFillColor(txt_c)
                c.setFont("Helvetica-Bold", 6.0)
                lns = labels_map[seq][pi].split('\n')
                n_l = len(lns)
                for li, ln in enumerate(lns):
                    c.drawCentredString(x + col_w/2,
                        y + row_h - 0.22*cm - li*0.26*cm, ln)
                x += col_w

        # Title
        c.setFillColor(C_DARK_GRAY)
        c.setFont("Helvetica-BoldOblique", 8)
        c.drawCentredString(w/2, 0.05*cm,
            "Signal shade = actual MRI appearance (white = bright, black = dark/signal void)")

    def wrap(self, aw, ah):
        return self.w, self.h


# ── Flowable: MRI Infarct Timeline ────────────────────────────────────────────
class MRIInfarctTimeline(Flowable):
    """Timeline of ischemic infarct on MRI sequences"""
    def __init__(self, w=None, h=None):
        Flowable.__init__(self)
        self.w = w or (PAGE_W - 4*cm)
        self.h = h or 6.0*cm

    def draw(self):
        c   = self.canv
        w, h = self.w, self.h

        phases = ["Hyperacute\n0-6 h", "Acute\n6h-5d",
                  "Subacute\n5-14 d", "Late Sub\n14-21 d", "Chronic\n>3 wks"]
        seqs   = ["DWI", "ADC", "FLAIR/T2", "T1"]
        # signal shade 0=dark/black, 1=bright/white
        signals = {
            "DWI":      [0.95, 0.95, 0.85, 0.7,  0.1],
            "ADC":      [0.05, 0.05, 0.1,  0.55, 0.85],
            "FLAIR/T2": [0.3,  0.8,  0.9,  0.85, 0.85],
            "T1":       [0.45, 0.3,  0.3,  0.4,  0.3],
        }
        labels_map = {
            "DWI":      ["BRIGHT\n★", "BRIGHT\n★", "Bright\n(fading)", "Fading\nT2 shine", "DARK"],
            "ADC":      ["DARK\n★", "DARK\n★", "Dark\n→ pseudo\nnormalizing", "Pseudo-\nnormal", "BRIGHT"],
            "FLAIR/T2": ["Normal/\nSubtle", "BRIGHT\n★", "BRIGHT\n+mass\neffect", "Bright", "Bright\nGliosis"],
            "T1":       ["Normal", "Hypo-\nintense", "Hypo-\nintense", "Gyral\nenhance\n(+Gd)", "Hypo-\nencephalo"],
        }

        n_phases = len(phases)
        n_seqs   = len(seqs)
        header_w = 1.6*cm
        col_w    = (w - header_w) / n_phases
        row_h    = (h - 1.1*cm) / (n_seqs + 1)

        # Phase headers
        x = header_w
        for i, ph in enumerate(phases):
            bg = colors.HexColor('#EBF5FB') if i%2==0 else colors.HexColor('#D6EAF8')
            c.setFillColor(bg)
            c.setStrokeColor(C_DARK_GRAY)
            c.setLineWidth(0.4)
            c.rect(x, h - row_h, col_w, row_h, fill=1, stroke=1)
            c.setFillColor(C_NAVY)
            c.setFont("Helvetica-Bold", 6.5)
            lns = ph.split('\n')
            for li, ln in enumerate(lns):
                c.drawCentredString(x+col_w/2, h - 0.22*cm - li*0.3*cm, ln)
            x += col_w

        # Corner
        c.setFillColor(C_TEAL)
        c.setStrokeColor(C_DARK_GRAY)
        c.setLineWidth(0.4)
        c.rect(0, h - row_h, header_w, row_h, fill=1, stroke=1)
        c.setFillColor(C_WHITE)
        c.setFont("Helvetica-Bold", 7)
        c.drawCentredString(header_w/2, h - row_h/2 - 0.1*cm, "SEQ")

        # Seq rows
        for si, seq in enumerate(seqs):
            y = h - (si+2)*row_h
            c.setFillColor(C_TEAL)
            c.setStrokeColor(C_DARK_GRAY)
            c.setLineWidth(0.4)
            c.rect(0, y, header_w, row_h, fill=1, stroke=1)
            c.setFillColor(C_WHITE)
            c.setFont("Helvetica-Bold", 8)
            c.drawCentredString(header_w/2, y + row_h/2 - 0.1*cm, seq)

            x = header_w
            for pi in range(n_phases):
                sig = signals[seq][pi]
                cell_color = colors.Color(sig, sig, sig)
                c.setFillColor(cell_color)
                c.setStrokeColor(C_DARK_GRAY)
                c.setLineWidth(0.3)
                c.rect(x, y, col_w, row_h, fill=1, stroke=1)
                txt_c = C_WHITE if sig < 0.5 else C_DARK_GRAY
                c.setFillColor(txt_c)
                c.setFont("Helvetica-Bold", 5.8)
                lns2 = labels_map[seq][pi].split('\n')
                for li, ln in enumerate(lns2):
                    c.drawCentredString(x+col_w/2,
                        y + row_h - 0.2*cm - li*0.24*cm, ln)
                x += col_w

        c.setFillColor(C_DARK_GRAY)
        c.setFont("Helvetica-BoldOblique", 7.5)
        c.drawString(0, 0.1*cm, "★ = Diagnostic window: DWI bright + ADC dark = ACUTE INFARCT (confirmed restricted diffusion)")

    def wrap(self, aw, ah):
        return self.w, self.h


# ── Flowable: DWI-ADC Schematic ───────────────────────────────────────────────
class DWIADCSchematic(Flowable):
    """3-panel: Acute infarct | Chronic gliosis | Hemorrhage on SWI"""
    def __init__(self, w=None, h=None):
        Flowable.__init__(self)
        self.w = w or (PAGE_W - 4*cm)
        self.h = h or 5.5*cm

    def draw(self):
        c   = self.canv
        w, h = self.w, self.h
        n   = 3
        pw  = w / n
        r   = min(h*0.35, pw*0.38)
        cy  = h * 0.53

        panels = [
            {
                "title": "ACUTE INFARCT",
                "subtitle": "DWI bright + ADC dark\n= INFARCTION",
                "tc": C_RED,
                "dwi_lesion": 1.0,   # bright
                "adc_lesion": 0.0,   # dark
                "label_d": "DWI",
                "label_a": "ADC",
            },
            {
                "title": "CHRONIC / GLIOSIS",
                "subtitle": "DWI dark + ADC bright\n= T2 shine-through\n(NOT acute infarct)",
                "tc": C_GREEN,
                "dwi_lesion": 0.15,
                "adc_lesion": 0.9,
                "label_d": "DWI",
                "label_a": "ADC",
            },
            {
                "title": "HEMORRHAGE",
                "subtitle": "SWI/GRE: BLACK\n'blooming'\n= Blood product",
                "tc": C_PURPLE,
                "dwi_lesion": None,  # SWI panel
                "adc_lesion": 0.0,   # very dark
                "label_d": "SWI",
                "label_a": "T2*",
            },
        ]

        for i, p in enumerate(panels):
            cx = pw * i + pw/2

            # Panel bg
            c.setFillColor(colors.HexColor('#F8F9FA'))
            c.setStrokeColor(p["tc"])
            c.setLineWidth(1.2)
            c.roundRect(pw*i + 0.1*cm, 0.4*cm, pw - 0.2*cm, h-0.5*cm, 4, fill=1, stroke=1)

            # Brain circle
            brain_bg = 0.65  # gray
            c.setFillColor(colors.Color(brain_bg, brain_bg, brain_bg))
            c.setStrokeColor(C_DARK_GRAY)
            c.setLineWidth(0.5)
            c.circle(cx, cy, r, fill=1, stroke=1)

            # Lesion
            if p["dwi_lesion"] is not None:
                lv = p["dwi_lesion"]
                c.setFillColor(colors.Color(lv, lv, lv))
                c.setStrokeColor(C_DARK_GRAY)
                c.setLineWidth(0.3)
                c.circle(cx + r*0.25, cy + r*0.2, r*0.38, fill=1, stroke=1)
            else:
                # SWI bloom effect - add bright ring + dark center
                c.setFillColor(colors.Color(0.3, 0.3, 0.3))
                c.circle(cx + r*0.25, cy + r*0.2, r*0.42, fill=1, stroke=0)
                c.setFillColor(colors.Color(0.02, 0.02, 0.02))
                c.circle(cx + r*0.25, cy + r*0.2, r*0.32, fill=1, stroke=0)
                # bloom glow
                c.setFillColor(colors.Color(0.0, 0.0, 0.0))
                c.setStrokeColor(colors.HexColor('#555555'))
                c.setLineWidth(0.5)
                c.circle(cx + r*0.25, cy + r*0.2, r*0.38, fill=0, stroke=1)

            # Sequence labels
            c.setFillColor(p["tc"])
            c.setFont("Helvetica-Bold", 7)
            c.drawCentredString(cx, 0.65*cm, p["label_d"] if p["dwi_lesion"] is None else
                (p["label_d"] + " " + ("BRIGHT" if p["dwi_lesion"] > 0.5 else "DARK")))

            # Title
            c.setFillColor(p["tc"])
            c.setFont("Helvetica-Bold", 8.5)
            c.drawCentredString(cx, h-0.2*cm, p["title"])

            # Subtitle
            c.setFillColor(C_DARK_GRAY)
            c.setFont("Helvetica", 6.8)
            lns = p["subtitle"].split('\n')
            for li, ln in enumerate(lns):
                c.drawCentredString(cx, h - 0.58*cm - li*0.26*cm, ln)

    def wrap(self, aw, ah):
        return self.w, self.h


# ── Flowable: Hemorrhage Types Diagram ───────────────────────────────────────
class HemorrhageTypesDiagram(Flowable):
    """
    Schematic cross-section showing EDH, SDH, SAH, ICH locations
    """
    def __init__(self, w=None, h=None):
        Flowable.__init__(self)
        self.w = w or (PAGE_W - 4*cm)
        self.h = h or 7.0*cm

    def draw(self):
        c   = self.canv
        w, h = self.w, self.h
        cx  = w / 2
        cy  = h * 0.52

        # Skull outer
        skull_r = min(h*0.44, w*0.28)
        c.setFillColor(colors.HexColor('#D5D8DC'))
        c.setStrokeColor(C_BLACK)
        c.setLineWidth(1.0)
        c.circle(cx, cy, skull_r, fill=1, stroke=1)

        # Skull inner (diploe)
        c.setFillColor(colors.HexColor('#BDC3C7'))
        c.circle(cx, cy, skull_r*0.88, fill=1, stroke=0)

        # Dura
        c.setFillColor(colors.HexColor('#C0392B'))
        c.circle(cx, cy, skull_r*0.87, fill=0, stroke=1)
        c.setLineWidth(2.0)
        c.setStrokeColor(colors.HexColor('#C0392B'))
        c.circle(cx, cy, skull_r*0.87, fill=0, stroke=1)

        # Brain parenchyma
        c.setFillColor(C_CT_BRAIN)
        c.setStrokeColor(colors.HexColor('#888888'))
        c.setLineWidth(0.5)
        c.circle(cx, cy, skull_r*0.75, fill=1, stroke=1)

        # Gyri texture
        c.setStrokeColor(colors.HexColor('#999999'))
        c.setLineWidth(0.6)
        for angle in range(0, 360, 30):
            rad = math.radians(angle)
            x1 = cx + skull_r*0.66*math.cos(rad)
            y1 = cy + skull_r*0.66*math.sin(rad)
            x2 = cx + skull_r*0.74*math.cos(rad)
            y2 = cy + skull_r*0.74*math.sin(rad)
            c.line(x1, y1, x2, y2)

        # Ventricles
        c.setFillColor(C_CT_CSF)
        c.ellipse(cx-0.28*cm, cy-0.1*cm, cx+0.28*cm, cy+0.35*cm, fill=1, stroke=0)
        c.ellipse(cx-0.2*cm, cy-0.45*cm, cx-0.03*cm, cy-0.1*cm, fill=1, stroke=0)
        c.ellipse(cx+0.03*cm, cy-0.45*cm, cx+0.2*cm, cy-0.1*cm, fill=1, stroke=0)

        # ── EDH (lenticular / biconvex) at top right ──
        edh_angle = 50  # degrees from top
        edh_rad   = math.radians(edh_angle)
        edh_cx    = cx + skull_r*0.82*math.cos(edh_rad)
        edh_cy    = cy + skull_r*0.82*math.sin(edh_rad)
        c.setFillColor(colors.HexColor('#E74C3C'))
        c.saveState()
        c.translate(edh_cx, edh_cy)
        c.rotate(edh_angle - 90)
        c.ellipse(-0.35*cm, -0.12*cm, 0.35*cm, 0.12*cm, fill=1, stroke=0)
        c.restoreState()

        # ── SDH (crescent) at upper left ──
        sdh_angle = 130
        sdh_rad   = math.radians(sdh_angle)
        # Draw crescent as thick arc
        c.setFillColor(colors.HexColor('#E67E22'))
        c.saveState()
        c.translate(cx, cy)
        c.setFillColor(colors.HexColor('#E67E22'))
        # approximate crescent with a wide stroke arc
        c.setStrokeColor(colors.HexColor('#E67E22'))
        c.setLineWidth(5.0)
        c.arc(-skull_r*0.80, -skull_r*0.80, skull_r*0.80, skull_r*0.80,
              startAng=95, extent=85)
        c.restoreState()

        # ── SAH (thin layer in subarachnoid) at bottom ──
        c.setFillColor(colors.HexColor('#F39C12'))
        c.saveState()
        c.translate(cx, cy)
        c.setStrokeColor(colors.HexColor('#F39C12'))
        c.setLineWidth(3.5)
        c.arc(-skull_r*0.76, -skull_r*0.76, skull_r*0.76, skull_r*0.76,
              startAng=210, extent=60)
        c.restoreState()

        # ── ICH (parenchymal) - bright spot in right basal ganglia area ──
        ich_x = cx + 0.35*cm
        ich_y = cy + 0.1*cm
        c.setFillColor(colors.HexColor('#ECF0F1'))
        c.setStrokeColor(colors.HexColor('#BDC3C7'))
        c.setLineWidth(0.3)
        c.circle(ich_x, ich_y, 0.32*cm, fill=1, stroke=1)

        # Labels with arrows
        label_data = [
            (cx + skull_r*0.95, cy + skull_r*0.6,  cx + skull_r*1.3, cy + skull_r*0.9,  "EDH\n(Biconvex)\nArt. bleed", colors.HexColor('#E74C3C')),
            (cx - skull_r*0.92, cy + skull_r*0.55, cx - skull_r*1.35, cy + skull_r*0.9, "SDH\n(Crescent)\nVenous", colors.HexColor('#E67E22')),
            (cx - skull_r*0.55, cy - skull_r*0.85, cx - skull_r*0.8, cy - skull_r*1.25, "SAH\n(Cisterns/\nSulci)", colors.HexColor('#F39C12')),
            (cx + 0.35*cm, cy + 0.1*cm, cx + skull_r*1.15, cy - skull_r*0.5, "ICH\n(Parenchymal)\nBasal ganglia", colors.HexColor('#AAAAAA')),
        ]
        for x1, y1, x2, y2, lbl, clr in label_data:
            c.setStrokeColor(clr)
            c.setLineWidth(0.8)
            c.line(x1, y1, x2, y2)
            c.setFillColor(clr)
            c.setFont("Helvetica-Bold", 7.5)
            lns = lbl.split('\n')
            align_right = x2 < cx
            for li, ln in enumerate(lns):
                if align_right:
                    c.drawRightString(x2, y2 - li*0.25*cm, ln)
                else:
                    c.drawString(x2, y2 - li*0.25*cm, ln)

        # Title
        c.setFillColor(C_DARK_GRAY)
        c.setFont("Helvetica-BoldOblique", 8)
        c.drawCentredString(w/2, 0.1*cm,
            "Schematic cross-section of cranium showing types & locations of intracranial hemorrhage")

    def wrap(self, aw, ah):
        return self.w, self.h


# ── Flowable: HU Scale Bar ────────────────────────────────────────────────────
class HUScaleBar(Flowable):
    def __init__(self, w=None, h=None):
        Flowable.__init__(self)
        self.w = w or (PAGE_W - 4*cm)
        self.h = h or 2.2*cm

    def draw(self):
        c   = self.canv
        w, h = self.w, self.h
        bar_h = 0.6*cm
        bar_y = h*0.5
        bar_x = 1.0*cm
        bar_w = w - 2.0*cm
        n     = 200

        # Gradient bar: black → white
        for i in range(n):
            frac = i / n
            shade = frac
            c.setFillColor(colors.Color(shade, shade, shade))
            c.setStrokeColor(colors.Color(shade, shade, shade))
            c.rect(bar_x + frac*bar_w, bar_y, bar_w/n + 0.5, bar_h, fill=1, stroke=0)

        # Border
        c.setStrokeColor(C_DARK_GRAY)
        c.setLineWidth(0.5)
        c.rect(bar_x, bar_y, bar_w, bar_h, fill=0, stroke=1)

        # Tick marks & labels
        ticks = [
            (0.0,   "< -100\nFat/Air"),
            (0.15,  "0\nWater/CSF"),
            (0.32,  "~25-40\nBrain"),
            (0.52,  "~50-80\nBlood"),
            (0.68,  "~100\nCalcification"),
            (1.0,   ">400\nBone"),
        ]
        for frac, lbl in ticks:
            tx = bar_x + frac*bar_w
            c.setStrokeColor(C_DARK_GRAY)
            c.setLineWidth(0.5)
            c.line(tx, bar_y-0.05*cm, tx, bar_y-0.25*cm)
            c.setFillColor(C_DARK_GRAY)
            c.setFont("Helvetica", 6.5)
            lns = lbl.split('\n')
            for li, ln in enumerate(lns):
                c.drawCentredString(tx, bar_y - 0.32*cm - li*0.2*cm, ln)

        # Arrow labels above bar
        blood_x = bar_x + 0.52*bar_w
        c.setFillColor(C_RED)
        c.setFont("Helvetica-Bold", 7)
        c.drawCentredString(blood_x, bar_y + bar_h + 0.2*cm, "BLOOD\n(Hyperdense)")

        c.setFillColor(C_BLUE)
        infarct_x = bar_x + 0.22*bar_w
        c.drawCentredString(infarct_x, bar_y + bar_h + 0.2*cm, "INFARCT\n(Hypodense)")

        c.setFillColor(C_DARK_GRAY)
        c.setFont("Helvetica-Bold", 8)
        c.drawString(0, bar_y + bar_h*0.2, "HU:")
        c.drawString(w - 0.8*cm, bar_y + bar_h*0.2, "DENSE")
        c.setFont("Helvetica", 8)
        c.drawString(0, bar_y - 0.1*cm, "")

    def wrap(self, aw, ah):
        return self.w, self.h


# ── Build PDF ─────────────────────────────────────────────────────────────────
def build_pdf(output_path):
    doc = SimpleDocTemplate(
        output_path,
        pagesize=A4,
        leftMargin=2*cm, rightMargin=2*cm,
        topMargin=2*cm,  bottomMargin=2*cm,
        title="Brain CT & MRI: Bleed vs Infarct - Reference Guide",
        author="Orris Medical Reference"
    )

    styles = getSampleStyleSheet()
    avail_w = PAGE_W - 4*cm

    # Custom styles
    title_style = ParagraphStyle('Title2',
        fontName='Helvetica-Bold', fontSize=20, leading=24,
        textColor=C_WHITE, alignment=TA_CENTER, spaceAfter=4)
    subtitle_style = ParagraphStyle('Subtitle2',
        fontName='Helvetica', fontSize=11, leading=14,
        textColor=colors.HexColor('#AED6F1'), alignment=TA_CENTER, spaceAfter=6)
    h2_style = ParagraphStyle('H2',
        fontName='Helvetica-Bold', fontSize=11, leading=14,
        textColor=C_NAVY, spaceAfter=4, spaceBefore=8)
    body_style = ParagraphStyle('Body2',
        fontName='Helvetica', fontSize=8.5, leading=12,
        textColor=C_DARK_GRAY, spaceAfter=4, alignment=TA_JUSTIFY)
    small_style = ParagraphStyle('Small',
        fontName='Helvetica', fontSize=7.5, leading=10,
        textColor=C_DARK_GRAY, spaceAfter=2)
    callout_style = ParagraphStyle('Callout',
        fontName='Helvetica-BoldOblique', fontSize=8.5, leading=12,
        textColor=C_NAVY, spaceAfter=3,
        borderPad=6, borderColor=C_LIGHTBLUE,
        backColor=colors.HexColor('#EBF5FB'))
    note_style = ParagraphStyle('Note',
        fontName='Helvetica-Oblique', fontSize=7.5, leading=10,
        textColor=C_MID_GRAY, spaceAfter=4)

    story = []

    # ══════════════════════════════════════════════════════════════════════════
    # COVER PAGE
    # ══════════════════════════════════════════════════════════════════════════
    class CoverPage(Flowable):
        def __init__(self):
            Flowable.__init__(self)
            self.w = avail_w
            self.h = PAGE_H - 4*cm

        def draw(self):
            c = self.canv
            w, h = self.w, self.h

            # Background gradient block
            c.setFillColor(C_NAVY)
            c.rect(0, h*0.45, w, h*0.55, fill=1, stroke=0)
            c.setFillColor(C_BLUE)
            c.rect(0, h*0.3, w, h*0.15, fill=1, stroke=0)
            c.setFillColor(C_LIGHTBLUE)
            c.rect(0, 0, w, h*0.3, fill=1, stroke=0)

            # Title
            c.setFillColor(C_WHITE)
            c.setFont("Helvetica-Bold", 22)
            c.drawCentredString(w/2, h*0.82, "BRAIN CT & MRI")
            c.setFont("Helvetica-Bold", 18)
            c.drawCentredString(w/2, h*0.74, "READING GUIDE")
            c.setFont("Helvetica", 12)
            c.setFillColor(colors.HexColor('#AED6F1'))
            c.drawCentredString(w/2, h*0.66, "Hemorrhage vs. Infarction")
            c.drawCentredString(w/2, h*0.61, "Acute • Subacute • Chronic")

            # Decorative CT circle (brain schematic)
            brain_cx = w/2
            brain_cy = h*0.44
            brain_r  = 1.8*cm
            c.setFillColor(colors.HexColor('#1A1A2E'))
            c.circle(brain_cx, brain_cy, brain_r*1.15, fill=1, stroke=0)
            c.setFillColor(colors.HexColor('#888888'))
            c.circle(brain_cx, brain_cy, brain_r, fill=1, stroke=0)
            c.setFillColor(colors.HexColor('#111111'))
            c.ellipse(brain_cx-0.3*cm, brain_cy-0.1*cm, brain_cx+0.3*cm, brain_cy+0.35*cm, fill=1, stroke=0)
            # Bleed spot
            c.setFillColor(colors.HexColor('#F0F0F0'))
            c.circle(brain_cx+0.6*cm, brain_cy+0.3*cm, 0.38*cm, fill=1, stroke=0)
            # Infarct
            c.setFillColor(colors.HexColor('#555555'))
            c.circle(brain_cx-0.55*cm, brain_cy+0.2*cm, 0.32*cm, fill=1, stroke=0)

            c.setFillColor(C_WHITE)
            c.setFont("Helvetica-Bold", 7)
            c.drawCentredString(brain_cx+0.6*cm, brain_cy+0.1*cm, "BLEED")
            c.drawCentredString(brain_cx-0.55*cm, brain_cy+0.02*cm, "INFARCT")

            # Bottom section
            c.setFillColor(C_WHITE)
            c.setFont("Helvetica-Bold", 10)
            c.drawCentredString(w/2, h*0.22, "Complete Timeline Reference")
            c.setFont("Helvetica", 9)
            c.setFillColor(colors.HexColor('#1B2631'))
            c.drawCentredString(w/2, h*0.15,
                "CT Density | MRI Signal | DWI/ADC | SWI | Hemorrhage Types")
            c.drawCentredString(w/2, h*0.09,
                "Based on: Adams & Victor's Neurology • Bradley & Daroff • Grainger & Allison")

            c.setFillColor(C_NAVY)
            c.setFont("Helvetica-Oblique", 7.5)
            c.drawCentredString(w/2, h*0.02, "Orris Medical Reference  |  For educational purposes only")

        def wrap(self, aw, ah):
            return self.w, self.h

    story.append(CoverPage())
    story.append(PageBreak())

    # ══════════════════════════════════════════════════════════════════════════
    # PAGE 2: CT FUNDAMENTALS
    # ══════════════════════════════════════════════════════════════════════════
    story.append(SectionHeader("1.  HOW TO READ A BRAIN CT",
        "Non-contrast CT (NCCT) — First-line for ALL stroke and head injury presentations", bg=C_NAVY, w=avail_w))
    story.append(Spacer(1, 0.3*cm))

    story.append(Paragraph("The Hounsfield Unit (HU) Scale", h2_style))
    story.append(Paragraph(
        "Every CT pixel is assigned a Hounsfield Unit (HU) value representing tissue density. "
        "Air = −1000 HU, water = 0 HU, bone = ~700+ HU. <b>Blood clots are denser than brain "
        "(~60–80 HU vs ~35 HU), so they appear bright white (hyperdense). Infarcted brain loses "
        "fluid equilibrium and becomes hypodense (dark, &lt;30 HU).</b>", body_style))
    story.append(HUScaleBar(w=avail_w, h=2.4*cm))
    story.append(Spacer(1, 0.5*cm))

    story.append(Paragraph("CT Window Settings", h2_style))
    win_data = [
        ["Window Setting", "Width (WW)", "Level (WL)", "Best For"],
        ["Brain window",        "~80",  "~35",  "Parenchyma, gray-white differentiation, blood"],
        ["Bone window",         "~2000","~300", "Skull fractures, calvarium"],
        ["Stroke window",       "~35",  "~35",  "Subtle early ischemia, improves gray-white contrast"],
        ["Subdural window",     "~200", "~60",  "Subdural hematoma vs. brain"],
    ]
    win_table = Table(win_data, colWidths=[3.5*cm, 2.2*cm, 2.2*cm, 8.1*cm])
    win_table.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), C_BLUE),
        ('TEXTCOLOR',  (0,0), (-1,0), C_WHITE),
        ('FONTNAME',   (0,0), (-1,0), 'Helvetica-Bold'),
        ('FONTSIZE',   (0,0), (-1,-1), 8),
        ('ROWBACKGROUNDS', (0,1), (-1,-1), [C_CREAM, C_LIGHT_GRAY]),
        ('GRID',       (0,0), (-1,-1), 0.4, C_MID_GRAY),
        ('VALIGN',     (0,0), (-1,-1), 'MIDDLE'),
        ('TOPPADDING', (0,0), (-1,-1), 3),
        ('BOTTOMPADDING', (0,0), (-1,-1), 3),
    ]))
    story.append(win_table)
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph("CT Schematic: Hemorrhage vs. Infarct", h2_style))
    story.append(CTBrainSchematic(w=avail_w, h=6.5*cm))
    story.append(Spacer(1, 0.3*cm))

    story.append(Paragraph(
        "<b>Key early CT signs of ischemic stroke (first 6 hours):</b> "
        "(1) Loss of gray-white differentiation at the basal ganglia or insular cortex.  "
        "(2) <b>Insular ribbon sign</b> — loss of sharp insular cortex outline (MCA territory).  "
        "(3) <b>Sulcal effacement</b> — swollen gyri obliterate normal sulci.  "
        "(4) <b>Hyperdense vessel sign</b> — bright MCA or basilar artery = acute thrombus.  "
        "(5) <b>1/3 MCA rule</b> — if >1/3 MCA territory is already hypodense, IV tPA is contraindicated.",
        body_style))

    story.append(PageBreak())

    # ══════════════════════════════════════════════════════════════════════════
    # PAGE 3: CT TIMELINE
    # ══════════════════════════════════════════════════════════════════════════
    story.append(SectionHeader("2.  CT TIMELINE — HEMORRHAGE vs. INFARCT",
        "How CT appearance changes over time for each pathology", bg=C_RED, w=avail_w))
    story.append(Spacer(1, 0.3*cm))

    story.append(CTTimelineDiagram(w=avail_w, h=5.8*cm))
    story.append(Spacer(1, 0.4*cm))

    story.append(Paragraph("Detailed CT Timeline Table", h2_style))
    ct_data = [
        ["Phase", "Time", "Hemorrhage on CT", "Infarct on CT"],
        ["Hyperacute", "0–6 hrs",
         "HYPERDENSE (white, ~60-80 HU)\nClot formed, protein concentrated",
         "NORMAL or very subtle\nPossible sulcal effacement only"],
        ["Acute", "6–24 hrs",
         "HYPERDENSE\nPeripheral edema halo may appear",
         "HYPODENSE territory\nGray-white differentiation lost\nInsular ribbon sign"],
        ["Early Subacute", "1–7 days",
         "HYPERDENSE → becoming ISODENSE\nEdema increases, mass effect",
         "CLEARLY HYPODENSE\nMass effect peaks day 3–5\nMidline shift possible"],
        ["Late Subacute", "1–3 wks",
         "ISODENSE — can be MISSED!\nLook for mass effect clue",
         "FOGGING EFFECT (~1-3 wks)\nMay look falsely normal\n(macrophage infiltration)"],
        ["Chronic", ">3 wks",
         "HYPODENSE (dark)\nMay calcify; subdural → crescent",
         "ENCEPHALOMALACIA\nHypodense cavity\nEx-vacuo ventricular dilation"],
    ]
    ct_table = Table(ct_data,
        colWidths=[2.8*cm, 2.0*cm, 6.2*cm, 5.0*cm])
    ct_table.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), C_DARK_GRAY),
        ('TEXTCOLOR',  (0,0), (-1,0), C_WHITE),
        ('FONTNAME',   (0,0), (-1,0), 'Helvetica-Bold'),
        ('BACKGROUND', (2,1), (2,-1), colors.HexColor('#FDFEFE')),
        ('BACKGROUND', (3,1), (3,-1), colors.HexColor('#EBF5FB')),
        ('FONTSIZE',   (0,0), (-1,-1), 7.5),
        ('ROWBACKGROUNDS', (0,1), (1,-1), [colors.HexColor('#FDEBD0'), colors.HexColor('#FEF9E7')]),
        ('GRID',       (0,0), (-1,-1), 0.4, C_MID_GRAY),
        ('VALIGN',     (0,0), (-1,-1), 'TOP'),
        ('TOPPADDING', (0,0), (-1,-1), 4),
        ('BOTTOMPADDING', (0,0), (-1,-1), 4),
        ('FONTNAME',   (2,1), (2,-1), 'Helvetica-Bold'),
    ]))
    story.append(ct_table)
    story.append(Spacer(1, 0.3*cm))

    story.append(Paragraph(
        "<b>Pitfall — Isodense Subdural Hematoma:</b> "
        "At ~7–14 days, subdural blood becomes isodense with brain on CT. "
        "Clues: asymmetric sulci, displaced cortex, midline shift without visible hyperdensity. "
        "MRI (T1) will show it as bright (methemoglobin).", callout_style))

    story.append(PageBreak())

    # ══════════════════════════════════════════════════════════════════════════
    # PAGE 4: HEMORRHAGE TYPES
    # ══════════════════════════════════════════════════════════════════════════
    story.append(SectionHeader("3.  TYPES OF INTRACRANIAL HEMORRHAGE",
        "Location determines shape, likely etiology, and clinical presentation", bg=C_RED, w=avail_w))
    story.append(Spacer(1, 0.3*cm))

    story.append(HemorrhageTypesDiagram(w=avail_w, h=7.0*cm))
    story.append(Spacer(1, 0.4*cm))

    hem_type_data = [
        ["Type", "CT Shape", "Location", "Typical Cause", "Key Clue"],
        ["EDH\n(Epidural)", "BICONVEX\n(lens-shaped)", "Between skull & dura\nDoes NOT cross sutures",
         "Middle meningeal\nartery (temporal\nfracture)", "Lucid interval\nTemporal fracture"],
        ["SDH\n(Subdural)", "CRESCENT\n(concave inner edge)", "Between dura & brain\nCrosses sutures freely",
         "Bridging veins\n(shear force)", "Elderly, falls\nAnticoagulants"],
        ["SAH\n(Subarachnoid)", "Fills sulci/basal\ncisterns — 'star'\npattern", "Subarachnoid space",
         "Ruptured Berry\naneurysm, AVM,\nTrauma", "Thunderclap HA\nFisher grade"],
        ["ICH\n(Intracerebral)", "Round/oval\nparenchymal", "Basal ganglia\nThalamus, pons,\ncerebellum",
         "HTN (most common)\nAmyloid angiopathy", "Location predicts\ncause: deep=HTN\ncortical=amyloid"],
    ]
    hem_t = Table(hem_type_data, colWidths=[2.0*cm, 2.8*cm, 3.2*cm, 3.2*cm, 4.8*cm])
    hem_t.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), C_RED),
        ('TEXTCOLOR',  (0,0), (-1,0), C_WHITE),
        ('FONTNAME',   (0,0), (-1,0), 'Helvetica-Bold'),
        ('FONTSIZE',   (0,0), (-1,-1), 7.5),
        ('ROWBACKGROUNDS', (0,1), (-1,-1),
         [colors.HexColor('#FDEDEC'), colors.HexColor('#FEF9E7'),
          colors.HexColor('#FEF5E7'), colors.HexColor('#FDFEFE')]),
        ('GRID',       (0,0), (-1,-1), 0.4, C_MID_GRAY),
        ('VALIGN',     (0,0), (-1,-1), 'TOP'),
        ('TOPPADDING', (0,0), (-1,-1), 4),
        ('BOTTOMPADDING', (0,0), (-1,-1), 4),
        ('FONTNAME',   (0,1), (0,-1), 'Helvetica-Bold'),
        ('TEXTCOLOR',  (0,1), (0,-1), C_RED),
    ]))
    story.append(hem_t)

    story.append(PageBreak())

    # ══════════════════════════════════════════════════════════════════════════
    # PAGE 5: MRI SEQUENCES
    # ══════════════════════════════════════════════════════════════════════════
    story.append(SectionHeader("4.  MRI SEQUENCES — WHAT EACH ONE SHOWS",
        "Understanding each sequence is key to accurate interpretation", bg=C_TEAL, w=avail_w))
    story.append(Spacer(1, 0.3*cm))

    mri_seq_data = [
        ["Sequence", "TE/TR", "Bright (hyperintense)", "Dark (hypointense)", "Best Used For"],
        ["T1", "Short TE\nShort TR",
         "Fat, methemoglobin,\nprotein-rich fluid,\ngadolinium (contrast)",
         "CSF, edema,\ndeoxyhemoglobin,\nacute infarct",
         "Anatomy, subacute\nblood, contrast\nenhancement"],
        ["T2", "Long TE\nLong TR",
         "CSF, edema, gliosis,\nlate subacute blood\n(extracellular methHb)",
         "Acute blood (deoxy),\nhemosiderin,\ncalcification, cortical bone",
         "Infarcts, edema,\ntumors, inflammation"],
        ["FLAIR", "Long TE\nLong TR\n+inversion",
         "Edema, gliosis,\nSAH (bloody CSF bright),\nperiventricular lesions",
         "Normal CSF\n(suppressed — black)",
         "Periventricular MS,\nSAH, infarct age\n(DWI-FLAIR mismatch)"],
        ["DWI", "EPI\nsequence",
         "ACUTE INFARCT ★\n(restricted diffusion)\nAlso: abscess, epidermoid",
         "Chronic lesions,\nblood after several days,\nCSF",
         "ACUTE STROKE\ndiagnosis — positive\nwithin minutes"],
        ["ADC", "Derived\nfrom DWI",
         "Vasogenic edema,\nchronic gliosis\n(unrestricted diffusion)",
         "ACUTE INFARCT ★\n(true restriction)\nAlso abscess",
         "Confirm restricted\ndiffusion; exclude\nT2 shine-through"],
        ["SWI/GRE", "T2*\ngradient\necho",
         "None significant",
         "ALL blood products\n★ 'Blooms' dark\nCalcification, iron, veins",
         "Microhemorrhages,\nvenous thrombosis,\nhemorrhagic transform."],
        ["MRA", "TOF or\ncontrast",
         "Flowing blood\n(patent vessel)",
         "Thrombosed vessel\n(flow void lost)",
         "Intracranial\nvessel patency,\naneurysm, LVO"],
    ]
    mri_t = Table(mri_seq_data, colWidths=[1.8*cm, 1.5*cm, 4.0*cm, 3.8*cm, 4.9*cm])
    mri_t.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), C_TEAL),
        ('TEXTCOLOR',  (0,0), (-1,0), C_WHITE),
        ('FONTNAME',   (0,0), (-1,0), 'Helvetica-Bold'),
        ('FONTSIZE',   (0,0), (-1,-1), 7.5),
        ('ROWBACKGROUNDS', (0,1), (-1,-1),
         [colors.HexColor('#E8F8F5'), colors.HexColor('#FDFEFE')]*5),
        ('GRID',       (0,0), (-1,-1), 0.4, C_MID_GRAY),
        ('VALIGN',     (0,0), (-1,-1), 'TOP'),
        ('TOPPADDING', (0,0), (-1,-1), 4),
        ('BOTTOMPADDING', (0,0), (-1,-1), 4),
        ('FONTNAME',   (0,1), (0,-1), 'Helvetica-Bold'),
        ('TEXTCOLOR',  (0,1), (0,-1), C_TEAL),
    ]))
    story.append(mri_t)
    story.append(Spacer(1, 0.3*cm))

    story.append(Paragraph(
        "<b>The DWI-ADC Rule (most important concept in acute stroke MRI):</b><br/>"
        "• <b>DWI bright + ADC dark</b> = TRUE restricted diffusion = ACUTE INFARCTION ✓<br/>"
        "• <b>DWI bright + ADC bright/normal</b> = T2 shine-through (NOT acute infarct)<br/>"
        "• <b>DWI dark + ADC bright</b> = chronic gliosis / encephalomalacia",
        callout_style))

    story.append(DWIADCSchematic(w=avail_w, h=5.5*cm))

    story.append(PageBreak())

    # ══════════════════════════════════════════════════════════════════════════
    # PAGE 6: MRI HEMORRHAGE TIMELINE
    # ══════════════════════════════════════════════════════════════════════════
    story.append(SectionHeader("5.  MRI HEMORRHAGE SIGNAL TIMELINE",
        "Signal depends on hemoglobin oxidation state and RBC integrity", bg=C_PURPLE, w=avail_w))
    story.append(Spacer(1, 0.3*cm))

    story.append(Paragraph(
        "Hemoglobin undergoes a predictable series of chemical transformations after bleeding. "
        "Each stage has unique magnetic properties that create characteristic MRI signal patterns. "
        "The key variable is whether hemoglobin has <b>unpaired electrons</b> (paramagnetic = shortens T2) "
        "and whether it is <b>intracellular or extracellular</b> (determines T1 effect).",
        body_style))

    story.append(MRISequenceGrid(w=avail_w, h=7.5*cm))
    story.append(Spacer(1, 0.3*cm))

    mri_hem_data = [
        ["Stage", "Time", "Hb Form", "Location", "T1", "T2", "SWI", "Mechanism"],
        ["Hyperacute", "0–6 hrs", "Oxyhemoglobin\n(OxyHb)", "Intracellular",
         "Gray\n(iso)", "Lt Gray\n(slightly\nbright)", "Dark", "OxyHb has NO unpaired electrons\n→ no T1 shortening; T2 bright from\nwater in intact RBCs"],
        ["Acute", "1–3 days", "Deoxyhemoglobin\n(DeoxyHb)", "Intracellular",
         "Gray\n(iso)", "DARK\n(black)", "VERY\nDARK", "DeoxyHb has 4 unpaired electrons\n→ strong T2 shortening; intact cell\nmembrane prevents T1 effect"],
        ["Early\nSubacute", "3–7 days", "Intracellular\nMethemoglobin", "Intracellular",
         "BRIGHT\n(white)", "DARK\n(black)", "DARK", "MetHb has 5 unpaired electrons\n→ strong T1 shortening; RBCs still\nintact → T2 still dark"],
        ["Late\nSubacute", "7–30 days", "Extracellular\nMethemoglobin", "Extracellular\n(RBC lysis)",
         "BRIGHT\n(white)", "BRIGHT\n(white)", "Dark", "RBC lysis → MetHb disperses\nextracellularly → T2 shortening\nrelieved → both T1 & T2 bright"],
        ["Chronic", ">14 days", "Hemosiderin +\nFerritin", "In macrophages\n(outer rim)",
         "Dk Gray\n(hypo)", "BLACK\n(very\ndark)", "BLACK\n'Blooms'", "Hemosiderin = highly paramagnetic\n→ profound T2* susceptibility\neffect → 'blooming' on SWI/GRE"],
    ]
    mri_h_t = Table(mri_hem_data,
        colWidths=[1.7*cm, 1.5*cm, 2.2*cm, 2.0*cm, 1.2*cm, 1.2*cm, 1.2*cm, 5.0*cm])
    mri_h_t.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), C_PURPLE),
        ('TEXTCOLOR',  (0,0), (-1,0), C_WHITE),
        ('FONTNAME',   (0,0), (-1,0), 'Helvetica-Bold'),
        ('FONTSIZE',   (0,0), (-1,-1), 7),
        # Color-code T1/T2 columns by signal
        ('BACKGROUND', (4,2), (4,2), colors.HexColor('#888888')),  # hyper T1
        ('BACKGROUND', (5,2), (5,2), colors.HexColor('#111111')),  # dark T2 acute
        ('TEXTCOLOR',  (5,2), (5,2), C_WHITE),
        ('BACKGROUND', (4,3), (4,3), colors.HexColor('#F0F0F0')),  # bright T1 early sub
        ('BACKGROUND', (5,3), (5,3), colors.HexColor('#111111')),  # dark T2
        ('TEXTCOLOR',  (5,3), (5,3), C_WHITE),
        ('BACKGROUND', (4,4), (4,4), colors.HexColor('#F5F5F5')),  # bright T1 late sub
        ('BACKGROUND', (5,4), (5,4), colors.HexColor('#F5F5F5')),  # bright T2 late sub
        ('BACKGROUND', (5,5), (5,5), colors.HexColor('#111111')),  # black chronic
        ('TEXTCOLOR',  (5,5), (5,5), C_WHITE),
        ('BACKGROUND', (6,5), (6,5), colors.HexColor('#000000')),  # SWI black
        ('TEXTCOLOR',  (6,5), (6,5), C_WHITE),
        ('ROWBACKGROUNDS', (0,1), (3,-1),
         [colors.HexColor('#F9EBF8'), colors.HexColor('#F5EEF8'),
          colors.HexColor('#EBF5FB'), colors.HexColor('#E8F8F5'),
          colors.HexColor('#FDFEFE')]),
        ('GRID',       (0,0), (-1,-1), 0.4, C_MID_GRAY),
        ('VALIGN',     (0,0), (-1,-1), 'TOP'),
        ('TOPPADDING', (0,0), (-1,-1), 3),
        ('BOTTOMPADDING', (0,0), (-1,-1), 3),
        ('FONTNAME',   (0,1), (0,-1), 'Helvetica-Bold'),
        ('TEXTCOLOR',  (0,1), (0,-1), C_PURPLE),
    ]))
    story.append(mri_h_t)
    story.append(Spacer(1, 0.2*cm))
    story.append(Paragraph(
        "<b>Memory aid — T1/T2 for hemorrhage:</b>  "
        "<i>\"It's Bright After Bleeding\"</i> — "
        "Iso → Iso → <b>T1 Bright</b> → <b>Both Bright</b> → Dark (hemosiderin ring)",
        note_style))

    story.append(PageBreak())

    # ══════════════════════════════════════════════════════════════════════════
    # PAGE 7: MRI INFARCT TIMELINE
    # ══════════════════════════════════════════════════════════════════════════
    story.append(SectionHeader("6.  MRI INFARCTION TIMELINE",
        "How ischemic infarct evolves on each MRI sequence", bg=C_TEAL, w=avail_w))
    story.append(Spacer(1, 0.3*cm))

    story.append(MRIInfarctTimeline(w=avail_w, h=6.0*cm))
    story.append(Spacer(1, 0.4*cm))

    mri_inf_data = [
        ["Phase", "Time", "DWI", "ADC", "T2/FLAIR", "T1", "Clinical Note"],
        ["Hyperacute", "0–6 hrs",
         "BRIGHT ★\n(from 5 min)", "DARK ★",
         "Normal or\nvery subtle", "Normal",
         "DWI+/ADC− = infarction\nconfirmed. CT often normal.\nStart tPA workup."],
        ["Acute", "6 hrs–5 days",
         "BRIGHT ★", "DARK ★",
         "Clearly\nBRIGHT\n+ mass effect", "Hypointense",
         "Full extent visible on DWI.\nMass effect may mimic tumor.\nPeak cytotoxic edema."],
        ["Subacute\n(early)", "5–14 days",
         "Fading\n(T2 shine-\nthrough)", "Pseudonorm-\nalizing\n(~day 7-10)",
         "BRIGHT\nMass effect\nresolvingv", "Hypointense\nGyral\nenhancement",
         "ADC PSEUDONORMALIZATION\nTrap! ADC looks normal but\nT2/FLAIR still bright."],
        ["Chronic", ">3 wks",
         "DARK\n(unrestricted)", "BRIGHT\n(free water)",
         "BRIGHT\ngliosis\n(permanent)", "Hypointense\n(cavity)",
         "Encephalomalacia.\nEx-vacuo dilation of\nadjacent ventricle."],
    ]
    mri_inf_t = Table(mri_inf_data,
        colWidths=[2.2*cm, 1.8*cm, 1.8*cm, 1.8*cm, 2.2*cm, 2.2*cm, 4.0*cm])
    mri_inf_t.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), C_TEAL),
        ('TEXTCOLOR',  (0,0), (-1,0), C_WHITE),
        ('FONTNAME',   (0,0), (-1,0), 'Helvetica-Bold'),
        ('FONTSIZE',   (0,0), (-1,-1), 7.5),
        ('ROWBACKGROUNDS', (0,1), (-1,-1),
         [colors.HexColor('#E8F8F5'), colors.HexColor('#FDFEFE'),
          colors.HexColor('#EBF5FB'), colors.HexColor('#F0F9FF')]),
        ('GRID',       (0,0), (-1,-1), 0.4, C_MID_GRAY),
        ('VALIGN',     (0,0), (-1,-1), 'TOP'),
        ('TOPPADDING', (0,0), (-1,-1), 4),
        ('BOTTOMPADDING', (0,0), (-1,-1), 4),
        ('FONTNAME',   (2,1), (3,2), 'Helvetica-Bold'),
        ('BACKGROUND', (2,1), (2,2), colors.HexColor('#F5F5F5')),  # DWI bright
        ('BACKGROUND', (3,1), (3,2), colors.HexColor('#111111')),  # ADC dark
        ('TEXTCOLOR',  (3,1), (3,2), C_WHITE),
        ('BACKGROUND', (2,4), (2,4), colors.HexColor('#333333')),  # DWI dark chronic
        ('TEXTCOLOR',  (2,4), (2,4), C_WHITE),
        ('BACKGROUND', (3,4), (3,4), colors.HexColor('#EEEEEE')),  # ADC bright chronic
    ]))
    story.append(mri_inf_t)
    story.append(Spacer(1, 0.3*cm))

    story.append(Paragraph(
        "<b>DWI-FLAIR Mismatch (Wake-up Stroke):</b> "
        "If DWI is bright but FLAIR is normal/subtle → infarct is likely &lt;4.5 hours old "
        "→ consider thrombolysis even if time of onset is unknown. "
        "If both DWI and FLAIR are bright → infarct is probably &gt;4.5–6 hours.", callout_style))
    story.append(Spacer(1, 0.2*cm))
    story.append(Paragraph(
        "<b>ADC Pseudonormalization Trap:</b> "
        "Around days 7–14, vasogenic edema water dilutes the restricted diffusion signal "
        "and ADC may normalize. Do not mistake this for normal or resolving infarct — "
        "FLAIR and clinical history will clarify.", callout_style))

    story.append(PageBreak())

    # ══════════════════════════════════════════════════════════════════════════
    # PAGE 8: MASTER COMPARISON + WORKFLOW
    # ══════════════════════════════════════════════════════════════════════════
    story.append(SectionHeader("7.  MASTER COMPARISON: BLEED vs. INFARCT",
        "Quick-reference summary for clinical decision-making", bg=C_NAVY, w=avail_w))
    story.append(Spacer(1, 0.3*cm))

    master_data = [
        ["Feature", "HEMORRHAGE", "INFARCT"],
        ["CT density\n(acute)", "HYPERDENSE (white)\n~60–80 HU", "HYPODENSE (dark)\n<30 HU (after 6 hrs)"],
        ["CT visibility", "Immediately visible", "Subtle <6 hrs; clear >6 hrs"],
        ["CT shape", "Round/oval, well-defined", "Wedge-shaped, follows\nvascular territory"],
        ["Location clue", "Deep (HTN): BG, thalamus\npons, cerebellum", "Cortex + subcortex\nfollowing arterial supply"],
        ["MRI T1", "Iso → Iso → BRIGHT →\nBRIGHT → Dark (rim)", "Normal → Hypo → Hypo"],
        ["MRI T2", "Bright → DARK → DARK\n→ BRIGHT → BLACK", "Normal → BRIGHT →\nBRIGHT (permanent gliosis)"],
        ["MRI DWI", "Dark (blood quenches\nDWI after few days)", "BRIGHT ★ (from 5 min\nonset — gold standard)"],
        ["MRI SWI", "BLACK 'blooms'\n(all stages)", "Negative\n(unless hemorrhagic Tx)"],
        ["Mass effect", "Yes, immediate +\nsurrounding edema", "Peaks day 3–5 (large\ninfarcts)"],
        ["Enhancement\n(+contrast)", "Peripheral ring in\nsubacute phase", "Gyral enhancement\n>1–2 weeks"],
        ["Chronic\nappearance", "Hypodense cavity ±\ncalcification; hemosiderin\nring on MRI", "Encephalomalacia\nEx-vacuo dilation"],
    ]
    master_t = Table(master_data, colWidths=[3.5*cm, 6.5*cm, 6.0*cm])
    master_t.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), C_NAVY),
        ('TEXTCOLOR',  (0,0), (-1,0), C_WHITE),
        ('FONTNAME',   (0,0), (-1,0), 'Helvetica-Bold'),
        ('FONTSIZE',   (0,0), (-1,-1), 8),
        ('BACKGROUND', (1,1), (1,-1), colors.HexColor('#FDEDEC')),
        ('BACKGROUND', (2,1), (2,-1), colors.HexColor('#EBF5FB')),
        ('ROWBACKGROUNDS', (0,1), (0,-1),
         [C_LIGHT_GRAY, colors.HexColor('#D5D8DC')]*10),
        ('GRID',       (0,0), (-1,-1), 0.4, C_MID_GRAY),
        ('VALIGN',     (0,0), (-1,-1), 'TOP'),
        ('TOPPADDING', (0,0), (-1,-1), 4),
        ('BOTTOMPADDING', (0,0), (-1,-1), 4),
        ('FONTNAME',   (0,1), (0,-1), 'Helvetica-Bold'),
        ('FONTNAME',   (1,1), (1,-1), 'Helvetica'),
        ('FONTNAME',   (2,1), (2,-1), 'Helvetica'),
    ]))
    story.append(master_t)
    story.append(Spacer(1, 0.4*cm))

    # Emergency workflow
    story.append(SectionHeader("8.  EMERGENCY IMAGING WORKFLOW",
        "Step-by-step approach for acute stroke / head injury", bg=C_GREEN, w=avail_w))
    story.append(Spacer(1, 0.3*cm))

    workflow_data = [
        ["Step", "Modality", "Question Asked", "Action Based On Result"],
        ["1", "NCCT Brain\n(Non-contrast CT)",
         "Is there hemorrhage?\nIs there a mass?",
         "YES hemorrhage → manage accordingly, NO tPA\nNO hemorrhage → proceed to step 2"],
        ["2", "CT Angiography\n(CTA) Arch→Skull",
         "Is there a Large\nVessel Occlusion (LVO)?",
         "LVO present → consider thrombectomy\nCheck for hyperdense vessel sign"],
        ["3", "CT Perfusion\n(if available)",
         "Is there salvageable\npenumbra vs. core?",
         "Mismatch (large penumbra) → proceed with\nthrombectomy even at extended time window"],
        ["4", "MRI DWI+ADC\n+FLAIR+SWI\n(if CT negative\nor wake-up stroke)",
         "Confirm infarct?\nHow old? Microbleeds?",
         "DWI+/ADC− → acute infarct\nDWI-FLAIR mismatch → <4.5 hrs window\nSWI dark → hemorrhagic component"],
        ["5", "MRA or CTA\nfollow-up",
         "Vessel patency?\nRecanalisation?",
         "Assess response to thrombolysis/thrombectomy\nLook for residual stenosis"],
    ]
    wf_t = Table(workflow_data, colWidths=[0.7*cm, 3.0*cm, 4.0*cm, 8.3*cm])
    wf_t.setStyle(TableStyle([
        ('BACKGROUND', (0,0), (-1,0), C_GREEN),
        ('TEXTCOLOR',  (0,0), (-1,0), C_WHITE),
        ('FONTNAME',   (0,0), (-1,0), 'Helvetica-Bold'),
        ('FONTSIZE',   (0,0), (-1,-1), 7.5),
        ('ROWBACKGROUNDS', (0,1), (-1,-1),
         [colors.HexColor('#EAFAF1'), colors.HexColor('#FDFEFE')]*4),
        ('GRID',       (0,0), (-1,-1), 0.4, C_MID_GRAY),
        ('VALIGN',     (0,0), (-1,-1), 'TOP'),
        ('TOPPADDING', (0,0), (-1,-1), 4),
        ('BOTTOMPADDING', (0,0), (-1,-1), 4),
        ('FONTNAME',   (0,1), (0,-1), 'Helvetica-Bold'),
        ('TEXTCOLOR',  (0,1), (0,-1), C_GREEN),
        ('ALIGN',      (0,1), (0,-1), 'CENTER'),
    ]))
    story.append(wf_t)
    story.append(Spacer(1, 0.3*cm))

    # FLAIR vascular sign note
    story.append(Paragraph(
        "<b>FLAIR Vascular Sign:</b> Hyperintense signal within an artery on FLAIR (not DWI) "
        "indicates slow/absent flow distal to an occlusion — a marker of perfusion failure "
        "even before infarction is visible. On SWI, a thrombosed vessel shows as a markedly "
        "hypointense 'blooming' serpiginous structure — the <b>susceptibility vessel sign</b>.",
        callout_style))

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

    # Sources
    story.append(HRFlowable(width=avail_w, thickness=0.5, color=C_MID_GRAY))
    story.append(Spacer(1, 0.15*cm))
    story.append(Paragraph(
        "<b>Sources:</b> Adams and Victor's Principles of Neurology 12th Ed.  |  "
        "Bradley and Daroff's Neurology in Clinical Practice  |  "
        "Grainger & Allison's Diagnostic Radiology  |  "
        "Neuroanatomy through Clinical Cases 3rd Ed.  |  Goldman-Cecil Medicine  |  "
        "Orris Medical Library",
        note_style))

    # ── Page number footer ────────────────────────────────────────────────────
    def add_page_number(canvas, doc):
        canvas.saveState()
        canvas.setFont("Helvetica", 8)
        canvas.setFillColor(C_MID_GRAY)
        canvas.drawCentredString(PAGE_W/2, 1.2*cm,
            f"Brain CT & MRI Reference Guide  |  Page {doc.page}")
        canvas.setFillColor(C_LIGHTBLUE)
        canvas.rect(2*cm, 1.0*cm, PAGE_W-4*cm, 0.05*cm, fill=1, stroke=0)
        canvas.restoreState()

    doc.build(story, onFirstPage=add_page_number, onLaterPages=add_page_number)
    print(f"PDF saved to: {output_path}")


if __name__ == "__main__":
    build_pdf("/home/daytona/workspace/brain-imaging-guide/Brain_CT_MRI_Reference_Guide.pdf")
Running Command

cd /home/daytona/workspace/brain-imaging-guide && python build_pdf.py 2>&1

The CoverPage flowable needs a fixed size. Let me fix it:
Editing File

~/brain-imaging-guide/build_pdf.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/brain-imaging-guide/build_pdf.py'
I need to remove the duplicate wrap that was already there and fix the issue. Let me check the cover page section:
Searching Content

"def wrap" in ~/brain-imaging-guide

Reading File
The CoverPage wrap is already there at line 926. The problem is the cover page height PAGE_H - 4*cm is too large for the frame. The issue is the cover page + the story.append(CoverPage()) - the cover page height should not exceed the available frame height. Let me fix this to use a smaller height and also ensure the cover page fits within a single frame:
Editing File

~/brain-imaging-guide/build_pdf.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/brain-imaging-guide/build_pdf.py'
Running Command

cd /home/daytona/workspace/brain-imaging-guide && python build_pdf.py 2>&1

The hexval() format issue. Let me fix all the color contrast logic to use a simpler approach:
Editing File

~/brain-imaging-guide/build_pdf.py

Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/brain-imaging-guide/build_pdf.py'
Running Command

cd /home/daytona/workspace/brain-imaging-guide && python build_pdf.py 2>&1

PDF built successfully. Let me verify it:
Running Command

ls -lh /home/daytona/workspace/brain-imaging-guide/Brain_CT_MRI_Reference_Guide.pdf && python3 -c "from pypdf import PdfReader; r=PdfReader('/home/daytona/workspace/brain-imaging-guide/Brain_CT_MRI_Reference_Guide.pdf'); print(f'Pages: {len(r.pages)}'); print('Title:', r.metadata.title)"

Your 9-page PDF reference guide is ready:

Brain CT MRI Reference Guide

PDF Document · PDF


Here's what's inside each page:
PageContent
1 - CoverColor-coded title page with brain schematic showing bleed vs. infarct
2 - CT FundamentalsHounsfield Unit gradient scale bar, window settings table, side-by-side CT brain schematics (hyperdense hemorrhage vs. hypodense infarct), early CT signs of stroke
3 - CT TimelineColor-coded timeline diagram (darker = darker on CT) for both hemorrhage and infarct across all 5 phases, plus detailed comparison table with the fogging effect warning
4 - Hemorrhage TypesLabeled cross-section schematic of EDH/SDH/SAH/ICH with table of shapes, locations, causes, and key clues
5 - MRI SequencesFull table of all sequences (T1/T2/FLAIR/DWI/ADC/SWI/MRA) with what each shows, plus 3-panel DWI-ADC schematic (acute infarct vs. gliosis vs. SWI hemorrhage)
6 - MRI Hemorrhage TimelineSignal grid (actual gray shades showing MRI appearance) across 5 stages with mechanism table - oxyhemoglobin through hemosiderin
7 - MRI Infarct TimelineColor-coded grid for DWI/ADC/FLAIR/T1 across phases, with DWI-FLAIR mismatch rule and ADC pseudonormalization trap
8 - Master ComparisonSide-by-side bleed vs. infarct on all modalities + Emergency imaging workflow (NCCT → CTA → Perfusion → MRI)

I didn’t find any real images in the pdf so that it’s easy for me to learn :(

Let me pull all the real brain imaging content from the textbooks in parallel:
Finding Sources
Finding Sources
Finding Sources
Now let me read all those sections to extract image URLs in parallel:
Reading File
Reading File
Reading File
Excellent! Found rich image content. Let me read more sections to collect all images:
Reading File
Reading File
Reading File
Reading File
Reading File
Reading File
Reading File
I now have all the image URLs. Let me compile them and download all images to sandbox, then rebuild the PDF:
Running Command

mkdir -p /home/daytona/workspace/brain-imaging-guide/images && cd /home/daytona/workspace/brain-imaging-guide/images && \ # Download all real brain imaging images from textbooks wget -q -O img_acute_parenchymal_hemorrhage.png "https://cdn.orris.care/cdss_images/f51679d1ddf27c82f627a9f081af7dee8466a719aaa36dc9bdca4b952a53be5f.png" && \ wget -q -O img_late_subacute_hemorrhage_t1.png "https://cdn.orris.care/cdss_images/1205f4b4d48201e219935c5f7534d06061e9034356aa9a09a9ab333c9afb898e.png" && \ wget -q -O img_late_subacute_hemorrhage_t2.png "https://cdn.orris.care/cdss_images/7ba0bee47186749ebec8e59795dcd43a9a8901f4aca0380c9e6e7f973d8a532f.png" && \ wget -q -O img_chronic_hemorrhage_t2.png "https://cdn.orris.care/cdss_images/0c46318256dd35ec1a08044f57aa7014e683e0ec2a365c8e15863301e31b0337.png" && \ wget -q -O img_chronic_hemorrhage_gre.png "https://cdn.orris.care/cdss_images/b6320e816bd8d3df79044c080d4b8cd2bfa8d1330069b0a86fe21e6157a7e2df.png" && \ wget -q -O img_skull_fracture_sah_ct.png "https://cdn.orris.care/cdss_images/2ac36e75c6d7e939cbe429dc63c2e8eefa524a95b43de00172f73ebe50e5194c.png" && \ wget -q -O img_acute_subdural_mri.png "https://cdn.orris.care/cdss_images/9ba034e262eb2d087b9ef35c1a0138698277e5859176b22a4ed314e679fc4dce.png" && \ wget -q -O img_haemorrhagic_transformation.png "https://cdn.orris.care/cdss_images/88464415e093083fe605fd4da166aad72244d208a5a82d16e93e85cb5e1e7118.png" && \ wget -q -O img_wake_up_stroke_dwi_flair.png "https://cdn.orris.care/cdss_images/c5b7a81bb16ee2aab6a978589938740e8b6bbe1efde86ba8a14e110a1213efe8.png" && \ wget -q -O img_subacute_chronic_infarct.png "https://cdn.orris.care/cdss_images/b4116dbe264a05fa9994ecd4bae07349f577c1a0432fdf3861e4b142f7e4a08d.png" && \ wget -q -O img_acute_mca_infarct_dwi.png "https://cdn.orris.care/cdss_images/783c4c9969b6c071852848653cf19417684cbad0502c76eeeb27a28839e21d79.png" && \ wget -q -O img_acute_stroke_ct_mri.png "https://cdn.orris.care/cdss_images/2fdbf6e88505f6867580e6f0f8290b0263b159b81638edd0ecd044fd045ee505.png" && \ wget -q -O img_chronic_infarct_free_diffusion.png "https://cdn.orris.care/cdss_images/b4116dbe264a05fa9994ecd4bae07349f577c1a0432fdf3861e4b142f7e4a08d.png" && \ echo "Done" && ls -la

All 13 real images downloaded. Now let me build the comprehensive PDF with real images:
Writing File

~/brain-imaging-guide/build_pdf_v2.py

#!/usr/bin/env python3
"""
Brain CT & MRI Reference Guide - Version 2
Includes REAL textbook images alongside explanatory diagrams.
"""

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

IMG_DIR = "/home/daytona/workspace/brain-imaging-guide/images"
OUT_PATH = "/home/daytona/workspace/brain-imaging-guide/Brain_CT_MRI_Reference_Guide_v2.pdf"

# ── Colors ────────────────────────────────────────────────────────────────────
C_NAVY      = colors.HexColor('#0D1B2A')
C_BLUE      = colors.HexColor('#1B4F72')
C_LIGHTBLUE = colors.HexColor('#2E86AB')
C_TEAL      = colors.HexColor('#17A589')
C_RED       = colors.HexColor('#C0392B')
C_ORANGE    = colors.HexColor('#E67E22')
C_GREEN     = colors.HexColor('#1E8449')
C_PURPLE    = colors.HexColor('#7D3C98')
C_DARK_GRAY = colors.HexColor('#2C3E50')
C_MID_GRAY  = colors.HexColor('#7F8C8D')
C_LIGHT_GRAY= colors.HexColor('#ECF0F1')
C_WHITE     = colors.white
C_BLACK     = colors.black
C_CREAM     = colors.HexColor('#FDFEFE')

PAGE_W, PAGE_H = A4
AVAIL_W = PAGE_W - 4*cm

# ── Styles ─────────────────────────────────────────────────────────────────────
styles = getSampleStyleSheet()

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

TITLE_S  = make_style('T1', fontName='Helvetica-Bold', fontSize=18, leading=22,
                       textColor=C_WHITE, alignment=TA_CENTER)
HEAD_S   = make_style('H1', fontName='Helvetica-Bold', fontSize=13, leading=16,
                       textColor=C_WHITE, alignment=TA_LEFT)
H2_S     = make_style('H2', fontName='Helvetica-Bold', fontSize=10.5, leading=13,
                       textColor=C_NAVY, spaceAfter=3, spaceBefore=6)
BODY_S   = make_style('BD', fontName='Helvetica', fontSize=8.5, leading=12,
                       textColor=C_DARK_GRAY, spaceAfter=4, alignment=TA_JUSTIFY)
CAPTION_S= make_style('CP', fontName='Helvetica-Oblique', fontSize=7.5, leading=10,
                       textColor=C_MID_GRAY, spaceAfter=4, alignment=TA_CENTER)
LABEL_S  = make_style('LB', fontName='Helvetica-Bold', fontSize=8, leading=10,
                       textColor=C_NAVY, spaceAfter=2)
NOTE_S   = make_style('NT', fontName='Helvetica-Oblique', fontSize=7.5, leading=10,
                       textColor=C_MID_GRAY, spaceAfter=3)
CALLOUT_S= make_style('CO', fontName='Helvetica-Bold', fontSize=8.5, leading=12,
                       textColor=C_NAVY, spaceAfter=4,
                       backColor=colors.HexColor('#EBF5FB'),
                       borderPad=5)
WARN_S   = make_style('WN', fontName='Helvetica-Bold', fontSize=8.5, leading=12,
                       textColor=colors.HexColor('#7B241C'), spaceAfter=4,
                       backColor=colors.HexColor('#FDEDEC'),
                       borderPad=5)

# ── Helper: section banner ────────────────────────────────────────────────────
class Banner(Flowable):
    def __init__(self, title, subtitle="", bg=C_BLUE, w=None, h=None):
        Flowable.__init__(self)
        self.title    = title
        self.subtitle = subtitle
        self.bg       = bg
        self.w        = w or AVAIL_W
        self.h        = h or (1.5*cm if subtitle else 1.1*cm)

    def wrap(self, aw, ah):
        return self.w, self.h

    def draw(self):
        c = self.canv
        c.setFillColor(self.bg)
        c.roundRect(0, 0, self.w, self.h, 6, fill=1, stroke=0)
        c.setFillColor(C_WHITE)
        c.setFont("Helvetica-Bold", 13)
        c.drawString(0.4*cm, self.h - 0.58*cm, self.title)
        if self.subtitle:
            c.setFont("Helvetica", 8.5)
            c.setFillColor(colors.HexColor('#AED6F1'))
            c.drawString(0.4*cm, 0.22*cm, self.subtitle)

# ── Helper: image with border + caption ──────────────────────────────────────
def img_block(path, width, caption, label_color=C_NAVY):
    """Returns a list of flowables: bordered image + caption paragraph."""
    items = []
    if os.path.exists(path):
        img = RLImage(path, width=width)
        img.hAlign = 'CENTER'
        items.append(img)
    if caption:
        items.append(Paragraph(caption, CAPTION_S))
    return items

def img_table(rows):
    """
    rows: list of lists of flowables (table cells)
    Creates a centered table of images with spacing.
    """
    col_w = AVAIL_W / max(len(r) for r in rows)
    flat  = []
    for row in rows:
        flat.append(row)
    t = Table(flat, colWidths=[col_w]*len(flat[0]) if flat else [AVAIL_W])
    t.setStyle(TableStyle([
        ('VALIGN',      (0,0), (-1,-1), 'TOP'),
        ('ALIGN',       (0,0), (-1,-1), 'CENTER'),
        ('TOPPADDING',  (0,0), (-1,-1), 4),
        ('BOTTOMPADDING',(0,0),(-1,-1), 4),
        ('LEFTPADDING', (0,0), (-1,-1), 3),
        ('RIGHTPADDING',(0,0), (-1,-1), 3),
    ]))
    return t

# ── Helper: CT timeline diagram ───────────────────────────────────────────────
class CTTimelineBar(Flowable):
    def __init__(self, w=None, h=3.8*cm):
        Flowable.__init__(self)
        self.w = w or AVAIL_W
        self.h = h

    def wrap(self, aw, ah): return self.w, self.h

    def draw(self):
        c   = self.canv
        w, h = self.w, self.h
        phases = [("Hyperacute\n0-6 hrs",0.18),("Acute\n6-24 hrs",0.18),
                  ("Subacute\n1-7 days",0.22),("Late Sub\n1-3 wks",0.22),("Chronic\n>3 wks",0.20)]
        hem_shade = [0.94, 0.92, 0.55, 0.45, 0.28]
        inf_shade = [0.58, 0.42, 0.33, 0.40, 0.22]
        hem_lbl = ["BRIGHT\nHyperdense","BRIGHT\nHyperdense","ISODENSE\n(can miss!)","ISODENSE\n→Hypodense","HYPODENSE"]
        inf_lbl = ["NORMAL\nor Subtle","HYPODENSE\nGW blur","CLEARLY\nHypodense","FOGGING\nEffect","ENCEPHALO-\nMALACIA"]

        hdr_h = h*0.28; row_h = h*0.34
        y_hem = h - hdr_h - row_h
        y_inf = h - hdr_h - 2*row_h

        x = 0.0
        for i,(ph,frac) in enumerate(phases):
            bw = w*frac
            bg = C_LIGHT_GRAY if i%2==0 else colors.HexColor('#D5D8DC')
            c.setFillColor(bg); c.setStrokeColor(C_DARK_GRAY); c.setLineWidth(0.4)
            c.rect(x, h-hdr_h, bw, hdr_h, fill=1, stroke=1)
            c.setFillColor(C_DARK_GRAY); c.setFont("Helvetica-Bold",7)
            for li,ln in enumerate(ph.split('\n')):
                c.drawCentredString(x+bw/2, h-0.2*cm-li*0.28*cm, ln)

            sh = hem_shade[i]
            c.setFillColor(colors.Color(sh,sh,sh)); c.setStrokeColor(C_DARK_GRAY); c.setLineWidth(0.4)
            c.rect(x, y_hem, bw, row_h, fill=1, stroke=1)
            tc = C_DARK_GRAY if sh>0.6 else C_WHITE
            c.setFillColor(tc); c.setFont("Helvetica-Bold",6.5)
            for li,ln in enumerate(hem_lbl[i].split('\n')):
                c.drawCentredString(x+bw/2, y_hem+row_h-0.22*cm-li*0.26*cm, ln)

            si = inf_shade[i]
            c.setFillColor(colors.Color(si,si,si)); c.setStrokeColor(C_DARK_GRAY); c.setLineWidth(0.4)
            c.rect(x, y_inf, bw, row_h, fill=1, stroke=1)
            tc2 = C_DARK_GRAY if si>0.6 else C_WHITE
            c.setFillColor(tc2); c.setFont("Helvetica-Bold",6.5)
            for li,ln in enumerate(inf_lbl[i].split('\n')):
                c.drawCentredString(x+bw/2, y_inf+row_h-0.22*cm-li*0.26*cm, ln)
            x += bw

        c.setFillColor(C_RED); c.setFont("Helvetica-Bold",8)
        c.drawRightString(-0.1*cm, y_hem+row_h*0.4, "BLEED")
        c.setFillColor(C_BLUE)
        c.drawRightString(-0.1*cm, y_inf+row_h*0.4, "INFARCT")

# ── Helper: MRI hemorrhage signal grid ────────────────────────────────────────
class MRIHemGrid(Flowable):
    def __init__(self, w=None, h=5.5*cm):
        Flowable.__init__(self)
        self.w = w or AVAIL_W
        self.h = h

    def wrap(self, aw, ah): return self.w, self.h

    def draw(self):
        c   = self.canv
        w, h = self.w, self.h
        phases=["Hyperacute\n0-6h\nOxyHb","Acute\n1-3d\nDeoxyHb",
                "Early Sub\n3-7d\nIntracel\nMetHb","Late Sub\n7-30d\nExtracel\nMetHb","Chronic\n>14d\nHemosiderin"]
        seqs=["T1","T2","SWI"]
        sigs={"T1":[0.5,0.5,1.0,1.0,0.15],"T2":[0.85,0.05,0.1,0.9,0.0],"SWI":[0.25,0.02,0.03,0.1,0.0]}
        lbls={"T1":["Iso\n(Gray)","Iso\n(Gray)","BRIGHT\n(White)","BRIGHT\n(White)","Hypo\n(Dark)"],
              "T2":["Bright\nLt Gray","DARK\nBlack","DARK\nBlack","BRIGHT\nWhite","BLACK"],
              "SWI":["Dark","VERY\nDARK","VERY\nDARK","Dark","BLACK\n'Blooms'"]}

        hw = 1.3*cm; col_w = (w-hw)/len(phases)
        hdr_h = h*0.22; row_h = (h-hdr_h)/(len(seqs)+0.2)

        # Corner
        c.setFillColor(C_PURPLE); c.setStrokeColor(C_DARK_GRAY); c.setLineWidth(0.4)
        c.rect(0, h-hdr_h, hw, hdr_h, fill=1, stroke=1)
        c.setFillColor(C_WHITE); c.setFont("Helvetica-Bold",7)
        c.drawCentredString(hw/2, h-hdr_h/2-0.1*cm, "SEQ")

        # Phase headers
        x = hw
        for i,ph in enumerate(phases):
            bg = colors.HexColor('#F9EBF8') if i%2==0 else colors.HexColor('#E8DAEF')
            c.setFillColor(bg); c.setStrokeColor(C_DARK_GRAY); c.setLineWidth(0.4)
            c.rect(x, h-hdr_h, col_w, hdr_h, fill=1, stroke=1)
            c.setFillColor(C_PURPLE); c.setFont("Helvetica-Bold",6)
            lns=ph.split('\n')
            for li,ln in enumerate(lns):
                c.drawCentredString(x+col_w/2, h-0.15*cm-li*0.22*cm, ln)
            x += col_w

        # Rows
        for si,seq in enumerate(seqs):
            y = h - hdr_h - (si+1)*row_h
            c.setFillColor(C_PURPLE); c.setStrokeColor(C_DARK_GRAY); c.setLineWidth(0.4)
            c.rect(0, y, hw, row_h, fill=1, stroke=1)
            c.setFillColor(C_WHITE); c.setFont("Helvetica-Bold",8)
            c.drawCentredString(hw/2, y+row_h/2-0.1*cm, seq)
            x = hw
            for pi in range(len(phases)):
                sg = sigs[seq][pi]
                c.setFillColor(colors.Color(sg,sg,sg)); c.setStrokeColor(C_DARK_GRAY); c.setLineWidth(0.3)
                c.rect(x, y, col_w, row_h, fill=1, stroke=1)
                tc = C_WHITE if sg<0.5 else C_DARK_GRAY
                c.setFillColor(tc); c.setFont("Helvetica-Bold",6.5)
                lns2=lbls[seq][pi].split('\n')
                for li,ln in enumerate(lns2):
                    c.drawCentredString(x+col_w/2, y+row_h-0.18*cm-li*0.24*cm, ln)
                x += col_w

# ── Helper: MRI infarct signal grid ──────────────────────────────────────────
class MRIInfarctGrid(Flowable):
    def __init__(self, w=None, h=4.5*cm):
        Flowable.__init__(self)
        self.w = w or AVAIL_W
        self.h = h

    def wrap(self, aw, ah): return self.w, self.h

    def draw(self):
        c   = self.canv
        w, h = self.w, self.h
        phases=["Hyperacute\n0-6h","Acute\n6h-5d","Subacute\n5-14d","Late Sub\n2-3wk","Chronic\n>3wks"]
        seqs=["DWI","ADC","FLAIR"]
        sigs={"DWI":[0.95,0.95,0.80,0.65,0.1],"ADC":[0.05,0.05,0.15,0.55,0.88],"FLAIR":[0.30,0.82,0.88,0.85,0.82]}
        lbls={"DWI":["BRIGHT★","BRIGHT★","Bright\n(fading)","Fading\nshine","DARK"],"ADC":["DARK★","DARK★","→Pseudo\nnorm","Pseudo-\nnormal","BRIGHT"],"FLAIR":["Normal\nor subtle","BRIGHT","BRIGHT\n+Swelling","Bright","Bright\nGliosis"]}

        hw = 1.3*cm; col_w = (w-hw)/len(phases)
        hdr_h = h*0.24; row_h = (h-hdr_h)/(len(seqs)+0.2)

        c.setFillColor(C_TEAL); c.setStrokeColor(C_DARK_GRAY); c.setLineWidth(0.4)
        c.rect(0, h-hdr_h, hw, hdr_h, fill=1, stroke=1)
        c.setFillColor(C_WHITE); c.setFont("Helvetica-Bold",7)
        c.drawCentredString(hw/2, h-hdr_h/2-0.1*cm, "SEQ")

        x = hw
        for i,ph in enumerate(phases):
            bg = colors.HexColor('#E8F8F5') if i%2==0 else colors.HexColor('#D1F2EB')
            c.setFillColor(bg); c.setStrokeColor(C_DARK_GRAY); c.setLineWidth(0.4)
            c.rect(x, h-hdr_h, col_w, hdr_h, fill=1, stroke=1)
            c.setFillColor(C_TEAL); c.setFont("Helvetica-Bold",6.5)
            for li,ln in enumerate(ph.split('\n')):
                c.drawCentredString(x+col_w/2, h-0.18*cm-li*0.27*cm, ln)
            x += col_w

        for si,seq in enumerate(seqs):
            y = h - hdr_h - (si+1)*row_h
            c.setFillColor(C_TEAL); c.setStrokeColor(C_DARK_GRAY); c.setLineWidth(0.4)
            c.rect(0, y, hw, row_h, fill=1, stroke=1)
            c.setFillColor(C_WHITE); c.setFont("Helvetica-Bold",8)
            c.drawCentredString(hw/2, y+row_h/2-0.1*cm, seq)
            x = hw
            for pi in range(len(phases)):
                sg = sigs[seq][pi]
                c.setFillColor(colors.Color(sg,sg,sg)); c.setStrokeColor(C_DARK_GRAY); c.setLineWidth(0.3)
                c.rect(x, y, col_w, row_h, fill=1, stroke=1)
                tc = C_WHITE if sg<0.5 else C_DARK_GRAY
                c.setFillColor(tc); c.setFont("Helvetica-Bold",6.5)
                lns2=lbls[seq][pi].split('\n')
                for li,ln in enumerate(lns2):
                    c.drawCentredString(x+col_w/2, y+row_h-0.18*cm-li*0.24*cm, ln)
                x += col_w

        c.setFillColor(C_DARK_GRAY); c.setFont("Helvetica-Oblique",7)
        c.drawString(0, -0.3*cm, "★ DWI bright + ADC dark = TRUE restricted diffusion = ACUTE INFARCT (gold standard diagnosis)")

# ── HU Scale Bar ──────────────────────────────────────────────────────────────
class HUBar(Flowable):
    def __init__(self, w=None, h=1.8*cm):
        Flowable.__init__(self)
        self.w = w or AVAIL_W
        self.h = h
    def wrap(self, aw, ah): return self.w, self.h
    def draw(self):
        c = self.canv; w,h = self.w,self.h
        bx=1.0*cm; bw=w-2.0*cm; by=h*0.45; bh=0.55*cm
        n=150
        for i in range(n):
            s=i/n; c.setFillColor(colors.Color(s,s,s))
            c.rect(bx+s*bw, by, bw/n+0.5, bh, fill=1, stroke=0)
        c.setStrokeColor(C_DARK_GRAY); c.setLineWidth(0.5)
        c.rect(bx, by, bw, bh, fill=0, stroke=1)
        tks=[(0.0,"-1000\nAir"),(0.14,"0\nCSF"),(0.30,"~35\nBrain"),(0.52,"~70\nBlood"),(0.72,"~130\nCalc."),(1.0,">400\nBone")]
        for f,lbl in tks:
            tx=bx+f*bw
            c.setStrokeColor(C_DARK_GRAY); c.setLineWidth(0.5); c.line(tx,by-0.05*cm,tx,by-0.22*cm)
            c.setFillColor(C_DARK_GRAY); c.setFont("Helvetica",6.5)
            for li,ln in enumerate(lbl.split('\n')):
                c.drawCentredString(tx, by-0.3*cm-li*0.18*cm, ln)
        c.setFillColor(C_RED); c.setFont("Helvetica-Bold",7)
        c.drawCentredString(bx+0.52*bw, by+bh+0.15*cm, "BLOOD ~60-80 HU (BRIGHT)")
        c.setFillColor(C_BLUE)
        c.drawCentredString(bx+0.22*bw, by+bh+0.15*cm, "INFARCT <30 HU (DARK)")


# ══════════════════════════════════════════════════════════════════════════════
# BUILD PDF
# ══════════════════════════════════════════════════════════════════════════════
def build():
    doc = SimpleDocTemplate(OUT_PATH, pagesize=A4,
        leftMargin=2*cm, rightMargin=2*cm, topMargin=2*cm, bottomMargin=2*cm,
        title="Brain CT & MRI: Bleed vs Infarct - Visual Reference Guide",
        author="Orris Medical Reference")

    story = []

    # ── COVER ──────────────────────────────────────────────────────────────────
    class Cover(Flowable):
        def __init__(self):
            Flowable.__init__(self)
            self.w = AVAIL_W; self.h = 24*cm
        def wrap(self,aw,ah): return self.w,self.h
        def draw(self):
            c=self.canv; w,h=self.w,self.h
            c.setFillColor(C_NAVY); c.rect(0,h*0.42,w,h*0.58,fill=1,stroke=0)
            c.setFillColor(C_BLUE); c.rect(0,h*0.28,w,h*0.14,fill=1,stroke=0)
            c.setFillColor(colors.HexColor('#1A5276')); c.rect(0,0,w,h*0.28,fill=1,stroke=0)
            c.setFillColor(C_WHITE); c.setFont("Helvetica-Bold",24)
            c.drawCentredString(w/2,h*0.82,"BRAIN CT & MRI")
            c.setFont("Helvetica-Bold",17)
            c.drawCentredString(w/2,h*0.74,"VISUAL READING GUIDE")
            c.setFont("Helvetica",13); c.setFillColor(colors.HexColor('#AED6F1'))
            c.drawCentredString(w/2,h*0.66,"Hemorrhage  •  Infarction  •  Timeline")
            c.drawCentredString(w/2,h*0.61,"Acute  |  Subacute  |  Chronic")
            # Brain icon
            bx=w/2; by=h*0.43; br=2.0*cm
            c.setFillColor(colors.HexColor('#1A1A2E'))
            c.circle(bx,by,br*1.18,fill=1,stroke=0)
            c.setFillColor(colors.HexColor('#7F8C8D'))
            c.circle(bx,by,br,fill=1,stroke=0)
            c.setFillColor(colors.HexColor('#111111'))
            c.ellipse(bx-0.3*cm,by-0.1*cm,bx+0.3*cm,by+0.38*cm,fill=1,stroke=0)
            c.setFillColor(colors.HexColor('#F5F5F5'))
            c.circle(bx+0.65*cm,by+0.35*cm,0.4*cm,fill=1,stroke=0)
            c.setFillColor(colors.HexColor('#505050'))
            c.circle(bx-0.6*cm,by+0.2*cm,0.35*cm,fill=1,stroke=0)
            c.setFillColor(C_WHITE); c.setFont("Helvetica-Bold",7)
            c.drawCentredString(bx+0.65*cm,by+0.15*cm,"BLEED")
            c.drawCentredString(bx-0.6*cm,by+0.02*cm,"INFARCT")
            # Bottom text
            c.setFillColor(C_WHITE); c.setFont("Helvetica-Bold",11)
            c.drawCentredString(w/2,h*0.22,"Real Textbook Images + Explanatory Diagrams")
            c.setFont("Helvetica",9); c.setFillColor(colors.HexColor('#AED6F1'))
            c.drawCentredString(w/2,h*0.16,"CT Density  |  MRI Sequences  |  DWI/ADC  |  SWI  |  Hemorrhage Types")
            c.setFont("Helvetica-Oblique",8.5); c.setFillColor(colors.HexColor('#85C1E9'))
            c.drawCentredString(w/2,h*0.1,"Sources: Adams & Victor's Neurology  •  Bradley & Daroff  •  Grainger & Allison")
            c.setFont("Helvetica",7); c.setFillColor(colors.HexColor('#5D6D7E'))
            c.drawCentredString(w/2,h*0.03,"Orris Medical Reference  |  For educational purposes only")

    story.append(Cover())
    story.append(PageBreak())

    # ── PAGE 1: CT BASICS + HU SCALE ──────────────────────────────────────────
    story.append(Banner("1.  NON-CONTRAST CT BRAIN BASICS",
        "First-line investigation for ALL stroke and head injury presentations", bg=C_NAVY))
    story.append(Spacer(1,0.3*cm))

    story.append(Paragraph("Hounsfield Unit (HU) Density Scale", H2_S))
    story.append(Paragraph(
        "Every CT pixel has a Hounsfield Unit (HU) value. Brain tissue is ~25-40 HU (gray). "
        "<b>Blood clots are denser (~60-80 HU) and appear BRIGHT WHITE (hyperdense). "
        "Infarcted brain loses fluid balance and appears DARK (hypodense, &lt;30 HU).</b> "
        "This single principle lets you differentiate bleed from infarct at a glance.", BODY_S))
    story.append(HUBar(w=AVAIL_W, h=1.9*cm))
    story.append(Spacer(1,0.4*cm))

    story.append(Paragraph("CT Window Settings", H2_S))
    wt = Table([
        ["Window","Width (WW)","Level (WL)","Use For"],
        ["Brain window","~80","~35","Parenchyma, hemorrhage, edema"],
        ["Bone window","~2000","~300","Skull fractures"],
        ["Stroke window","~35","~35","Early subtle ischemia - widens gray/white contrast"],
        ["Subdural window","~200","~60","Subdural hematoma detection"],
    ], colWidths=[3.5*cm,2.3*cm,2.3*cm,7.9*cm])
    wt.setStyle(TableStyle([
        ('BACKGROUND',(0,0),(-1,0),C_BLUE),('TEXTCOLOR',(0,0),(-1,0),C_WHITE),
        ('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),('FONTSIZE',(0,0),(-1,-1),8),
        ('ROWBACKGROUNDS',(0,1),(-1,-1),[C_CREAM,C_LIGHT_GRAY]),
        ('GRID',(0,0),(-1,-1),0.4,C_MID_GRAY),('VALIGN',(0,0),(-1,-1),'MIDDLE'),
        ('TOPPADDING',(0,0),(-1,-1),3),('BOTTOMPADDING',(0,0),(-1,-1),3),
    ]))
    story.append(wt)
    story.append(Spacer(1,0.4*cm))

    # ── REAL IMAGE: SAH + CT skull fracture ────────────────────────────────────
    story.append(Paragraph("Real CT Example: Skull Fracture + Subarachnoid Hemorrhage", H2_S))
    story.append(Paragraph(
        "Below: axial CT scans showing bone window (fracture), brain window (hyperdense SAH in sulci), "
        "and FLAIR MRI confirming subarachnoid blood as hyperintense signal in the cortical sulci.",BODY_S))

    ipath = os.path.join(IMG_DIR, "img_skull_fracture_sah_ct.png")
    if os.path.exists(ipath):
        img = RLImage(ipath, width=AVAIL_W, height=5.5*cm)
        img.hAlign = 'CENTER'
        story.append(img)
    story.append(Paragraph(
        "Fig. A: Bone window - right temporal fracture (arrow). "
        "Fig. B: Brain window - hyperdense contusion + SAH in sulci (arrowheads). "
        "Fig. C: FLAIR MRI - hyperintense subarachnoid blood in sulci.  "
        "[Bradley & Daroff's Neurology in Clinical Practice]", CAPTION_S))
    story.append(Spacer(1,0.2*cm))

    story.append(Paragraph(
        "<b>Key early CT signs of ischemic stroke:</b> "
        "(1) Loss of gray-white differentiation at basal ganglia/insular cortex.  "
        "(2) <b>Insular ribbon sign</b> — blurring of the insular cortex (MCA territory).  "
        "(3) <b>Sulcal effacement</b> — swollen gyri obliterate normal sulci.  "
        "(4) <b>Hyperdense vessel sign</b> — bright MCA/basilar = acute thrombus.  "
        "(5) <b>One-third MCA rule</b> — >1/3 MCA hypodense = contraindication to tPA.", BODY_S))

    story.append(PageBreak())

    # ── PAGE 2: CT TIMELINE ────────────────────────────────────────────────────
    story.append(Banner("2.  CT TIMELINE — HEMORRHAGE vs. INFARCT",
        "How CT density changes over time for each pathology", bg=C_RED))
    story.append(Spacer(1,0.3*cm))

    story.append(CTTimelineBar(w=AVAIL_W, h=3.8*cm))
    story.append(Spacer(1,0.5*cm))

    # ── REAL IMAGE: Subacute & chronic infarct on CT+MRI ──────────────────────
    story.append(Paragraph("Real Images: Subacute & Chronic Infarct Progression", H2_S))
    story.append(Paragraph(
        "The panels below show progressive CT and MRI changes of an infarct as it matures: "
        "CT becomes progressively more hypodense (darker) with volume loss and ventricular dilation; "
        "MRI shows increasing T2/FLAIR hyperintensity and ultimately encephalomalacia.", BODY_S))

    ipath2 = os.path.join(IMG_DIR, "img_subacute_chronic_infarct.png")
    if os.path.exists(ipath2):
        img2 = RLImage(ipath2, width=AVAIL_W, height=5.5*cm)
        img2.hAlign = 'CENTER'
        story.append(img2)
    story.append(Paragraph(
        "Subacute and chronic infarct: CT (A,B) shows progressive hypodensity and volume loss; "
        "MRI T2/FLAIR (C,D) shows progressive hyperintensity; chronic stage (E) = encephalomalacia "
        "approaching CSF signal. [Grainger & Allison's Diagnostic Radiology]", CAPTION_S))

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

    ct_detail = [
        ["Phase","Time","HEMORRHAGE on CT","INFARCT on CT"],
        ["Hyperacute","0-6 hrs","HYPERDENSE (white)\n~60-80 HU, immediate","Normal CT or sulcal effacement only"],
        ["Acute","6-24 hrs","HYPERDENSE\nPeripheral edema halo","Definite HYPODENSE territory\nGray-white differentiation lost"],
        ["Early Sub","1-7 days","Hyperdense → ISODENSE\nMass effect increasing","CLEARLY HYPODENSE\nMass effect peaks day 3-5"],
        ["Late Sub","1-3 wks","ISODENSE — easy to miss!\nCheck for displaced cortex","FOGGING EFFECT\nMay look falsely normal"],
        ["Chronic",">3 wks","HYPODENSE (dark)\nMay calcify; subdural = crescent","ENCEPHALOMALACIA\nEx-vacuo ventricular dilation"],
    ]
    ctdt = Table(ct_detail, colWidths=[2.5*cm,1.8*cm,7.0*cm,4.7*cm])
    ctdt.setStyle(TableStyle([
        ('BACKGROUND',(0,0),(-1,0),C_DARK_GRAY),('TEXTCOLOR',(0,0),(-1,0),C_WHITE),
        ('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),('FONTSIZE',(0,0),(-1,-1),7.5),
        ('BACKGROUND',(2,1),(2,-1),colors.HexColor('#FEF9E7')),
        ('BACKGROUND',(3,1),(3,-1),colors.HexColor('#EBF5FB')),
        ('ROWBACKGROUNDS',(0,1),(1,-1),[colors.HexColor('#FDEBD0'),colors.HexColor('#FEF9E7')]),
        ('GRID',(0,0),(-1,-1),0.4,C_MID_GRAY),('VALIGN',(0,0),(-1,-1),'TOP'),
        ('TOPPADDING',(0,0),(-1,-1),3),('BOTTOMPADDING',(0,0),(-1,-1),3),
    ]))
    story.append(ctdt)
    story.append(Spacer(1,0.15*cm))
    story.append(Paragraph(
        "<b>Pitfall:</b> At 7-14 days a subdural hematoma becomes ISODENSE with brain and can "
        "be completely invisible on CT without contrast. Clues: displaced cortex, asymmetric sulci, "
        "midline shift without visible hyperdensity.", WARN_S))

    story.append(PageBreak())

    # ── PAGE 3: HEMORRHAGE TYPES ───────────────────────────────────────────────
    story.append(Banner("3.  TYPES OF INTRACRANIAL HEMORRHAGE",
        "Location determines shape, etiology, and clinical presentation", bg=C_RED))
    story.append(Spacer(1,0.3*cm))

    story.append(Paragraph("Real CT + MRI: Acute Subdural Hematoma", H2_S))
    story.append(Paragraph(
        "Acute SDH is typically hyperdense (white) on CT - the classic crescent shape "
        "follows the brain surface and crosses suture lines. On MRI, acute SDH is "
        "ISOINTENSE on T1 (hard to see) but HYPERINTENSE on T2 - explaining why T2 is "
        "crucial for acute SDH detection.", BODY_S))

    ipath3 = os.path.join(IMG_DIR, "img_acute_subdural_mri.png")
    if os.path.exists(ipath3):
        img3 = RLImage(ipath3, width=AVAIL_W*0.75, height=4.5*cm)
        img3.hAlign = 'CENTER'
        story.append(img3)
    story.append(Paragraph(
        "Acute Subdural Hematoma on MRI. A: T1 - subdural collection over left temporal lobe "
        "is isointense to brain (arrowheads) - nearly invisible! B: T2 - the same collection is "
        "now clearly HYPERINTENSE (bright). [Bradley & Daroff's Neurology in Clinical Practice]", CAPTION_S))

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

    hem_types = [
        ["Type","CT Shape","Location","Typical Cause","Key Features"],
        ["EDH\n(Epidural)","BICONVEX\n(lens-shaped)\nDoes NOT cross\nsutures","Between skull\n& dura","Middle meningeal\nartery (temporal\nfracture)","Lucid interval;\ntemporal fracture;\narterial speed"],
        ["SDH\n(Subdural)","CRESCENT\n(concave inner)\nCrosses sutures\nfreely","Between dura\n& brain","Bridging veins\n(shear force)","Elderly; falls;\nanticoag use;\n venous bleeder"],
        ["SAH","Fills sulci &\nbasal cisterns;\n'star' pattern","Subarachnoid\nspace","Berry aneurysm;\nAVM; trauma","Thunderclap HA;\nFisher grade;\nCSF xanthochromia"],
        ["ICH\n(Parenchymal)","Round/oval;\nwell defined","Basal ganglia;\nthalamus; pons;\ncerebellum","HTN (deep);\nAmyloid\nangiopathy\n(cortical)","Location predicts\ncause: deep=HTN;\ncortical=amyloid"],
    ]
    htt = Table(hem_types, colWidths=[1.9*cm,2.8*cm,2.6*cm,3.2*cm,5.5*cm])
    htt.setStyle(TableStyle([
        ('BACKGROUND',(0,0),(-1,0),C_RED),('TEXTCOLOR',(0,0),(-1,0),C_WHITE),
        ('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),('FONTSIZE',(0,0),(-1,-1),7.5),
        ('ROWBACKGROUNDS',(0,1),(-1,-1),[colors.HexColor('#FDEDEC'),colors.HexColor('#FEF9E7'),
         colors.HexColor('#FEF5E7'),colors.HexColor('#FDFEFE')]),
        ('GRID',(0,0),(-1,-1),0.4,C_MID_GRAY),('VALIGN',(0,0),(-1,-1),'TOP'),
        ('TOPPADDING',(0,0),(-1,-1),3),('BOTTOMPADDING',(0,0),(-1,-1),3),
        ('FONTNAME',(0,1),(0,-1),'Helvetica-Bold'),('TEXTCOLOR',(0,1),(0,-1),C_RED),
    ]))
    story.append(htt)
    story.append(PageBreak())

    # ── PAGE 4: MRI SEQUENCES ──────────────────────────────────────────────────
    story.append(Banner("4.  MRI SEQUENCES — WHAT EACH SHOWS",
        "Understand each sequence before reading an MRI", bg=C_TEAL))
    story.append(Spacer(1,0.3*cm))

    mri_seq = [
        ["Sequence","Bright (hyperintense)","Dark (hypointense)","Best For"],
        ["T1","Fat, methemoglobin (subacute blood),\ngadolinium, protein-rich fluid","CSF, water, edema,\ndeoxyhemoglobin","Anatomy; subacute\nblood; contrast enhancement"],
        ["T2","CSF, water, edema, gliosis,\nlate subacute blood (extracel MetHb)","Acute blood (deoxy),\nhemosiderin, calcium","Infarcts, edema,\ntumors, inflammation"],
        ["FLAIR","Edema, gliosis, subarachnoid blood,\nperiventricular lesions","CSF (suppressed = BLACK)","SAH; periventricular MS;\ninfarct staging (DWI-FLAIR mismatch)"],
        ["DWI ★","ACUTE INFARCT (restricted diffusion)\nAbscess, epidermoid","Chronic lesions, blood\n(after several days)","ACUTE STROKE DIAGNOSIS\nPositive from 5 minutes!"],
        ["ADC ★","Vasogenic edema, chronic gliosis,\nfree diffusion","ACUTE INFARCT (dark = true\nrestriction), abscess","Confirm true restriction;\nexclude T2 shine-through"],
        ["SWI/GRE","Nothing significant","ALL blood products ('blooms')\ncalcification, iron, veins","Microhemorrhages;\nvenous thrombosis; amyloid"],
    ]
    msqt = Table(mri_seq, colWidths=[2.0*cm,4.8*cm,3.8*cm,5.4*cm])
    msqt.setStyle(TableStyle([
        ('BACKGROUND',(0,0),(-1,0),C_TEAL),('TEXTCOLOR',(0,0),(-1,0),C_WHITE),
        ('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),('FONTSIZE',(0,0),(-1,-1),7.5),
        ('ROWBACKGROUNDS',(0,1),(-1,-1),[colors.HexColor('#E8F8F5'),C_CREAM]*5),
        ('GRID',(0,0),(-1,-1),0.4,C_MID_GRAY),('VALIGN',(0,0),(-1,-1),'TOP'),
        ('TOPPADDING',(0,0),(-1,-1),3),('BOTTOMPADDING',(0,0),(-1,-1),3),
        ('FONTNAME',(0,4),(0,5),'Helvetica-Bold'),('TEXTCOLOR',(0,4),(0,5),C_TEAL),
    ]))
    story.append(msqt)
    story.append(Spacer(1,0.3*cm))

    # ── REAL IMAGE: Acute MCA infarct DWI + ADC ────────────────────────────────
    story.append(Paragraph("Real MRI: Acute MCA Infarct — DWI vs ADC", H2_S))
    story.append(Paragraph(
        "The DWI-ADC pair is the single most important MRI finding in stroke. "
        "<b>DWI bright + ADC dark = restricted diffusion = acute infarction confirmed.</b> "
        "This is positive within minutes of onset — far earlier than CT.", BODY_S))

    ipath4 = os.path.join(IMG_DIR, "img_acute_mca_infarct_dwi.png")
    if os.path.exists(ipath4):
        img4 = RLImage(ipath4, width=AVAIL_W*0.85, height=5.0*cm)
        img4.hAlign = 'CENTER'
        story.append(img4)
    story.append(Paragraph(
        "Acute left MCA infarct. A: DWI — large BRIGHT hyperintense territory with sulcal effacement "
        "and midline shift. B: ADC map — corresponding DARK hypointensity confirming restricted "
        "diffusion = irreversible infarction. [Bradley & Daroff's Neurology in Clinical Practice]", CAPTION_S))

    story.append(Spacer(1,0.25*cm))
    story.append(Paragraph(
        "<b>DWI-ADC Rule:</b><br/>"
        "• DWI BRIGHT + ADC DARK = TRUE restricted diffusion = ACUTE INFARCT ✓<br/>"
        "• DWI BRIGHT + ADC normal/bright = T2 shine-through = NOT acute infarct<br/>"
        "• DWI DARK + ADC BRIGHT = chronic gliosis / encephalomalacia", CALLOUT_S))

    story.append(PageBreak())

    # ── PAGE 5: ACUTE STROKE CT + MRI COMPARISON ──────────────────────────────
    story.append(Banner("5.  ACUTE STROKE: CT vs. MRI — REAL IMAGES",
        "Why MRI (DWI) is superior to CT in the first 6 hours", bg=C_LIGHTBLUE))
    story.append(Spacer(1,0.3*cm))

    story.append(Paragraph("Real Images: Acute MCA Infarct on CT AND MRI", H2_S))
    story.append(Paragraph(
        "This case shows the same left MCA territory infarct imaged at 7 hours after onset. "
        "CT already shows subtle hypodensity in the frontal operculum and insula (A). "
        "T2/FLAIR shows hyperintensity and gyral swelling (B, C). "
        "But the FULL extent — including basal ganglia involvement — is ONLY clearly "
        "visible on DWI (D) and its ADC map (E). This is why DWI is the gold standard.", BODY_S))

    ipath5 = os.path.join(IMG_DIR, "img_acute_stroke_ct_mri.png")
    if os.path.exists(ipath5):
        img5 = RLImage(ipath5, width=AVAIL_W, height=5.2*cm)
        img5.hAlign = 'CENTER'
        story.append(img5)
    story.append(Paragraph(
        "Acute left MCA infarct at 7 hours. A: CT — low attenuation in left frontal operculum/insula (subtle). "
        "B,C: T2/FLAIR — hyperintensity + gyral swelling (more than CT shows). D: DWI — full territory bright "
        "including basal ganglia. E: ADC — corresponding dark hypointensity confirms restriction. "
        "[Grainger & Allison's Diagnostic Radiology]", CAPTION_S))

    story.append(Spacer(1,0.3*cm))
    story.append(Paragraph("DWI-FLAIR Mismatch — The Wake-Up Stroke Rule", H2_S))
    story.append(Paragraph(
        "In patients who wake up with a stroke (unknown onset time), MRI can estimate "
        "whether the infarct is within the treatment window. "
        "<b>DWI bright but FLAIR normal/subtle = infarct is likely &lt;4.5 hours old = can give tPA.</b> "
        "If both DWI and FLAIR are bright = infarct is likely &gt;4.5-6 hours.", BODY_S))

    ipath6 = os.path.join(IMG_DIR, "img_wake_up_stroke_dwi_flair.png")
    if os.path.exists(ipath6):
        img6 = RLImage(ipath6, width=AVAIL_W, height=5.5*cm)
        img6.hAlign = 'CENTER'
        story.append(img6)
    story.append(Paragraph(
        "Wake-up strokes. Top row (A-D): Large DWI infarct + clear FLAIR signal + no perfusion mismatch "
        "→ beyond treatment window. Bottom row (E-G): Small DWI infarct in left insula + minimal FLAIR "
        "signal (within 4.5h window) + large CT perfusion mismatch → treated with tPA. "
        "[Grainger & Allison's Diagnostic Radiology]", CAPTION_S))

    story.append(PageBreak())

    # ── PAGE 6: MRI HEMORRHAGE TIMELINE ───────────────────────────────────────
    story.append(Banner("6.  MRI HEMORRHAGE SIGNAL TIMELINE",
        "Signal evolves as hemoglobin changes form — know each stage", bg=C_PURPLE))
    story.append(Spacer(1,0.3*cm))

    story.append(MRIHemGrid(w=AVAIL_W, h=5.5*cm))
    story.append(Spacer(1,0.5*cm))

    # ── REAL IMAGES: Acute parenchymal hemorrhage ──────────────────────────────
    story.append(Paragraph("Real MRI: Acute Parenchymal Hemorrhage (T1 + T2)", H2_S))
    story.append(Paragraph(
        "In ACUTE hemorrhage (deoxyhemoglobin stage, days 1-3): T1 shows the hematoma as "
        "ISOINTENSE (gray — hard to see!). T2 shows it as STRIKINGLY HYPOINTENSE (dark/black) "
        "due to deoxyhemoglobin's paramagnetic effect. Surrounding edema is hyperintense on T2.", BODY_S))

    ipath7 = os.path.join(IMG_DIR, "img_acute_parenchymal_hemorrhage.png")
    if os.path.exists(ipath7):
        img7 = RLImage(ipath7, width=AVAIL_W, height=6.0*cm)
        img7.hAlign = 'CENTER'
        story.append(img7)
    story.append(Paragraph(
        "Two cases of acute parenchymal hemorrhage. A,B: Large acute left basal ganglia hemorrhage — "
        "T1 (A) shows faint hypointensity; T2 (B) shows STRIKING DARK hypointensity with bright surrounding "
        "edema + mass effect. C,D: More advanced stage — T1 (C) center turning bright (intracellular MetHb "
        "developing, arrowheads); T2 (D) still dark. Note ventricular hemorrhage (arrows) = poor prognosis. "
        "[Bradley & Daroff's Neurology in Clinical Practice]", CAPTION_S))

    story.append(PageBreak())

    # ── PAGE 7: SUBACUTE + CHRONIC HEMORRHAGE ─────────────────────────────────
    story.append(Banner("7.  SUBACUTE & CHRONIC HEMORRHAGE ON MRI",
        "Methemoglobin and hemosiderin — the characteristic MRI signatures", bg=C_PURPLE))
    story.append(Spacer(1,0.3*cm))

    story.append(Paragraph("Real MRI: Late Subacute Hemorrhage (Extracellular MetHb)", H2_S))
    story.append(Paragraph(
        "In the LATE SUBACUTE stage (7-30+ days), RBCs lyse and methemoglobin spills "
        "into the extracellular space. This is the classic 'bright on T1 AND bright on T2' stage. "
        "A dark hemosiderin rim beginning to form at the periphery on T2 is an early sign of transition "
        "to the chronic phase.", BODY_S))

    # Side-by-side: T1 and T2 of late subacute
    ip_t1 = os.path.join(IMG_DIR, "img_late_subacute_hemorrhage_t1.png")
    ip_t2 = os.path.join(IMG_DIR, "img_late_subacute_hemorrhage_t2.png")
    row_imgs = []
    cell1 = []
    cell2 = []
    if os.path.exists(ip_t1):
        i_t1 = RLImage(ip_t1, width=AVAIL_W*0.48, height=4.8*cm)
        i_t1.hAlign='CENTER'
        cell1 = [i_t1, Paragraph("T1: BRIGHT (hyperintense)\nHomogeneous white signal\n= Extracellular MetHb", CAPTION_S)]
    if os.path.exists(ip_t2):
        i_t2 = RLImage(ip_t2, width=AVAIL_W*0.48, height=4.8*cm)
        i_t2.hAlign='CENTER'
        cell2 = [i_t2, Paragraph("T2: BRIGHT (hyperintense)\nAlso white signal\n+ dark hemosiderin rim (arrowheads)", CAPTION_S)]
    if cell1 and cell2:
        t = Table([[cell1, cell2]], colWidths=[AVAIL_W*0.5, AVAIL_W*0.5])
        t.setStyle(TableStyle([('VALIGN',(0,0),(-1,-1),'TOP'),('ALIGN',(0,0),(-1,-1),'CENTER'),
                                ('TOPPADDING',(0,0),(-1,-1),2),('BOTTOMPADDING',(0,0),(-1,-1),2)]))
        story.append(t)
    story.append(Paragraph(
        "Late Subacute Hematoma in right corona radiata — T1 (left) AND T2 (right) both show "
        "BRIGHT homogeneous signal = extracellular methemoglobin. Note early dark hemosiderin "
        "rim on T2 (arrowheads) signaling transition to chronic phase. "
        "[Bradley & Daroff's Neurology in Clinical Practice]", CAPTION_S))

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

    story.append(Paragraph("Real MRI: Chronic Hemorrhage — Hemosiderin Ring", H2_S))
    story.append(Paragraph(
        "In CHRONIC hemorrhage (&gt;4 weeks), macrophages phagocytose the blood products and "
        "deposit hemosiderin and ferritin. This creates a characteristic DARK RIM on T2 and GRE/SWI. "
        "The center forms a CSF-like cavity. The dark rim NEVER disappears — it permanently "
        "marks the site of prior hemorrhage.", BODY_S))

    ip_c1 = os.path.join(IMG_DIR, "img_chronic_hemorrhage_t2.png")
    ip_c2 = os.path.join(IMG_DIR, "img_chronic_hemorrhage_gre.png")
    cell3=[]; cell4=[]
    if os.path.exists(ip_c1):
        ic1 = RLImage(ip_c1, width=AVAIL_W*0.48, height=4.5*cm); ic1.hAlign='CENTER'
        cell3=[ic1, Paragraph("T2: Slit-like CSF cavity (bright center)\n+ DARK hemosiderin rim (arrowheads)", CAPTION_S)]
    if os.path.exists(ip_c2):
        ic2 = RLImage(ip_c2, width=AVAIL_W*0.48, height=4.5*cm); ic2.hAlign='CENTER'
        cell4=[ic2, Paragraph("GRE (T2*): Markedly DARK hemosiderin\ndeposition — 'blooming' effect\n(more prominent than on T2)", CAPTION_S)]
    if cell3 and cell4:
        t2 = Table([[cell3, cell4]], colWidths=[AVAIL_W*0.5, AVAIL_W*0.5])
        t2.setStyle(TableStyle([('VALIGN',(0,0),(-1,-1),'TOP'),('ALIGN',(0,0),(-1,-1),'CENTER'),
                                 ('TOPPADDING',(0,0),(-1,-1),2),('BOTTOMPADDING',(0,0),(-1,-1),2)]))
        story.append(t2)
    story.append(Paragraph(
        "Chronic parietal lobe hemorrhage. T2 (left): slitlike cavity with CSF-bright center + "
        "dark hemosiderin rim (arrowheads). GRE (right): markedly hypointense hemosiderin with "
        "'blooming' artifact — GRE/SWI is MORE sensitive than T2 for blood products. "
        "[Bradley & Daroff's Neurology in Clinical Practice]", CAPTION_S))

    story.append(PageBreak())

    # ── PAGE 8: MRI INFARCT TIMELINE + HAEMORRHAGIC TRANSFORMATION ────────────
    story.append(Banner("8.  MRI INFARCT TIMELINE + HAEMORRHAGIC TRANSFORMATION",
        "Evolution of ischemic infarct and the complication of hemorrhagic conversion", bg=C_TEAL))
    story.append(Spacer(1,0.3*cm))

    story.append(MRIInfarctGrid(w=AVAIL_W, h=4.5*cm))
    story.append(Spacer(1,0.45*cm))

    story.append(Paragraph("Real MRI: Haemorrhagic Transformation of Infarct", H2_S))
    story.append(Paragraph(
        "Haemorrhagic transformation (HT) occurs in up to 80% of infarcts on MRI. "
        "It is more common in large infarcts, cardioembolic strokes, after thrombolysis, "
        "and in diabetics. CT shows hyperdense blood within the infarct. "
        "SWI is exquisitely sensitive — it detects blood products as strikingly dark 'blooming' "
        "even when CT shows only subtle hyperdensity.", BODY_S))

    ipath8 = os.path.join(IMG_DIR, "img_haemorrhagic_transformation.png")
    if os.path.exists(ipath8):
        img8 = RLImage(ipath8, width=AVAIL_W, height=5.5*cm)
        img8.hAlign='CENTER'
        story.append(img8)
    story.append(Paragraph(
        "Haemorrhagic Transformation. A: DWI shows large right MCA territory infarct. B: Follow-up "
        "CT at 60 hours — acute haemorrhage (hyperdense) within the infarct + midline shift. "
        "C: Separate case — large left MCA infarct. D: SWI shows markedly dark 'blooming' blood "
        "products in the basal ganglia (white arrows) — barely visible on CT (C). "
        "[Grainger & Allison's Diagnostic Radiology]", CAPTION_S))

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

    mri_inf_detail = [
        ["Phase","Time","DWI","ADC","T2/FLAIR","Clinical Action"],
        ["Hyperacute","0-6 hrs","BRIGHT ★\n(5 min+)","DARK ★","Normal/subtle","DWI+ADC− = confirmed infarct\nStart tPA workup immediately"],
        ["Acute","6h-5d","BRIGHT ★","DARK ★","BRIGHT\n+mass effect","Full DWI extent = treatment target\nMass effect may mimic tumor"],
        ["Subacute","5-14d","Fading\n(T2 shine-through)","PSEUDO-\nNORMAL\n~day7-10","BRIGHT\n+gyral enhance","ADC trap! Still bright on T2/FLAIR.\nCheck for haemorrhagic transformation"],
        ["Chronic",">3wks","DARK","BRIGHT\n(free)","BRIGHT\n(gliosis)","Encephalomalacia.\nEx-vacuo ventricular enlargement"],
    ]
    midt = Table(mri_inf_detail, colWidths=[2.2*cm,1.8*cm,2.0*cm,2.0*cm,2.5*cm,5.5*cm])
    midt.setStyle(TableStyle([
        ('BACKGROUND',(0,0),(-1,0),C_TEAL),('TEXTCOLOR',(0,0),(-1,0),C_WHITE),
        ('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),('FONTSIZE',(0,0),(-1,-1),7.5),
        ('ROWBACKGROUNDS',(0,1),(-1,-1),[colors.HexColor('#E8F8F5'),C_CREAM]*4),
        ('BACKGROUND',(2,1),(3,2),colors.HexColor('#F5F5F5')),
        ('BACKGROUND',(3,1),(3,2),colors.HexColor('#222222')),('TEXTCOLOR',(3,1),(3,2),C_WHITE),
        ('GRID',(0,0),(-1,-1),0.4,C_MID_GRAY),('VALIGN',(0,0),(-1,-1),'TOP'),
        ('TOPPADDING',(0,0),(-1,-1),3),('BOTTOMPADDING',(0,0),(-1,-1),3),
    ]))
    story.append(midt)

    story.append(PageBreak())

    # ── PAGE 9: MASTER COMPARISON + WORKFLOW ──────────────────────────────────
    story.append(Banner("9.  MASTER QUICK-REFERENCE + EMERGENCY WORKFLOW",
        "Summary comparison and step-by-step imaging algorithm", bg=C_NAVY))
    story.append(Spacer(1,0.3*cm))

    master = [
        ["Feature","HEMORRHAGE","INFARCT"],
        ["CT (acute)","HYPERDENSE (bright white)\n~60-80 HU","HYPODENSE (dark)\n<30 HU after 6 hrs"],
        ["CT visibility","Immediately visible","Subtle <6 hrs; clear >6 hrs"],
        ["CT shape","Round/oval, well-defined","Wedge, follows vascular territory"],
        ["Location","Deep (HTN): BG, thalamus, pons\nCortical: amyloid angiopathy","Cortex+subcortex; arterial territory"],
        ["MRI T1","Iso → Iso → BRIGHT → BRIGHT → Dark","Normal → Hypointense"],
        ["MRI T2","Bright → DARK → DARK → BRIGHT → BLACK","Normal → BRIGHT (permanent gliosis)"],
        ["MRI DWI","Dark (blood suppresses DWI)","BRIGHT ★ from 5 minutes onset"],
        ["MRI ADC","Variable (not useful for blood)","DARK ★ (confirmed restriction)"],
        ["SWI/GRE","BLACK 'blooms' — all stages","Negative (unless haemorrhagic Tx)"],
        ["Mass effect","Yes, immediate + edema halo","Peaks day 3-5 in large infarcts"],
        ["Chronic","Hypodense cavity; hemosiderin ring\n(permanent dark rim on MRI)","Encephalomalacia; ex-vacuo dilation"],
    ]
    mt = Table(master, colWidths=[3.5*cm,6.5*cm,6.0*cm])
    mt.setStyle(TableStyle([
        ('BACKGROUND',(0,0),(-1,0),C_NAVY),('TEXTCOLOR',(0,0),(-1,0),C_WHITE),
        ('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),('FONTSIZE',(0,0),(-1,-1),8),
        ('BACKGROUND',(1,1),(1,-1),colors.HexColor('#FDEDEC')),
        ('BACKGROUND',(2,1),(2,-1),colors.HexColor('#EBF5FB')),
        ('ROWBACKGROUNDS',(0,1),(0,-1),[C_LIGHT_GRAY,colors.HexColor('#D5D8DC')]*8),
        ('GRID',(0,0),(-1,-1),0.4,C_MID_GRAY),('VALIGN',(0,0),(-1,-1),'TOP'),
        ('TOPPADDING',(0,0),(-1,-1),3),('BOTTOMPADDING',(0,0),(-1,-1),3),
        ('FONTNAME',(0,1),(0,-1),'Helvetica-Bold'),
    ]))
    story.append(mt)
    story.append(Spacer(1,0.3*cm))

    story.append(Paragraph("Emergency Imaging Workflow", H2_S))
    wf = [
        ["Step","Modality","Question","Action"],
        ["1","NCCT Brain","Hemorrhage? Mass?","YES bleed → no tPA; manage per bleed type\nNO bleed → proceed to step 2"],
        ["2","CT Angiography\n(arch→skull)","Large Vessel\nOcclusion (LVO)?","LVO present → thrombectomy pathway\nLook for hyperdense vessel sign"],
        ["3","CT Perfusion\n(if available)","Penumbra vs core?\nSalvageable tissue?","Large mismatch → extended window thrombectomy\nSmall/no mismatch → conservative"],
        ["4","MRI DWI+ADC\n+FLAIR+SWI","Confirm infarct?\nAge? Microbleeds?","DWI+/ADC− = acute infarct confirmed\nDWI-FLAIR mismatch = <4.5h → tPA eligible\nSWI dark = haemorrhagic component"],
        ["5","MRA/CTA\nfollow-up","Recanalisation?","Assess response; plan secondary prevention"],
    ]
    wft = Table(wf, colWidths=[0.8*cm,3.0*cm,3.8*cm,8.4*cm])
    wft.setStyle(TableStyle([
        ('BACKGROUND',(0,0),(-1,0),C_GREEN),('TEXTCOLOR',(0,0),(-1,0),C_WHITE),
        ('FONTNAME',(0,0),(-1,0),'Helvetica-Bold'),('FONTSIZE',(0,0),(-1,-1),7.5),
        ('ROWBACKGROUNDS',(0,1),(-1,-1),[colors.HexColor('#EAFAF1'),C_CREAM]*4),
        ('GRID',(0,0),(-1,-1),0.4,C_MID_GRAY),('VALIGN',(0,0),(-1,-1),'TOP'),
        ('TOPPADDING',(0,0),(-1,-1),3),('BOTTOMPADDING',(0,0),(-1,-1),3),
        ('FONTNAME',(0,1),(0,-1),'Helvetica-Bold'),('TEXTCOLOR',(0,1),(0,-1),C_GREEN),
        ('ALIGN',(0,1),(0,-1),'CENTER'),
    ]))
    story.append(wft)
    story.append(Spacer(1,0.2*cm))

    story.append(HRFlowable(width=AVAIL_W, thickness=0.5, color=C_MID_GRAY))
    story.append(Paragraph(
        "<b>Sources:</b> Adams and Victor's Principles of Neurology 12th Ed.  |  "
        "Bradley and Daroff's Neurology in Clinical Practice  |  "
        "Grainger & Allison's Diagnostic Radiology  |  "
        "Neuroanatomy through Clinical Cases 3rd Ed.  |  "
        "Plum & Posner's Stupor & Coma  |  Goldman-Cecil Medicine", NOTE_S))

    # ── Footer ─────────────────────────────────────────────────────────────────
    def footer(canvas, doc):
        canvas.saveState()
        canvas.setFont("Helvetica", 8); canvas.setFillColor(C_MID_GRAY)
        canvas.drawCentredString(PAGE_W/2, 1.2*cm,
            f"Brain CT & MRI Visual Reference Guide  |  Page {doc.page}")
        canvas.setFillColor(C_LIGHTBLUE)
        canvas.rect(2*cm, 1.0*cm, PAGE_W-4*cm, 0.04*cm, fill=1, stroke=0)
        canvas.restoreState()

    doc.build(story, onFirstPage=footer, onLaterPages=footer)
    print(f"✓ PDF saved: {OUT_PATH}")
    import os as _os
    sz = _os.path.getsize(OUT_PATH)
    print(f"  Size: {sz/1024:.0f} KB  |  Path: {OUT_PATH}")

build()
Running Command

cd /home/daytona/workspace/brain-imaging-guide && python build_pdf_v2.py 2>&1

Running Command

python3 -c "from pypdf import PdfReader; r=PdfReader('/home/daytona/workspace/brain-imaging-guide/Brain_CT_MRI_Reference_Guide_v2.pdf'); print(f'Pages: {len(r.pages)}')"

Here is the new version with real textbook images:

Brain CT MRI Reference Guide v2

PDF Document · PDF

~11 MB | 10 pages — every section now has real clinical images from the textbooks, side-by-side with explanatory diagrams and labels.

Here's what real images are on each page:
PageReal Images Included
1 - CT BasicsReal CT (bone window fracture + brain window SAH + FLAIR MRI) — skull fracture + subarachnoid hemorrhage case
2 - CT TimelineReal multi-panel CT/MRI showing subacute and chronic infarct progression with volume loss
3 - Hemorrhage TypesReal MRI of acute subdural hematoma — T1 (nearly invisible, isointense) vs T2 (clearly bright)
4 - MRI SequencesReal MRI: acute MCA infarct — DWI (bright) vs ADC map (dark) side-by-side
5 - Acute Stroke CT vs MRIReal 5-panel case: CT + T2 + FLAIR + DWI + ADC of the same MCA infarct at 7 hours; PLUS wake-up stroke DWI-FLAIR mismatch cases
6 - MRI Hemorrhage TimelineReal MRI: acute basal ganglia hemorrhage — T1 (isointense, subtle) vs T2 (strikingly dark) — two cases at different stages
7 - Subacute & Chronic HemorrhageReal MRI: late subacute hemorrhage (T1 bright + T2 bright = MetHb); chronic hemorrhage (T2 slit cavity + dark rim; GRE blooming)
8 - Infarct Timeline + HTReal 4-panel case of hemorrhagic transformation: DWI infarct → CT hemorrhage → SWI blooming
9 - Master ReferenceSummary tables + emergency workflow
This is a shared conversation. Sign in to Orris to start your own chat.