~/mbbs-anatomy-guide/neuro_study_guide.py
"""
MBBS Finals Neuroanatomy Study Guide - PDF Generator
Uses ReportLab to create a comprehensive, well-formatted study guide.
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import cm, mm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_LEFT, TA_CENTER, TA_JUSTIFY
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
PageBreak, HRFlowable, KeepTogether
)
from reportlab.platypus.flowables import HRFlowable
from reportlab.graphics.shapes import Drawing, Rect, String, Line, Circle, Polygon
from reportlab.graphics import renderPDF
from reportlab.platypus import Flowable
import os
OUTPUT_PATH = "/tmp/workspace/mbbs-anatomy-guide/Neuroanatomy_MBBS_Study_Guide.pdf"
# ── Colour palette ──────────────────────────────────────────────────────────
C_NAVY = colors.HexColor("#1B3A6B")
C_TEAL = colors.HexColor("#0D7C8C")
C_AMBER = colors.HexColor("#E8A020")
C_RED = colors.HexColor("#C0392B")
C_GREEN = colors.HexColor("#1E8449")
C_LTBLUE = colors.HexColor("#D6EAF8")
C_LTYELLOW = colors.HexColor("#FEF9E7")
C_LTGREEN = colors.HexColor("#EAFAF1")
C_LTRED = colors.HexColor("#FDEDEC")
C_LTGREY = colors.HexColor("#F2F3F4")
C_WHITE = colors.white
C_BLACK = colors.black
C_PURPLE = colors.HexColor("#6C3483")
C_LTPURPLE = colors.HexColor("#F5EEF8")
# ── Document setup ───────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT_PATH,
pagesize=A4,
rightMargin=1.8*cm, leftMargin=1.8*cm,
topMargin=2*cm, bottomMargin=2*cm,
title="Neuroanatomy MBBS Finals Study Guide",
author="Orris Medical AI",
subject="Neuroanatomy - High Yield MBBS Finals",
)
W, H = A4
CONTENT_W = W - 3.6*cm
styles = getSampleStyleSheet()
# ── Custom styles ─────────────────────────────────────────────────────────────
def make_style(name, parent="Normal", **kw):
s = ParagraphStyle(name, parent=styles[parent], **kw)
styles.add(s)
return s
COVER_TITLE = make_style("CoverTitle", fontSize=32, textColor=C_WHITE, alignment=TA_CENTER, fontName="Helvetica-Bold", leading=38)
COVER_SUB = make_style("CoverSub", fontSize=16, textColor=C_AMBER, alignment=TA_CENTER, fontName="Helvetica-Bold", leading=22)
COVER_INFO = make_style("CoverInfo", fontSize=11, textColor=C_WHITE, alignment=TA_CENTER, fontName="Helvetica", leading=16)
SECTION_H = make_style("SectionH", fontSize=16, textColor=C_WHITE, fontName="Helvetica-Bold", leading=20, spaceAfter=2)
TOPIC_H = make_style("TopicH", fontSize=13, textColor=C_NAVY, fontName="Helvetica-Bold", leading=17, spaceBefore=8, spaceAfter=4)
SUB_H = make_style("SubH", fontSize=11, textColor=C_TEAL, fontName="Helvetica-Bold", leading=15, spaceBefore=4, spaceAfter=2)
BODY = make_style("Body", fontSize=9.5, textColor=C_BLACK, fontName="Helvetica", leading=14, spaceAfter=4, alignment=TA_JUSTIFY)
BULLET = make_style("Bullet", fontSize=9.5, textColor=C_BLACK, fontName="Helvetica", leading=14, leftIndent=12, firstLineIndent=-10, spaceAfter=2)
BOLD_BULLET = make_style("BoldBullet", fontSize=9.5, textColor=C_NAVY, fontName="Helvetica-Bold", leading=14, leftIndent=12, firstLineIndent=-10, spaceAfter=2)
CLINICAL_TXT = make_style("ClinicalTxt",fontSize=9.5, textColor=C_BLACK, fontName="Helvetica", leading=14, leftIndent=6, spaceAfter=3, alignment=TA_JUSTIFY)
MNEMONIC_TXT = make_style("MnemonicTxt",fontSize=10, textColor=C_PURPLE, fontName="Helvetica-Bold", leading=15, alignment=TA_CENTER)
TABLE_H = make_style("TableH", fontSize=9, textColor=C_WHITE, fontName="Helvetica-Bold", alignment=TA_CENTER, leading=12)
TABLE_C = make_style("TableC", fontSize=8.5, textColor=C_BLACK, fontName="Helvetica", leading=12, alignment=TA_LEFT)
TABLE_CB = make_style("TableCB", fontSize=8.5, textColor=C_NAVY, fontName="Helvetica-Bold", leading=12, alignment=TA_LEFT)
CAPTION = make_style("Caption", fontSize=8, textColor=C_TEAL, fontName="Helvetica-Oblique", leading=11, alignment=TA_CENTER, spaceAfter=4)
TOC_ENTRY = make_style("TocEntry", fontSize=10, textColor=C_NAVY, fontName="Helvetica", leading=16)
TOC_H = make_style("TocH", fontSize=11, textColor=C_NAVY, fontName="Helvetica-Bold", leading=16)
FOOTER_TXT = make_style("FooterTxt", fontSize=7.5, textColor=colors.grey, fontName="Helvetica", leading=10, alignment=TA_CENTER)
# ── Helper functions ──────────────────────────────────────────────────────────
def section_header(title, color=C_NAVY):
"""Full-width coloured section header band."""
data = [[Paragraph(title, SECTION_H)]]
t = Table(data, colWidths=[CONTENT_W])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), color),
("TOPPADDING", (0,0),(-1,-1), 7),
("BOTTOMPADDING", (0,0),(-1,-1), 7),
("LEFTPADDING", (0,0),(-1,-1), 10),
("RIGHTPADDING", (0,0),(-1,-1), 10),
("ROUNDEDCORNERS", [4]),
]))
return t
def clinical_box(title, content_items):
"""Red-tinted clinical scenario box."""
rows = [[Paragraph(f"🩺 {title}", make_style(f"CB_{title[:8]}", fontSize=10,
textColor=C_RED, fontName="Helvetica-Bold", leading=14))]]
for item in content_items:
rows.append([Paragraph(f"• {item}", CLINICAL_TXT)])
t = Table(rows, colWidths=[CONTENT_W - 12])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_LTRED),
("BACKGROUND", (0,0), (-1,0), colors.HexColor("#FADBD8")),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("BOX", (0,0), (-1,-1), 0.8, C_RED),
("LINEBELOW", (0,0), (-1,0), 0.5, C_RED),
]))
return t
def mnemonic_box(mnemonic, meaning):
"""Purple mnemonic highlight box."""
rows = [
[Paragraph("MNEMONIC", make_style(f"MNH_{mnemonic[:5]}", fontSize=8,
textColor=C_WHITE, fontName="Helvetica-Bold", alignment=TA_CENTER, leading=11))],
[Paragraph(mnemonic, MNEMONIC_TXT)],
[Paragraph(meaning, make_style(f"MNB_{mnemonic[:5]}", fontSize=9,
textColor=C_PURPLE, fontName="Helvetica", alignment=TA_CENTER, leading=13))],
]
t = Table(rows, colWidths=[CONTENT_W - 12])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_LTPURPLE),
("BACKGROUND", (0,0), (-1,0), C_PURPLE),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("BOX", (0,0), (-1,-1), 0.8, C_PURPLE),
]))
return t
def key_table(headers, rows, col_widths=None, header_color=C_NAVY):
"""Styled data table."""
if col_widths is None:
col_widths = [CONTENT_W / len(headers)] * len(headers)
header_row = [Paragraph(h, TABLE_H) for h in headers]
data = [header_row]
for i, row in enumerate(rows):
data.append([Paragraph(str(c), TABLE_CB if i == 0 else TABLE_C) for c in row])
t = Table(data, colWidths=col_widths)
style = [
("BACKGROUND", (0,0), (-1,0), header_color),
("TEXTCOLOR", (0,0), (-1,0), C_WHITE),
("ROWBACKGROUNDS",(0,1), (-1,-1), [C_WHITE, C_LTBLUE]),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("GRID", (0,0), (-1,-1), 0.4, colors.HexColor("#BDC3C7")),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
]
t.setStyle(TableStyle(style))
return t
def info_box(title, items, bg=C_LTBLUE, border=C_TEAL):
"""Generic info box."""
rows = [[Paragraph(title, make_style(f"IB_{title[:8]}", fontSize=10,
textColor=border, fontName="Helvetica-Bold", leading=14))]]
for item in items:
rows.append([Paragraph(f"• {item}", BODY)])
t = Table(rows, colWidths=[CONTENT_W - 12])
t.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("BACKGROUND", (0,0), (-1,0), colors.Color(
border.red*0.2 + 0.8, border.green*0.2 + 0.8, border.blue*0.2 + 0.8)),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("BOX", (0,0), (-1,-1), 0.8, border),
]))
return t
def sp(n=1):
return Spacer(1, n * 4 * mm)
def hr(color=C_TEAL, thickness=0.5):
return HRFlowable(width="100%", thickness=thickness, color=color, spaceAfter=3, spaceBefore=3)
# ── ASCII / Drawing Diagrams ─────────────────────────────────────────────────
class SpinalCordDiagram(Flowable):
"""ASCII-style spinal cord cross-section diagram."""
def __init__(self, width=CONTENT_W, height=200):
super().__init__()
self.width = width
self.height = height
def draw(self):
w, h = self.width, self.height
cx, cy = w/2, h/2
# Background
self.canv.setFillColor(C_LTGREY)
self.canv.roundRect(0, 0, w, h, 6, fill=1, stroke=0)
# Title
self.canv.setFont("Helvetica-Bold", 10)
self.canv.setFillColor(C_NAVY)
self.canv.drawCentredString(cx, h - 14, "Spinal Cord Cross-Section – Key Tracts")
# Outer cord shape (oval)
self.canv.setStrokeColor(C_NAVY)
self.canv.setFillColor(colors.HexColor("#FFF9F0"))
self.canv.setLineWidth(1.5)
self.canv.ellipse(cx-80, cy-60, cx+80, cy+55, fill=1, stroke=1)
# Grey matter H shape
self.canv.setFillColor(colors.HexColor("#E8D5B7"))
self.canv.setStrokeColor(colors.HexColor("#A0856A"))
self.canv.setLineWidth(1)
# Central oval
self.canv.ellipse(cx-22, cy-20, cx+22, cy+20, fill=1, stroke=1)
# Dorsal horns
self.canv.roundRect(cx-10, cy+18, 20, 22, 4, fill=1, stroke=1)
# Ventral horns
self.canv.roundRect(cx-18, cy-40, 36, 22, 4, fill=1, stroke=1)
# Central canal
self.canv.setFillColor(C_LTBLUE)
self.canv.circle(cx, cy, 4, fill=1, stroke=1)
# ── Tract labels ──────────────────────────────────────
self.canv.setFont("Helvetica-Bold", 7.5)
# Dorsal columns (posterior) - top
self.canv.setFillColor(C_GREEN)
self.canv.roundRect(cx-35, cy+42, 70, 12, 3, fill=1, stroke=0)
self.canv.setFillColor(C_WHITE)
self.canv.drawCentredString(cx, cy+49, "Dorsal Columns")
self.canv.setFont("Helvetica", 6.5)
self.canv.setFillColor(C_GREEN)
self.canv.drawCentredString(cx, cy+36, "Fine touch / Proprioception / Vibration")
self.canv.drawCentredString(cx, cy+29, "Ipsilateral → crosses at medulla")
# Lateral corticospinal - right side (descending)
self.canv.setFont("Helvetica-Bold", 7.5)
self.canv.setFillColor(C_RED)
self.canv.roundRect(cx+42, cy-10, 52, 12, 3, fill=1, stroke=0)
self.canv.setFillColor(C_WHITE)
self.canv.drawString(cx+44, cy-3, "Lat. Corticospinal")
self.canv.setFont("Helvetica", 6.5)
self.canv.setFillColor(C_RED)
self.canv.drawString(cx+44, cy-14, "Voluntary motor")
self.canv.drawString(cx+44, cy-21, "Contralateral (crossed)")
# Spinothalamic - left side (ascending)
self.canv.setFont("Helvetica-Bold", 7.5)
self.canv.setFillColor(C_TEAL)
self.canv.roundRect(cx-96, cy-10, 52, 12, 3, fill=1, stroke=0)
self.canv.setFillColor(C_WHITE)
self.canv.drawString(cx-94, cy-3, "Spinothalamic")
self.canv.setFont("Helvetica", 6.5)
self.canv.setFillColor(C_TEAL)
self.canv.drawString(cx-94, cy-14, "Pain / Temp / Crude touch")
self.canv.drawString(cx-94, cy-21, "Contralateral (crosses @ entry)")
# Arrows
self.canv.setStrokeColor(C_RED)
self.canv.setLineWidth(1.2)
self.canv.line(cx+42, cy-4, cx+26, cy-4) # CST arrow
self.canv.setStrokeColor(C_TEAL)
self.canv.line(cx-44, cy-4, cx-26, cy-4) # Spinothalamic arrow
# Anterior corticospinal label
self.canv.setFont("Helvetica", 6.5)
self.canv.setFillColor(colors.HexColor("#884EA0"))
self.canv.drawCentredString(cx, cy-50, "Anterior Corticospinal Tract (uncrossed, midline)")
# Legend
self.canv.setFont("Helvetica-Bold", 7)
self.canv.setFillColor(C_NAVY)
self.canv.drawString(8, 10, "▲ = Ascending (sensory) ▼ = Descending (motor)")
class BrainstemDiagram(Flowable):
"""Simplified brainstem levels diagram."""
def __init__(self, width=CONTENT_W, height=220):
super().__init__()
self.width = width
self.height = height
def draw(self):
w, h = self.width, self.height
self.canv.setFillColor(C_LTGREY)
self.canv.roundRect(0, 0, w, h, 6, fill=1, stroke=0)
self.canv.setFont("Helvetica-Bold", 10)
self.canv.setFillColor(C_NAVY)
self.canv.drawCentredString(w/2, h-14, "Brainstem – CN Nuclei & Levels")
levels = [
("MIDBRAIN", colors.HexColor("#D6EAF8"), h-35, 65,
["CN III (Oculomotor) – Weber syndrome",
"CN IV (Trochlear) – exits dorsally",
"Red nucleus, Substantia nigra",
"Cerebral aqueduct (CSF)"]),
("PONS", colors.HexColor("#D5F5E3"), h-110, 65,
["CN V (Trigeminal) – large nerve",
"CN VI (Abducens) – medial wall 4th V",
"CN VII (Facial) + CN VIII (Vestibulocochlear)",
"Foville & Millard-Gubler syndromes"]),
("MEDULLA", colors.HexColor("#FADBD8"), h-185, 65,
["CN IX (Glossopharyngeal)",
"CN X (Vagus) – DMNV + NTS",
"CN XI (Accessory spinal root)",
"CN XII (Hypoglossal) – midline",
"Lateral medullary (Wallenberg) syndrome"]),
]
for name, bg, y, lh, notes in levels:
# Box
self.canv.setFillColor(bg)
self.canv.roundRect(8, y - lh + 5, w - 16, lh, 4, fill=1, stroke=0)
self.canv.setStrokeColor(C_NAVY)
self.canv.setLineWidth(0.8)
self.canv.roundRect(8, y - lh + 5, w - 16, lh, 4, fill=0, stroke=1)
# Level name
self.canv.setFont("Helvetica-Bold", 9)
self.canv.setFillColor(C_NAVY)
self.canv.drawString(14, y - 6, name)
# Notes
self.canv.setFont("Helvetica", 7.5)
self.canv.setFillColor(C_BLACK)
for i, note in enumerate(notes):
self.canv.drawString(22, y - 17 - i * 11, f"• {note}")
# Arrow showing superior→inferior
self.canv.setStrokeColor(C_NAVY)
self.canv.setLineWidth(1)
self.canv.line(w - 20, h - 30, w - 20, 20)
self.canv.drawString(w - 35, 12, "Inferior")
self.canv.drawString(w - 35, h - 22, "Superior")
class BasalGangliaCircuit(Flowable):
"""Basal ganglia direct/indirect pathway diagram."""
def __init__(self, width=CONTENT_W, height=210):
super().__init__()
self.width = width
self.height = height
def draw(self):
w, h = self.width, self.height
self.canv.setFillColor(C_LTGREY)
self.canv.roundRect(0, 0, w, h, 6, fill=1, stroke=0)
self.canv.setFont("Helvetica-Bold", 10)
self.canv.setFillColor(C_NAVY)
self.canv.drawCentredString(w/2, h-14, "Basal Ganglia Circuits – Direct & Indirect Pathways")
def node(x, y, label, sublabel="", bg=C_LTBLUE, tc=C_NAVY, w2=90, h2=22):
self.canv.setFillColor(bg)
self.canv.setStrokeColor(tc)
self.canv.setLineWidth(0.8)
self.canv.roundRect(x - w2/2, y - h2/2, w2, h2, 4, fill=1, stroke=1)
self.canv.setFont("Helvetica-Bold", 8)
self.canv.setFillColor(tc)
if sublabel:
self.canv.drawCentredString(x, y+4, label)
self.canv.setFont("Helvetica", 7)
self.canv.drawCentredString(x, y-5, sublabel)
else:
self.canv.drawCentredString(x, y, label)
def arrow(x1, y1, x2, y2, color=C_NAVY, label="", inh=False):
self.canv.setStrokeColor(color)
self.canv.setLineWidth(1.2)
self.canv.line(x1, y1, x2, y2)
# arrowhead
dx, dy = x2-x1, y2-y1
length = (dx**2+dy**2)**0.5
if length > 0:
ux, uy = dx/length, dy/length
ax1 = x2 - 8*ux + 4*uy
ay1 = y2 - 8*uy - 4*ux
ax2 = x2 - 8*ux - 4*uy
ay2 = y2 - 8*uy + 4*ux
self.canv.line(x2, y2, ax1, ay1)
self.canv.line(x2, y2, ax2, ay2)
if label:
mx, my = (x1+x2)/2, (y1+y2)/2
self.canv.setFont("Helvetica", 6.5)
self.canv.setFillColor(color)
self.canv.drawCentredString(mx+10, my, label)
# Nodes
# Cortex top
node(w/2, h-35, "CEREBRAL CORTEX", "(Glutamate +)",
bg=colors.HexColor("#D5F5E3"), tc=C_GREEN, w2=140)
# Striatum
node(w/2, h-85, "STRIATUM", "(Caudate + Putamen)",
bg=C_LTBLUE, tc=C_NAVY, w2=130)
# GPi / SNr - direct path (left)
node(w/2 - 85, h-135, "GPi / SNr", "(GABA -)",
bg=colors.HexColor("#FADBD8"), tc=C_RED, w2=90)
# GPe - indirect path (right)
node(w/2 + 85, h-135, "GPe", "(GABA -)",
bg=colors.HexColor("#FEF9E7"), tc=C_AMBER, w2=80)
# STN (indirect)
node(w/2 + 85, h-180, "Subthalamic N.", "(Glutamate +)",
bg=colors.HexColor("#FEF9E7"), tc=C_AMBER, w2=90)
# Thalamus
node(w/2, h-180, "THALAMUS", "(VA/VL nuclei)",
bg=C_LTPURPLE, tc=C_PURPLE, w2=110)
# SN compacta (dopamine) - far right
node(w - 55, h-115, "SNc", "(Dopamine)",
bg=colors.HexColor("#E8DAEF"), tc=C_PURPLE, w2=70, h2=20)
# Arrows
arrow(w/2, h-46, w/2, h-74, C_GREEN, "Glu +") # Cortex -> Striatum
arrow(w/2-18, h-96, w/2-76, h-123, C_RED, "GABA- (direct)") # Striatum -> GPi
arrow(w/2+18, h-96, w/2+68, h-123, C_AMBER, "GABA- (indir.)") # Striatum -> GPe
arrow(w/2+85, h-146, w/2+85, h-169, C_AMBER, "GABA-") # GPe -> STN
arrow(w/2+40, h-180, w/2+60, h-180, C_AMBER, "Glu+") # STN -> GPi (horizontal)
arrow(w/2-46, h-146, w/2-25, h-169, C_RED, "GABA-") # GPi -> Thalamus
arrow(w/2, h-169, w/2, h-46, C_PURPLE, "Glu+ (to cortex)") # Thal -> Cortex (feedback, dashed)
# Dopamine arrows
self.canv.setStrokeColor(C_PURPLE)
self.canv.setLineWidth(0.8)
self.canv.setDash(3, 2)
self.canv.line(w-55, h-126, w/2+22, h-90)
self.canv.setDash()
self.canv.setFont("Helvetica", 6.5)
self.canv.setFillColor(C_PURPLE)
self.canv.drawString(w/2+65, h-103, "D1+ (direct)")
self.canv.drawString(w/2+65, h-112, "D2- (indirect)")
# Legend
self.canv.setFont("Helvetica", 7)
self.canv.setFillColor(C_RED)
self.canv.drawString(8, 10, "Direct pathway: EXCITATORY (facilitates movement)")
self.canv.setFillColor(C_AMBER)
self.canv.drawString(8, 3, "Indirect pathway: INHIBITORY (suppresses movement)")
# ═══════════════════════════════════════════════════════════════════════════════
# BUILD THE PDF
# ═══════════════════════════════════════════════════════════════════════════════
story = []
# ── PAGE 1: COVER ─────────────────────────────────────────────────────────────
cover_data = [[Paragraph("NEUROANATOMY", COVER_TITLE)],
[Paragraph("MBBS Finals Study Guide", COVER_SUB)],
[Spacer(1, 8*mm)],
[Paragraph("Upper Motor Neurons • Sensory Pathways • Brainstem", COVER_INFO)],
[Paragraph("Cerebellum • Basal Ganglia • Cranial Nerves • Thalamus", COVER_INFO)],
[Paragraph("Clinical Correlations • High-Yield Diagrams • Exam Mnemonics", COVER_INFO)],
[Spacer(1, 5*mm)],
[Paragraph("Source: Gray's Anatomy for Students | Neuroanatomy through Clinical Cases | Costanzo Physiology",
make_style("CoverSrc", fontSize=8, textColor=colors.HexColor("#BDC3C7"),
fontName="Helvetica-Oblique", alignment=TA_CENTER, leading=12))],
]
cover = Table(cover_data, colWidths=[CONTENT_W])
cover.setStyle(TableStyle([
("BACKGROUND", (0,0), (-1,-1), C_NAVY),
("TOPPADDING", (0,0), (-1,-1), 8),
("BOTTOMPADDING", (0,0), (-1,-1), 8),
("LEFTPADDING", (0,0), (-1,-1), 20),
("RIGHTPADDING", (0,0), (-1,-1), 20),
("ROUNDEDCORNERS", [8]),
]))
story.append(cover)
story.append(sp(3))
# Intro blurb
story.append(Paragraph(
"This guide covers the highest-yield neuroanatomy topics for MBBS final university examinations. "
"Each section pairs structural anatomy with clinical correlations, pathological lesion patterns, "
"and exam-ready mnemonics. Drawn from Gray's Anatomy for Students, Neuroanatomy through Clinical Cases (Blumenfeld), "
"Costanzo Physiology, and Harrison's Principles.",
BODY))
story.append(sp(2))
# TOC
story.append(hr(C_NAVY, 1))
story.append(Paragraph("CONTENTS", TOC_H))
story.append(hr(C_NAVY, 0.5))
toc_items = [
("1", "Motor Pathways – UMN, LMN & Corticospinal Tract", "2"),
("2", "Sensory Pathways – Dorsal Column & Spinothalamic", "3"),
("3", "Spinal Cord Cross-Section Diagram", "4"),
("4", "Brainstem – Levels, CN Nuclei & Syndromes", "5"),
("5", "Cerebellum – Structure, Peduncles & Clinical Features", "7"),
("6", "Basal Ganglia – Circuits, Direct & Indirect Pathways", "8"),
("7", "Thalamus & Hypothalamus", "9"),
("8", "Cranial Nerves – All 12 with Functions & Lesions", "10"),
("9", "Internal Capsule & Cerebral Localisation", "12"),
("10", "Clinical Scenarios & Exam Questions", "13"),
("11", "Quick-Reference Mnemonics", "15"),
]
for num, title, page in toc_items:
row = [[Paragraph(f"{num}. {title}", TOC_ENTRY),
Paragraph(page, make_style(f"TOCp{num}", fontSize=10, textColor=C_TEAL,
fontName="Helvetica-Bold", alignment=TA_CENTER, leading=16))]]
t = Table(row, colWidths=[CONTENT_W - 30, 30])
t.setStyle(TableStyle([
("TOPPADDING", (0,0),(-1,-1), 2), ("BOTTOMPADDING", (0,0),(-1,-1), 2),
("LINEBELOW", (0,0),(-1,-1), 0.3, colors.HexColor("#BDC3C7")),
]))
story.append(t)
story.append(PageBreak())
# ── SECTION 1: MOTOR PATHWAYS ─────────────────────────────────────────────────
story.append(section_header("1. MOTOR PATHWAYS – UMN, LMN & CORTICOSPINAL TRACT", C_NAVY))
story.append(sp(1))
story.append(Paragraph("1.1 Upper Motor Neuron (UMN) vs Lower Motor Neuron (LMN)", TOPIC_H))
story.append(Paragraph(
"The corticospinal tract is the primary voluntary motor pathway. Upper motor neurons "
"originate in the precentral gyrus (Brodmann area 4) and project down to lower motor "
"neurons in the anterior horn of the spinal cord. Over 85% of fibres decussate at the "
"<b>pyramidal decussation</b> in the caudal medulla to form the <b>lateral corticospinal tract</b>. "
"Remaining ~15% travel uncrossed as the <b>anterior corticospinal tract</b>.",
BODY))
story.append(sp(1))
umn_lmn_table = key_table(
["Feature", "UMN Lesion", "LMN Lesion"],
[
["Weakness", "Yes (spastic)", "Yes (flaccid)"],
["Muscle Tone", "Increased (spasticity)", "Decreased (hypotonia)"],
["Reflexes", "Hyperreflexia", "Hyporeflexia / absent"],
["Atrophy", "Disuse (mild, late)", "Early, severe"],
["Fasciculations","Absent", "Present"],
["Babinski sign", "Present (extensor)", "Absent (or flexor)"],
["Clonus", "May be present", "Absent"],
["Examples", "Stroke, MS, cord lesion","Polio, GBS, neuropathy"],
],
col_widths=[CONTENT_W*0.28, CONTENT_W*0.36, CONTENT_W*0.36],
header_color=C_NAVY,
)
story.append(umn_lmn_table)
story.append(sp(1))
story.append(Paragraph("1.2 Course of the Lateral Corticospinal Tract", TOPIC_H))
story.append(key_table(
["Level", "Location", "Key Structure"],
[
["Motor cortex", "Precentral gyrus (area 4)", "Betz cells (giant pyramidal)"],
["Corona radiata", "Cerebral white matter", "Fan-shaped fibre spread"],
["Internal capsule","Posterior limb", "Genu – corticobulbar fibres"],
["Cerebral peduncle","Middle 3/5 (crus cerebri)", "Surrounds by pontine nuclei"],
["Pons", "Broken into bundles", "Pontine nuclei"],
["Medulla pyramid", "Anterior surface", "Pyramid visible externally"],
["Decussation", "Pyramidal decussation", "~85% cross here"],
["Spinal cord", "Lateral funiculus", "Synapses on ventral horn LMN"],
],
col_widths=[CONTENT_W*0.22, CONTENT_W*0.38, CONTENT_W*0.40],
))
story.append(sp(1))
story.append(mnemonic_box(
"Real Teenagers Can Play Piano Daily",
"Radiated fibres → Corona radiata → internal Capsule → Peduncle → Pons → Decussation"
))
story.append(sp(1))
story.append(clinical_box("Clinical: Stroke affecting internal capsule posterior limb", [
"Contralateral hemiplegia (face + arm + leg) – all fibres crowded together here",
"UMN signs: spasticity, hyperreflexia, Babinski sign",
"Caused by lenticulostriate artery occlusion (branch of MCA)",
"If genu involved: contralateral lower facial weakness + dysarthria (corticobulbar fibres)",
]))
story.append(sp(2))
# ── SECTION 2: SENSORY PATHWAYS ───────────────────────────────────────────────
story.append(section_header("2. SENSORY PATHWAYS", C_TEAL))
story.append(sp(1))
story.append(Paragraph("Two major ascending somatosensory pathways carry different modalities and cross at different levels:", BODY))
story.append(sp(1))
story.append(key_table(
["Feature", "Dorsal Column–Medial Lemniscal", "Anterolateral (Spinothalamic)"],
[
["Modality", "Fine touch, vibration, proprioception, 2-pt discrimination",
"Pain, temperature, crude touch, pressure"],
["1st order neuron", "Dorsal root ganglion → ipsilateral dorsal column",
"Dorsal root ganglion → posterior horn (laminae I, V)"],
["Where it crosses", "Caudal medulla (internal arcuate fibres)",
"Anterior commissure, 1-2 segments above entry"],
["2nd order nucleus","Nucleus gracilis (lower body) / Nucleus cuneatus (upper body)",
"Posterior horn cells"],
["Pathway name", "Medial lemniscus after crossing",
"Spinothalamic tract (anterior & lateral)"],
["3rd order", "VPL nucleus of thalamus → parietal cortex",
"VPL nucleus of thalamus → parietal cortex"],
["Fibres", "Gracile fasciculus (leg, sacral→lateral)\nCuneate fasciculus (arm)",
"Axons cross and ascend contralateral side"],
["Lesion result", "Ipsilateral loss below the level of lesion",
"Contralateral loss 1-2 levels below lesion"],
],
col_widths=[CONTENT_W*0.22, CONTENT_W*0.39, CONTENT_W*0.39],
header_color=C_TEAL,
))
story.append(sp(1))
story.append(clinical_box("Clinical: Brown-Séquard Syndrome (Hemisection of spinal cord)", [
"Ipsilateral: UMN signs (corticospinal tract), loss of fine touch/vibration/proprioception (dorsal columns)",
"Contralateral: loss of pain and temperature (spinothalamic tract crosses early)",
"Cause: penetrating trauma, tumour, MS plaque",
"Key exam point: dissociated sensory loss – different modalities lost on opposite sides",
]))
story.append(sp(1))
story.append(clinical_box("Clinical: Syringomyelia", [
"Fluid-filled cavity (syrinx) in central spinal cord, expanding anteriorly",
"Damages crossing spinothalamic fibres in anterior commissure first",
"Cape/shawl distribution: bilateral loss of pain and temperature in arms/upper trunk",
"Fine touch and proprioception PRESERVED (dorsal columns spared)",
"Later: UMN signs in legs (CST), LMN signs in hands (ventral horn)",
]))
story.append(sp(2))
# ── SECTION 3: SPINAL CORD DIAGRAM ────────────────────────────────────────────
story.append(PageBreak())
story.append(section_header("3. SPINAL CORD CROSS-SECTION DIAGRAM", C_GREEN))
story.append(sp(1))
story.append(SpinalCordDiagram(width=CONTENT_W, height=195))
story.append(sp(1))
story.append(Paragraph("Caption: The dorsal columns (posterior) carry ipsilateral fine touch and proprioception, crossing at the medulla. "
"The lateral corticospinal tract (motor, descending) and spinothalamic tract (pain/temp, ascending) are both crossed pathways.", CAPTION))
story.append(sp(1))
story.append(Paragraph("Spinal Cord Lesion Patterns", TOPIC_H))
story.append(key_table(
["Syndrome", "Structures Damaged", "Key Features"],
[
["Complete transection", "Everything", "Loss of all modalities + motor below level"],
["Brown-Séquard (hemisection)", "Ipsilateral half", "Ipsi: motor + dorsal column; Contra: spinothalamic"],
["Central cord syndrome", "Central grey + commissure","Bilateral pain/temp loss (cape); arms > legs"],
["Anterior cord syndrome", "Anterior 2/3", "Motor + pain/temp loss; fine touch PRESERVED"],
["Posterior cord syndrome", "Dorsal columns", "Vibration + proprioception loss; motor intact"],
["Tabes dorsalis (syphilis)","Dorsal columns + roots", "Ataxia, Romberg +ve, lightning pains, Argyll Robertson pupil"],
["ALS (Amyotrophic Lat. Scl.)","CST + anterior horn", "Mixed UMN + LMN; no sensory loss"],
["Subacute combined degen.","Dorsal + lateral columns","B12 deficiency; proprioception + UMN signs"],
],
col_widths=[CONTENT_W*0.28, CONTENT_W*0.32, CONTENT_W*0.40],
))
story.append(sp(2))
# ── SECTION 4: BRAINSTEM ──────────────────────────────────────────────────────
story.append(PageBreak())
story.append(section_header("4. BRAINSTEM – LEVELS, CN NUCLEI & VASCULAR SYNDROMES", C_RED))
story.append(sp(1))
story.append(BrainstemDiagram(width=CONTENT_W, height=215))
story.append(sp(1))
story.append(Paragraph("4.1 Brainstem Vascular Syndromes (High-Yield)", TOPIC_H))
story.append(key_table(
["Syndrome", "Vessel", "Level", "Key Features"],
[
["Weber's syndrome", "Posterior cerebral artery", "Midbrain",
"Ipsi CN III palsy (dilated pupil, down-out) + Contra hemiplegia"],
["Benedikt's syndrome", "Paramedian midbrain artery", "Midbrain",
"Ipsi CN III palsy + Contra tremor/ataxia (red nucleus)"],
["Foville's syndrome", "Basilar/AICA", "Pons",
"Ipsi CN VI + CN VII palsy + Contra hemiplegia"],
["Millard-Gubler syndrome", "Basilar branches", "Pons",
"Ipsi CN VI + VII palsy + Contra hemiplegia (facial spared)"],
["Lateral medullary (Wallenberg)", "PICA", "Medulla",
"Ipsi: CN V (face pain/temp), CN IX/X (dysphagia/hoarse), Horner's, cerebellar\nContra: body pain/temp loss"],
["Medial medullary syndrome", "Anterior spinal artery", "Medulla",
"Ipsi CN XII palsy + Contra hemiplegia + Contra dorsal column loss"],
],
col_widths=[CONTENT_W*0.22, CONTENT_W*0.20, CONTENT_W*0.12, CONTENT_W*0.46],
header_color=C_RED,
))
story.append(sp(1))
story.append(mnemonic_box(
"Ipsilateral CN + Contralateral Hemiplegia = Brainstem lesion",
"The KEY to brainstem localisation: CN palsy same side as lesion + motor/sensory loss opposite side"
))
story.append(sp(1))
story.append(Paragraph("4.2 Reticular Formation & Key Brainstem Nuclei", TOPIC_H))
story.append(key_table(
["Structure", "Location", "Function / Importance"],
[
["Nucleus tractus solitarius (NTS)", "Medulla", "Taste (CN VII, IX, X afferents); cardiovascular reflexes"],
["Dorsal motor nucleus of vagus", "Medulla", "Parasympathetic preganglionic to thorax/abdomen"],
["Nucleus ambiguus", "Medulla", "Motor: CN IX, X, XI (pharynx, larynx, soft palate)"],
["Locus coeruleus", "Pons", "Major noradrenergic nucleus; arousal, attention"],
["Raphe nuclei", "Pons/Medulla", "Serotonin synthesis; mood, sleep, pain modulation"],
["Red nucleus", "Midbrain","Rubrospinal tract; motor coordination"],
["Substantia nigra (pars compacta)", "Midbrain","Dopamine → striatum; degeneration = Parkinson's disease"],
["Periaqueductal grey (PAG)", "Midbrain","Endogenous pain modulation; opioid receptors"],
],
col_widths=[CONTENT_W*0.30, CONTENT_W*0.18, CONTENT_W*0.52],
))
story.append(sp(2))
# ── SECTION 5: CEREBELLUM ─────────────────────────────────────────────────────
story.append(PageBreak())
story.append(section_header("5. CEREBELLUM", C_GREEN))
story.append(sp(1))
story.append(Paragraph("5.1 Functional Divisions", TOPIC_H))
story.append(key_table(
["Division", "Also Called", "Input", "Function", "Lesion → Deficit"],
[
["Vestibulocerebellum", "Flocculonodular lobe", "Vestibular nuclei",
"Balance, eye movements, posture", "Gait ataxia, nystagmus, truncal instability"],
["Spinocerebellum", "Vermis + paravermal", "Spinal cord (spinocerebellar tracts)",
"Limb coordination, muscle tone", "Limb ataxia, dysmetria, hypotonia"],
["Pontocerebellum", "Cerebrocerebellum (hemispheres)", "Cerebral cortex via pontine nuclei",
"Planning, timing of skilled movements", "Intention tremor, dysdiadochokinesia, dysarthria"],
],
col_widths=[CONTENT_W*0.18, CONTENT_W*0.16, CONTENT_W*0.18, CONTENT_W*0.24, CONTENT_W*0.24],
header_color=C_GREEN,
))
story.append(sp(1))
story.append(Paragraph("5.2 Cerebellar Peduncles", TOPIC_H))
story.append(key_table(
["Peduncle", "Direction", "Key Tracts"],
[
["Superior cerebellar peduncle (SCP)", "Mainly EFFERENT (output)",
"Dentato-rubro-thalamic tract (main output) → contralateral thalamus/cortex"],
["Middle cerebellar peduncle (MCP)", "AFFERENT only (input)",
"Corticopontocerebellar tract (largest peduncle) – from contralateral cortex via pons"],
["Inferior cerebellar peduncle (ICP)", "Mainly AFFERENT (input)",
"Spinocerebellar tracts (posterior), vestibulocerebellar fibres, olivocerebellar fibres"],
],
col_widths=[CONTENT_W*0.30, CONTENT_W*0.20, CONTENT_W*0.50],
))
story.append(sp(1))
story.append(Paragraph("5.3 Cerebellar Cortex Layers & Cells", TOPIC_H))
story.append(key_table(
["Layer (Outside→In)", "Cells", "Key Point"],
[
["Molecular (outer)", "Stellate, basket cells; Purkinje dendrites", "Parallel fibres from granule cells synapse here"],
["Purkinje cell", "Purkinje cells", "ONLY OUTPUT of cerebellar cortex – always INHIBITORY (GABA)"],
["Granular (inner)", "Granule cells, Golgi II cells", "Granule cells receive mossy fibre input; most numerous neurons in brain"],
],
col_widths=[CONTENT_W*0.27, CONTENT_W*0.33, CONTENT_W*0.40],
))
story.append(sp(1))
story.append(info_box("Cerebellar Signs Mnemonic – DANISH", [
"D – Dysdiadochokinesia (impaired rapid alternating movements)",
"A – Ataxia (cerebellar gait – wide-based, reeling)",
"N – Nystagmus (horizontal, fast phase toward lesion; ipsilateral)",
"I – Intention tremor (tremor worsens as target is approached)",
"S – Slurred speech (dysarthria – scanning/staccato speech)",
"H – Hypotonia (decreased muscle tone)",
"Also: Dysmetria (past-pointing), Romberg negative (different from dorsal column lesion!)"
], bg=C_LTGREEN, border=C_GREEN))
story.append(sp(1))
story.append(clinical_box("Clinical: Cerebellar vs Sensory Ataxia", [
"Cerebellar ataxia: wide-based gait, Romberg NEGATIVE (worse with eyes OPEN), intention tremor",
"Sensory ataxia (dorsal column): narrow-based, Romberg POSITIVE (eyes closed), pseudoathetosis",
"Midline (vermis) lesions → truncal/gait ataxia; Hemisphere lesions → ipsilateral limb ataxia",
"Common causes: alcohol, MS, stroke (PICA), posterior fossa tumour (medulloblastoma in children)",
]))
story.append(sp(2))
# ── SECTION 6: BASAL GANGLIA ──────────────────────────────────────────────────
story.append(PageBreak())
story.append(section_header("6. BASAL GANGLIA – CIRCUITS & DISEASE", C_PURPLE))
story.append(sp(1))
story.append(BasalGangliaCircuit(width=CONTENT_W, height=210))
story.append(sp(1))
story.append(Paragraph("6.1 Components of the Basal Ganglia", TOPIC_H))
story.append(key_table(
["Structure", "Location", "Role"],
[
["Caudate nucleus", "C-shaped, flanks lateral ventricle", "Cognitive motor planning"],
["Putamen", "Lateral to globus pallidus", "Motor execution input"],
["Globus pallidus (GP)","Medial to putamen", "GPi = main output (inhibitory to thalamus)"],
["Subthalamic nucleus","Diencephalon (below thalamus)", "Indirect pathway; glutamatergic excitation"],
["Substantia nigra", "Midbrain", "SNc = dopamine; SNr = GABA output"],
["Striatum", "Caudate + putamen", "Main INPUT structure of BG"],
["Lentiform nucleus", "Putamen + globus pallidus", "Anatomical grouping term"],
],
col_widths=[CONTENT_W*0.25, CONTENT_W*0.30, CONTENT_W*0.45],
header_color=C_PURPLE,
))
story.append(sp(1))
story.append(Paragraph("6.2 Diseases of the Basal Ganglia", TOPIC_H))
story.append(key_table(
["Disease", "Pathology", "Features", "Treatment"],
[
["Parkinson's disease", "Loss of SNc dopaminergic neurones; Lewy bodies (α-synuclein)",
"TRAP: Tremor (resting, pill-rolling), Rigidity (cogwheel), Akinesia/bradykinesia, Postural instability",
"Levodopa + carbidopa; dopamine agonists (bromocriptine); MAO-B inhibitors (selegiline)"],
["Huntington's disease", "CAG repeat expansion on chromosome 4; caudate/cortical neuronal loss (GABA+Ach neurons)",
"Chorea (writhing movements), dementia, personality change; onset 30–50 yrs; autosomal dominant",
"Symptomatic only (tetrabenazine for chorea); no cure"],
["Hemiballismus", "Contralateral subthalamic nucleus lesion (usually lacunar infarct)",
"Wild flinging movements of contralateral limb (arm > leg)",
"Tetrabenazine; often self-limiting"],
["Wilson's disease", "ATP7B mutation → copper accumulation in basal ganglia + liver",
"Young patient: dysarthria, dystonia, hepatic disease, Kayser-Fleischer rings",
"D-penicillamine, zinc; liver transplant if severe"],
],
col_widths=[CONTENT_W*0.18, CONTENT_W*0.25, CONTENT_W*0.32, CONTENT_W*0.25],
))
story.append(sp(1))
story.append(mnemonic_box(
"TRAP – Parkinson's disease",
"Tremor (resting) · Rigidity (cogwheel/leadpipe) · Akinesia · Postural instability"
))
story.append(sp(2))
# ── SECTION 7: THALAMUS & HYPOTHALAMUS ────────────────────────────────────────
story.append(PageBreak())
story.append(section_header("7. THALAMUS & HYPOTHALAMUS", C_AMBER))
story.append(sp(1))
story.append(Paragraph("7.1 Key Thalamic Nuclei", TOPIC_H))
story.append(key_table(
["Nucleus", "Connects To", "Function"],
[
["VPL (ventral posterolateral)", "Somatosensory cortex", "Contralateral body sensation (pain, temp, touch)"],
["VPM (ventral posteromedial)", "Somatosensory cortex", "Contralateral face sensation (CN V)"],
["VA/VL (ventral ant./lat.)", "Motor cortex (area 4, 6)", "Relay: cerebellum + basal ganglia → motor cortex"],
["LGN (lateral geniculate)", "Visual cortex (area 17)", "Visual relay"],
["MGN (medial geniculate)", "Auditory cortex (area 41)","Auditory relay"],
["Pulvinar", "Parietal/temporal cortex", "Visual attention, language"],
["Anterior nucleus", "Cingulate gyrus", "Limbic; memory, emotion"],
["Mediodorsal nucleus", "Prefrontal cortex", "Cognition, emotion; damaged in Korsakoff's"],
["Intralaminar nuclei", "Widespread cortex", "Arousal, consciousness (reticular activating system)"],
],
col_widths=[CONTENT_W*0.25, CONTENT_W*0.25, CONTENT_W*0.50],
header_color=C_AMBER,
))
story.append(sp(1))
story.append(Paragraph("7.2 Hypothalamus – Nuclei and Functions", TOPIC_H))
story.append(key_table(
["Nucleus / Region", "Function", "Lesion / Disease"],
[
["Anterior", "Heat dissipation (sweating, vasodilation)", "Hyperthermia"],
["Posterior", "Heat conservation (shivering)", "Hypothermia, Wernicke's encephalopathy (mammillary bodies)"],
["Supraoptic + paraventricular", "ADH (vasopressin), oxytocin synthesis", "Diabetes insipidus (ADH deficiency)"],
["Ventromedial (VMH)", "Satiety centre", "Lesion → hyperphagia + obesity"],
["Lateral", "Hunger/feeding centre", "Lesion → anorexia + weight loss"],
["Suprachiasmatic", "Circadian rhythm", "Jet lag, circadian disorders"],
["Preoptic area", "GnRH release, temperature regulation", "Hypogonadism"],
["Mammillary bodies", "Memory (Papez circuit)", "Korsakoff's syndrome (thiamine deficiency)"],
],
col_widths=[CONTENT_W*0.28, CONTENT_W*0.32, CONTENT_W*0.40],
))
story.append(sp(2))
# ── SECTION 8: CRANIAL NERVES ─────────────────────────────────────────────────
story.append(PageBreak())
story.append(section_header("8. CRANIAL NERVES – ALL 12 WITH FUNCTIONS & LESIONS", C_NAVY))
story.append(sp(1))
story.append(mnemonic_box(
"Oh Oh Oh, To Touch And Feel Very Good Velvet – Ah Heaven!",
"CN I·II·III·IV·V·VI·VII·VIII·IX·X·XI·XII"
))
story.append(sp(1))
story.append(key_table(
["CN", "Name", "Type", "Function", "Lesion Signs"],
[
["I", "Olfactory", "Sensory", "Smell",
"Anosmia; anterior cranial fossa #; meningioma"],
["II", "Optic", "Sensory", "Vision; pupillary afferent",
"Blindness; RAPD; bitemporal hemianopia (chiasm)"],
["III", "Oculomotor", "Motor+Para","EOM (except SO & LR); pupil constriction; levator palpebrae",
"Down-and-out eye + ptosis + dilated pupil; PCA aneurysm; uncal herniation"],
["IV", "Trochlear", "Motor", "Superior oblique (intorsion + depression)",
"Vertical diplopia; head tilt to opposite side; exits dorsally"],
["V", "Trigeminal", "Sensory+Motor","Facial sensation (3 divisions); mastication",
"V1: corneal reflex loss; V3: jaw deviation to lesion side; tic douloureux"],
["VI", "Abducens", "Motor", "Lateral rectus (abduction)",
"Medial squint; inability to abduct; false localising sign (raised ICP)"],
["VII", "Facial", "Motor+Sen+Para","Facial muscles; taste ant. 2/3 tongue; lacrimation/salivation",
"LMN (Bell's): all ipsilateral face; UMN: forehead spared; parotid tumour"],
["VIII","Vestibulocochlear","Sensory", "Hearing (cochlear); balance (vestibular)",
"Sensorineural deafness; vertigo; acoustic neuroma"],
["IX", "Glossopharyngeal","Mixed", "Taste post. 1/3; pharyngeal sensation; carotid body/sinus",
"Loss of gag reflex (afferent); loss of taste post 1/3; glossopharyngeal neuralgia"],
["X", "Vagus", "Mixed", "Pharynx, larynx, thoracic/abdominal viscera; speech",
"Hoarseness (RLN); uvula deviates AWAY from lesion; dysphagia"],
["XI", "Accessory", "Motor", "Trapezius (shoulder shrug); SCM (head turn)",
"Drooped shoulder; weak head turn to opposite side; posterior triangle surgery"],
["XII", "Hypoglossal", "Motor", "Intrinsic + extrinsic tongue muscles",
"Tongue deviates TOWARD lesion; LMN: atrophy + fasciculations"],
],
col_widths=[CONTENT_W*0.05, CONTENT_W*0.14, CONTENT_W*0.10, CONTENT_W*0.32, CONTENT_W*0.39],
))
story.append(sp(1))
story.append(Paragraph("CN III – Pupil Involvement Rule", TOPIC_H))
story.append(info_box("Pupil-involving CN III palsy = compressive (surgical) cause", [
"Parasympathetic fibres run in the PERIPHERY of the CN III nerve → compressed first by external pressure",
"Pupil-involving (dilated pupil + EOM palsy): posterior communicating artery aneurysm, uncal herniation",
"Pupil-sparing CN III palsy: diabetic mononeuropathy (ischaemia damages central fibres first)",
"Always image urgently if pupil is involved – may be life-threatening aneurysm",
], bg=C_LTYELLOW, border=C_AMBER))
story.append(sp(1))
story.append(Paragraph("Visual Field Defects", TOPIC_H))
story.append(key_table(
["Lesion Site", "Visual Field Defect"],
[
["Optic nerve (complete)", "Monocular blindness (ipsilateral)"],
["Optic chiasm (centre/crossing fibres)", "Bitemporal hemianopia"],
["Optic tract", "Homonymous hemianopia (contralateral)"],
["Temporal lobe radiation (Meyer's loop)", "Contralateral upper quadrantanopia ('pie in the sky')"],
["Parietal lobe radiation", "Contralateral lower quadrantanopia ('pie on the floor')"],
["Occipital cortex (complete)", "Contralateral homonymous hemianopia + macular SPARING"],
],
col_widths=[CONTENT_W*0.45, CONTENT_W*0.55],
))
story.append(sp(2))
# ── SECTION 9: INTERNAL CAPSULE & CEREBRAL LOCALISATION ─────────────────────
story.append(PageBreak())
story.append(section_header("9. INTERNAL CAPSULE & CEREBRAL LOCALISATION", C_TEAL))
story.append(sp(1))
story.append(Paragraph("9.1 Internal Capsule – Somatotopic Organisation", TOPIC_H))
story.append(key_table(
["Part of IC", "Tracts / Fibres"],
[
["Anterior limb", "Frontopontine fibres; thalamocortical to prefrontal; anterior thalamic radiation"],
["Genu", "Corticobulbar fibres (face, head, neck – CN motor nuclei)"],
["Posterior limb (anterior part)", "Corticospinal (face → arm → leg, anterior→posterior)"],
["Posterior limb (posterior part)","Sensory thalamocortical radiation (VPL → parietal cortex)"],
["Sublenticular", "Optic radiation (lower fibres – Meyer's loop → temporal lobe)"],
["Retrolenticular", "Optic radiation (main – lateral geniculate → occipital cortex)"],
],
col_widths=[CONTENT_W*0.35, CONTENT_W*0.65],
))
story.append(sp(1))
story.append(Paragraph("9.2 Cerebral Lobes – Localisation of Function", TOPIC_H))
story.append(key_table(
["Lobe", "Key Area", "Function", "Lesion"],
[
["Frontal", "Primary motor (area 4)", "Voluntary movement (contralateral)", "Contralateral hemiplegia"],
["Frontal", "Premotor (area 6)", "Motor planning, preparation", "Apraxia"],
["Frontal", "Broca's (area 44/45)", "Speech production", "Expressive aphasia (non-fluent)"],
["Parietal", "Primary sensory (3,1,2)", "Somatosensory perception", "Contralateral sensory loss"],
["Parietal", "Superior parietal", "Spatial orientation, praxis", "Neglect, apraxia"],
["Temporal", "Wernicke's (area 22)", "Speech comprehension", "Receptive aphasia (fluent, jargon)"],
["Temporal", "Auditory cortex (41/42)", "Hearing", "Cortical deafness"],
["Occipital", "Visual cortex (area 17)", "Vision", "Cortical blindness + macular sparing"],
["Limbic", "Hippocampus", "Memory consolidation", "Anterograde amnesia (Korsakoff's)"],
],
col_widths=[CONTENT_W*0.12, CONTENT_W*0.22, CONTENT_W*0.30, CONTENT_W*0.36],
))
story.append(sp(2))
# ── SECTION 10: CLINICAL SCENARIOS ────────────────────────────────────────────
story.append(PageBreak())
story.append(section_header("10. CLINICAL SCENARIOS & EXAM QUESTIONS", C_RED))
story.append(sp(1))
scenarios = [
("A 65-year-old wakes with right arm + leg weakness, right facial droop, and slurred speech. BP 180/110.",
["Left MCA territory stroke (internal capsule/corticospinal tract)",
"Right hemiplegia = left hemisphere UMN lesion (corticospinal tract crosses)",
"Facial droop: lower face only (forehead spared) = UMN CN VII lesion",
"Slurred speech = dysarthria from corticobulbar tract; if Broca's area involved → expressive aphasia",
"Management: CT head (exclude haemorrhage), IV tPA if within 4.5 hrs, aspirin + statin"]),
("A 55-year-old has sudden severe headache 'worst of life', neck stiffness, photophobia, no focal neurology.",
["Subarachnoid haemorrhage (ruptured berry aneurysm of circle of Willis)",
"Blood in subarachnoid space irritates meninges → meningism (stiff neck, Kernig's, Brudzinski's)",
"CT head first (bright blood); LP if CT negative (xanthochromia after 12 hrs)",
"CN III palsy with dilated pupil = PCA aneurysm compression",
"Management: neurosurgical clipping or endovascular coiling"]),
("A 70-year-old presents with dysphagia, hoarseness, loss of pain/temperature on left face and right body, and right arm incoordination.",
["Lateral medullary (Wallenberg) syndrome – right PICA occlusion",
"Ipsilateral (right) face pain/temp loss: CN V spinal nucleus",
"Contralateral (left) body pain/temp loss: lateral spinothalamic tract",
"Dysphagia + hoarseness: CN IX, X nuclei (nucleus ambiguus)",
"Ipsilateral Horner's syndrome: descending sympathetic fibres",
"Ipsilateral cerebellar signs: inferior cerebellar peduncle"]),
("A 60-year-old man with a resting tremor of his right hand, shuffling gait, mask-like facies, and cogwheel rigidity.",
["Parkinson's disease – degeneration of substantia nigra pars compacta (SNc)",
"Loss of dopaminergic input → indirect pathway overactive → excess inhibition of thalamus",
"TRAP: Tremor (resting, 4-6 Hz, pill-rolling), Rigidity (cogwheel), Akinesia, Postural instability",
"Lewy bodies (α-synuclein aggregates) on pathology",
"Treatment: Levodopa + carbidopa (peripheral DOPA decarboxylase inhibitor)"]),
("A 45-year-old MS patient has bilateral loss of position sense and vibration in legs, brisk reflexes and Babinski sign. Touch preserved.",
["Subacute combined degeneration of spinal cord (but check B12; MS also possible)",
"Posterior columns (proprioception/vibration) + lateral corticospinal (UMN signs)",
"Anterior columns spared → pain/temperature preserved",
"In B12 deficiency: check serum B12, MCV (macrocytosis), anti-IF antibodies",
"Positive Romberg test (unsteady with eyes closed)"]),
("A child presents with morning headache, vomiting, and truncal ataxia. MRI shows a midline posterior fossa mass.",
["Medulloblastoma – most common posterior fossa tumour in children",
"Arises from vermis → truncal ataxia (midline cerebellar lesion), wide-based gait",
"Obstructs 4th ventricle → hydrocephalus (morning headaches, vomiting, papilloedema)",
"DANISH signs of cerebellar dysfunction",
"Treatment: surgical resection + craniospinal radiotherapy + chemotherapy"]),
]
for q, answers in scenarios:
story.append(KeepTogether([
clinical_box(f"Clinical Scenario: {q[:60]}...", [q] + answers),
sp(1),
]))
story.append(sp(1))
# ── SECTION 11: MNEMONICS ─────────────────────────────────────────────────────
story.append(PageBreak())
story.append(section_header("11. QUICK-REFERENCE MNEMONICS", C_PURPLE))
story.append(sp(1))
mnemonics = [
("CN types (S/M/B)",
"Some Say Marry Money But My Brother Says Big Brains Matter More",
"I=S, II=S, III=B, IV=M, V=B, VI=M, VII=B, VIII=S, IX=B, X=B, XI=M, XII=M"),
("Corticospinal tract course",
"Real Teenagers Can Play Piano Daily",
"Cortex → Corona Radiata → internal Capsule → cerebral Peduncle → Pons → Decussation"),
("Cerebellar signs",
"DANISH",
"Dysdiadochokinesia · Ataxia · Nystagmus · Intention tremor · Slurred speech · Hypotonia"),
("Parkinson's",
"TRAP",
"Tremor (resting) · Rigidity · Akinesia · Postural instability"),
("Brainstem rule",
"Ipsi CN + Contra body = Brainstem",
"Cranial nerve palsy on one side + hemiplegia on the other = brainstem lesion (not cortex)"),
("Sensory pathway crossing",
"Dorsal columns cross HIGH (medulla); Spinothalamic crosses LOW (entry level)",
"This explains dissociated sensory loss in hemisection and central cord syndromes"),
("Visual fields",
"The radiation goes from the eye toward the BACK of the head",
"Temporal loop = upper field loss; Parietal = lower field loss; Occipital = hemianopia + macular sparing"),
("Thalamic relay",
"VPL = body, VPM = face",
"VPL → contralateral body somatosensory; VPM → face (CN V); LGN → vision; MGN → hearing"),
("Wernicke vs Broca",
"Wernicke = Wordy nonsense; Broca = Broken speech",
"Wernicke: fluent but incomprehensible; Broca: non-fluent but comprehends"),
("Basal ganglia: Direct vs Indirect",
"Direct = Desired movement (excitatory net effect on cortex)",
"Indirect = Inhibitory net effect; Parkinson's = direct↓ indirect↑ → akinesia"),
]
for title, mnemonic, meaning in mnemonics:
story.append(Paragraph(title, SUB_H))
story.append(mnemonic_box(mnemonic, meaning))
story.append(sp(1))
# Final reference note
story.append(hr(C_NAVY, 1))
story.append(sp(1))
story.append(Paragraph(
"<b>Sources:</b> Gray's Anatomy for Students (9780323934237) | "
"Neuroanatomy through Clinical Cases 3rd Ed. (Blumenfeld, 9781605359625) | "
"Costanzo Physiology 7th Ed. (9780323793339) | "
"Harrison's Principles of Internal Medicine 22E | "
"Goldman-Cecil Medicine",
make_style("Sources", fontSize=8, textColor=colors.grey, fontName="Helvetica-Oblique",
alignment=TA_CENTER, leading=12)
))
# ── PAGE TEMPLATE (headers/footers) ──────────────────────────────────────────
def on_page(canvas, doc):
canvas.saveState()
# Header line
canvas.setStrokeColor(C_NAVY)
canvas.setLineWidth(0.5)
canvas.line(1.8*cm, H - 1.5*cm, W - 1.8*cm, H - 1.5*cm)
canvas.setFont("Helvetica-Bold", 8)
canvas.setFillColor(C_NAVY)
canvas.drawString(1.8*cm, H - 1.3*cm, "NEUROANATOMY — MBBS FINALS STUDY GUIDE")
canvas.drawRightString(W - 1.8*cm, H - 1.3*cm, f"Page {doc.page}")
# Footer line
canvas.setLineWidth(0.5)
canvas.line(1.8*cm, 1.5*cm, W - 1.8*cm, 1.5*cm)
canvas.setFont("Helvetica", 7.5)
canvas.setFillColor(colors.grey)
canvas.drawCentredString(W/2, 0.9*cm, "Generated by Orris Medical AI | Based on Gray's Anatomy, Neuroanatomy through Clinical Cases, Costanzo Physiology")
canvas.restoreState()
# ── BUILD ─────────────────────────────────────────────────────────────────────
doc.build(story, onFirstPage=on_page, onLaterPages=on_page)
print(f"PDF generated: {OUTPUT_PATH}")