Cranial nerve 1 to 12 examination in detail
cranial nerve examination clinical
Minimum screen: Check fundi, visual fields, pupil size and reactivity, extraocular movements, and facial movements. For a full exam, test all CNs in numerical order (grouping III, IV, VI together).
| Nerve | Muscle(s) | Action |
|---|---|---|
| CN III | Medial, superior, inferior rectus; inferior oblique; levator palpebrae; pupillary constrictor | Elevation, depression, adduction; eyelid elevation; pupil constriction |
| CN IV | Superior oblique | Intorsion, depression in adduction |
| CN VI | Lateral rectus | Abduction |

| Division | Territory |
|---|---|
| V1 (Ophthalmic) | Forehead, scalp, upper eyelid, cornea |
| V2 (Maxillary) | Cheek, lower eyelid, upper lip, upper teeth |
| V3 (Mandibular) | Lower jaw, lower lip, lower teeth, anterior tongue |
| Pattern | Lesion |
|---|---|
| Weakness of lower 2/3 face, forehead spared | Upper motor neuron (UMN) — contralateral cortex/corticobulbar tract |
| Weakness of entire ipsilateral face (forehead + lower) | Lower motor neuron (LMN) — CN VII nucleus or nerve itself (Bell's palsy) |


| CN | Name | Key Functions | Test Method | Abnormal Finding |
|---|---|---|---|---|
| I | Olfactory | Smell | Identify odorant (eyes closed) | Anosmia |
| II | Optic | Vision, afferent pupillary | Acuity, fields, fundoscopy, RAPD | Decreased acuity, field defects, RAPD |
| III | Oculomotor | EOM (except SO, LR), ptosis, pupil constriction | Pupil light reflex, accommodation, H-pattern | Ptosis, mydriasis, "down and out" eye |
| IV | Trochlear | Superior oblique (down + in) | H-pattern, head tilt | Vertical diplopia, worse going down stairs |
| V | Trigeminal | Facial sensation, mastication | Touch/pin/temp in 3 divisions, jaw clench, corneal reflex | Hemisensory loss, absent corneal reflex, jaw deviation |
| VI | Abducens | Lateral rectus (abduction) | H-pattern (lateral gaze) | Esotropia, failure to abduct |
| VII | Facial | Facial expression, taste ant. 2/3 tongue | Wrinkle forehead, close eyes, smile, puff cheeks; taste | LMN: entire face weak; UMN: forehead spared |
| VIII | Vestibulocochlear | Hearing, balance | Whisper test, Rinne, Weber | Conductive vs. sensorineural loss |
| IX | Glossopharyngeal | Pharyngeal sensation, taste post. 1/3 tongue | Gag reflex (afferent) | Loss of gag (afferent) |
| X | Vagus | Palate/larynx motor, gag efferent | "Aah," uvula, gag reflex (efferent) | Uvula deviation, hoarseness, absent gag (efferent) |
| XI | Spinal Accessory | SCM, trapezius | Shoulder shrug, head turn against resistance | Weakness, atrophy of SCM/trapezius |
| XII | Hypoglossal | Tongue movement | Protrude tongue, press against cheeks | Deviation toward lesion, atrophy, fasciculations |
Create a study guide PDF for cranial nerve testing I can print or share
ls -la /home/daytona/workspace/ 2>/dev/null || echo "workspace empty or missing"
mkdir -p /home/daytona/workspace/cranial-nerve-guide && python3 -c "import reportlab; print('reportlab ok')" && python3 -c "from reportlab.lib.pagesizes import A4; print('A4 ok')"
~/cranial-nerve-guide/build_pdf.py
#!/usr/bin/env python3
"""
Cranial Nerve Examination Study Guide PDF Builder
Uses ReportLab to produce a clean, print-ready A4 PDF.
"""
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, KeepTogether
)
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_JUSTIFY
from reportlab.pdfbase import pdfmetrics
from reportlab.pdfbase.ttfonts import TTFont
OUTPUT = "/home/daytona/workspace/cranial-nerve-guide/Cranial_Nerve_Examination_Study_Guide.pdf"
# ── Colour palette ────────────────────────────────────────────────────────────
NAVY = colors.HexColor("#1B3A6B")
TEAL = colors.HexColor("#0A7E8C")
LIGHT_BG = colors.HexColor("#EAF4F6")
AMBER = colors.HexColor("#E8A020")
GREY_TEXT = colors.HexColor("#444444")
WHITE = colors.white
LIGHT_ROW = colors.HexColor("#F5FBFC")
ALT_ROW = colors.HexColor("#DCEEF2")
# ── Document ──────────────────────────────────────────────────────────────────
doc = SimpleDocTemplate(
OUTPUT,
pagesize=A4,
leftMargin=1.8*cm, rightMargin=1.8*cm,
topMargin=2*cm, bottomMargin=2*cm,
title="Cranial Nerve Examination Study Guide",
author="Orris Medical Reference"
)
W, H = A4
CONTENT_W = W - 3.6*cm # usable width
styles = getSampleStyleSheet()
# ── Custom styles ─────────────────────────────────────────────────────────────
def S(name, **kw):
return ParagraphStyle(name, **kw)
cover_title = S("CoverTitle",
fontSize=28, leading=34, alignment=TA_CENTER,
textColor=WHITE, fontName="Helvetica-Bold")
cover_sub = S("CoverSub",
fontSize=13, leading=18, alignment=TA_CENTER,
textColor=colors.HexColor("#C8E8F0"), fontName="Helvetica")
section_head = S("SectionHead",
fontSize=13, leading=16, spaceBefore=14, spaceAfter=4,
textColor=WHITE, fontName="Helvetica-Bold",
backColor=NAVY, borderPad=5,
leftIndent=0)
cn_head = S("CNHead",
fontSize=11, leading=14, spaceBefore=10, spaceAfter=3,
textColor=NAVY, fontName="Helvetica-Bold")
body = S("Body",
fontSize=9, leading=13, textColor=GREY_TEXT,
fontName="Helvetica", spaceAfter=2, alignment=TA_JUSTIFY)
bullet = S("Bullet",
fontSize=9, leading=13, textColor=GREY_TEXT,
fontName="Helvetica", leftIndent=12, firstLineIndent=-8,
spaceAfter=2)
label = S("Label",
fontSize=8.5, leading=12, textColor=TEAL,
fontName="Helvetica-Bold")
caption = S("Caption",
fontSize=7.5, leading=11, textColor=colors.HexColor("#666666"),
fontName="Helvetica-Oblique", alignment=TA_CENTER)
footer_style = S("Footer",
fontSize=7.5, textColor=colors.HexColor("#888888"),
fontName="Helvetica", alignment=TA_CENTER)
# ── Helper: coloured box paragraph ───────────────────────────────────────────
def tip_box(text, bg=LIGHT_BG, text_color=NAVY):
return Table(
[[Paragraph(text, S("tip", fontSize=8.5, leading=12,
textColor=text_color, fontName="Helvetica"))]],
colWidths=[CONTENT_W],
style=TableStyle([
("BACKGROUND", (0,0), (-1,-1), bg),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
("BOX", (0,0), (-1,-1), 0.5, TEAL),
])
)
def section_banner(text):
return Table(
[[Paragraph(text, section_head)]],
colWidths=[CONTENT_W],
style=TableStyle([
("BACKGROUND", (0,0), (-1,-1), NAVY),
("TOPPADDING", (0,0), (-1,-1), 6),
("BOTTOMPADDING", (0,0), (-1,-1), 6),
("LEFTPADDING", (0,0), (-1,-1), 10),
("RIGHTPADDING", (0,0), (-1,-1), 10),
])
)
def cn_banner(num, name, function_text):
left = Paragraph(f"<b>CN {num} — {name}</b>", S("cnb_l",
fontSize=11, leading=14, textColor=WHITE, fontName="Helvetica-Bold"))
right = Paragraph(f"<i>{function_text}</i>", S("cnb_r",
fontSize=9, leading=12, textColor=colors.HexColor("#BEE8F0"),
fontName="Helvetica-Oblique", alignment=TA_LEFT))
return Table(
[[left, right]],
colWidths=[CONTENT_W*0.38, CONTENT_W*0.62],
style=TableStyle([
("BACKGROUND", (0,0), (-1,-1), TEAL),
("TOPPADDING", (0,0), (-1,-1), 5),
("BOTTOMPADDING", (0,0), (-1,-1), 5),
("LEFTPADDING", (0,0), (-1,-1), 8),
("RIGHTPADDING", (0,0), (-1,-1), 8),
("VALIGN", (0,0), (-1,-1), "MIDDLE"),
])
)
def two_col_table(rows, col_ratio=(0.32, 0.68)):
w0 = CONTENT_W * col_ratio[0]
w1 = CONTENT_W * col_ratio[1]
data = []
for label_text, value_text in rows:
data.append([
Paragraph(f"<b>{label_text}</b>", S("tl", fontSize=8.5, leading=12,
textColor=NAVY, fontName="Helvetica-Bold")),
Paragraph(value_text, S("tv", fontSize=8.5, leading=12,
textColor=GREY_TEXT, fontName="Helvetica"))
])
ts = TableStyle([
("BACKGROUND", (0,0), (-1,-1), LIGHT_ROW),
("ROWBACKGROUNDS",(0,0), (-1,-1), [LIGHT_ROW, ALT_ROW]),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 6),
("RIGHTPADDING", (0,0), (-1,-1), 6),
("VALIGN", (0,0), (-1,-1), "TOP"),
("BOX", (0,0), (-1,-1), 0.5, colors.HexColor("#BBBBBB")),
("LINEBELOW", (0,0), (-1,-1), 0.3, colors.HexColor("#CCCCCC")),
])
return Table(data, colWidths=[w0, w1], style=ts, hAlign="LEFT")
# ── Page numbering ────────────────────────────────────────────────────────────
def on_page(canvas, doc):
canvas.saveState()
# Header bar
canvas.setFillColor(NAVY)
canvas.rect(1.8*cm, H - 1.3*cm, CONTENT_W, 0.55*cm, fill=1, stroke=0)
canvas.setFillColor(WHITE)
canvas.setFont("Helvetica-Bold", 8)
canvas.drawString(1.9*cm, H - 1.05*cm, "CRANIAL NERVE EXAMINATION | STUDY GUIDE")
canvas.setFont("Helvetica", 8)
canvas.drawRightString(1.8*cm + CONTENT_W - 0.1*cm, H - 1.05*cm,
f"Page {doc.page}")
# Footer
canvas.setFillColor(colors.HexColor("#888888"))
canvas.setFont("Helvetica", 7)
canvas.drawCentredString(W/2, 1.3*cm,
"Sources: Harrison's Principles of Internal Medicine 22E • Textbook of Family Medicine 9e • Goldman-Cecil Medicine")
canvas.restoreState()
def on_first_page(canvas, doc):
# Solid navy cover background for page 1
canvas.saveState()
canvas.setFillColor(NAVY)
canvas.rect(0, 0, W, H, fill=1, stroke=0)
canvas.restoreState()
# ── Build content ─────────────────────────────────────────────────────────────
story = []
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# COVER PAGE
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
story.append(Spacer(1, 5.5*cm))
story.append(Paragraph("CRANIAL NERVE", cover_title))
story.append(Paragraph("EXAMINATION", cover_title))
story.append(Spacer(1, 0.6*cm))
story.append(Paragraph("Complete Study Guide • CN I – XII", cover_sub))
story.append(Spacer(1, 0.4*cm))
story.append(Paragraph("Testing Techniques • Normal Findings • Abnormal Signs • Clinical Pearls", cover_sub))
story.append(Spacer(1, 3.5*cm))
# Decorative accent line
story.append(Table(
[[""]],
colWidths=[CONTENT_W],
style=TableStyle([
("LINEABOVE", (0,0), (-1,-1), 2, AMBER),
("TOPPADDING", (0,0), (-1,-1), 0),
("BOTTOMPADDING", (0,0), (-1,-1), 0),
])
))
story.append(Spacer(1, 0.5*cm))
story.append(Paragraph(
"Based on Harrison's Principles of Internal Medicine 22E,<br/>"
"Textbook of Family Medicine 9e & Goldman-Cecil Medicine",
S("src", fontSize=9, leading=13, alignment=TA_CENTER,
textColor=colors.HexColor("#A0C8D8"), fontName="Helvetica-Oblique")))
story.append(PageBreak())
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# QUICK REFERENCE TABLE (page 2)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
story.append(Spacer(1, 0.3*cm))
story.append(section_banner("QUICK REFERENCE — ALL 12 CRANIAL NERVES"))
story.append(Spacer(1, 0.3*cm))
qr_headers = ["CN", "Name", "Type", "Key Function", "Test Method", "Abnormal Finding"]
qr_col_w = [1.0*cm, 2.8*cm, 1.4*cm, 3.5*cm, 4.3*cm, 4.3*cm]
qr_data = [
["I", "Olfactory", "Sensory", "Smell",
"Identify odorant (eyes closed)",
"Anosmia — unilateral (mass) or bilateral (neurodegen./post-viral)"],
["II", "Optic", "Sensory", "Vision; afferent pupil reflex",
"Snellen acuity; confrontation fields; fundoscopy; RAPD",
"↓ acuity; field defect; papilledema; RAPD"],
["III", "Oculomotor", "Motor+PS", "EOM (MR,IR,SR,IO); lid; pupil constriction",
"Pupil light/accommodation; H-pattern EOM",
"Ptosis; fixed dilated pupil; 'down & out' eye"],
["IV", "Trochlear", "Motor", "Superior oblique (depression in adduction)",
"H-pattern; vertical diplopia on downward gaze",
"Vertical diplopia; head tilt away from lesion"],
["V", "Trigeminal", "Mixed", "Facial sensation; mastication; corneal afferent",
"Touch/pin/temp in V1,V2,V3; jaw clench; corneal reflex",
"Hemisensory loss; absent corneal reflex; jaw deviation"],
["VI", "Abducens", "Motor", "Lateral rectus (abduction)",
"Lateral gaze — H-pattern",
"Esotropia; failure to abduct; horizontal diplopia"],
["VII", "Facial", "Mixed", "Facial expression; taste ant. 2/3 tongue",
"Wrinkle forehead; close eyes; smile; puff cheeks; taste",
"LMN: whole face weak; UMN: forehead spared"],
["VIII", "Vestibulocochlear","Sensory", "Hearing; balance",
"Whisper; Rinne (512 Hz); Weber; finger rub",
"Conductive vs sensorineural loss; lateralising Weber"],
["IX", "Glossopharyngeal","Mixed", "Pharyngeal sensation; taste post. 1/3 tongue",
"Gag reflex (afferent arc)",
"Absent gag (afferent); loss of posterior tongue taste"],
["X", "Vagus", "Mixed", "Palate/larynx motor; parasympathetics",
"Say 'Aah' — palate/uvula elevation; voice",
"Uvula deviates away from lesion; hoarseness"],
["XI", "Spinal Accessory","Motor", "Sternocleidomastoid; trapezius",
"Shoulder shrug vs resistance; head turn vs resistance",
"Weakness/wasting of SCM or trapezius"],
["XII", "Hypoglossal", "Motor", "Tongue movement",
"Protrude tongue; press tongue against cheek",
"Tongue deviates toward lesion; atrophy; fasciculations"],
]
# Header row
qr_table_data = [[Paragraph(f"<b>{h}</b>", S("qh", fontSize=8, leading=10,
textColor=WHITE, fontName="Helvetica-Bold")) for h in qr_headers]]
for i, row in enumerate(qr_data):
qr_table_data.append([
Paragraph(f"<b>{row[0]}</b>", S("qcn", fontSize=8.5, leading=11,
textColor=NAVY, fontName="Helvetica-Bold")),
Paragraph(f"<b>{row[1]}</b>", S("qn", fontSize=7.5, leading=11,
textColor=GREY_TEXT, fontName="Helvetica-Bold")),
Paragraph(row[2], S("qt", fontSize=7, leading=10,
textColor=colors.HexColor("#666666"), fontName="Helvetica-Oblique")),
Paragraph(row[3], S("qf", fontSize=7.5, leading=11,
textColor=GREY_TEXT, fontName="Helvetica")),
Paragraph(row[4], S("qtm", fontSize=7.5, leading=11,
textColor=GREY_TEXT, fontName="Helvetica")),
Paragraph(row[5], S("qa", fontSize=7.5, leading=11,
textColor=colors.HexColor("#8B1A1A"), fontName="Helvetica")),
])
qr_ts = TableStyle([
("BACKGROUND", (0,0), (-1,0), NAVY),
("ROWBACKGROUNDS",(0,1), (-1,-1), [LIGHT_ROW, ALT_ROW]),
("TOPPADDING", (0,0), (-1,-1), 4),
("BOTTOMPADDING", (0,0), (-1,-1), 4),
("LEFTPADDING", (0,0), (-1,-1), 4),
("RIGHTPADDING", (0,0), (-1,-1), 4),
("VALIGN", (0,0), (-1,-1), "TOP"),
("BOX", (0,0), (-1,-1), 0.8, NAVY),
("LINEBELOW", (0,0), (-1,-1), 0.3, colors.HexColor("#BBBBBB")),
("LINEAFTER", (0,0), (-1,-1), 0.3, colors.HexColor("#BBBBBB")),
])
qr_table = Table(qr_table_data, colWidths=qr_col_w, style=qr_ts, repeatRows=1)
story.append(qr_table)
# Mnemonic
story.append(Spacer(1, 0.4*cm))
story.append(tip_box(
"<b>Mnemonic — Type (S=Sensory, M=Motor, B=Both):</b> "
"\"<b>Some Say Money Matters But My Brother Says Big Brains Matter More</b>\"<br/>"
"CN I(S) II(S) III(M) IV(M) V(B) VI(M) VII(B) VIII(S) IX(B) X(B) XI(M) XII(M)",
bg=colors.HexColor("#FFF8E7"), text_color=colors.HexColor("#5A3800")
))
story.append(PageBreak())
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# DETAILED CN SECTIONS (pages 3–7)
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
def sp(n=0.2): return Spacer(1, n*cm)
# ── CN I ─────────────────────────────────────────────────────────────────────
story.append(section_banner("DETAILED CRANIAL NERVE EXAMINATION"))
story.append(sp(0.3))
story.append(cn_banner("I", "Olfactory", "Smell"))
story.append(sp(0.2))
story.append(two_col_table([
("When to test", "Suspected inferior frontal lobe disease, head trauma, or neurodegenerative disease (Parkinson's, Alzheimer's). Often omitted in routine screening."),
("Technique", "Patient closes eyes. Occlude one nostril. Present mild, non-irritant odorant (coffee, toothpaste, vanilla) to the other nostril. Ask patient to identify the smell. Repeat opposite side."),
("Normal", "Patient correctly identifies odorant bilaterally."),
("Abnormal", "Anosmia (absent) or hyposmia (reduced). Unilateral: meningioma, frontal lobe lesion, head trauma. Bilateral: neurodegenerative disease, post-viral (e.g., post-COVID-19)."),
("⚠ Caution", "Do NOT use ammonia — it stimulates CN V (trigeminal pain fibres), not CN I."),
]))
story.append(sp(0.4))
# ── CN II ────────────────────────────────────────────────────────────────────
story.append(cn_banner("II", "Optic", "Vision • Afferent pupillary reflex"))
story.append(sp(0.2))
story.append(two_col_table([
("Visual Acuity", "Test each eye separately with best correction (glasses/contacts). Use Snellen chart at 6 m or near card at 35 cm. Record as fraction (e.g., 20/20, 20/200). If no chart: count fingers (CF), hand motion (HM), light perception (LP)."),
("Visual Fields", "Sit 0.6–1.0 m from patient. Patient fixes on your nose. Hold both hands at periphery of your visual field. Wiggle finger in 4 quadrants (inferior then superior). Test both eyes simultaneously for neglect, then each eye individually. Formal perimetry if defect found."),
("Fundoscopy", "Examine optic disc (color, margins, cup-to-disc ratio), retinal vessels (AV nicking, caliber, hemorrhage, exudates), and macula. Pale disc = optic atrophy. Blurred disc margins = papilledema."),
("Pupillary Reflex", "Shine bright light in each eye. Normal: brisk, equal constriction. Swinging flashlight test for RAPD: affected eye dilates when light swings to it (optic nerve lesion)."),
("Abnormal", "↓ Acuity (optic neuritis, glaucoma); bitemporal hemianopia (chiasm — pituitary tumour); homonymous hemianopia (optic tract/radiation); RAPD (MS, optic neuritis); papilledema (↑ICP)."),
]))
story.append(sp(0.4))
# ── CN III, IV, VI ───────────────────────────────────────────────────────────
story.append(cn_banner("III, IV, VI", "Oculomotor • Trochlear • Abducens", "Extraocular movements • Pupil constriction • Eyelid"))
story.append(sp(0.2))
story.append(Paragraph("EOM Muscle Map:", label))
eom_data = [
[Paragraph("<b>Nerve</b>", S("eh",fontSize=8,textColor=WHITE,fontName="Helvetica-Bold")),
Paragraph("<b>Muscle(s)</b>", S("eh",fontSize=8,textColor=WHITE,fontName="Helvetica-Bold")),
Paragraph("<b>Action</b>", S("eh",fontSize=8,textColor=WHITE,fontName="Helvetica-Bold")),
Paragraph("<b>Test direction</b>", S("eh",fontSize=8,textColor=WHITE,fontName="Helvetica-Bold"))],
["CN III", "Medial rectus", "Adduction", "Look IN (nasally)"],
["CN III", "Superior rectus", "Elevation in abduction", "Look UP + OUT"],
["CN III", "Inferior rectus", "Depression in abduction", "Look DOWN + OUT"],
["CN III", "Inferior oblique", "Elevation in adduction", "Look UP + IN"],
["CN III", "Levator palpebrae", "Eyelid elevation", "Inspect for ptosis"],
["CN III (PS)", "Pupillary constrictor", "Miosis", "Light reflex"],
["CN IV", "Superior oblique", "Depression + intorsion in adduction", "Look DOWN + IN"],
["CN VI", "Lateral rectus", "Abduction", "Look OUT (laterally)"],
]
eom_ts = TableStyle([
("BACKGROUND", (0,0), (-1,0), TEAL),
("ROWBACKGROUNDS",(0,1), (-1,-1), [LIGHT_ROW, ALT_ROW]),
("TOPPADDING", (0,0), (-1,-1), 3), ("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5), ("RIGHTPADDING", (0,0), (-1,-1), 5),
("FONTNAME", (0,1), (-1,-1), "Helvetica"),
("FONTSIZE", (0,1), (-1,-1), 8),
("BOX", (0,0), (-1,-1), 0.5, TEAL),
("LINEBELOW", (0,0), (-1,-1), 0.3, colors.HexColor("#CCCCCC")),
])
story.append(Table(eom_data,
colWidths=[1.5*cm, 3.5*cm, 4.5*cm, CONTENT_W-9.5*cm],
style=eom_ts))
story.append(sp(0.2))
story.append(two_col_table([
("EOM Testing", "Ask patient to keep head still. Track your finger slowly in an H-pattern (horizontal then each diagonal). Hold lateral positions for a few seconds. Assess for limitation, nystagmus, or saccadic pursuit. Ask patient to report diplopia in any position — resolves on covering one eye."),
("Pupils", "Describe size in mm and symmetry. Direct reflex: light → same eye constricts (CN III efferent). Consensual: light → opposite eye constricts. Accommodation: convergence + miosis as target approaches nose."),
("Nystagmus", "Test at 45° lateral gaze (not extreme). Hold position ≥3 s. Horizontal nystagmus: jerk direction = fast phase. Vertical nystagmus: central lesion until proven otherwise."),
("Abnormal CN III", "Ptosis, fixed dilated pupil (mydriasis), eye deviated 'down and out'. Surgical CN III (compressive): pupil affected FIRST (outer parasympathetic fibres compressed). Medical CN III (diabetic): pupil SPARED."),
("Abnormal CN IV", "Vertical diplopia worse going downstairs. Patient tilts head away from affected side. CN IV palsy = most common cause of acquired vertical diplopia."),
("Abnormal CN VI", "Esotropia (convergent squint). Failure to abduct. Horizontal diplopia maximal on ipsilateral gaze. False localising sign with ↑ICP (long intracranial course)."),
("Horner Syndrome", "Miosis + ptosis + anhidrosis (± enophthalmos). Sympathetic chain disruption. Differentiate from CN III: Horner = small pupil; CN III = large pupil."),
]))
story.append(PageBreak())
# ── CN V ─────────────────────────────────────────────────────────────────────
story.append(cn_banner("V", "Trigeminal", "Facial sensation • Mastication • Corneal reflex (afferent)"))
story.append(sp(0.2))
story.append(two_col_table([
("Divisions", "V1 Ophthalmic: forehead, upper eyelid, scalp to vertex, cornea, nasal tip. V2 Maxillary: cheek, lower eyelid, upper lip, upper teeth, nasal ala. V3 Mandibular: lower jaw, lower lip, lower teeth, anterior 2/3 tongue (touch only — not taste)."),
("Sensory Testing", "Apply light touch (cotton wisp) and pinprick (or cold tuning fork for temperature) in all three divisions on each side. Compare left vs right and across divisions. Ask if sensation feels equal."),
("Corneal Reflex", "Touch cornea (not conjunctiva) with fine cotton wisp or sterile saline. Normal: bilateral brisk eye closure. Afferent = CN V1; efferent = CN VII. Absent on stimulus side only: CN V lesion. Absent closure ipsilaterally only (sensation intact): CN VII lesion."),
("Motor (V3)", "Ask patient to clench jaw. Palpate masseter and temporalis for bulk and symmetry. Ask patient to open mouth against resistance — jaw deviates toward the weak side (ipsilateral to lesion)."),
("Jaw Jerk Reflex", "Tap chin (lower jaw) gently with patient's mouth slightly open. Normal: minimal or absent. Exaggerated jaw jerk = bilateral UMN lesion above the pons (pseudobulbar palsy)."),
("Abnormal", "Unilateral facial numbness (tumour, MS, stroke). Trigeminal neuralgia: lancinating pain V2/V3 distribution, triggered by touch. Absent corneal reflex: CN V or CN VII lesion, or corneal disease."),
]))
story.append(sp(0.4))
# ── CN VII ───────────────────────────────────────────────────────────────────
story.append(cn_banner("VII", "Facial", "Facial expression • Taste ant. 2/3 tongue • Lacrimal/salivary glands"))
story.append(sp(0.2))
story.append(two_col_table([
("Motor Testing", "1. Raise eyebrows / wrinkle forehead. 2. Close eyes tightly (try to open them). Bell's phenomenon: normal upward globe rotation. 3. Puff out both cheeks — press to check air escape. 4. Show teeth / smile — assess symmetry. 5. Whistle or purse lips."),
("UMN vs LMN", "UMN lesion (contralateral cortex): lower 2/3 face weak, forehead SPARED (bilateral cortical representation). LMN lesion (ipsilateral nucleus or nerve): ENTIRE face weak including forehead — Bell's palsy, parotid tumour, skull base fracture."),
("Taste", "Test anterior 2/3 of tongue on each side with tongue extended (prevents contralateral spread). Apply sweet (sugar) or bitter (quinine) solution with cotton applicator. Patient identifies taste."),
("Other functions", "Hyperacusis: nerve to stapedius (in middle ear). Decreased lacrimation: greater petrosal nerve branch. Loss of taste + hyperacusis: lesion at geniculate ganglion or proximal to it."),
("Abnormal", "Bell's palsy (LMN, idiopathic), Ramsay Hunt syndrome (VZV at geniculate ganglion — vesicles in ear), parotid tumour, stroke (UMN), MS, Lyme disease, sarcoidosis."),
]))
story.append(sp(0.4))
# ── CN VIII ──────────────────────────────────────────────────────────────────
story.append(cn_banner("VIII", "Vestibulocochlear", "Hearing (cochlear) • Balance (vestibular)"))
story.append(sp(0.2))
story.append(two_col_table([
("Screening", "Rub fingers next to each ear or whisper a 2-syllable word (e.g., 'baseball') at 60 cm. Ask patient to repeat. Mask other ear by rubbing tragus. Test each side separately."),
("Rinne Test", "Use 512 Hz tuning fork. Strike fork, place on mastoid (bone conduction — BC). When no longer heard, move to ear canal entrance (air conduction — AC). Rinne POSITIVE (normal): AC > BC. Rinne NEGATIVE: BC > AC = conductive loss on that side. Sensorineural loss: both reduced but AC still > BC."),
("Weber Test", "Place vibrating 512 Hz fork on forehead midline. Normal: equal in both ears (or 'centre'). Lateralises to WORSE ear: conductive loss on that side (bone conducts better without air competition). Lateralises to BETTER ear: sensorineural loss on the opposite side."),
("Vestibular", "Not routinely tested. In dizziness: Dix-Hallpike maneuver (BPPV). In coma: ice-water caloric (oculocephalic reflex). Head impulse test for semicircular canal function."),
("Abnormal", "Conductive loss: wax, otitis media, otosclerosis. Sensorineural: presbycusis, acoustic neuroma (CN VIII schwannoma — also causes tinnitus and loss of corneal reflex), noise trauma, ototoxic drugs (aminoglycosides, cisplatin)."),
]))
story.append(PageBreak())
# ── CN IX & X ────────────────────────────────────────────────────────────────
story.append(cn_banner("IX & X", "Glossopharyngeal & Vagus", "Pharynx, palate, larynx • Gag reflex • Parasympathetics"))
story.append(sp(0.2))
story.append(two_col_table([
("Palate / Uvula", "Ask patient to say 'Aah'. Observe: symmetric elevation of soft palate; uvula remains MIDLINE. Unilateral vagal lesion: palate drops on AFFECTED side; uvula deviates AWAY from lesion (toward normal side)."),
("Gag Reflex", "Afferent = CN IX (posterior pharyngeal sensation). Efferent = CN X (pharyngeal contraction). Touch posterior pharyngeal wall on each side with tongue blade. Normal: brisk bilateral contraction of pharynx. May be absent in 20% of normal individuals — absence alone is not pathological."),
("CN IX specifics", "Sensation to posterior 1/3 of tongue and pharynx. Taste to posterior 1/3 of tongue. Glossopharyngeal neuralgia: severe pain in posterior tongue, tonsillar fossa, ear — triggered by swallowing."),
("CN X specifics", "Motor to pharynx, soft palate, larynx. Hoarseness or dysphonia: recurrent laryngeal nerve injury (left RLN at aortic arch, right RLN at subclavian artery). Parasympathetic: heart, lungs, gut to splenic flexure."),
("Abnormal", "Bulbar palsy (LMN CN IX/X/XII): nasal dysphonia, dysphagia, pooling of secretions, absent gag. Pseudobulbar palsy (UMN bilateral): exaggerated gag, emotional lability. Jugular foramen syndrome: CN IX, X, XI palsy together."),
]))
story.append(sp(0.4))
# ── CN XI ────────────────────────────────────────────────────────────────────
story.append(cn_banner("XI", "Spinal Accessory", "Sternocleidomastoid • Trapezius"))
story.append(sp(0.2))
story.append(two_col_table([
("Trapezius test", "Stand behind patient. Place hands on both shoulders. Ask patient to shrug upward against downward resistance. Compare strength and bulk bilaterally."),
("SCM test", "Place your hand on patient's cheek. Ask them to turn their head to that side against resistance. The active SCM is on the SAME side as head rotation (contralateral to the direction faced). Test both sides."),
("Inspection", "Inspect for wasting or asymmetry of trapezius (shoulder droop) and SCM."),
("Cortical representation", "Each SCM receives ipsilateral cortical input (unusual — most cranial nerve motor nuclei receive contralateral input). Therefore UMN lesion (e.g., stroke) causes weakness of the IPSILATERAL SCM and CONTRALATERAL trapezius."),
("Abnormal", "Neck dissection (surgical injury). Jugular foramen lesions. Shoulder drooping, inability to turn head. Accessory nerve mononeuropathy."),
]))
story.append(sp(0.4))
# ── CN XII ───────────────────────────────────────────────────────────────────
story.append(cn_banner("XII", "Hypoglossal", "Tongue movement — all intrinsic + most extrinsic tongue muscles"))
story.append(sp(0.2))
story.append(two_col_table([
("Inspection at rest", "Inspect tongue in floor of mouth (before protrusion). Look for: atrophy (hemiatrophy = LMN lesion), fasciculations (LMN — best seen at rest on floor of mouth)."),
("Protrusion", "Ask patient to 'stick out tongue'. LMN lesion: tongue deviates TOWARD the lesion (weak side cannot push). UMN lesion: tongue deviates AWAY from lesion (contralateral), no atrophy or fasciculations."),
("Strength", "Ask patient to press tongue against inner cheek on each side while you press from outside. Compare resistance bilaterally."),
("Rapid movements", "Ask patient to wag tongue rapidly side to side — tests speed and coordination of tongue movement (dysarthria assessment)."),
("Abnormal", "LMN (nucleus/nerve): atrophy, fasciculations, tongue deviation toward lesion — motor neuron disease, hypoglossal canal tumour, trauma. UMN (bilateral, above nucleus): pseudobulbar palsy, spastic tongue, no atrophy. Tongue weakness → dysarthria, dysphagia."),
]))
story.append(sp(0.5))
# ── CLINICAL PEARLS ──────────────────────────────────────────────────────────
story.append(PageBreak())
story.append(section_banner("CLINICAL PEARLS & COMMON EXAM TRAPS"))
story.append(sp(0.3))
pearls = [
("CN III: Surgical vs Medical palsy",
"Compressive (surgical) CN III palsy — e.g., posterior communicating artery aneurysm — affects pupil FIRST (parasympathetic fibres on outside of nerve). Pupil is dilated and unreactive. Diabetic (medical) CN III palsy spares the pupil because small vessel ischaemia affects the core motor fibres. Key rule: CN III palsy + dilated pupil = aneurysm until proven otherwise."),
("CN VI: False localising sign",
"CN VI has the longest intracranial course. Raised ICP can stretch CN VI causing a lateral rectus palsy that does NOT indicate a CN VI-territory lesion. Always consider ↑ICP when CN VI palsy lacks other localising signs."),
("UMN vs LMN Facial Palsy",
"UMN (cortical stroke): forehead spared, lower face weak. LMN (Bell's palsy, parotid): entire ipsilateral face weak, forehead involved. Corneal reflex may be lost in LMN. Test forehead wrinkling FIRST — it is the key discriminating feature."),
("RAPD (Marcus Gunn Pupil)",
"In the swinging flashlight test, the affected eye DILATES when light swings to it. Both pupils are equal in size; the reflex is what changes. Indicates unilateral optic nerve or severe retinal disease. Absent in cortical blindness (post-chiasmal) — pupils remain equal and reactive."),
("Tongue Deviation Direction",
"LMN hypoglossal palsy: tongue deviates TOWARD the lesion. UMN hypoglossal palsy: tongue deviates AWAY from the lesion (toward the contralateral weak side). Atrophy and fasciculations = LMN only."),
("Rinne + Weber Combined Interpretation",
"To determine which ear is pathological: Rinne negative (BC > AC) + Weber lateralises to SAME side = CONDUCTIVE loss on Rinne-negative side. Rinne positive (AC > BC) but reduced + Weber lateralises to OPPOSITE side = SENSORINEURAL loss on that side."),
("Gag Reflex Absence",
"The gag reflex is absent in up to 20% of normal adults. Its absence alone is NOT pathological. The key is asymmetry (one side absent, one intact) or complete absence in the context of other lower cranial nerve signs."),
("Corneal Reflex Clinical Use",
"Useful in comatose patients to assess CN V and CN VII integrity. Also an early, sensitive sign of ipsilateral CN V lesion. Contact lens wearers may have reduced corneal sensitivity — ask beforehand."),
]
for title_text, detail_text in pearls:
story.append(KeepTogether([
Paragraph(f"▸ {title_text}", S("pt", fontSize=9.5, leading=13,
textColor=NAVY, fontName="Helvetica-Bold", spaceBefore=8, spaceAfter=2)),
Paragraph(detail_text, S("pd", fontSize=8.5, leading=13,
textColor=GREY_TEXT, fontName="Helvetica",
leftIndent=14, spaceAfter=4)),
HRFlowable(width="100%", thickness=0.3, color=colors.HexColor("#CCCCCC"),
spaceAfter=2)
]))
story.append(sp(0.4))
# ── EXAMINATION SEQUENCE CHECKLIST ───────────────────────────────────────────
story.append(section_banner("BEDSIDE EXAMINATION SEQUENCE CHECKLIST"))
story.append(sp(0.2))
checklist = [
("□ CN I", "Smell test with mild odorant (if indicated)"),
("□ CN II", "Visual acuity (Snellen / near card)"),
("□ CN II", "Visual fields — confrontation (bilateral then monocular)"),
("□ CN II", "Fundoscopy — disc, vessels, macula"),
("□ CN II+III", "Pupil inspection (size, shape, symmetry)"),
("□ CN II+III", "Direct and consensual light reflex"),
("□ CN III", "Accommodation reflex (convergence + miosis)"),
("□ CN III+IV+VI", "Extraocular movements — H-pattern"),
("□ CN III+IV+VI", "Nystagmus at 45° lateral gaze"),
("□ CN V", "Facial sensation — light touch and pin/temp, all 3 divisions"),
("□ CN V+VII", "Corneal reflex"),
("□ CN V", "Jaw clench (masseter/temporalis) — motor V3"),
("□ CN VII", "Raise eyebrows / wrinkle forehead"),
("□ CN VII", "Close eyes tightly"),
("□ CN VII", "Puff cheeks / smile / show teeth"),
("□ CN VII", "Taste — anterior 2/3 tongue (if indicated)"),
("□ CN VIII","Whisper test / finger rub — each ear"),
("□ CN VIII","Rinne test (512 Hz tuning fork)"),
("□ CN VIII","Weber test (512 Hz tuning fork)"),
("□ CN IX+X","Say 'Aah' — palate elevation, uvula position"),
("□ CN IX+X","Gag reflex — both sides"),
("□ CN XI", "Shoulder shrug against resistance (trapezius)"),
("□ CN XI", "Head rotation against resistance (SCM)"),
("□ CN XII", "Inspect tongue at rest (atrophy, fasciculations)"),
("□ CN XII", "Protrude tongue — note deviation"),
("□ CN XII", "Tongue strength vs cheek"),
]
chk_data = [[
Paragraph(cn_t, S("ck", fontSize=8.5, leading=12, textColor=TEAL,
fontName="Helvetica-Bold")),
Paragraph(test_t, S("ck2", fontSize=8.5, leading=12, textColor=GREY_TEXT,
fontName="Helvetica"))
] for cn_t, test_t in checklist]
chk_ts = TableStyle([
("ROWBACKGROUNDS",(0,0), (-1,-1), [WHITE, colors.HexColor("#F3F9FA")]),
("TOPPADDING", (0,0), (-1,-1), 3),
("BOTTOMPADDING", (0,0), (-1,-1), 3),
("LEFTPADDING", (0,0), (-1,-1), 5),
("RIGHTPADDING", (0,0), (-1,-1), 5),
("LINEBELOW", (0,0), (-1,-1), 0.2, colors.HexColor("#CCCCCC")),
("BOX", (0,0), (-1,-1), 0.5, colors.HexColor("#AAAAAA")),
])
story.append(Table(chk_data, colWidths=[2.5*cm, CONTENT_W-2.5*cm], style=chk_ts))
story.append(sp(0.5))
story.append(tip_box(
"<b>Minimum screening exam (Harrison's):</b> Check the fundi, visual fields, "
"pupil size and reactivity, extraocular movements, and facial movements. "
"Examine CNs in numerical order, grouping CN III, IV, VI together.",
bg=LIGHT_BG, text_color=NAVY
))
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
# BUILD
# ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
doc.build(story, onFirstPage=on_first_page, onLaterPages=on_page)
print(f"PDF created: {OUTPUT}")
cd /home/daytona/workspace/cranial-nerve-guide && python3 build_pdf.py 2>&1
ls -lh /home/daytona/workspace/cranial-nerve-guide/Cranial_Nerve_Examination_Study_Guide.pdf