CNS examination in detail for MD medicine resistant based on standards book
| Type | Test |
|---|---|
| Immediate recall | Repeat 3 words immediately |
| Short-term (recent) memory | Recall 3 words after 5 minutes |
| Remote memory | Historical facts, past personal events |
| Episodic | Recall of specific dated personal events |
| Component | Test |
|---|---|
| Spontaneous speech | Fluency, content, word-finding |
| Comprehension | "Point to the window," execute commands |
| Repetition | Repeat "no ifs, ands, or buts" |
| Naming | Name objects, parts of objects (anomia) |
| Reading | Aloud and for comprehension |
| Writing | Spontaneous and to dictation |
| Spelling | Spell simple words forward/backward |
| Grade | Description |
|---|---|
| 0 | No contraction |
| 1 | Flicker of contraction, no movement |
| 2 | Movement with gravity eliminated |
| 3 | Movement against gravity but not resistance |
| 4 | Movement against some resistance (4-, 4, 4+ variants) |
| 5 | Normal power |
| Reflex | Stimulus Site | Root | Response |
|---|---|---|---|
| Biceps | Biceps tendon | C5-C6 | Elbow flexion |
| Supinator (Brachioradialis) | Radial styloid | C5-C6 | Forearm supination/flexion |
| Triceps | Triceps tendon | C7-C8 | Elbow extension |
| Finger flexion | Examiner's fingers on patient's | C8-T1 | Finger flexion |
| Patellar (Knee jerk) | Patellar tendon | L3-L4 | Knee extension |
| Achilles (Ankle jerk) | Achilles tendon | S1-S2 | Plantarflexion |
| Modality | Pathway | Test |
|---|---|---|
| Pain | Spinothalamic (contralateral) | New disposable pin; sharp vs dull |
| Temperature | Spinothalamic (contralateral) | Cold (metal tuning fork) vs warm; or test tubes |
| Vibration | Posterior column (ipsilateral) | 128 Hz tuning fork on bony prominences (toes, medial malleolus, tibial crest, knee, ASIS, fingers, wrist, elbow, shoulder) |
| Proprioception (Joint position sense - JPS) | Posterior column (ipsilateral) | Move distal phalanx up or down with fingers on lateral sides; start distally (toes, fingers); normal = detect small movements |
| Light touch | Posterior column + spinothalamic | Wisp of cotton; compare sides |
| Pattern | Lesion Site |
|---|---|
| Glove-and-stocking | Peripheral neuropathy (dying-back) |
| Dermatomal | Nerve root (radiculopathy) |
| Dissociated (pain/temp lost, vibration/JPS intact) | Spinothalamic only: syringomyelia, Brown-Sequard |
| Contralateral hemibody (all modalities) | Internal capsule/thalamus |
| Ipsilateral body + contralateral face | Lateral medullary syndrome (Wallenberg) |
| Saddle distribution | Cauda equina lesion |
| Cortical sensory loss with normal primary sensation | Parietal lobe lesion |
| Gait Type | Features | Localization |
|---|---|---|
| Hemiplegic | Arm flexed, adducted; leg circumducts | Corticospinal (contralateral) |
| Scissors (Spastic-bilateral) | Both legs stiff, cross in front | Bilateral corticospinal |
| Steppage (foot drop) | High-step, foot slaps floor; cannot heel-walk | L4-5, peroneal nerve |
| Trendelenburg (waddling) | Pelvis dips to opposite side on step | Hip abductors (L4-5, S1) |
| Cerebellar ataxic | Wide-based, reeling, irregular; falls to side of lesion | Cerebellum/cerebellar tracts |
| Sensory ataxic | Wide-based, high-stepping, "stamping"; worsens eyes closed (Romberg +) | Posterior columns / peripheral neuropathy |
| Parkinsonian | Shuffling small steps; stooped; reduced arm swing; festination; en-bloc turns | Basal ganglia |
| Apraxic (Magnetic) | "Feet glued to floor"; small shuffling steps; normal on lying | Frontal lobe (NPH, frontal mass) |
| Antalgic | Shortened stance phase on painful limb | Musculoskeletal |
| Component | Key Tests |
|---|---|
| Mental Status | Orientation, memory (3-word recall), language (spontaneous speech, naming), attention (serial 7s) |
| CN I | Smell (if indicated: trauma, Parkinson's) |
| CN II | Visual acuity, confrontation fields, fundoscopy, pupil reactions |
| CN III, IV, VI | Eye movements (H-pattern), pupils, nystagmus |
| CN V | Pin/touch on face, corneal reflex |
| CN VII | Raise brows, close eyes tightly, smile to command |
| CN VIII | Whisper test, Rinne & Weber |
| CN IX-X | Palate elevation, gag, voice quality |
| CN XI | Shoulder shrug, head turn |
| CN XII | Tongue protrusion, fasciculations |
| Motor | Tone, power (proximal and distal), pronator drift, heel/toe walk |
| Reflexes | Biceps (C5-6), triceps (C7), supinator (C6), knee (L3-4), ankle (S1), plantars |
| Coordination | Finger-nose-finger, heel-shin, rapid alternating movements |
| Sensory | Pin, vibration, joint position sense (fingers and toes) |
| Gait/Station | Observation, Romberg, tandem walk |
| Feature | UMN (Central) | LMN (Peripheral) |
|---|---|---|
| Tone | Spastic (clasp-knife) | Flaccid (hypotonia) |
| Weakness | Pyramidal pattern (extensors in arm, flexors in leg) | Distribution of nerve/root/muscle |
| Reflexes | Brisk, clonus | Reduced/absent |
| Plantar | Extensor (Babinski) | Flexor |
| Fasciculations | Absent | Present |
| Wasting | Late, disuse only | Early, prominent |
| Abdominal reflexes | Absent | Present |
Create a comprehensive PDF reference card of CNS examination techniques
ls -la /home/daytona/workspace/ 2>/dev/null || mkdir -p /home/daytona/workspace && ls -la /home/daytona/workspace/
mkdir -p /home/daytona/workspace/cns-reference-card && python3 -c "import reportlab; print(reportlab.Version)"
~/cns-reference-card/generate_cns_pdf.py
#!/usr/bin/env python3
"""
CNS Examination Reference Card - Comprehensive PDF Generator
Based on Adams & Victor's Principles of Neurology 12th Ed and
Bradley & Daroff's Neurology in Clinical Practice
"""
from reportlab.lib.pagesizes import A4
from reportlab.lib import colors
from reportlab.lib.units import mm, cm
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
from reportlab.platypus import (
SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle,
HRFlowable, KeepTogether, PageBreak
)
from reportlab.platypus.flowables import Flowable
from reportlab.pdfgen import canvas
from reportlab.lib.colors import HexColor
import os
OUTPUT_PATH = "/home/daytona/workspace/cns-reference-card/CNS_Examination_Reference_Card.pdf"
# ── Colour Palette ──────────────────────────────────────────────────────────
NAVY = HexColor("#1A2E4A")
TEAL = HexColor("#1E7B7B")
TEAL_LIGHT = HexColor("#E8F5F5")
BLUE_MID = HexColor("#2D5F8A")
BLUE_LIGHT = HexColor("#EBF3FA")
AMBER = HexColor("#D97706")
AMBER_LT = HexColor("#FFF8E7")
RED_DK = HexColor("#8B1A1A")
RED_LT = HexColor("#FDF0F0")
GREEN_DK = HexColor("#1A5C2E")
GREEN_LT = HexColor("#EDF7F0")
GREY_BG = HexColor("#F4F6F9")
GREY_RULE = HexColor("#C8D4E0")
WHITE = colors.white
BLACK = colors.black
TEXT_DARK = HexColor("#1C2B3A")
# ── Page Number Footer ───────────────────────────────────────────────────────
class NumberedCanvas(canvas.Canvas):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self._saved_page_states = []
def showPage(self):
self._saved_page_states.append(dict(self.__dict__))
self._startPage()
def save(self):
num_pages = len(self._saved_page_states)
for state in self._saved_page_states:
self.__dict__.update(state)
self.draw_page_number(num_pages)
canvas.Canvas.showPage(self)
canvas.Canvas.save(self)
def draw_page_number(self, page_count):
page_num = self._saved_page_states.index(dict(self.__dict__)) + 1
self.saveState()
self.setFont("Helvetica", 8)
self.setFillColor(HexColor("#6B7C8F"))
w, h = A4
self.drawCentredString(w / 2, 12 * mm,
f"CNS Examination Reference Card | Page {page_num} of {page_count} | Based on Adams & Victor 12e · Bradley & Daroff")
# footer rule
self.setStrokeColor(GREY_RULE)
self.setLineWidth(0.5)
self.line(15 * mm, 16 * mm, w - 15 * mm, 16 * mm)
self.restoreState()
# ── Styles ───────────────────────────────────────────────────────────────────
def make_styles():
base = getSampleStyleSheet()
styles = {}
styles['cover_title'] = ParagraphStyle(
'cover_title', fontName='Helvetica-Bold', fontSize=26,
textColor=WHITE, alignment=TA_CENTER, spaceAfter=6, leading=32)
styles['cover_sub'] = ParagraphStyle(
'cover_sub', fontName='Helvetica', fontSize=13,
textColor=HexColor("#B8D4E8"), alignment=TA_CENTER, spaceAfter=4)
styles['cover_note'] = ParagraphStyle(
'cover_note', fontName='Helvetica', fontSize=9,
textColor=HexColor("#8AAFC8"), alignment=TA_CENTER)
styles['section_header'] = ParagraphStyle(
'section_header', fontName='Helvetica-Bold', fontSize=13,
textColor=WHITE, alignment=TA_LEFT, leading=16)
styles['sub_header'] = ParagraphStyle(
'sub_header', fontName='Helvetica-Bold', fontSize=10,
textColor=NAVY, spaceAfter=2, spaceBefore=4, leading=13)
styles['body'] = ParagraphStyle(
'body', fontName='Helvetica', fontSize=8.5,
textColor=TEXT_DARK, spaceAfter=2, leading=12)
styles['body_bold'] = ParagraphStyle(
'body_bold', fontName='Helvetica-Bold', fontSize=8.5,
textColor=TEXT_DARK, spaceAfter=2, leading=12)
styles['bullet'] = ParagraphStyle(
'bullet', fontName='Helvetica', fontSize=8.2,
textColor=TEXT_DARK, leftIndent=10, firstLineIndent=-8,
spaceAfter=1.5, leading=11.5,
bulletText='•')
styles['small'] = ParagraphStyle(
'small', fontName='Helvetica', fontSize=7.5,
textColor=HexColor("#4A5568"), leading=10)
styles['cell_hdr'] = ParagraphStyle(
'cell_hdr', fontName='Helvetica-Bold', fontSize=8,
textColor=WHITE, alignment=TA_CENTER, leading=10)
styles['cell_body'] = ParagraphStyle(
'cell_body', fontName='Helvetica', fontSize=7.8,
textColor=TEXT_DARK, alignment=TA_LEFT, leading=10.5)
styles['cell_center'] = ParagraphStyle(
'cell_center', fontName='Helvetica', fontSize=7.8,
textColor=TEXT_DARK, alignment=TA_CENTER, leading=10.5)
styles['warning'] = ParagraphStyle(
'warning', fontName='Helvetica-Bold', fontSize=8.5,
textColor=RED_DK, leading=12)
styles['clinical_pearl'] = ParagraphStyle(
'clinical_pearl', fontName='Helvetica', fontSize=8.2,
textColor=GREEN_DK, leading=12, leftIndent=8)
styles['table_title'] = ParagraphStyle(
'table_title', fontName='Helvetica-Bold', fontSize=9,
textColor=NAVY, spaceAfter=2, spaceBefore=6)
return styles
S = make_styles()
W, H = A4
MARGIN = 15 * mm
CONTENT_W = W - 2 * MARGIN
# ── Helper Flowables ─────────────────────────────────────────────────────────
def section_banner(text, color=NAVY, icon=""):
"""Full-width coloured section banner."""
label = f"{icon} {text}" if icon else text
p = Paragraph(label, S['section_header'])
t = Table([[p]], colWidths=[CONTENT_W],
rowHeights=[None])
t.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, -1), color),
('TOPPADDING', (0, 0), (-1, -1), 5),
('BOTTOMPADDING', (0, 0), (-1, -1), 5),
('LEFTPADDING', (0, 0), (-1, -1), 8),
('RIGHTPADDING', (0, 0), (-1, -1), 8),
('ROUNDEDCORNERS', [4, 4, 4, 4]),
]))
return t
def shaded_box(content_flowables, bg=BLUE_LIGHT, border=BLUE_MID, radius=4):
"""Wrap flowables in a shaded rounded box."""
inner = Table([[f] for f in content_flowables],
colWidths=[CONTENT_W - 14])
t = Table([[inner]], colWidths=[CONTENT_W])
t.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, -1), bg),
('BOX', (0, 0), (-1, -1), 0.8, border),
('TOPPADDING', (0, 0), (-1, -1), 6),
('BOTTOMPADDING', (0, 0), (-1, -1), 6),
('LEFTPADDING', (0, 0), (-1, -1), 7),
('RIGHTPADDING', (0, 0), (-1, -1), 7),
]))
return t
def two_col(left_items, right_items, left_w_frac=0.5):
"""Two-column layout. Each arg is a list of flowables."""
lw = CONTENT_W * left_w_frac - 3 * mm
rw = CONTENT_W * (1 - left_w_frac) - 3 * mm
left_col = Table([[f] for f in left_items], colWidths=[lw])
right_col = Table([[f] for f in right_items], colWidths=[rw])
t = Table([[left_col, right_col]], colWidths=[lw + 3 * mm, rw + 3 * mm])
t.setStyle(TableStyle([
('VALIGN', (0, 0), (-1, -1), 'TOP'),
('LEFTPADDING', (0, 0), (-1, -1), 0),
('RIGHTPADDING', (0, 0), (-1, -1), 0),
('TOPPADDING', (0, 0), (-1, -1), 0),
('BOTTOMPADDING', (0, 0), (-1, -1), 0),
]))
return t
def standard_table(headers, rows, col_widths=None, hdr_color=NAVY):
"""Styled data table with coloured header row."""
hdr_cells = [Paragraph(h, S['cell_hdr']) for h in headers]
data = [hdr_cells]
for r in rows:
data.append([Paragraph(str(c), S['cell_body']) for c in r])
if col_widths is None:
col_widths = [CONTENT_W / len(headers)] * len(headers)
t = Table(data, colWidths=col_widths, repeatRows=1)
style = [
('BACKGROUND', (0, 0), (-1, 0), hdr_color),
('TEXTCOLOR', (0, 0), (-1, 0), WHITE),
('ROWBACKGROUNDS', (0, 1), (-1, -1), [WHITE, GREY_BG]),
('GRID', (0, 0), (-1, -1), 0.4, GREY_RULE),
('TOPPADDING', (0, 0), (-1, -1), 3),
('BOTTOMPADDING', (0, 0), (-1, -1), 3),
('LEFTPADDING', (0, 0), (-1, -1), 5),
('RIGHTPADDING', (0, 0), (-1, -1), 5),
('VALIGN', (0, 0), (-1, -1), 'MIDDLE'),
]
t.setStyle(TableStyle(style))
return t
def pearl(text):
return shaded_box(
[Paragraph(f"<b>Clinical Pearl:</b> {text}", S['clinical_pearl'])],
bg=GREEN_LT, border=GREEN_DK)
def alert_box(text):
return shaded_box(
[Paragraph(f"<b>Important:</b> {text}", S['warning'])],
bg=RED_LT, border=RED_DK)
def sp(h=4):
return Spacer(1, h * mm)
# ── Cover Page ───────────────────────────────────────────────────────────────
class CoverPage(Flowable):
def __init__(self):
super().__init__()
self.width = CONTENT_W
self.height = H - 2 * MARGIN
def draw(self):
c = self.canv
w, h = self.width, self.height
# Background gradient-like blocks
c.setFillColor(NAVY)
c.roundRect(0, h - 120 * mm, w, 120 * mm, 8, fill=1, stroke=0)
c.setFillColor(TEAL)
c.roundRect(0, h - 130 * mm, w, 18 * mm, 4, fill=1, stroke=0)
# Title
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 30)
c.drawCentredString(w / 2, h - 38 * mm, "CNS EXAMINATION")
c.setFont("Helvetica-Bold", 22)
c.drawCentredString(w / 2, h - 52 * mm, "REFERENCE CARD")
c.setFillColor(HexColor("#B8D4E8"))
c.setFont("Helvetica", 12)
c.drawCentredString(w / 2, h - 68 * mm, "Comprehensive Neurological Examination Guide")
c.drawCentredString(w / 2, h - 78 * mm, "for MD Internal Medicine")
# Teal stripe text
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 9)
c.drawCentredString(w / 2, h - 124 * mm,
"Adams & Victor 12e · Bradley & Daroff · Harrison's 22e")
# Section index boxes
sections = [
("I", "Mental Status"),
("II", "Cranial Nerves"),
("III", "Motor System"),
("IV", "Reflexes"),
("V", "Sensory"),
("VI", "Gait & Station"),
("VII", "Special Signs"),
("VIII", "Quick Screens"),
]
box_w = (w - 10 * mm) / 4
box_h = 22 * mm
start_y = h - 175 * mm
for i, (num, name) in enumerate(sections):
col = i % 4
row = i // 4
x = col * (box_w + 2 * mm)
y = start_y - row * (box_h + 3 * mm)
# box
c.setFillColor(BLUE_MID if row == 0 else TEAL)
c.roundRect(x, y, box_w, box_h, 5, fill=1, stroke=0)
c.setFillColor(WHITE)
c.setFont("Helvetica-Bold", 16)
c.drawCentredString(x + box_w / 2, y + 11 * mm, num)
c.setFont("Helvetica", 8)
c.drawCentredString(x + box_w / 2, y + 4 * mm, name)
# Bottom note
c.setFillColor(GREY_RULE)
c.setFont("Helvetica", 8)
c.drawCentredString(w / 2, 8 * mm,
"For educational and clinical reference. Always apply clinical judgment.")
# ── Content Builders ─────────────────────────────────────────────────────────
def build_mental_status():
elems = []
elems.append(section_banner("I. MENTAL STATUS EXAMINATION", NAVY))
elems.append(sp(3))
# Consciousness
elems.append(Paragraph("A. Level of Consciousness", S['sub_header']))
cons_rows = [
["Alert", "Fully awake, responds normally"],
["Drowsy", "Arousable by verbal stimuli; may drift off"],
["Stuporous", "Arousable only by vigorous/painful stimuli; minimal verbal"],
["Comatose", "Unarousable; no purposeful response to stimuli"],
]
elems.append(standard_table(["State", "Description"],
cons_rows,
[40*mm, CONTENT_W - 40*mm],
hdr_color=BLUE_MID))
elems.append(sp(3))
# Attention & Orientation
elems.append(Paragraph("B. Attention Tests", S['sub_header']))
att_rows = [
["Digit Span", "Forward: read digits 1/sec, patient repeats. Normal = 7 ± 2 digits\nBackward: Normal ≥ 5"],
["Serial Subtraction", "Subtract 7s from 100 (100, 93, 86…) or 3s from 20"],
['"A" Vigilance', "Tap on hearing letter A in random letter sequence read aloud"],
["Palm-Side-Fist", "Repeat three-step motor sequence — tests frontal function"],
["Spelling Reverse", "Spell WORLD backwards (D-L-R-O-W)"],
]
elems.append(standard_table(["Test", "Method / Normal"],
att_rows,
[38*mm, CONTENT_W - 38*mm],
hdr_color=BLUE_MID))
elems.append(sp(3))
# Orientation
elems.append(Paragraph("C. Orientation (Time · Place · Person)", S['sub_header']))
elems.append(shaded_box([
Paragraph("<b>Time:</b> Date (off >3 days = significant), day, month, year, time (off >4 hours = significant)", S['body']),
Paragraph("<b>Place:</b> Name of hospital/clinic, type of place, city, country", S['body']),
Paragraph("<b>Person:</b> Own name (lost only in very severe dementia)", S['body']),
], bg=BLUE_LIGHT, border=BLUE_MID))
elems.append(sp(3))
# Memory
elems.append(Paragraph("D. Memory", S['sub_header']))
mem_rows = [
["Immediate recall", "Repeat 3 words immediately after hearing them"],
["Short-term / Recent", "Recall 3 words after 5 minutes (note number recalled)"],
["Remote memory", "Historical facts (PM of India, last Olympics)"],
["Episodic memory", "Recall of specific dated personal events"],
["Anterograde", "Learning new information after an event"],
["Retrograde", "Recall of information before an event"],
]
elems.append(standard_table(["Type", "Test"], mem_rows,
[45*mm, CONTENT_W - 45*mm], TEAL))
elems.append(sp(3))
# Language
elems.append(Paragraph("E. Language (test if aphasia suspected)", S['sub_header']))
lang_rows = [
["Spontaneous speech", "Fluency, content, word-finding pauses, paraphrasias"],
["Comprehension", "'Point to the window'; execute one/two/three-step commands"],
["Repetition", "Repeat 'No ifs, ands, or buts'"],
["Naming", "Name objects and parts of objects (pen cap, watch strap)"],
["Reading", "Aloud and for comprehension"],
["Writing", "Spontaneous sentence and to dictation"],
]
elems.append(standard_table(["Component", "Method"], lang_rows,
[42*mm, CONTENT_W - 42*mm], TEAL))
elems.append(sp(2))
elems.append(pearl("Distinguish language of confusion (inattentive, intrusions, perseverations) from true aphasia (consistent breakdown in one or more language modalities)."))
elems.append(sp(3))
# Higher cortical
elems.append(Paragraph("F. Higher Cortical Functions", S['sub_header']))
left = [
Paragraph("<b>Praxis</b>", S['body_bold']),
Paragraph("• Hammer a nail (ideomotor)", S['bullet']),
Paragraph("• Blow out a candle", S['bullet']),
Paragraph("• Fist-edge-palm sequence (Luria)", S['bullet']),
Paragraph("• Dressing apraxia (observe while dressing)", S['bullet']),
sp(3),
Paragraph("<b>Executive / Frontal</b>", S['body_bold']),
Paragraph("• Abstract reasoning: interpret proverbs", S['bullet']),
Paragraph("• Similarities: apple vs orange", S['bullet']),
Paragraph("• Go/No-Go task", S['bullet']),
Paragraph("• Alternate sequence drawing", S['bullet']),
Paragraph("• Insight and judgment", S['bullet']),
]
right = [
Paragraph("<b>Visuospatial / Gnosis</b>", S['body_bold']),
Paragraph("• Clock drawing (set to 10 past 11)", S['bullet']),
Paragraph("• Line bisection (neglect = off-centre)", S['bullet']),
Paragraph("• Copy intersecting pentagons, cube", S['bullet']),
Paragraph("• Stereognosis: identify object by touch", S['bullet']),
Paragraph("• Graphesthesia: number written on palm", S['bullet']),
Paragraph("• Two-point discrimination (fingertip: 2–5 mm)", S['bullet']),
sp(3),
Paragraph("<b>Primitive Reflexes (frontal release)</b>", S['body_bold']),
Paragraph("• Grasp reflex (frontal lesion)", S['bullet']),
Paragraph("• Palmomental (scratch thenar → ipsilateral chin)", S['bullet']),
Paragraph("• Snout reflex (bilateral frontal disease)", S['bullet']),
Paragraph("• Glabellar tap: no habituation = Parkinson's", S['bullet']),
]
elems.append(two_col(left, right))
elems.append(sp(2))
return elems
def build_cranial_nerves():
elems = []
elems.append(section_banner("II. CRANIAL NERVE EXAMINATION", TEAL))
elems.append(sp(3))
cn_rows = [
["CN I\nOlfactory",
"Each nostril separately with familiar odors (coffee, vanilla, clove)",
"Anosmia → frontal mass, Parkinson's, head trauma",
"Test when: head trauma, suspected PD, anosmia"],
["CN II\nOptic",
"Visual acuity (Snellen/Rosenbaum); confrontation fields; fundoscopy; colour vision (Ishihara)",
"Reduced acuity → lens, optic nerve; Field defects → chiasm/tract/cortex",
"RAPD = optic nerve or severe retinal disease"],
["CN III\nOculomotor",
"Pupil size, shape, direct + consensual light reflex; near reflex; eyelid position",
"Complete CN III palsy: ptosis + dilated pupil + eye 'down and out'",
"Pupil-involving = surgical CN III until proven otherwise"],
["CN III, IV, VI\nEye Movements",
"H-pattern pursuit; saccades; VOR (doll's head); nystagmus direction",
"Diplopia on gaze → specific nerve palsy; INO → MLF lesion",
"Fast phase of nystagmus defines direction"],
["CN V\nTrigeminal",
"Pinprick + light touch: V1/V2/V3 bilaterally; corneal reflex; jaw strength + jaw jerk",
"Reduced corneal reflex: CN V1 or VII; Jaw deviates to weak side",
"Jaw jerk brisk = UMN above pons"],
["CN VII\nFacial",
"Raise brows, close eyes tight, smile to command, puff cheeks, show teeth",
"UMN: forehead spared (bilateral cortical supply); LMN: entire hemiface",
"Hyperacusis = stapedius branch involvement"],
["CN VIII\nVestibulocochlear",
"Whisper test; Rinne (512 Hz fork): AC vs BC; Weber (midline forehead)",
"Rinne −ve = conductive; Weber → worse ear (conductive) / better ear (SNHL)",
"Romberg test; Dix-Hallpike for BPPV"],
["CN IX, X\nGlossopharyngeal\nVagus",
"'Aah' — palate elevation + symmetry; gag reflex; swallowing; voice quality",
"Palate deviates to normal side (unilateral X palsy)",
"Hoarse/bovine cough = recurrent laryngeal n."],
["CN XI\nAccessory",
"SCM: head turn against resistance; Trapezius: shoulder shrug against resistance",
"Ipsilateral weakness; SCM turns head to opposite side",
""],
["CN XII\nHypoglossal",
"Tongue at rest (fasciculations, atrophy); protrusion; rapid movements 'La-la-la'",
"LMN: deviates to side of lesion; UMN: deviates away",
"Fasciculations + wasting = LMN / MND"],
]
elems.append(Paragraph("Summary Table", S['table_title']))
t = Table(
[[Paragraph(h, S['cell_hdr']) for h in ["Nerve", "Test Method", "Key Finding / Localisation", "Notes"]]]+
[[Paragraph(str(r[i]), S['cell_body']) for i in range(4)] for r in cn_rows],
colWidths=[28*mm, 54*mm, 60*mm, 38*mm],
repeatRows=1
)
t.setStyle(TableStyle([
('BACKGROUND', (0, 0), (-1, 0), TEAL),
('TEXTCOLOR', (0, 0), (-1, 0), WHITE),
('ROWBACKGROUNDS',(0, 1), (-1, -1), [WHITE, TEAL_LIGHT]),
('GRID', (0, 0), (-1, -1), 0.4, GREY_RULE),
('TOPPADDING', (0, 0), (-1, -1), 3),
('BOTTOMPADDING', (0, 0), (-1, -1), 3),
('LEFTPADDING', (0, 0), (-1, -1), 4),
('RIGHTPADDING', (0, 0), (-1, -1), 4),
('VALIGN', (0, 0), (-1, -1), 'TOP'),
]))
elems.append(t)
elems.append(sp(3))
elems.append(Paragraph("Swinging Flashlight Test (RAPD)", S['sub_header']))
elems.append(shaded_box([
Paragraph("Alternate the light between eyes every 3 seconds. Normally both pupils constrict equally.", S['body']),
Paragraph("RAPD (+): When light swings to affected eye → that pupil dilates (paradoxical dilatation) = afferent defect (optic nerve disease or severe retinal lesion).", S['body']),
], bg=AMBER_LT, border=AMBER))
elems.append(sp(3))
elems.append(Paragraph("Rinne and Weber Test Interpretation", S['sub_header']))
rw_rows = [
["Normal", "AC > BC (Rinne +ve)", "No lateralisation"],
["Conductive loss", "BC > AC (Rinne −ve) ipsilateral ear", "Lateralises to WORSE (deaf) ear"],
["Sensorineural loss (SNHL)", "AC > BC (Rinne +ve, but both reduced)", "Lateralises to BETTER ear"],
]
elems.append(standard_table(
["Condition", "Rinne Result", "Weber Result"],
rw_rows,
[48*mm, 65*mm, 67*mm],
TEAL
))
elems.append(sp(2))
return elems
def build_motor():
elems = []
elems.append(section_banner("III. MOTOR SYSTEM EXAMINATION", BLUE_MID))
elems.append(sp(3))
# Inspection & Tone side by side
left = [
Paragraph("A. Inspection", S['sub_header']),
Paragraph("• Muscle bulk: wasting (LMN/disuse/myopathy), hypertrophy", S['bullet']),
Paragraph("• Fasciculations: spontaneous twitching at rest (LMN/AHC)", S['bullet']),
Paragraph("• Abnormal movements: tremor, chorea, dystonia, myoclonus, tics", S['bullet']),
Paragraph("• Posture: external hip rotation (UMN), hemiplegic posture", S['bullet']),
Paragraph("• Atrophy pattern: global vs focal vs dermatomal", S['bullet']),
]
right = [
Paragraph("B. Tone (passive movement)", S['sub_header']),
Paragraph("• <b>Hypotonia:</b> LMN, cerebellar, acute shock, chorea", S['bullet']),
Paragraph("• <b>Spasticity:</b> velocity-dependent (UMN); clasp-knife; flexors > extensors in arms", S['bullet']),
Paragraph("• <b>Rigidity:</b> lead-pipe (extrapyramidal); cogwheel (PD — enhanced by Froment's)", S['bullet']),
Paragraph("• <b>Gegenhalten:</b> mirrors examiner's pressure (frontal lobe)", S['bullet']),
Paragraph("• <b>Clonus:</b> >5 beats = pathological (brisk at ankle); UMN lesion", S['bullet']),
]
elems.append(two_col(left, right))
elems.append(sp(3))
# MRC scale
elems.append(Paragraph("C. MRC Power Grading Scale", S['sub_header']))
mrc_rows = [
["0", "No contraction visible or palpable"],
["1", "Flicker/trace of contraction; no joint movement"],
["2", "Full range of movement with gravity eliminated (horizontal plane)"],
["3", "Movement against gravity but not against examiner's resistance"],
["4−/4/4+", "Movement against mild / moderate / strong resistance"],
["5", "Normal power against full resistance"],
]
elems.append(standard_table(["Grade", "Description"], mrc_rows,
[20*mm, CONTENT_W - 20*mm], BLUE_MID))
elems.append(sp(3))
# Muscle testing
elems.append(Paragraph("D. Key Muscle Testing (Root & Nerve)", S['sub_header']))
ul_rows = [
["Shoulder abduction", "C5", "Deltoid", "Axillary nerve"],
["Elbow flexion", "C5-6", "Biceps", "Musculocutaneous"],
["Elbow extension", "C7", "Triceps", "Radial nerve"],
["Wrist extension", "C6-7", "Ext. carpi rad.", "Radial nerve"],
["Wrist flexion", "C6-7", "Flex. carpi rad.", "Median nerve"],
["Finger extension", "C7", "Ext. digitorum", "Posterior interosseous"],
["Finger flexion (DIP)", "C8", "FDP", "Median/Ulnar"],
["Finger abduction", "T1", "Dorsal interossei","Ulnar nerve"],
["Thumb opposition", "C8-T1","Abductor poll. br.","Median nerve (recurrent br.)"],
]
ll_rows = [
["Hip flexion", "L1-2", "Iliopsoas", "Femoral/lumbar plexus"],
["Hip extension", "L5-S1","Glut. maximus", "Inferior gluteal"],
["Hip abduction", "L4-5", "Glut. medius", "Superior gluteal"],
["Knee extension", "L3-4", "Quadriceps", "Femoral nerve"],
["Knee flexion", "L5-S1","Hamstrings", "Sciatic nerve"],
["Ankle dorsiflexion", "L4-5", "Tibialis anterior","Deep peroneal"],
["Ankle plantarflexion", "S1-2", "Gastrocnemius", "Tibial nerve"],
["Hallux extension", "L5", "EHL", "Deep peroneal"],
["Toe flexion", "S1-2", "FHL/FDL", "Tibial nerve"],
]
col_w = [(CONTENT_W - 4*mm) / 2 - 1*mm] * 2
ul_t = Table(
[[Paragraph(h, S['cell_hdr']) for h in ["Movement", "Root", "Muscle", "Nerve"]]]+
[[Paragraph(r[i], S['cell_body']) for i in range(4)] for r in ul_rows],
colWidths=[40*mm, 12*mm, 35*mm, 40*mm],
repeatRows=1
)
ul_t.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,0), BLUE_MID),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('ROWBACKGROUNDS',(0,1),(-1,-1),[WHITE, BLUE_LIGHT]),
('GRID', (0,0),(-1,-1), 0.4, GREY_RULE),
('TOPPADDING', (0,0),(-1,-1), 2), ('BOTTOMPADDING',(0,0),(-1,-1),2),
('LEFTPADDING', (0,0),(-1,-1), 3), ('RIGHTPADDING', (0,0),(-1,-1),3),
('FONTSIZE', (0,1),(-1,-1), 7.5),
]))
ll_t = Table(
[[Paragraph(h, S['cell_hdr']) for h in ["Movement", "Root", "Muscle", "Nerve"]]]+
[[Paragraph(r[i], S['cell_body']) for i in range(4)] for r in ll_rows],
colWidths=[40*mm, 12*mm, 35*mm, 40*mm],
repeatRows=1
)
ll_t.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,0), TEAL),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('ROWBACKGROUNDS',(0,1),(-1,-1),[WHITE, TEAL_LIGHT]),
('GRID', (0,0),(-1,-1), 0.4, GREY_RULE),
('TOPPADDING', (0,0),(-1,-1), 2), ('BOTTOMPADDING',(0,0),(-1,-1),2),
('LEFTPADDING', (0,0),(-1,-1), 3), ('RIGHTPADDING', (0,0),(-1,-1),3),
('FONTSIZE', (0,1),(-1,-1), 7.5),
]))
elems.append(Paragraph("Upper Limb", S['body_bold']))
elems.append(ul_t)
elems.append(sp(2))
elems.append(Paragraph("Lower Limb", S['body_bold']))
elems.append(ll_t)
elems.append(sp(3))
# Screening manoeuvres
elems.append(Paragraph("E. Quick Screening Manoeuvres", S['sub_header']))
sm_rows = [
["Pronator Drift", "Arms outstretched, palms up, eyes closed ×10 sec; pronation/downward drift = UMN; upward drift = sensory ataxia"],
["Barre's Sign (legs)", "Patient prone, knees flexed 90°; weak leg drifts down = UMN"],
["Heel-walking", "Tests L4-5 (tibialis anterior); foot drop = cannot heel-walk"],
["Toe-walking", "Tests S1 (gastrocnemius); S1 lesion = cannot toe-walk"],
["Rise from floor", "Without hand support; tests proximal muscle power (hip extensors, quadriceps)"],
["Arm 'orbiting'", "Asymmetric orbiting of one forearm around the other = subtle contralateral UMN weakness"],
]
elems.append(standard_table(["Manoeuvre", "Interpretation"], sm_rows,
[40*mm, CONTENT_W - 40*mm], BLUE_MID))
elems.append(sp(3))
# Coordination
elems.append(Paragraph("F. Coordination (Cerebellar Function)", S['sub_header']))
coord_left = [
Paragraph("<b>Upper Limb Tests</b>", S['body_bold']),
Paragraph("• Finger-nose-finger: intention tremor, dysmetria, past-pointing", S['bullet']),
Paragraph("• Rapid alternating movements (dysdiadochokinesia): alternating pronation/supination", S['bullet']),
Paragraph("• Rapid finger-thumb tapping", S['bullet']),
Paragraph("• Rebound test: delayed check / overshoot on sudden release", S['bullet']),
]
coord_right = [
Paragraph("<b>Lower Limb + Truncal</b>", S['body_bold']),
Paragraph("• Heel-shin test: run heel down front of shin smoothly", S['bullet']),
Paragraph("• Foot tapping on examiner's hand", S['bullet']),
Paragraph("• Truncal ataxia: inability to sit upright unsupported (vermis lesion)", S['bullet']),
Paragraph("• Romberg — eyes open sway = cerebellar (not Romberg positive)", S['bullet']),
]
elems.append(two_col(coord_left, coord_right))
elems.append(sp(2))
elems.append(pearl("DANISH mnemonic for cerebellar signs: Dysdiadochokinesia, Ataxia, Nystagmus, Intention tremor, Scanning speech, Hypotonia/Heel-shin impairment"))
elems.append(sp(2))
return elems
def build_reflexes():
elems = []
elems.append(section_banner("IV. REFLEXES", NAVY))
elems.append(sp(3))
# DTRs
elems.append(Paragraph("A. Deep Tendon Reflexes (Muscle Stretch Reflexes)", S['sub_header']))
dtr_rows = [
["Biceps", "Biceps tendon (antecubital fossa)", "C5–C6", "Elbow flexion"],
["Supinator (BR)", "Radial styloid process", "C5–C6", "Forearm supination + flexion"],
["Triceps", "Triceps tendon (above olecranon)", "C7–C8", "Elbow extension"],
["Finger flexion", "Examiner's fingers over patient's DIP", "C8–T1","Finger flexion (Hoffman equivalent)"],
["Patellar", "Patellar tendon (just below patella)", "L3–L4", "Knee extension"],
["Achilles", "Achilles tendon", "S1–S2", "Plantarflexion"],
]
elems.append(standard_table(
["Reflex", "Stimulus Site", "Root", "Response"],
dtr_rows,
[32*mm, 55*mm, 18*mm, CONTENT_W - 105*mm], NAVY))
elems.append(sp(3))
# Grading
elems.append(Paragraph("B. DTR Grading", S['sub_header']))
grade_rows = [
["0", "Absent (even with reinforcement)"],
["1+", "Present only with Jendrassik reinforcement; hypoactive"],
["2+", "Normal"],
["3+", "Brisk; may spread to adjacent muscles; may be normal in anxious patient"],
["4+", "Very brisk; clonus present; pathological — UMN lesion"],
]
elems.append(standard_table(["Grade", "Description"], grade_rows,
[20*mm, CONTENT_W - 20*mm], BLUE_MID))
elems.append(sp(2))
elems.append(shaded_box([
Paragraph("<b>Jendrassik Manoeuvre:</b> Patient hooks interlocked fingers and pulls outward simultaneously as examiner taps lower limb reflexes. Reinforces barely obtainable reflexes by increasing gamma motor neuron activity.", S['body']),
], bg=AMBER_LT, border=AMBER))
elems.append(sp(3))
# Plantar response
elems.append(Paragraph("C. Plantar Response (Babinski Sign)", S['sub_header']))
elems.append(shaded_box([
Paragraph("<b>Technique:</b> Stroke the lateral sole firmly from heel toward the ball and across to the base of the big toe using a blunt instrument (orange stick).", S['body']),
Paragraph("<b>Flexor response (Normal):</b> Plantarflexion of all toes.", S['body']),
Paragraph("<b>Extensor response (Babinski +ve):</b> Dorsiflexion of big toe ± fanning of other toes = corticospinal tract damage (UMN).", S['warning']),
Paragraph("<b>Withdrawal:</b> Quick whole-foot withdrawal — not Babinski; no pathological significance.", S['body']),
Paragraph("<b>Triple flexion:</b> Hip + knee flexion + foot dorsiflexion — same significance as Babinski; severe spinal lesion.", S['body']),
], bg=RED_LT, border=RED_DK))
elems.append(sp(3))
# Equivalent signs
elems.append(Paragraph("D. Babinski Equivalent Signs", S['sub_header']))
bab_rows = [
["Oppenheim", "Knuckle pressed firmly down shin", "Extensor toe response"],
["Gordon", "Squeeze the calf muscle", "Extensor toe response"],
["Chaddock", "Stroke lateral dorsum of foot from lateral malleolus forward", "Extensor toe response"],
["Bing", "Prick dorsum of foot with pin", "Extensor toe response"],
["Gonda", "Flick or extend fourth toe down then release", "Extensor toe response"],
]
elems.append(standard_table(
["Sign", "Technique", "Response"],
bab_rows,
[28*mm, 95*mm, CONTENT_W - 123*mm], TEAL))
elems.append(sp(3))
# Superficial reflexes
elems.append(Paragraph("E. Superficial Reflexes", S['sub_header']))
sup_rows = [
["Abdominal", "Stroke skin lateral → midline in each quadrant", "T7-12",
"Absent ipsilateral to pyramidal lesion; absent bilaterally (obese, multiparous)"],
["Cremasteric", "Stroke inner thigh", "L1-2",
"Loss: UMN lesion, ipsilateral cord/root lesion"],
["Anal", "Perianal skin scratch", "S3-5",
"Sphincter contracts; Loss = sacral/conus/LMN"],
["Bulbocavernosus", "Squeeze glans penis / tap pubic symphysis", "S3-4",
"Bulbocavernosus contraction; Loss = sacral cord/conus"],
]
elems.append(standard_table(
["Reflex", "Stimulus", "Segment", "Significance"],
sup_rows,
[30*mm, 45*mm, 22*mm, CONTENT_W - 97*mm], NAVY))
elems.append(sp(2))
return elems
def build_sensory():
elems = []
elems.append(section_banner("V. SENSORY EXAMINATION", TEAL))
elems.append(sp(3))
elems.append(shaded_box([
Paragraph("Require full patient cooperation. Test one side against the other. Move from area of reduced sensation outward to normal. Start distally for vibration and JPS.", S['body']),
], bg=AMBER_LT, border=AMBER))
elems.append(sp(3))
# Primary modalities
elems.append(Paragraph("A. Primary Sensory Modalities", S['sub_header']))
prim_rows = [
["Pain (nociception)", "Spinothalamic tract (contralateral)",
"Fresh disposable pin; sharp vs dull; compare both sides",
"Use fresh pin; do not draw blood"],
["Temperature", "Spinothalamic tract (contralateral)",
"Cold metal tuning fork or cold/warm test tubes",
"Cold: 5°C; Warm: 40°C"],
["Vibration", "Posterior column (ipsilateral)",
"128 Hz tuning fork on bony prominences: toes → medial malleolus → tibial crest → ASIS → fingers → wrist → elbow",
"Ask: 'Is it buzzing or still?'; move proximally until felt"],
["Joint Position Sense (JPS)", "Posterior column (ipsilateral)",
"Hold distal phalanx laterally; move up/down; patient says 'up' or 'down'; start distally",
"Normal: detect 1–2° movements of DIP/MTP"],
["Light touch", "Post. column + spinothalamic",
"Wisp of cotton wool; compare both sides symmetrically",
"Not dermatomal 'dragging'"],
]
elems.append(standard_table(
["Modality", "Pathway", "Test Method", "Notes"],
prim_rows,
[32*mm, 40*mm, 65*mm, CONTENT_W - 137*mm], TEAL))
elems.append(sp(3))
# Cortical sensory
elems.append(Paragraph("B. Cortical Sensory Modalities (require intact primary sensation)", S['sub_header']))
cort_rows = [
["Stereognosis", "Identify common objects (key, coin, pen) placed in hand without looking", "Parietal lobe"],
["Graphesthesia", "Identify numbers / letters written on palm", "Parietal lobe"],
["Two-point discrimination", "Min. distance for 2 distinct stimuli; fingertip normal = 2–5 mm", "Parietal lobe"],
["Sensory extinction", "Touch both sides simultaneously; patient reports only one side = extinction/neglect", "Contralateral parietal (often non-dominant)"],
["Point localisation", "Patient points (eyes closed) to where examiner touched", "Parietal lobe"],
]
elems.append(standard_table(
["Test", "Method", "Lesion Site"],
cort_rows,
[38*mm, 100*mm, CONTENT_W - 138*mm], BLUE_MID))
elems.append(sp(3))
# Patterns
elems.append(Paragraph("C. Patterns of Sensory Loss — Localisation", S['sub_header']))
pat_rows = [
["Glove-and-stocking", "Peripheral neuropathy (length-dependent / dying-back)",
"Pain + vibration + JPS; starts distally"],
["Dermatomal", "Single nerve root (radiculopathy)",
"Follows dermatome; associated radicular pain"],
["Single peripheral nerve", "Mononeuropathy (trauma, entrapment)",
"Distribution of specific nerve territory"],
["Dissociated loss\n(pain/temp ↓, vibration/JPS intact)", "Spinothalamic only\n(hemisection: Brown-Séquard, syringomyelia)",
"Crossed: lesion side loses vibration; opposite side loses pain/temp"],
["Contralateral hemibody (all modalities)", "Internal capsule or thalamus (VPL nucleus)",
"Sudden onset → vascular; deep thalamic lesion"],
["Ipsilateral body + contralateral face", "Lateral medullary syndrome (Wallenberg)",
"PICA territory infarct"],
["Saddle (perineal) distribution", "Cauda equina or conus medullaris",
"S3–S5; bladder/bowel involved"],
["Cortical sensory loss only\n(primary sensation intact)", "Parietal cortex lesion",
"Astereognosis, agraphesthesia, extinction, reduced 2-PD"],
]
elems.append(standard_table(
["Pattern", "Lesion", "Key Features"],
pat_rows,
[45*mm, 65*mm, CONTENT_W - 110*mm], TEAL))
elems.append(sp(2))
return elems
def build_gait():
elems = []
elems.append(section_banner("VI. GAIT AND STATION", BLUE_MID))
elems.append(sp(3))
gait_rows = [
["Hemiplegic", "Arm flexed/adducted, leg circumducts (swings out in arc)",
"Contralateral corticospinal tract lesion", "UMN signs on affected side"],
["Spastic-Bilateral (Scissors)", "Both legs stiff, adducted, crossing each other; small steps",
"Bilateral corticospinal (cervical myelopathy, CP)", "Bilateral UMN signs"],
["Steppage (Foot Drop)", "Exaggerated hip/knee flexion to clear dropped foot; foot slap",
"L4-5; common peroneal nerve; deep peroneal", "Cannot heel-walk on affected side"],
["Trendelenburg (Waddling)", "Pelvis dips to opposite side on weight-bearing step",
"Hip abductors (L4-5, S1); bilateral = waddling gait",
"Proximal myopathy / muscular dystrophy"],
["Cerebellar Ataxic", "Wide-based, reeling, irregular, falls to side of lesion; worse on tandem",
"Ipsilateral cerebellum / cerebellar tracts",
"Sway with eyes open AND closed (not Romberg)"],
["Sensory Ataxic", "Wide-based, high-stepping, 'stamping'; watches feet; worsens in dark",
"Posterior columns or large-fiber peripheral neuropathy",
"Romberg positive (eyes closed markedly worse)"],
["Parkinsonian", "Shuffling small steps; stooped; reduced arm swing; festination; en-bloc turns",
"Basal ganglia (dopaminergic pathway)",
"Retropulsion on pull test; hypomimia"],
["Apraxic / Magnetic", "'Feet glued to floor'; short shuffling steps; normal arm swing; normal on lying",
"Frontal lobe (NPH, bilateral frontal lesions)",
"Classic triad of NPH: gait + dementia + incontinence"],
["Antalgic", "Shortened stance phase on painful limb; limp",
"Pain (musculoskeletal / radicular)",
"Not neurological in origin"],
["Myopathic", "Wide-based; Trendelenburg; difficulty rising from chair; waddling",
"Proximal muscle weakness (myopathy, muscular dystrophy)",
"Gowers' sign positive"],
]
t = Table(
[[Paragraph(h, S['cell_hdr']) for h in ["Gait Type", "Features", "Localization", "Associated Signs"]]]+
[[Paragraph(str(r[i]), S['cell_body']) for i in range(4)] for r in gait_rows],
colWidths=[30*mm, 55*mm, 58*mm, 37*mm],
repeatRows=1
)
t.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,0), BLUE_MID),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('ROWBACKGROUNDS',(0,1),(-1,-1),[WHITE, BLUE_LIGHT]),
('GRID', (0,0),(-1,-1), 0.4, GREY_RULE),
('TOPPADDING', (0,0),(-1,-1),3),('BOTTOMPADDING',(0,0),(-1,-1),3),
('LEFTPADDING', (0,0),(-1,-1),4),('RIGHTPADDING', (0,0),(-1,-1),4),
('VALIGN', (0,0),(-1,-1),'TOP'),
]))
elems.append(t)
elems.append(sp(3))
elems.append(Paragraph("Romberg Test", S['sub_header']))
elems.append(shaded_box([
Paragraph("<b>Technique:</b> Patient stands with feet together, arms at sides. Observe for 30 seconds with eyes open, then 30 seconds with eyes closed. Stand close to catch the patient.", S['body']),
Paragraph("<b>Positive Romberg:</b> Markedly increased sway or falls with eyes closed = proprioceptive/posterior column loss or large-fiber peripheral neuropathy. Vision was compensating for lost proprioception.", S['body']),
Paragraph("<b>Important:</b> Cerebellar ataxia produces sway with eyes BOTH open and closed — this is NOT a positive Romberg test.", S['warning']),
], bg=BLUE_LIGHT, border=BLUE_MID))
elems.append(sp(3))
elems.append(Paragraph("Additional Station Tests", S['sub_header']))
add_rows = [
["Tandem (heel-to-toe) gait", "Walk on a straight line; unmasks subtle cerebellar or vestibular imbalance"],
["One-leg standing", "Stand on each leg separately; Trendelenburg detected; detects unilateral weakness/imbalance"],
["Hopping on one foot", "Inability = proximal weakness or balance impairment"],
["Rising from chair", "Without arm support tests quadriceps and hip extensors (L3-L4, L5-S1)"],
["Walk on heels / toes", "Foot drop = cannot heel-walk (L4-5); S1 weakness = cannot toe-walk"],
]
elems.append(standard_table(["Test", "Purpose"], add_rows,
[55*mm, CONTENT_W - 55*mm], TEAL))
elems.append(sp(2))
return elems
def build_special_signs():
elems = []
elems.append(section_banner("VII. SPECIAL SIGNS & SYNDROMES", TEAL))
elems.append(sp(3))
# UMN vs LMN
elems.append(Paragraph("A. UMN vs LMN Differentiation", S['sub_header']))
ulm_rows = [
["Tone", "Spastic (clasp-knife); velocity-dependent", "Flaccid / hypotonia"],
["Weakness pattern","Pyramidal: extensors↓ in arm, flexors↓ in leg", "Distribution of nerve/root/muscle"],
["Reflexes", "Brisk; clonus; spread", "Reduced or absent"],
["Plantar response","Extensor (Babinski +ve)", "Flexor (normal)"],
["Fasciculations", "Absent", "Present (anterior horn / LMN)"],
["Wasting", "Late; disuse atrophy only", "Early; prominent; severe"],
["Abdominal reflex","Absent ipsilaterally", "Present"],
["Clonus", "May be present", "Absent"],
]
elems.append(standard_table(
["Feature", "UMN (Central)", "LMN (Peripheral)"],
ulm_rows,
[35*mm, 85*mm, 60*mm], NAVY))
elems.append(sp(3))
# Meningeal signs
elems.append(Paragraph("B. Meningeal Irritation Signs", S['sub_header']))
mening_rows = [
["Nuchal rigidity", "Passive neck flexion resisted; chin cannot touch chest",
"Meningitis, SAH, cervical spondylosis"],
["Kernig's sign", "Flex hip 90°; extend knee; resistance / pain at <135° of extension",
"Meningitis, SAH"],
["Brudzinski's sign", "Passive neck flexion → involuntary hip + knee flexion",
"Meningitis, SAH"],
["Jolt accentuation", "Horizontal head rotation 2–3 Hz worsens headache",
"Meningitis (sensitivity ~97%)"],
]
elems.append(standard_table(
["Sign", "Technique", "Significance"],
mening_rows,
[35*mm, 80*mm, CONTENT_W - 115*mm], RED_DK))
elems.append(sp(3))
# Raised ICP
elems.append(Paragraph("C. Signs of Raised Intracranial Pressure", S['sub_header']))
elems.append(shaded_box([
Paragraph("• <b>Papilloedema</b> on fundoscopy (blurring of disc margins, loss of venous pulsation, haemorrhages)", S['bullet']),
Paragraph("• <b>Sixth nerve palsy</b> (false localising sign — CN VI has long intracranial course)", S['bullet']),
Paragraph("• <b>Cushing's Triad:</b> Bradycardia + Hypertension + Irregular respiration (late ominous sign of herniation)", S['bullet']),
Paragraph("• Declining level of consciousness, new cranial nerve palsies", S['bullet']),
], bg=RED_LT, border=RED_DK))
elems.append(sp(3))
# Cerebellar signs
elems.append(Paragraph("D. Cerebellar Signs (DANISH)", S['sub_header']))
danish_rows = [
["D", "Dysdiadochokinesia", "Impaired rapid alternating movements"],
["A", "Ataxia", "Gait/truncal/limb ataxia; wide-based gait"],
["N", "Nystagmus", "Horizontal; fast phase toward lesion (ipsilateral)"],
["I", "Intention tremor", "Worsens as target approached; finger-nose test"],
["S", "Scanning speech", "Dysarthria; slow, slurred, monotone ('staccato')"],
["H", "Hypotonia + Heel-shin", "Reduced tone; impaired heel-shin coordination"],
]
elems.append(standard_table(
["Letter", "Sign", "Description"],
danish_rows,
[12*mm, 40*mm, CONTENT_W - 52*mm], TEAL))
elems.append(sp(3))
# Autonomic examination
elems.append(Paragraph("E. Autonomic Nervous System Assessment", S['sub_header']))
auto_left = [
Paragraph("<b>Cardiovascular</b>", S['body_bold']),
Paragraph("• BP lying and standing (orthostatic hypotension: ≥20 mmHg systolic drop)", S['bullet']),
Paragraph("• Heart rate variability (R-R intervals on ECG)", S['bullet']),
Paragraph("• Valsalva manoeuvre response", S['bullet']),
Paragraph("• Carotid sinus massage (with care)", S['bullet']),
]
auto_right = [
Paragraph("<b>Other Autonomic Functions</b>", S['body_bold']),
Paragraph("• Pupil: Horner's syndrome (miosis, ptosis, anhidrosis)", S['bullet']),
Paragraph("• Sweating: anhydrosis pattern", S['bullet']),
Paragraph("• Bladder/bowel function", S['bullet']),
Paragraph("• Sexual function", S['bullet']),
Paragraph("• Skin: temperature, colour, trophic changes", S['bullet']),
]
elems.append(two_col(auto_left, auto_right))
elems.append(sp(2))
return elems
def build_quick_screen():
elems = []
elems.append(section_banner("VIII. QUICK SCREENING EXAMINATION & MNEMONICS", NAVY))
elems.append(sp(3))
elems.append(Paragraph("A. 10-Point Bedside Neurological Screen", S['sub_header']))
elems.append(Paragraph("(Adapted from Adams & Victor Table 1-4 — adds only minutes to the physical examination)", S['small']))
elems.append(sp(2))
screen_rows = [
["1", "Mental Status", "Orientation, insight, language — assessed during history"],
["2", "Cranial Nerves II", "Pupil size, reactivity; visual acuity; fundoscopy"],
["3", "CN III, IV, VI, VII", "Eye movements; facial movements during speech and smile"],
["4", "Upper limb motor", "Pronator drift ×10 sec; grip strength; wrist dorsiflexion"],
["5", "Upper limb reflexes", "Biceps (C5-6), supinator (C6), triceps (C7)"],
["6", "Lower limb inspection", "Active flexion/extension of hips, knees, feet — observe"],
["7", "Lower limb reflexes", "Knee jerk (L3-4), ankle jerk (S1-2), plantars"],
["8", "Vibration sensation", "128 Hz tuning fork — toes and fingers"],
["9", "Coordination", "Finger-nose-finger; heel-shin"],
["10","Gait + Romberg", "Observation of gait; tandem walk; Romberg test"],
]
t = Table(
[[Paragraph(h, S['cell_hdr']) for h in ["#", "Component", "Key Test"]]]+
[[Paragraph(r[0], S['cell_hdr']), Paragraph(r[1], S['body_bold']), Paragraph(r[2], S['cell_body'])]
for r in screen_rows],
colWidths=[10*mm, 45*mm, CONTENT_W - 55*mm],
repeatRows=1
)
t.setStyle(TableStyle([
('BACKGROUND', (0,0),(-1,0), NAVY),
('BACKGROUND', (0,1),(-1,-1), WHITE),
('BACKGROUND', (0,2),(-1,2), GREY_BG),
('BACKGROUND', (0,4),(-1,4), GREY_BG),
('BACKGROUND', (0,6),(-1,6), GREY_BG),
('BACKGROUND', (0,8),(-1,8), GREY_BG),
('BACKGROUND', (0,10),(-1,10), GREY_BG),
('TEXTCOLOR', (0,0),(-1,0), WHITE),
('TEXTCOLOR', (0,1),(-1,-1), NAVY),
('GRID', (0,0),(-1,-1), 0.4, GREY_RULE),
('TOPPADDING', (0,0),(-1,-1),4),('BOTTOMPADDING',(0,0),(-1,-1),4),
('LEFTPADDING', (0,0),(-1,-1),5),('RIGHTPADDING', (0,0),(-1,-1),5),
('VALIGN', (0,0),(-1,-1),'MIDDLE'),
('ALIGN', (0,0),(0,-1), 'CENTER'),
]))
elems.append(t)
elems.append(sp(3))
# Mnemonics
elems.append(Paragraph("B. Key Mnemonics", S['sub_header']))
left_mn = [
Paragraph("<b>Cranial Nerves (I–XII)</b>", S['body_bold']),
Paragraph('"Oh Oh Oh, To Touch And Feel Very Good Velvet, AHH"', S['body']),
Paragraph("Olfactory, Optic, Oculomotor, Trochlear, Trigeminal, Abducens, Facial, Vestibulocochlear, Glossopharyngeal, Vagus, Accessory, Hypoglossal", S['small']),
sp(3),
Paragraph("<b>CN Function (Sensory/Motor/Both)</b>", S['body_bold']),
Paragraph('"Some Say Marry Money But My Brother Says Big Brains Matter More"', S['body']),
Paragraph("S M B M B B S B B M M B → Sensory/Motor/Both for CN I–XII", S['small']),
sp(3),
Paragraph("<b>Cerebellar signs: DANISH</b>", S['body_bold']),
Paragraph("Dysdiadochokinesia, Ataxia, Nystagmus, Intention tremor, Scanning speech, Hypotonia", S['body']),
]
right_mn = [
Paragraph("<b>Dermatomes — Quick reference</b>", S['body_bold']),
Paragraph("C3-4: Shoulder cape; C6: Thumb; C7: Middle finger; C8: Little finger", S['small']),
Paragraph("T4: Nipple line; T10: Umbilicus; T12: Inguinal ligament", S['small']),
Paragraph("L2: Anterior thigh; L3: Medial knee; L4: Medial leg; L5: Dorsum foot; S1: Lateral foot + sole", S['small']),
sp(3),
Paragraph("<b>MRC Scale: 0–5</b>", S['body_bold']),
Paragraph("0 = None; 1 = Flicker; 2 = Gravity-free; 3 = vs Gravity; 4 = vs Resistance; 5 = Normal", S['body']),
sp(3),
Paragraph("<b>Aphasia types</b>", S['body_bold']),
Paragraph("Broca's (expressive): fluency↓, comprehension intact, repetition↓", S['small']),
Paragraph("Wernicke's (receptive): fluency normal, comprehension↓, repetition↓", S['small']),
Paragraph("Conduction: fluency ± normal, comprehension intact, repetition↓↓", S['small']),
Paragraph("Global: all modalities impaired (large MCA territory)", S['small']),
]
elems.append(two_col(left_mn, right_mn))
elems.append(sp(3))
# Sources
elems.append(HRFlowable(width=CONTENT_W, thickness=0.5, color=GREY_RULE))
elems.append(sp(2))
elems.append(Paragraph("<b>References / Sources</b>", S['small']))
elems.append(Paragraph(
"1. Adams and Victor's Principles of Neurology, 12th Edition (2023), McGraw-Hill — Chapter 1, pp. 14–21\n"
"2. Bradley and Daroff's Neurology in Clinical Practice, 8th Edition — Chapter 1: Neurological Examination, Table 1.1\n"
"3. Harrison's Principles of Internal Medicine, 22nd Edition (2025) — Neurological Examination sections\n"
"4. DeMyer's Technique of the Neurologic Examination, 6th Edition", S['small']))
elems.append(sp(2))
return elems
# ── Main Build ───────────────────────────────────────────────────────────────
def build_pdf():
doc = SimpleDocTemplate(
OUTPUT_PATH,
pagesize=A4,
leftMargin=MARGIN, rightMargin=MARGIN,
topMargin=MARGIN, bottomMargin=22 * mm,
title="CNS Examination Reference Card",
author="Orris Medical AI",
subject="Comprehensive Neurological Examination",
)
story = []
# Cover
story.append(CoverPage())
story.append(PageBreak())
# Sections
story.extend(build_mental_status())
story.append(PageBreak())
story.extend(build_cranial_nerves())
story.append(PageBreak())
story.extend(build_motor())
story.append(PageBreak())
story.extend(build_reflexes())
story.append(PageBreak())
story.extend(build_sensory())
story.append(PageBreak())
story.extend(build_gait())
story.append(PageBreak())
story.extend(build_special_signs())
story.append(PageBreak())
story.extend(build_quick_screen())
doc.build(story, canvasmaker=NumberedCanvas)
print(f"PDF generated: {OUTPUT_PATH}")
return OUTPUT_PATH
if __name__ == "__main__":
build_pdf()
cd /home/daytona/workspace/cns-reference-card && python3 generate_cns_pdf.py 2>&1
~/cns-reference-card/generate_cns_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/cns-reference-card/generate_cns_pdf.py'
~/cns-reference-card/generate_cns_pdf.py
Successfully replaced 1 instance(s) of the string in '/home/daytona/workspace/cns-reference-card/generate_cns_pdf.py'
cd /home/daytona/workspace/cns-reference-card && python3 generate_cns_pdf.py 2>&1
python3 -c " from pypdf import PdfReader r = PdfReader('/home/daytona/workspace/cns-reference-card/CNS_Examination_Reference_Card.pdf') import os size = os.path.getsize('/home/daytona/workspace/cns-reference-card/CNS_Examination_Reference_Card.pdf') print(f'Pages: {len(r.pages)}') print(f'Size: {size/1024:.1f} KB') print(f'Title: {r.metadata.title}') "
CNS Examination Reference Card
PDF Document · PDF
| Page | Section | Contents |
|---|---|---|
| 1 | Cover | Full navy/teal design with 8-section index |
| 2 | Mental Status | Consciousness grading, attention tests, orientation, memory types, language components, praxis, visuospatial, frontal tests |
| 3 | Cranial Nerves | All 12 CNs - technique, key finding, localisation; RAPD test; Rinne/Weber interpretation table |
| 4 | Motor System | Inspection, tone types (spasticity/rigidity/clonus), MRC 0-5 scale, upper + lower limb muscle testing with roots & nerves |
| 5 | Motor cont. | Screening manoeuvres (pronator drift, Barré, heel/toe walk), coordination tests (DANISH mnemonic) |
| 6 | Reflexes | DTR table with roots, grading 0-4+, Jendrassik, Babinski technique + equivalents (Oppenheim, Gordon, Chaddock, Bing), superficial reflexes |
| 7 | Sensory | Primary modalities with pathways, cortical sensory tests, 8-pattern localisation table |
| 8 | Gait & Station | 10 gait patterns with features and localisation, Romberg test, additional station tests |
| 9 | Special Signs | UMN vs LMN differentiation table, meningeal signs (Kernig, Brudzinski, jolt), raised ICP signs, autonomic examination |
| 10-11 | Quick Screens | 10-point bedside neurological screen, key mnemonics (cranial nerves, dermatomes, aphasia types) |
| 12 | References | Adams & Victor 12e, Bradley & Daroff, Harrison's 22e, DeMyer's |
Pdf not opening
python3 -c " from pypdf import PdfReader r = PdfReader('/home/daytona/workspace/cns-reference-card/CNS_Examination_Reference_Card.pdf') print('Pages:', len(r.pages)) # Try reading first page text print('Page 1 text sample:', r.pages[0].extract_text()[:200]) " 2>&1
Generate a new PDF with simpler styling without pypdf merging
Localisation of stroke according to history and cns examination findings